Repository: olive-editor/olive Branch: master Commit: 7e0e94abf661 Files: 1145 Total size: 27.0 MB Directory structure: gitextract_2jek1uqd/ ├── .clang-format ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── 00-olive_unsupported.md │ │ ├── 01-crash_issue.md │ │ ├── 50-build_issue.md │ │ ├── 50-cache_issue.md │ │ ├── 50-codec_issue.md │ │ ├── 50-color_issue.md │ │ ├── 50-editing_issue.md │ │ ├── 50-export_issue.md │ │ ├── 50-node_issue.md │ │ ├── 50-playback_issue.md │ │ ├── 50-project_issue.md │ │ ├── 50-renderer_issue.md │ │ ├── 50-ui_issue.md │ │ └── config.yml │ ├── ISSUE_TEMPLATE.md │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app/ │ ├── CMakeLists.txt │ ├── audio/ │ │ ├── CMakeLists.txt │ │ ├── audiomanager.cpp │ │ ├── audiomanager.h │ │ ├── audioprocessor.cpp │ │ ├── audioprocessor.h │ │ ├── audiovisualwaveform.cpp │ │ └── audiovisualwaveform.h │ ├── cli/ │ │ ├── CMakeLists.txt │ │ ├── cliexport/ │ │ │ ├── cliexportmanager.cpp │ │ │ └── cliexportmanager.h │ │ ├── cliprogress/ │ │ │ ├── CMakeLists.txt │ │ │ ├── cliprogressdialog.cpp │ │ │ └── cliprogressdialog.h │ │ └── clitask/ │ │ ├── CMakeLists.txt │ │ ├── clitaskdialog.cpp │ │ └── clitaskdialog.h │ ├── codec/ │ │ ├── CMakeLists.txt │ │ ├── conformmanager.cpp │ │ ├── conformmanager.h │ │ ├── decoder.cpp │ │ ├── decoder.h │ │ ├── encoder.cpp │ │ ├── encoder.h │ │ ├── exportcodec.cpp │ │ ├── exportcodec.h │ │ ├── exportformat.cpp │ │ ├── exportformat.h │ │ ├── ffmpeg/ │ │ │ ├── CMakeLists.txt │ │ │ ├── ffmpegdecoder.cpp │ │ │ ├── ffmpegdecoder.h │ │ │ ├── ffmpegencoder.cpp │ │ │ └── ffmpegencoder.h │ │ ├── frame.cpp │ │ ├── frame.h │ │ ├── oiio/ │ │ │ ├── CMakeLists.txt │ │ │ ├── oiiodecoder.cpp │ │ │ ├── oiiodecoder.h │ │ │ ├── oiioencoder.cpp │ │ │ └── oiioencoder.h │ │ ├── planarfiledevice.cpp │ │ └── planarfiledevice.h │ ├── common/ │ │ ├── CMakeLists.txt │ │ ├── autoscroll.h │ │ ├── cancelableobject.h │ │ ├── channellayout.h │ │ ├── commandlineparser.cpp │ │ ├── commandlineparser.h │ │ ├── crashpadinterface.cpp │ │ ├── crashpadinterface.h │ │ ├── crashpadutils.h │ │ ├── debug.cpp │ │ ├── debug.h │ │ ├── decibel.h │ │ ├── define.h │ │ ├── digit.h │ │ ├── ffmpegutils.cpp │ │ ├── ffmpegutils.h │ │ ├── filefunctions.cpp │ │ ├── filefunctions.h │ │ ├── html.cpp │ │ ├── html.h │ │ ├── jobtime.cpp │ │ ├── jobtime.h │ │ ├── lerp.h │ │ ├── memorypool.h │ │ ├── ocioutils.cpp │ │ ├── ocioutils.h │ │ ├── oiioutils.cpp │ │ ├── oiioutils.h │ │ ├── otioutils.h │ │ ├── power.h │ │ ├── qtutils.cpp │ │ ├── qtutils.h │ │ ├── range.h │ │ ├── ratiodialog.cpp │ │ ├── ratiodialog.h │ │ ├── threadsafemap.h │ │ ├── tohex.h │ │ ├── util.h │ │ ├── xmlutils.cpp │ │ └── xmlutils.h │ ├── config/ │ │ ├── CMakeLists.txt │ │ ├── config.cpp │ │ └── config.h │ ├── core.cpp │ ├── core.h │ ├── crashhandler/ │ │ ├── CMakeLists.txt │ │ ├── crashhandler.cpp │ │ └── crashhandler.h │ ├── dialog/ │ │ ├── CMakeLists.txt │ │ ├── about/ │ │ │ ├── CMakeLists.txt │ │ │ ├── about.cpp │ │ │ ├── about.h │ │ │ ├── patreon.h │ │ │ ├── patreon.py │ │ │ ├── scrollinglabel.cpp │ │ │ └── scrollinglabel.h │ │ ├── actionsearch/ │ │ │ ├── CMakeLists.txt │ │ │ ├── actionsearch.cpp │ │ │ └── actionsearch.h │ │ ├── autorecovery/ │ │ │ ├── CMakeLists.txt │ │ │ ├── autorecoverydialog.cpp │ │ │ └── autorecoverydialog.h │ │ ├── color/ │ │ │ ├── CMakeLists.txt │ │ │ ├── colordialog.cpp │ │ │ └── colordialog.h │ │ ├── configbase/ │ │ │ ├── CMakeLists.txt │ │ │ ├── configdialogbase.cpp │ │ │ ├── configdialogbase.h │ │ │ ├── configdialogbasetab.cpp │ │ │ └── configdialogbasetab.h │ │ ├── diskcache/ │ │ │ ├── CMakeLists.txt │ │ │ ├── diskcachedialog.cpp │ │ │ └── diskcachedialog.h │ │ ├── export/ │ │ │ ├── CMakeLists.txt │ │ │ ├── codec/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── av1section.cpp │ │ │ │ ├── av1section.h │ │ │ │ ├── cineformsection.cpp │ │ │ │ ├── cineformsection.h │ │ │ │ ├── codecsection.cpp │ │ │ │ ├── codecsection.h │ │ │ │ ├── codecstack.cpp │ │ │ │ ├── codecstack.h │ │ │ │ ├── h264section.cpp │ │ │ │ ├── h264section.h │ │ │ │ ├── imagesection.cpp │ │ │ │ └── imagesection.h │ │ │ ├── export.cpp │ │ │ ├── export.h │ │ │ ├── exportadvancedvideodialog.cpp │ │ │ ├── exportadvancedvideodialog.h │ │ │ ├── exportaudiotab.cpp │ │ │ ├── exportaudiotab.h │ │ │ ├── exportformatcombobox.cpp │ │ │ ├── exportformatcombobox.h │ │ │ ├── exportsavepresetdialog.cpp │ │ │ ├── exportsavepresetdialog.h │ │ │ ├── exportsubtitlestab.cpp │ │ │ ├── exportsubtitlestab.h │ │ │ ├── exportvideotab.cpp │ │ │ └── exportvideotab.h │ │ ├── footageproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── footageproperties.cpp │ │ │ ├── footageproperties.h │ │ │ └── streamproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── audiostreamproperties.cpp │ │ │ ├── audiostreamproperties.h │ │ │ ├── streamproperties.cpp │ │ │ ├── streamproperties.h │ │ │ ├── videostreamproperties.cpp │ │ │ └── videostreamproperties.h │ │ ├── footagerelink/ │ │ │ ├── CMakeLists.txt │ │ │ ├── footagerelinkdialog.cpp │ │ │ └── footagerelinkdialog.h │ │ ├── keyframeproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── keyframeproperties.cpp │ │ │ └── keyframeproperties.h │ │ ├── markerproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── markerpropertiesdialog.cpp │ │ │ └── markerpropertiesdialog.h │ │ ├── otioproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── otiopropertiesdialog.cpp │ │ │ └── otiopropertiesdialog.h │ │ ├── preferences/ │ │ │ ├── CMakeLists.txt │ │ │ ├── keysequenceeditor.cpp │ │ │ ├── keysequenceeditor.h │ │ │ ├── preferences.cpp │ │ │ ├── preferences.h │ │ │ └── tabs/ │ │ │ ├── CMakeLists.txt │ │ │ ├── preferencesappearancetab.cpp │ │ │ ├── preferencesappearancetab.h │ │ │ ├── preferencesaudiotab.cpp │ │ │ ├── preferencesaudiotab.h │ │ │ ├── preferencesbehaviortab.cpp │ │ │ ├── preferencesbehaviortab.h │ │ │ ├── preferencesdisktab.cpp │ │ │ ├── preferencesdisktab.h │ │ │ ├── preferencesgeneraltab.cpp │ │ │ ├── preferencesgeneraltab.h │ │ │ ├── preferenceskeyboardtab.cpp │ │ │ └── preferenceskeyboardtab.h │ │ ├── progress/ │ │ │ ├── CMakeLists.txt │ │ │ ├── progress.cpp │ │ │ └── progress.h │ │ ├── projectproperties/ │ │ │ ├── CMakeLists.txt │ │ │ ├── projectproperties.cpp │ │ │ └── projectproperties.h │ │ ├── rendercancel/ │ │ │ ├── CMakeLists.txt │ │ │ ├── rendercancel.cpp │ │ │ └── rendercancel.h │ │ ├── sequence/ │ │ │ ├── CMakeLists.txt │ │ │ ├── presetmanager.h │ │ │ ├── sequence.cpp │ │ │ ├── sequence.h │ │ │ ├── sequencedialogparametertab.cpp │ │ │ ├── sequencedialogparametertab.h │ │ │ ├── sequencedialogpresettab.cpp │ │ │ ├── sequencedialogpresettab.h │ │ │ └── sequencepreset.h │ │ ├── speedduration/ │ │ │ ├── CMakeLists.txt │ │ │ ├── speeddurationdialog.cpp │ │ │ └── speeddurationdialog.h │ │ ├── task/ │ │ │ ├── CMakeLists.txt │ │ │ ├── task.cpp │ │ │ └── task.h │ │ └── text/ │ │ ├── CMakeLists.txt │ │ ├── text.cpp │ │ └── text.h │ ├── main.cpp │ ├── node/ │ │ ├── CMakeLists.txt │ │ ├── audio/ │ │ │ ├── CMakeLists.txt │ │ │ ├── pan/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── pan.cpp │ │ │ │ └── pan.h │ │ │ └── volume/ │ │ │ ├── CMakeLists.txt │ │ │ ├── volume.cpp │ │ │ └── volume.h │ │ ├── block/ │ │ │ ├── CMakeLists.txt │ │ │ ├── block.cpp │ │ │ ├── block.h │ │ │ ├── clip/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── clip.cpp │ │ │ │ └── clip.h │ │ │ ├── gap/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── gap.cpp │ │ │ │ └── gap.h │ │ │ ├── subtitle/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── subtitle.cpp │ │ │ │ └── subtitle.h │ │ │ └── transition/ │ │ │ ├── CMakeLists.txt │ │ │ ├── crossdissolve/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── crossdissolvetransition.cpp │ │ │ │ └── crossdissolvetransition.h │ │ │ ├── diptocolor/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── diptocolortransition.cpp │ │ │ │ └── diptocolortransition.h │ │ │ ├── transition.cpp │ │ │ └── transition.h │ │ ├── color/ │ │ │ ├── CMakeLists.txt │ │ │ ├── colormanager/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── colormanager.cpp │ │ │ │ └── colormanager.h │ │ │ ├── displaytransform/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── displaytransform.cpp │ │ │ │ └── displaytransform.h │ │ │ ├── ociobase/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── ociobase.cpp │ │ │ │ └── ociobase.h │ │ │ └── ociogradingtransformlinear/ │ │ │ ├── CMakeLists.txt │ │ │ ├── ociogradingtransformlinear.cpp │ │ │ └── ociogradingtransformlinear.h │ │ ├── distort/ │ │ │ ├── CMakeLists.txt │ │ │ ├── cornerpin/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── cornerpindistortnode.cpp │ │ │ │ └── cornerpindistortnode.h │ │ │ ├── crop/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── cropdistortnode.cpp │ │ │ │ └── cropdistortnode.h │ │ │ ├── flip/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flipdistortnode.cpp │ │ │ │ └── flipdistortnode.h │ │ │ ├── mask/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── mask.cpp │ │ │ │ └── mask.h │ │ │ ├── ripple/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── rippledistortnode.cpp │ │ │ │ └── rippledistortnode.h │ │ │ ├── swirl/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── swirldistortnode.cpp │ │ │ │ └── swirldistortnode.h │ │ │ ├── tile/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── tiledistortnode.cpp │ │ │ │ └── tiledistortnode.h │ │ │ ├── transform/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── transformdistortnode.cpp │ │ │ │ └── transformdistortnode.h │ │ │ └── wave/ │ │ │ ├── CMakeLists.txt │ │ │ ├── wavedistortnode.cpp │ │ │ └── wavedistortnode.h │ │ ├── effect/ │ │ │ ├── CMakeLists.txt │ │ │ └── opacity/ │ │ │ ├── CMakeLists.txt │ │ │ ├── opacityeffect.cpp │ │ │ └── opacityeffect.h │ │ ├── factory.cpp │ │ ├── factory.h │ │ ├── filter/ │ │ │ ├── CMakeLists.txt │ │ │ ├── blur/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── blur.cpp │ │ │ │ └── blur.h │ │ │ ├── dropshadow/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── dropshadowfilter.cpp │ │ │ │ └── dropshadowfilter.h │ │ │ ├── mosaic/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── mosaicfilternode.cpp │ │ │ │ └── mosaicfilternode.h │ │ │ └── stroke/ │ │ │ ├── CMakeLists.txt │ │ │ ├── stroke.cpp │ │ │ └── stroke.h │ │ ├── generator/ │ │ │ ├── CMakeLists.txt │ │ │ ├── matrix/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── matrix.cpp │ │ │ │ └── matrix.h │ │ │ ├── noise/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── noise.cpp │ │ │ │ └── noise.h │ │ │ ├── polygon/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── polygon.cpp │ │ │ │ └── polygon.h │ │ │ ├── shape/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── generatorwithmerge.cpp │ │ │ │ ├── generatorwithmerge.h │ │ │ │ ├── shapenode.cpp │ │ │ │ ├── shapenode.h │ │ │ │ ├── shapenodebase.cpp │ │ │ │ └── shapenodebase.h │ │ │ ├── solid/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── solid.cpp │ │ │ │ └── solid.h │ │ │ └── text/ │ │ │ ├── CMakeLists.txt │ │ │ ├── textv1.cpp │ │ │ ├── textv1.h │ │ │ ├── textv2.cpp │ │ │ ├── textv2.h │ │ │ ├── textv3.cpp │ │ │ └── textv3.h │ │ ├── gizmo/ │ │ │ ├── CMakeLists.txt │ │ │ ├── draggable.cpp │ │ │ ├── draggable.h │ │ │ ├── gizmo.cpp │ │ │ ├── gizmo.h │ │ │ ├── line.cpp │ │ │ ├── line.h │ │ │ ├── path.cpp │ │ │ ├── path.h │ │ │ ├── point.cpp │ │ │ ├── point.h │ │ │ ├── polygon.cpp │ │ │ ├── polygon.h │ │ │ ├── screen.cpp │ │ │ ├── screen.h │ │ │ ├── text.cpp │ │ │ └── text.h │ │ ├── globals.cpp │ │ ├── globals.h │ │ ├── group/ │ │ │ ├── CMakeLists.txt │ │ │ ├── group.cpp │ │ │ └── group.h │ │ ├── input/ │ │ │ ├── CMakeLists.txt │ │ │ ├── multicam/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── multicamnode.cpp │ │ │ │ └── multicamnode.h │ │ │ ├── time/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── timeinput.cpp │ │ │ │ └── timeinput.h │ │ │ └── value/ │ │ │ ├── CMakeLists.txt │ │ │ ├── valuenode.cpp │ │ │ └── valuenode.h │ │ ├── inputdragger.cpp │ │ ├── inputdragger.h │ │ ├── inputimmediate.cpp │ │ ├── inputimmediate.h │ │ ├── keyframe.cpp │ │ ├── keyframe.h │ │ ├── keying/ │ │ │ ├── CMakeLists.txt │ │ │ ├── chromakey/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── chromakey.cpp │ │ │ │ └── chromakey.h │ │ │ ├── colordifferencekey/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── colordifferencekey.cpp │ │ │ │ └── colordifferencekey.h │ │ │ └── despill/ │ │ │ ├── CMakeLists.txt │ │ │ ├── despill.cpp │ │ │ └── despill.h │ │ ├── math/ │ │ │ ├── CMakeLists.txt │ │ │ ├── math/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── math.cpp │ │ │ │ ├── math.h │ │ │ │ ├── mathbase.cpp │ │ │ │ └── mathbase.h │ │ │ ├── merge/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── merge.cpp │ │ │ │ └── merge.h │ │ │ └── trigonometry/ │ │ │ ├── CMakeLists.txt │ │ │ ├── trigonometry.cpp │ │ │ └── trigonometry.h │ │ ├── node.cpp │ │ ├── node.h │ │ ├── nodeundo.cpp │ │ ├── nodeundo.h │ │ ├── output/ │ │ │ ├── CMakeLists.txt │ │ │ ├── track/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── track.cpp │ │ │ │ ├── track.h │ │ │ │ ├── tracklist.cpp │ │ │ │ └── tracklist.h │ │ │ └── viewer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── viewer.cpp │ │ │ └── viewer.h │ │ ├── param.cpp │ │ ├── param.h │ │ ├── project/ │ │ │ ├── CMakeLists.txt │ │ │ ├── folder/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── folder.cpp │ │ │ │ └── folder.h │ │ │ ├── footage/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── footage.cpp │ │ │ │ ├── footage.h │ │ │ │ ├── footagedescription.cpp │ │ │ │ └── footagedescription.h │ │ │ ├── sequence/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── sequence.cpp │ │ │ │ └── sequence.h │ │ │ └── serializer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── serializer.cpp │ │ │ ├── serializer.h │ │ │ ├── serializer190219.cpp │ │ │ ├── serializer190219.h │ │ │ ├── serializer210528.cpp │ │ │ ├── serializer210528.h │ │ │ ├── serializer210907.cpp │ │ │ ├── serializer210907.h │ │ │ ├── serializer211228.cpp │ │ │ ├── serializer211228.h │ │ │ ├── serializer220403.cpp │ │ │ ├── serializer220403.h │ │ │ ├── serializer230220.cpp │ │ │ ├── serializer230220.h │ │ │ ├── typeserializer.cpp │ │ │ └── typeserializer.h │ │ ├── project.cpp │ │ ├── project.h │ │ ├── serializeddata.cpp │ │ ├── serializeddata.h │ │ ├── splitvalue.h │ │ ├── time/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timeformat/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── timeformat.cpp │ │ │ │ └── timeformat.h │ │ │ ├── timeoffset/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── timeoffsetnode.cpp │ │ │ │ └── timeoffsetnode.h │ │ │ └── timeremap/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timeremap.cpp │ │ │ └── timeremap.h │ │ ├── traverser.cpp │ │ ├── traverser.h │ │ ├── value.cpp │ │ ├── value.h │ │ ├── valuedatabase.cpp │ │ └── valuedatabase.h │ ├── packaging/ │ │ ├── CMakeLists.txt │ │ ├── linux/ │ │ │ ├── AppRun │ │ │ ├── CMakeLists.txt │ │ │ ├── org.olivevideoeditor.Olive.appdata.xml.in │ │ │ ├── org.olivevideoeditor.Olive.desktop │ │ │ └── org.olivevideoeditor.Olive.xml │ │ ├── macos/ │ │ │ ├── MacOSXBundleInfo.plist.in │ │ │ └── olive.icns │ │ └── windows/ │ │ ├── nsis/ │ │ │ └── olive.nsi │ │ ├── resources.rc │ │ └── version.h │ ├── panel/ │ │ ├── CMakeLists.txt │ │ ├── audiomonitor/ │ │ │ ├── CMakeLists.txt │ │ │ ├── audiomonitor.cpp │ │ │ └── audiomonitor.h │ │ ├── curve/ │ │ │ ├── CMakeLists.txt │ │ │ ├── curve.cpp │ │ │ └── curve.h │ │ ├── footageviewer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── footageviewer.cpp │ │ │ └── footageviewer.h │ │ ├── history/ │ │ │ ├── CMakeLists.txt │ │ │ ├── historypanel.cpp │ │ │ └── historypanel.h │ │ ├── multicam/ │ │ │ ├── CMakeLists.txt │ │ │ ├── multicampanel.cpp │ │ │ └── multicampanel.h │ │ ├── node/ │ │ │ ├── CMakeLists.txt │ │ │ ├── node.cpp │ │ │ └── node.h │ │ ├── panel.cpp │ │ ├── panel.h │ │ ├── panelmanager.cpp │ │ ├── panelmanager.h │ │ ├── param/ │ │ │ ├── CMakeLists.txt │ │ │ ├── param.cpp │ │ │ └── param.h │ │ ├── pixelsampler/ │ │ │ ├── CMakeLists.txt │ │ │ ├── pixelsamplerpanel.cpp │ │ │ └── pixelsamplerpanel.h │ │ ├── project/ │ │ │ ├── CMakeLists.txt │ │ │ ├── footagemanagementpanel.h │ │ │ ├── project.cpp │ │ │ └── project.h │ │ ├── scope/ │ │ │ ├── CMakeLists.txt │ │ │ ├── scope.cpp │ │ │ └── scope.h │ │ ├── sequenceviewer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── sequenceviewer.cpp │ │ │ └── sequenceviewer.h │ │ ├── table/ │ │ │ ├── CMakeLists.txt │ │ │ ├── table.cpp │ │ │ └── table.h │ │ ├── taskmanager/ │ │ │ ├── CMakeLists.txt │ │ │ ├── taskmanager.cpp │ │ │ └── taskmanager.h │ │ ├── timebased/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timebased.cpp │ │ │ └── timebased.h │ │ ├── timeline/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timeline.cpp │ │ │ └── timeline.h │ │ ├── tool/ │ │ │ ├── CMakeLists.txt │ │ │ ├── tool.cpp │ │ │ └── tool.h │ │ └── viewer/ │ │ ├── CMakeLists.txt │ │ ├── viewer.cpp │ │ ├── viewer.h │ │ ├── viewerbase.cpp │ │ └── viewerbase.h │ ├── render/ │ │ ├── CMakeLists.txt │ │ ├── alphaassoc.h │ │ ├── audioplaybackcache.cpp │ │ ├── audioplaybackcache.h │ │ ├── audiowaveformcache.cpp │ │ ├── audiowaveformcache.h │ │ ├── cancelatom.h │ │ ├── colorprocessor.cpp │ │ ├── colorprocessor.h │ │ ├── colorprocessorcache.h │ │ ├── colortransform.h │ │ ├── diskmanager.cpp │ │ ├── diskmanager.h │ │ ├── framehashcache.cpp │ │ ├── framehashcache.h │ │ ├── framemanager.cpp │ │ ├── framemanager.h │ │ ├── job/ │ │ │ ├── CMakeLists.txt │ │ │ ├── acceleratedjob.cpp │ │ │ ├── acceleratedjob.h │ │ │ ├── cachejob.h │ │ │ ├── colortransformjob.h │ │ │ ├── footagejob.h │ │ │ ├── generatejob.h │ │ │ ├── samplejob.h │ │ │ └── shaderjob.h │ │ ├── loopmode.h │ │ ├── managedcolor.cpp │ │ ├── managedcolor.h │ │ ├── ocioconf/ │ │ │ ├── CMakeLists.txt │ │ │ ├── config.ocio │ │ │ ├── looks/ │ │ │ │ ├── Filmic_False_Colour.spi3d │ │ │ │ ├── Filmic_to_0-35_1-30.spi1d │ │ │ │ ├── Filmic_to_0-48_1-09.spi1d │ │ │ │ ├── Filmic_to_0-60_1-04.spi1d │ │ │ │ ├── Filmic_to_0-70_1-03.spi1d │ │ │ │ ├── Filmic_to_0-85_1-011.spi1d │ │ │ │ ├── Filmic_to_0.99_1-0075.spi1d │ │ │ │ └── Filmic_to_1.20_1-00.spi1d │ │ │ ├── luts/ │ │ │ │ ├── Blackmagic_FilmWideGamut_Gen5_to_linear.spi1d │ │ │ │ ├── F-Log_to_Linear.spi1d │ │ │ │ ├── V-Log_to_linear.spi1d │ │ │ │ ├── V3_LogC_400_to_linear.spi1d │ │ │ │ ├── V3_LogC_800_to_linear.spi1d │ │ │ │ ├── desat65cube.spi3d │ │ │ │ ├── rec709_to_linear.spi1d │ │ │ │ └── sRGB_OETF_to_Linear.spi1d │ │ │ └── ocioconf.qrc.in │ │ ├── opengl/ │ │ │ ├── CMakeLists.txt │ │ │ ├── openglrenderer.cpp │ │ │ └── openglrenderer.h │ │ ├── playbackcache.cpp │ │ ├── playbackcache.h │ │ ├── previewaudiodevice.cpp │ │ ├── previewaudiodevice.h │ │ ├── previewautocacher.cpp │ │ ├── previewautocacher.h │ │ ├── projectcopier.cpp │ │ ├── projectcopier.h │ │ ├── rendercache.h │ │ ├── renderer.cpp │ │ ├── renderer.h │ │ ├── renderjobtracker.cpp │ │ ├── renderjobtracker.h │ │ ├── rendermanager.cpp │ │ ├── rendermanager.h │ │ ├── rendermodes.h │ │ ├── renderprocessor.cpp │ │ ├── renderprocessor.h │ │ ├── renderticket.cpp │ │ ├── renderticket.h │ │ ├── shadercode.h │ │ ├── subtitleparams.cpp │ │ ├── subtitleparams.h │ │ ├── texture.cpp │ │ ├── texture.h │ │ ├── videoparams.cpp │ │ └── videoparams.h │ ├── shaders/ │ │ ├── CMakeLists.txt │ │ ├── alphaover.frag │ │ ├── blur.frag │ │ ├── chromakey.frag │ │ ├── colordifferencekey.frag │ │ ├── colormanage.frag │ │ ├── cornerpin.frag │ │ ├── cornerpin.vert │ │ ├── crop.frag │ │ ├── crossdissolve.frag │ │ ├── default.frag │ │ ├── default.vert │ │ ├── deinterlace.frag │ │ ├── deinterlace2.frag │ │ ├── despill.frag │ │ ├── diptoblack.frag │ │ ├── dropshadow.frag │ │ ├── flip.frag │ │ ├── interlace.frag │ │ ├── invertrgb.frag │ │ ├── invertrgba.frag │ │ ├── mosaic.frag │ │ ├── multiply.frag │ │ ├── noise.frag │ │ ├── opacity.frag │ │ ├── opacity_rgb.frag │ │ ├── rgb.frag │ │ ├── rgbhistogram.frag │ │ ├── rgbhistogram.vert │ │ ├── rgbhistogram_secondary.frag │ │ ├── rgbwaveform.frag │ │ ├── rgbwaveform.vert │ │ ├── ripple.frag │ │ ├── shaders.qrc.in │ │ ├── shape.frag │ │ ├── solid.frag │ │ ├── stroke.frag │ │ ├── swirl.frag │ │ ├── tile.frag │ │ ├── wave.frag │ │ └── yuv2rgb.frag │ ├── task/ │ │ ├── CMakeLists.txt │ │ ├── conform/ │ │ │ ├── CMakeLists.txt │ │ │ ├── conform.cpp │ │ │ └── conform.h │ │ ├── customcache/ │ │ │ ├── CMakeLists.txt │ │ │ ├── customcachetask.cpp │ │ │ └── customcachetask.h │ │ ├── export/ │ │ │ ├── CMakeLists.txt │ │ │ ├── export.cpp │ │ │ └── export.h │ │ ├── precache/ │ │ │ ├── CMakeLists.txt │ │ │ ├── precachetask.cpp │ │ │ └── precachetask.h │ │ ├── project/ │ │ │ ├── CMakeLists.txt │ │ │ ├── import/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── import.cpp │ │ │ │ ├── import.h │ │ │ │ ├── importerrordialog.cpp │ │ │ │ └── importerrordialog.h │ │ │ ├── load/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── load.cpp │ │ │ │ ├── load.h │ │ │ │ ├── loadbasetask.cpp │ │ │ │ └── loadbasetask.h │ │ │ ├── loadotio/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── loadotio.cpp │ │ │ │ └── loadotio.h │ │ │ ├── save/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── save.cpp │ │ │ │ └── save.h │ │ │ └── saveotio/ │ │ │ ├── CMakeLists.txt │ │ │ ├── saveotio.cpp │ │ │ └── saveotio.h │ │ ├── render/ │ │ │ ├── CMakeLists.txt │ │ │ ├── render.cpp │ │ │ └── render.h │ │ ├── task.h │ │ ├── taskmanager.cpp │ │ └── taskmanager.h │ ├── timeline/ │ │ ├── CMakeLists.txt │ │ ├── timelinecommon.h │ │ ├── timelinecoordinate.cpp │ │ ├── timelinecoordinate.h │ │ ├── timelinemarker.cpp │ │ ├── timelinemarker.h │ │ ├── timelineundocommon.h │ │ ├── timelineundogeneral.cpp │ │ ├── timelineundogeneral.h │ │ ├── timelineundopointer.cpp │ │ ├── timelineundopointer.h │ │ ├── timelineundoripple.cpp │ │ ├── timelineundoripple.h │ │ ├── timelineundosplit.cpp │ │ ├── timelineundosplit.h │ │ ├── timelineundotrack.cpp │ │ ├── timelineundotrack.h │ │ ├── timelineundoworkarea.cpp │ │ ├── timelineundoworkarea.h │ │ ├── timelineworkarea.cpp │ │ └── timelineworkarea.h │ ├── tool/ │ │ ├── CMakeLists.txt │ │ └── tool.h │ ├── ts/ │ │ ├── CMakeLists.txt │ │ ├── ar_AR.ts │ │ ├── bs_BA.ts │ │ ├── cs_CZ.ts │ │ ├── de_DE.ts │ │ ├── en_US.ts │ │ ├── es_ES.ts │ │ ├── fr_FR.ts │ │ ├── hr_HR.ts │ │ ├── id_ID.ts │ │ ├── it_IT.ts │ │ ├── ja_JP.ts │ │ ├── pt_BR.ts │ │ ├── ru_RU.ts │ │ ├── sr_RS.ts │ │ ├── tr_TR.ts │ │ ├── translations.qrc.in │ │ ├── uk_UK.ts │ │ ├── zh_CN.ts │ │ └── zh_TW.ts │ ├── ui/ │ │ ├── CMakeLists.txt │ │ ├── colorcoding.cpp │ │ ├── colorcoding.h │ │ ├── cursors/ │ │ │ ├── CMakeLists.txt │ │ │ └── cursors.qrc │ │ ├── graphics/ │ │ │ ├── CMakeLists.txt │ │ │ └── graphics.qrc │ │ ├── humanstrings.cpp │ │ ├── humanstrings.h │ │ ├── icons/ │ │ │ ├── CMakeLists.txt │ │ │ ├── icons.cpp │ │ │ └── icons.h │ │ └── style/ │ │ ├── CMakeLists.txt │ │ ├── HOWTO.md │ │ ├── generate-style.sh │ │ ├── olive-dark/ │ │ │ ├── CMakeLists.txt │ │ │ ├── palette.ini │ │ │ ├── res.qrc.in │ │ │ └── style.css │ │ ├── olive-light/ │ │ │ ├── CMakeLists.txt │ │ │ ├── palette.ini │ │ │ ├── res.qrc.in │ │ │ ├── style.css │ │ │ └── svg/ │ │ │ └── convert-to-dark.sh │ │ ├── style.cpp │ │ └── style.h │ ├── undo/ │ │ ├── CMakeLists.txt │ │ ├── undocommand.cpp │ │ ├── undocommand.h │ │ ├── undostack.cpp │ │ └── undostack.h │ ├── version.cpp │ ├── version.h │ ├── widget/ │ │ ├── CMakeLists.txt │ │ ├── audiomonitor/ │ │ │ ├── CMakeLists.txt │ │ │ ├── audiomonitor.cpp │ │ │ └── audiomonitor.h │ │ ├── bezier/ │ │ │ ├── CMakeLists.txt │ │ │ ├── bezierwidget.cpp │ │ │ └── bezierwidget.h │ │ ├── clickablelabel/ │ │ │ ├── CMakeLists.txt │ │ │ ├── clickablelabel.cpp │ │ │ └── clickablelabel.h │ │ ├── collapsebutton/ │ │ │ ├── CMakeLists.txt │ │ │ ├── collapsebutton.cpp │ │ │ └── collapsebutton.h │ │ ├── colorbutton/ │ │ │ ├── CMakeLists.txt │ │ │ ├── colorbutton.cpp │ │ │ └── colorbutton.h │ │ ├── colorlabelmenu/ │ │ │ ├── CMakeLists.txt │ │ │ ├── colorcodingcombobox.cpp │ │ │ ├── colorcodingcombobox.h │ │ │ ├── colorlabelmenu.cpp │ │ │ └── colorlabelmenu.h │ │ ├── colorwheel/ │ │ │ ├── CMakeLists.txt │ │ │ ├── colorgradientwidget.cpp │ │ │ ├── colorgradientwidget.h │ │ │ ├── colorpreviewbox.cpp │ │ │ ├── colorpreviewbox.h │ │ │ ├── colorspacechooser.cpp │ │ │ ├── colorspacechooser.h │ │ │ ├── colorswatchchooser.cpp │ │ │ ├── colorswatchchooser.h │ │ │ ├── colorswatchwidget.cpp │ │ │ ├── colorswatchwidget.h │ │ │ ├── colorvalueswidget.cpp │ │ │ ├── colorvalueswidget.h │ │ │ ├── colorwheelwidget.cpp │ │ │ └── colorwheelwidget.h │ │ ├── columnedgridlayout/ │ │ │ ├── CMakeLists.txt │ │ │ ├── columnedgridlayout.cpp │ │ │ └── columnedgridlayout.h │ │ ├── curvewidget/ │ │ │ ├── CMakeLists.txt │ │ │ ├── curveview.cpp │ │ │ ├── curveview.h │ │ │ ├── curvewidget.cpp │ │ │ └── curvewidget.h │ │ ├── filefield/ │ │ │ ├── CMakeLists.txt │ │ │ ├── filefield.cpp │ │ │ └── filefield.h │ │ ├── flowlayout/ │ │ │ ├── CMakeLists.txt │ │ │ ├── flowlayout.cpp │ │ │ └── flowlayout.h │ │ ├── focusablelineedit/ │ │ │ ├── CMakeLists.txt │ │ │ ├── focusablelineedit.cpp │ │ │ └── focusablelineedit.h │ │ ├── handmovableview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── handmovableview.cpp │ │ │ └── handmovableview.h │ │ ├── history/ │ │ │ ├── CMakeLists.txt │ │ │ ├── historywidget.cpp │ │ │ └── historywidget.h │ │ ├── keyframeview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── keyframeview.cpp │ │ │ ├── keyframeview.h │ │ │ ├── keyframeviewinputconnection.cpp │ │ │ ├── keyframeviewinputconnection.h │ │ │ ├── keyframeviewundo.cpp │ │ │ └── keyframeviewundo.h │ │ ├── manageddisplay/ │ │ │ ├── CMakeLists.txt │ │ │ ├── manageddisplay.cpp │ │ │ └── manageddisplay.h │ │ ├── menu/ │ │ │ ├── CMakeLists.txt │ │ │ ├── menu.cpp │ │ │ ├── menu.h │ │ │ ├── menushared.cpp │ │ │ └── menushared.h │ │ ├── multicam/ │ │ │ ├── CMakeLists.txt │ │ │ ├── multicamdisplay.cpp │ │ │ ├── multicamdisplay.h │ │ │ ├── multicamwidget.cpp │ │ │ └── multicamwidget.h │ │ ├── nodecombobox/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodecombobox.cpp │ │ │ └── nodecombobox.h │ │ ├── nodeparamview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodeparamview.cpp │ │ │ ├── nodeparamview.h │ │ │ ├── nodeparamviewarraywidget.cpp │ │ │ ├── nodeparamviewarraywidget.h │ │ │ ├── nodeparamviewconnectedlabel.cpp │ │ │ ├── nodeparamviewconnectedlabel.h │ │ │ ├── nodeparamviewcontext.cpp │ │ │ ├── nodeparamviewcontext.h │ │ │ ├── nodeparamviewdockarea.cpp │ │ │ ├── nodeparamviewdockarea.h │ │ │ ├── nodeparamviewitem.cpp │ │ │ ├── nodeparamviewitem.h │ │ │ ├── nodeparamviewitembase.cpp │ │ │ ├── nodeparamviewitembase.h │ │ │ ├── nodeparamviewitemtitlebar.cpp │ │ │ ├── nodeparamviewitemtitlebar.h │ │ │ ├── nodeparamviewkeyframecontrol.cpp │ │ │ ├── nodeparamviewkeyframecontrol.h │ │ │ ├── nodeparamviewtextedit.cpp │ │ │ ├── nodeparamviewtextedit.h │ │ │ ├── nodeparamviewwidgetbridge.cpp │ │ │ └── nodeparamviewwidgetbridge.h │ │ ├── nodetableview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodetableview.cpp │ │ │ ├── nodetableview.h │ │ │ ├── nodetablewidget.cpp │ │ │ └── nodetablewidget.h │ │ ├── nodetreeview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodetreeview.cpp │ │ │ └── nodetreeview.h │ │ ├── nodevaluetree/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodevaluetree.cpp │ │ │ └── nodevaluetree.h │ │ ├── nodeview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nodeview.cpp │ │ │ ├── nodeview.h │ │ │ ├── nodeviewcommon.h │ │ │ ├── nodeviewcontext.cpp │ │ │ ├── nodeviewcontext.h │ │ │ ├── nodeviewedge.cpp │ │ │ ├── nodeviewedge.h │ │ │ ├── nodeviewitem.cpp │ │ │ ├── nodeviewitem.h │ │ │ ├── nodeviewitemconnector.cpp │ │ │ ├── nodeviewitemconnector.h │ │ │ ├── nodeviewminimap.cpp │ │ │ ├── nodeviewminimap.h │ │ │ ├── nodeviewscene.cpp │ │ │ ├── nodeviewscene.h │ │ │ ├── nodeviewtoolbar.cpp │ │ │ ├── nodeviewtoolbar.h │ │ │ ├── nodewidget.cpp │ │ │ └── nodewidget.h │ │ ├── path/ │ │ │ ├── CMakeLists.txt │ │ │ ├── pathwidget.cpp │ │ │ └── pathwidget.h │ │ ├── pixelsampler/ │ │ │ ├── CMakeLists.txt │ │ │ ├── pixelsampler.cpp │ │ │ └── pixelsampler.h │ │ ├── playbackcontrols/ │ │ │ ├── CMakeLists.txt │ │ │ ├── dragbutton.cpp │ │ │ ├── dragbutton.h │ │ │ ├── playbackcontrols.cpp │ │ │ └── playbackcontrols.h │ │ ├── projectexplorer/ │ │ │ ├── CMakeLists.txt │ │ │ ├── projectexplorer.cpp │ │ │ ├── projectexplorer.h │ │ │ ├── projectexplorericonview.cpp │ │ │ ├── projectexplorericonview.h │ │ │ ├── projectexplorericonviewitemdelegate.cpp │ │ │ ├── projectexplorericonviewitemdelegate.h │ │ │ ├── projectexplorerlistview.cpp │ │ │ ├── projectexplorerlistview.h │ │ │ ├── projectexplorerlistviewbase.cpp │ │ │ ├── projectexplorerlistviewbase.h │ │ │ ├── projectexplorerlistviewitemdelegate.cpp │ │ │ ├── projectexplorerlistviewitemdelegate.h │ │ │ ├── projectexplorernavigation.cpp │ │ │ ├── projectexplorernavigation.h │ │ │ ├── projectexplorertreeview.cpp │ │ │ ├── projectexplorertreeview.h │ │ │ ├── projectexplorerundo.h │ │ │ ├── projectviewmodel.cpp │ │ │ └── projectviewmodel.h │ │ ├── projecttoolbar/ │ │ │ ├── CMakeLists.txt │ │ │ ├── projecttoolbar.cpp │ │ │ └── projecttoolbar.h │ │ ├── resizablescrollbar/ │ │ │ ├── CMakeLists.txt │ │ │ ├── resizablescrollbar.cpp │ │ │ ├── resizablescrollbar.h │ │ │ ├── resizabletimelinescrollbar.cpp │ │ │ └── resizabletimelinescrollbar.h │ │ ├── scope/ │ │ │ ├── CMakeLists.txt │ │ │ ├── histogram/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── histogram.cpp │ │ │ │ └── histogram.h │ │ │ ├── scopebase/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── scopebase.cpp │ │ │ │ └── scopebase.h │ │ │ └── waveform/ │ │ │ ├── CMakeLists.txt │ │ │ ├── waveform.cpp │ │ │ └── waveform.h │ │ ├── slider/ │ │ │ ├── CMakeLists.txt │ │ │ ├── base/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── decimalsliderbase.cpp │ │ │ │ ├── decimalsliderbase.h │ │ │ │ ├── numericsliderbase.cpp │ │ │ │ ├── numericsliderbase.h │ │ │ │ ├── sliderbase.cpp │ │ │ │ ├── sliderbase.h │ │ │ │ ├── sliderlabel.cpp │ │ │ │ ├── sliderlabel.h │ │ │ │ ├── sliderladder.cpp │ │ │ │ └── sliderladder.h │ │ │ ├── floatslider.cpp │ │ │ ├── floatslider.h │ │ │ ├── integerslider.cpp │ │ │ ├── integerslider.h │ │ │ ├── rationalslider.cpp │ │ │ ├── rationalslider.h │ │ │ ├── stringslider.cpp │ │ │ └── stringslider.h │ │ ├── standardcombos/ │ │ │ ├── CMakeLists.txt │ │ │ ├── channellayoutcombobox.h │ │ │ ├── frameratecombobox.h │ │ │ ├── interlacedcombobox.h │ │ │ ├── pixelaspectratiocombobox.h │ │ │ ├── pixelformatcombobox.h │ │ │ ├── sampleformatcombobox.h │ │ │ ├── sampleratecombobox.h │ │ │ ├── standardcombos.h │ │ │ └── videodividercombobox.h │ │ ├── taskview/ │ │ │ ├── CMakeLists.txt │ │ │ ├── elapsedcounterwidget.cpp │ │ │ ├── elapsedcounterwidget.h │ │ │ ├── taskview.cpp │ │ │ ├── taskview.h │ │ │ ├── taskviewitem.cpp │ │ │ └── taskviewitem.h │ │ ├── timebased/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timebasedview.cpp │ │ │ ├── timebasedview.h │ │ │ ├── timebasedviewselectionmanager.cpp │ │ │ ├── timebasedviewselectionmanager.h │ │ │ ├── timebasedwidget.cpp │ │ │ ├── timebasedwidget.h │ │ │ ├── timescaledobject.cpp │ │ │ └── timescaledobject.h │ │ ├── timelinewidget/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timelineandtrackview.cpp │ │ │ ├── timelineandtrackview.h │ │ │ ├── timelinewidget.cpp │ │ │ ├── timelinewidget.h │ │ │ ├── timelinewidgetselections.cpp │ │ │ ├── timelinewidgetselections.h │ │ │ ├── tool/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── add.cpp │ │ │ │ ├── add.h │ │ │ │ ├── beam.cpp │ │ │ │ ├── beam.h │ │ │ │ ├── edit.cpp │ │ │ │ ├── edit.h │ │ │ │ ├── import.cpp │ │ │ │ ├── import.h │ │ │ │ ├── pointer.cpp │ │ │ │ ├── pointer.h │ │ │ │ ├── razor.cpp │ │ │ │ ├── razor.h │ │ │ │ ├── record.cpp │ │ │ │ ├── record.h │ │ │ │ ├── ripple.cpp │ │ │ │ ├── ripple.h │ │ │ │ ├── rolling.cpp │ │ │ │ ├── rolling.h │ │ │ │ ├── slide.cpp │ │ │ │ ├── slide.h │ │ │ │ ├── slip.cpp │ │ │ │ ├── slip.h │ │ │ │ ├── tool.cpp │ │ │ │ ├── tool.h │ │ │ │ ├── trackselect.cpp │ │ │ │ ├── trackselect.h │ │ │ │ ├── transition.cpp │ │ │ │ ├── transition.h │ │ │ │ ├── zoom.cpp │ │ │ │ └── zoom.h │ │ │ ├── trackview/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── trackview.cpp │ │ │ │ ├── trackview.h │ │ │ │ ├── trackviewitem.cpp │ │ │ │ ├── trackviewitem.h │ │ │ │ ├── trackviewsplitter.cpp │ │ │ │ └── trackviewsplitter.h │ │ │ └── view/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timelineview.cpp │ │ │ ├── timelineview.h │ │ │ ├── timelineviewghostitem.h │ │ │ └── timelineviewmouseevent.h │ │ ├── timeruler/ │ │ │ ├── CMakeLists.txt │ │ │ ├── seekablewidget.cpp │ │ │ ├── seekablewidget.h │ │ │ ├── timeruler.cpp │ │ │ └── timeruler.h │ │ ├── timetarget/ │ │ │ ├── CMakeLists.txt │ │ │ ├── timetarget.cpp │ │ │ └── timetarget.h │ │ ├── toolbar/ │ │ │ ├── CMakeLists.txt │ │ │ ├── toolbar.cpp │ │ │ ├── toolbar.h │ │ │ ├── toolbarbutton.cpp │ │ │ └── toolbarbutton.h │ │ └── viewer/ │ │ ├── CMakeLists.txt │ │ ├── audiowaveformview.cpp │ │ ├── audiowaveformview.h │ │ ├── footageviewer.cpp │ │ ├── footageviewer.h │ │ ├── viewer.cpp │ │ ├── viewer.h │ │ ├── viewerdisplay.cpp │ │ ├── viewerdisplay.h │ │ ├── viewerplaybacktimer.cpp │ │ ├── viewerplaybacktimer.h │ │ ├── viewerpreventsleep.cpp │ │ ├── viewerpreventsleep.h │ │ ├── viewerqueue.h │ │ ├── viewersafemargininfo.h │ │ ├── viewersizer.cpp │ │ ├── viewersizer.h │ │ ├── viewertexteditor.cpp │ │ ├── viewertexteditor.h │ │ ├── viewerwindow.cpp │ │ └── viewerwindow.h │ └── window/ │ ├── CMakeLists.txt │ └── mainwindow/ │ ├── CMakeLists.txt │ ├── mainmenu.cpp │ ├── mainmenu.h │ ├── mainstatusbar.cpp │ ├── mainstatusbar.h │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindowlayoutinfo.cpp │ ├── mainwindowlayoutinfo.h │ ├── mainwindowundo.cpp │ └── mainwindowundo.h ├── cmake/ │ ├── FindFFMPEG.cmake │ ├── FindGoogleCrashpad.cmake │ ├── FindOlive.cmake │ ├── FindOpenColorIO.cmake │ ├── FindOpenEXR.cmake │ ├── FindOpenImageIO.cmake │ ├── FindOpenTimelineIO.cmake │ ├── FindPortAudio.cmake │ └── Sanitizers.cmake ├── docker/ │ ├── README.md │ ├── ci-common/ │ │ └── Dockerfile │ ├── ci-crashpad/ │ │ └── Dockerfile │ ├── ci-ffmpeg/ │ │ └── Dockerfile │ ├── ci-ocio/ │ │ └── Dockerfile │ ├── ci-oiio/ │ │ └── Dockerfile │ ├── ci-olive/ │ │ └── Dockerfile │ ├── ci-otio/ │ │ └── Dockerfile │ └── scripts/ │ ├── base/ │ │ └── install_cmake.sh │ ├── build_crashpad.sh │ ├── build_ffmpeg.sh │ ├── build_ocio.sh │ ├── build_oiio.sh │ ├── build_olive.sh │ ├── build_otio.sh │ └── common/ │ ├── before_build.sh │ ├── copy_new_files.sh │ └── install_yumpackages.sh ├── ext/ │ └── CMakeLists.txt ├── tests/ │ ├── CMakeLists.txt │ ├── compositing/ │ │ ├── CMakeLists.txt │ │ └── compositing-tests.cpp │ ├── general/ │ │ ├── CMakeLists.txt │ │ └── common-tests.cpp │ ├── testutil.h │ └── timeline/ │ ├── CMakeLists.txt │ └── timeline-tests.cpp └── update-copyright.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: true DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^' Priority: 2 - Regex: '^<.*\.h>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Never ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left RawStringFormats: - Language: Cpp Delimiters: - cc - CC - cpp - Cpp - CPP - 'c++' - 'C++' CanonicalDelimiter: '' BasedOnStyle: google - Language: TextProto Delimiters: - pb - PB - proto - PROTO EnclosingFunctions: - EqualsProto - EquivToProto - PARSE_PARTIAL_TEXT_PROTO - PARSE_TEST_PROTO - PARSE_TEXT_PROTO - ParseTextOrDie - ParseTextProtoOrDie CanonicalDelimiter: '' BasedOnStyle: google ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 8 UseTab: Never ... ================================================ FILE: .gitattributes ================================================ # Default behavior * text=auto # Enforce LF line endings on source files *.h text eol=lf *.cpp text eol=lf *.sh text eol=lf *.desktop text eol=lf ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: olivevideoeditor open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/00-olive_unsupported.md ================================================ --- name: Legacy Olive 0.1.x issue ⛔ about: >- Olive 0.1 is no longer supported and issues made about it will be automatically closed. title: '[UNSUPPORTED] ' labels: 'Legacy (Unsupported)' assignees: '' --- # Olive 0.1 is unsupported Unfortunately no one is supporting Olive 0.1 at this time. Any reports pertaining to it will be closed to keep the issue tracker focused on supported versions. ================================================ FILE: .github/ISSUE_TEMPLATE/01-crash_issue.md ================================================ --- name: Crash about: Report a fatal crash that resulted in Olive unexpectedly closing. title: '[CRASH] ' labels: 'Crash, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Steps to Reproduce** 1. 2. 3.
Crash Report




**Additional Information** ================================================ FILE: .github/ISSUE_TEMPLATE/50-build_issue.md ================================================ --- name: Build/Packaging about: >- Report an issue related to compiling or packaging. Note that we do not officially support custom build configurations and may not address issues involving them. title: '[BUILD] ' labels: 'Building/Packaging, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-cache_issue.md ================================================ --- name: Disk Cache about: >- Report an issue related to the disk cache system, including failure to cache, frames being set to appear at the wrong time, disk space or memory usage issues, etc. title: '[CACHE] ' labels: 'Disk Cache, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-codec_issue.md ================================================ --- name: Codec about: >- Report an issue related to codec handling, including importing footage or any footage usage issues while editing. title: '[CODEC] ' labels: 'Codec, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-color_issue.md ================================================ --- name: Color Management about: >- Report an issue related to the management of pixels and color, including inaccurate results, color inconsistencies, etc. title: '[COLOR] ' labels: 'Color Management, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-editing_issue.md ================================================ --- name: Timeline/Editing about: >- Report an issue related to the overall editing experience, including usage of the timeline, interchange, synchronization, multi-camera support, etc. title: '[EDIT] ' labels: 'Timeline/Editing, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-export_issue.md ================================================ --- name: Export about: >- Report an issue related to exporting videos from Olive, including errors while exporting, issues with the resulting video, etc. title: '[EXPORT] ' labels: 'Export, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-node_issue.md ================================================ --- name: Node/Compositing about: >- Report an issue related to the node-based compositing system, including node graph usability issues, issues working with node effects, issues with composited output, etc. title: '[NODES] ' labels: 'Nodes/Compositing, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-playback_issue.md ================================================ --- name: Playback about: >- Report an issue related to the playback of video or audio, including laggy or inconsistent playback, failure to playback, etc. title: '[PLAYBACK] ' labels: 'Playback, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-project_issue.md ================================================ --- name: Project Management about: >- Report an issue related to project management, including working with and organizing imported files. title: '[PROJECT] ' labels: 'Project, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-renderer_issue.md ================================================ --- name: Renderer about: >- Report an issue related to rendering, including corrupted frames, incorrect render results, unexpected black or white frames, etc. title: '[RENDER] ' labels: 'Renderer, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/50-ui_issue.md ================================================ --- name: User Interface about: >- Report an issue related to general user interface usability, including behavior issues, usability issues, look and feel, etc. title: '[UI] ' labels: 'User Interface, Triage' assignees: '' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Community Support url: https://discordapp.com/invite/4Ae9KZn about: Join the Olive Discord server ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ --- name: Default issue template labels: 'Triage' --- **Commit Hash** **Platform** **Summary** **Additional Information / Output** ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - master paths-ignore: - '.github/ISSUE_TEMPLATE/**' - '.github/FUNDING.yml' - 'docker/**' - 'CONTRIBUTING.md' - 'README.md' pull_request: paths-ignore: - '.github/ISSUE_TEMPLATE/**' - '.github/FUNDING.yml' - 'app/ts/*.ts' - 'docker/**' - 'CONTRIBUTING.md' - 'README.md' env: DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60 UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60 CMAKE_ARGS: -DUSE_WERROR=ON -DBUILD_TESTS=ON jobs: linux: strategy: fail-fast: false matrix: include: # Clang should catch any issues that GCC would also report #- build-type: RelWithDebInfo # cc-compiler: gcc # cxx-compiler: g++ # compiler-name: GCC 9.3.1 # cmake-gen: Ninja # os-name: Linux (CentOS 7) - build-type: RelWithDebInfo cc-compiler: clang cxx-compiler: clang++ compiler-name: Clang 10.0.0 cmake-gen: Ninja os-name: Linux (CentOS 7) name: | ${{ matrix.os-name }} <${{ matrix.compiler-name }}, ${{ matrix.build-type }}, ${{ matrix.cmake-gen }}> runs-on: ubuntu-20.04 container: image: olivevideoeditor/ci-olive:2022.3 steps: - name: Install Node.js 16 run: | $DOWNLOAD_TOOL https://nodejs.org/dist/v16.20.2/node-v16.20.2-linux-x64.tar.xz tar -xf node-v16.20.2-linux-x64.tar.xz mkdir -p ${{ github.workspace }} cp -a ./node-v16.20.2-linux-x64/bin/node ${{ github.workspace }}/node16 ${{ github.workspace }}/node16 --version - name: Checkout Source Code env: INPUT_SUBMODULES: 'true' INPUT_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir checkout-v3 cd checkout-v3 $DOWNLOAD_TOOL https://raw.githubusercontent.com/actions/checkout/refs/tags/v3/dist/index.js $DOWNLOAD_TOOL https://raw.githubusercontent.com/actions/checkout/refs/tags/v3/dist/problem-matcher.json cd .. ${{ github.workspace }}/node16 ./checkout-v3/index.js #uses: actions/checkout@v3 #with: # submodules: true - name: Generate Patreon List env: PATREON_KEY: ${{ secrets.PATREON_KEY }} run: | pip3 install requests cd $GITHUB_WORKSPACE/app/dialog/about python3 patreon.py if: github.event_name == 'push' continue-on-error: true - name: Configure CMake run: | mkdir build cd build cmake .. -G "${{ matrix.cmake-gen }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \ -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" \ $CMAKE_ARGS - name: Build working-directory: build run: | cmake --build . - name: Test working-directory: build run: | ctest -C ${{ matrix.build-type }} -V - name: Create Package id: package working-directory: build env: ARCH: x86_64 run: | # Create install tree cmake --install app --prefix appdir/usr # Inject custom AppRun (linuxdeployqt won't replace if it already exists) cp $GITHUB_WORKSPACE/app/packaging/linux/AppRun appdir # Process AppDir /usr/local/linuxdeployqt-x86_64.AppImage \ appdir/usr/share/applications/org.olivevideoeditor.Olive.desktop \ -exclude-libs=libQt5Pdf.so,libQt5Qml.so,libQt5QmlModels.so,libQt5Quick.so,libQt5VirtualKeyboard.so \ -bundle-non-qt-libs \ -executable=appdir/usr/bin/crashpad_handler \ -executable=appdir/usr/bin/minidump_stackwalk \ -executable=appdir/usr/bin/olive-crashhandler \ --appimage-extract-and-run # Dump Crashpad symbols dump_syms appdir/usr/bin/olive-editor > olive-editor.sym # HACK: For some reason, minidump_stackwalk reads identifier as all 0s SYM_DIR=appdir/usr/share/olive-editor/symbols/olive-editor/000000000000000000000000000000000 mkdir -p "$SYM_DIR" mv olive-editor.sym "$SYM_DIR" # Package AppImage $DOWNLOAD_TOOL https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage VERSION=${GITHUB_SHA::8} ./appimagetool-x86_64.AppImage appdir --appimage-extract-and-run # Set env variables filename=$(echo Olive*.AppImage) pkgname="${filename/x86_64/Linux-x86_64}" mv "${filename}" "${pkgname}" basename="${filename%.*}" echo "pkgname=${pkgname}" >> $GITHUB_OUTPUT echo "artifact=${basename/x86_64/Linux-x86_64-${{ matrix.cc-compiler }}}" >> $GITHUB_OUTPUT - uses: Simran-B/actions-export-envs@v1.2.0 id: envs - name: Upload Artifact to GitHub env: INPUT_NAME: '${{ steps.package.outputs.artifact }}' INPUT_PATH: 'build/Olive*.AppImage' INPUT_INCLUDE-HIDDEN-FILES: 'true' INPUT_IF-NO-FILES-FOUND: error INPUT_TOKEN: ${{ secrets.GITHUB_TOKEN }} ACTIONS_RUNTIME_TOKEN: ${{ steps.envs.outputs.ACTIONS_RUNTIME_TOKEN }} ACTIONS_RUNTIME_URL: ${{ steps.envs.outputs.ACTIONS_RUNTIME_URL }} run: | mkdir upload-artifact-v3 cd upload-artifact-v3 $DOWNLOAD_TOOL https://raw.githubusercontent.com/actions/upload-artifact/refs/tags/v3/dist/index.js cd .. ${{ github.workspace }}/node16 ./upload-artifact-v3/index.js continue-on-error: true #uses: actions/upload-artifact@v3 #with: # name: ${{ steps.package.outputs.artifact }} # path: build/Olive*.AppImage windows: strategy: matrix: include: - build-type: RelWithDebInfo compiler-name: MSVC 16.x os-name: Windows os-arch: x86_64 os: windows-2019 cmake-gen: Ninja name: | ${{ matrix.os-name }} <${{ matrix.compiler-name }}, ${{ matrix.build-type }}, ${{ matrix.cmake-gen }}> runs-on: ${{ matrix.os }} steps: - name: Checkout Source Code uses: actions/checkout@v4 with: submodules: true show-progress: false # HACK: Override CMake version to avoid an FFmpeg discovery problem - name: Setup CMake uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: '3.27.x' - name: CMake version run: cmake --version - name: Automatically Generate Package Name shell: bash env: PLATFORM: ${{ matrix.os-name }} ARCH: ${{ matrix.os-arch }} run: | echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV - name: Create Build Folder run: | cmake -E make_directory ${{ runner.workspace }}/build - name: Enable Developer Command Prompt (Windows) uses: ilammy/msvc-dev-cmd@v1 - name: Acquire Dependencies shell: bash working-directory: ${{ runner.workspace }} run: | $DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-win32-Release.tar.gz tar xzf olive-dep-win32-Release.tar.gz echo "$(pwd -W)/install" >> $GITHUB_PATH echo "$(pwd -W)/install/bin" >> $GITHUB_PATH - name: Acquire Google Crashpad shell: bash working-directory: ${{ runner.workspace }} run: | $DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-win32-RelWithDebInfo.tar.gz tar xzf crashpad-win32-RelWithDebInfo.tar.gz echo "$(pwd -W)/install" >> $GITHUB_PATH echo "$(pwd -W)/install/bin" >> $GITHUB_PATH echo "$(pwd -W)/install/crashpad" >> $GITHUB_PATH - name: Generate Patreon List shell: bash env: PATREON_KEY: ${{ secrets.PATREON_KEY }} run: | pip3 install requests cd $GITHUB_WORKSPACE/app/dialog/about python3 patreon.py if: github.event_name == 'push' continue-on-error: true - name: Configure CMake shell: bash working-directory: ${{ runner.workspace }}/build run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \ $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build shell: bash run: | cmake --build . #- name: Test # working-directory: ${{ runner.workspace }}/build # shell: bash # run: | # ctest -C ${{ matrix.build-type }} -V - name: Create Package working-directory: ${{ runner.workspace }}/build shell: bash env: ORIGINAL_WORKSPACE: ${{ runner.workspace }} run: | mkdir olive-editor cp app/olive-editor.exe olive-editor cp app/crashhandler/olive-crashhandler.exe olive-editor cp app/olive-editor.pdb olive-editor windeployqt --no-angle olive-editor/olive-crashhandler.exe windeployqt --no-angle olive-editor/olive-editor.exe cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/*.dll olive-editor cp $(cygpath $ORIGINAL_WORKSPACE)/install/crashpad/out/Default/crashpad_handler.exe olive-editor cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/minidump_stackwalk.exe olive-editor - name: Export Crashpad symbols working-directory: ${{ runner.workspace }}/build shell: bash run: | curl -fLSs https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true > dump_syms.exe ./dump_syms app/olive-editor.pdb > olive-editor.sym SYM_HEADER=($(head -n 1 olive-editor.sym)) # Read first line of symbol file SYM_DIR=olive-editor/symbols/olive-editor.pdb/${SYM_HEADER[3]} mkdir -p "$SYM_DIR" mv olive-editor.sym "$SYM_DIR" - name: Deploy Packages working-directory: ${{ runner.workspace }}/build shell: bash run: | # Create Installer Executable #$DOWNLOAD_TOOL http://web.archive.org/web/20210226132532/http://download.microsoft.com/download/3/2/2/3224B87F-CFA0-4E70-BDA3-3DE650EFEBA5/vcredist_x64.exe cp $(cygpath $GITHUB_WORKSPACE)/app/packaging/windows/nsis/* . cp $(cygpath $GITHUB_WORKSPACE)/LICENSE . $DOWNLOAD_TOOL https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z 7z e ShellExecAsUser_amd64-Unicode.7z Plugins/x86-unicode/ShellExecAsUser.dll makensis -V4 -DX64 "-XOutFile $PKGNAME.exe" "-X!AddPluginDir /x86-unicode $(pwd -W)" olive.nsi # Create Portable ZIP echo -n > olive-editor/portable mkdir deploy mv olive-editor deploy - name: Upload Installer Artifact to GitHub uses: actions/upload-artifact@v3 with: name: ${{ env.PKGNAME }}-Installer path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.exe - name: Upload Portable Artifact to GitHub uses: actions/upload-artifact@v3 with: name: ${{ env.PKGNAME }}-Portable path: ${{ runner.workspace }}/build/deploy macos: strategy: fail-fast: false matrix: include: - build-type: RelWithDebInfo compiler-name: Clang LLVM os-name: macOS os-arch: x86_64 os: macos-13 cmake-gen: Ninja min-deploy: 10.13 - build-type: RelWithDebInfo compiler-name: Clang LLVM os-name: macOS os-arch: arm64 os: macos-13 cmake-gen: Ninja min-deploy: 11.0 env: DEP_LOCATION: /opt/olive-editor name: | ${{ matrix.os-name }} <${{ matrix.os-arch }}, ${{ matrix.compiler-name }}, ${{ matrix.build-type }}, ${{ matrix.cmake-gen }}> runs-on: ${{ matrix.os }} steps: - name: Checkout Source Code uses: actions/checkout@v4 with: submodules: true show-progress: false - name: Automatically Generate Package Name shell: bash env: PLATFORM: ${{ matrix.os-name }} ARCH: ${{ matrix.os-arch }} run: | echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV - name: Create Build Folder run: | cmake -E make_directory ${{ runner.workspace }}/build - name: Acquire Dependencies shell: bash working-directory: ${{ runner.workspace }} run: | $DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-${{ matrix.os-arch }}.tar.gz sudo tar xzf olive-dep-mac-${{ matrix.os-arch }}.tar.gz -C / - name: Acquire Google Crashpad shell: bash working-directory: ${{ runner.workspace }} run: | $DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-${{ matrix.os-arch }}.tar.gz sudo tar xzf crashpad-mac-${{ matrix.os-arch }}.tar.gz -C / - name: Generate Patreon List env: PATREON_KEY: ${{ secrets.PATREON_KEY }} run: | pip3 install requests cd $GITHUB_WORKSPACE/app/dialog/about python3 patreon.py if: github.event_name == 'push' continue-on-error: true - name: Install Ninja shell: bash run: | brew update brew install ninja - name: Configure CMake shell: bash working-directory: ${{ runner.workspace }}/build run: | PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \ -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" \ $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build shell: bash run: | cmake --build . - name: Test working-directory: ${{ runner.workspace }}/build shell: bash run: | ctest -C ${{ matrix.build-type }} -V if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners - name: Bundle Application working-directory: ${{ runner.workspace }}/build shell: bash run: | BUNDLE_NAME="Olive.app" brew install dylibbundler mkdir deploy-${{ matrix.os-arch }} cd deploy-${{ matrix.os-arch }} mv ../app/$BUNDLE_NAME . mkdir $BUNDLE_NAME/Contents/Frameworks # Copy Qt frameworks and plugins cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" \ -x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler -x $BUNDLE_NAME/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork # HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64, # but perhaps I'll bring it up with them soon. cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib # Crashpad symbols $DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]} mkdir -p "$SYM_DIR" mv Olive.sym "$SYM_DIR" - name: Sign Application working-directory: ${{ runner.workspace }}/build/deploy-${{ matrix.os-arch }} shell: bash if: github.event_name == 'push' env: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | BUNDLE_NAME="Olive.app" # Install certificate CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db # import certificate from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode > $CERTIFICATE_PATH # create temporary keychain security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # import certificate to keychain security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH # HACK: Remove unsignable frameworks rm -r $BUNDLE_NAME/Contents/Frameworks/QtUiPlugin.framework if [ "${{ matrix.os-arch }}" == "arm64" ] then rm -r $BUNDLE_NAME/Contents/Frameworks/QtZlib.framework fi # Sign application codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME - name: Deploy shell: bash working-directory: ${{ runner.workspace }}/build/deploy-${{ matrix.os-arch }} run: | ln -s /Applications Applications cd .. hdiutil create img-${{ matrix.os-arch }}.dmg -volname Olive -fs HFS+ -srcfolder deploy-${{ matrix.os-arch }} hdiutil convert img-${{ matrix.os-arch }}.dmg -format UDZO -o $PKGNAME.dmg - name: Upload Artifact to GitHub uses: actions/upload-artifact@v3 continue-on-error: true with: name: ${{ env.PKGNAME }} path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.dmg ================================================ FILE: .gitignore ================================================ # CMake artifacts /build*/ # Doxygen /docs/ # Visual Studio (Code) .localhistory/ .history/ .vscode/ .vs/ /out/ CmakeSettings.json *.code-workspace # clangd's index and likely other things that need not be in the repository .cache/ # macOS General .DS_Store .AppleDouble .LSOverride # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Windows folder config file [Dd]esktop.ini # Recycle Bin used on file shares (Windows) $RECYCLE.BIN/ # Windows shortcuts *.lnk # # Qt ignores taken from https://github.com/github/gitignore/blob/master/Qt.gitignore # # C++ objects and libs *.slo *.lo *.o *.a *.la *.lai *.so *.so.* *.dll *.dylib # Qt-es object_script.*.Release object_script.*.Debug *_plugin_import.cpp /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp moc_*.h qrc_*.cpp ui_*.h *.qmlc *.jsc Makefile* *build-* *.qm *.prl # Qt unit tests target_wrapper.* # QtCreator *.autosave # QtCreator Qml *.qmlproject.user *.qmlproject.user.* # QtCreator CMake CMakeLists.txt.user* # QtCreator 4.8< compilation database compile_commands.json # Hand-written compilation database listing compilation flags for clangd to use when parsing the code, similarly to the compilation database above # https://clangd.llvm.org/design/compile-commands#where-do-compile-commands-come-from compile_flags.txt # QtCreator local machine specific files for imported projects *creator.user* *_qmlcache.qrc ================================================ FILE: .gitmodules ================================================ [submodule "ext/core"] path = ext/core url = https://github.com/olive-editor/core [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets url = https://github.com/olive-editor/KDDockWidgets.git ================================================ FILE: CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2023 Olive Studios LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . cmake_minimum_required(VERSION 3.13 FATAL_ERROR) project(olive-editor VERSION 0.2.0 LANGUAGES CXX) option(BUILD_QT6 "Build with Qt 6 over 5 (experimental)" OFF) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) option(BUILD_TESTS "Build unit tests" OFF) option(USE_WERROR "Error on compile warning" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) # Generates a compile_commands.json in the build dir, link that to the repo # root to enrich your IDE with clangd language server protocol functionalities set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Sanitizers add_library(olive-sanitizers INTERFACE) include(cmake/Sanitizers.cmake) enable_sanitizers(olive-sanitizers) list(APPEND OLIVE_LIBRARIES olive-sanitizers) # Set compiler options if(MSVC) set(OLIVE_COMPILE_OPTIONS /wd4267 /wd4244 /experimental:external /external:anglebrackets /external:W0 "$<$:/O2>" "$<$:/MP>" ) if (USE_WERROR) list(APPEND OLIVE_COMPILE_OPTIONS "/WX") endif() else() set(OLIVE_COMPILE_OPTIONS "$<$:-O2>" -Wuninitialized -pedantic-errors -Wall -Wextra -Wno-unused-parameter -Wshadow ) if (USE_WERROR) list(APPEND OLIVE_COMPILE_OPTIONS "-Werror") endif() endif() set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS) if (WIN32) list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") # Link OpenGL if(UNIX AND NOT APPLE AND NOT DEFINED OpenGL_GL_PREFERENCE) set(OpenGL_GL_PREFERENCE LEGACY) endif() find_package(OpenGL REQUIRED) list(APPEND OLIVE_LIBRARIES OpenGL::GL) # Link OpenColorIO find_package(OpenColorIO 2.1.1 REQUIRED) list(APPEND OLIVE_LIBRARIES ${OCIO_LIBRARIES}) list(APPEND OLIVE_INCLUDE_DIRS ${OCIO_INCLUDE_DIRS}) # Link OpenImageIO find_package(OpenImageIO 2.1.12 REQUIRED) list(APPEND OLIVE_LIBRARIES ${OIIO_LIBRARIES}) list(APPEND OLIVE_INCLUDE_DIRS ${OIIO_INCLUDE_DIRS}) # Link OpenEXR find_package(OpenEXR REQUIRED) list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES}) list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES}) # Link Olive list(APPEND OLIVE_LIBRARIES olivecore) list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ext/core/include) # Link Qt set(QT_LIBRARIES Core Gui Widgets OpenGL LinguistTools Concurrent ) if (UNIX AND NOT APPLE) list(APPEND QT_LIBRARIES DBus) endif() if (BUILD_QT6) set(QT_NAME Qt6) else() set(QT_NAME Qt5) endif() find_package(QT NAMES ${QT_NAME} REQUIRED COMPONENTS ${QT_LIBRARIES} OPTIONAL_COMPONENTS Network ) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${QT_LIBRARIES} OPTIONAL_COMPONENTS Network ) if (NOT Qt${QT_VERSION_MAJOR}Network_FOUND) message(" Qt${QT_VERSION_MAJOR}::Network module not found, crash reporting will be disabled.") endif() list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGL Qt${QT_VERSION_MAJOR}::Concurrent ) if (${QT_VERSION_MAJOR} EQUAL "6") find_package(Qt${QT_VERSION_MAJOR} REQUIRED OpenGLWidgets ) list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::OpenGLWidgets ) # Link KDDockWidgets #find_package(KDDockWidgets-qt6 CONFIG REQUIRED) else() # Link KDDockWidgets #find_package(KDDockWidgets CONFIG REQUIRED) endif() list(APPEND OLIVE_LIBRARIES KDAB::kddockwidgets ) # Link FFmpeg find_package(FFMPEG 3.0 REQUIRED COMPONENTS avutil avcodec avformat avfilter swscale swresample ) list(APPEND OLIVE_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS}) list(APPEND OLIVE_LIBRARIES FFMPEG::avutil FFMPEG::avcodec FFMPEG::avformat FFMPEG::avfilter FFMPEG::swscale FFMPEG::swresample ) # Link PortAudio find_package(PortAudio REQUIRED) set(CMAKE_REQUIRED_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) include(CheckIncludeFileCXX) check_include_file_cxx( "pa_jack.h" PA_HAS_JACK) if (PA_HAS_JACK) list(APPEND OLIVE_DEFINITIONS PA_HAS_JACK) endif() list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS}) list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES}) # Optional: Link OpenTimelineIO find_package(OpenTimelineIO) if (OpenTimelineIO_FOUND) list(APPEND OLIVE_DEFINITIONS USE_OTIO) list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS}) list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES}) else() message(" OpenTimelineIO interchange will be disabled.") endif() # Optional: Link Google Crashpad find_package(GoogleCrashpad) if (GoogleCrashpad_FOUND) list(APPEND OLIVE_DEFINITIONS USE_CRASHPAD) list(APPEND OLIVE_INCLUDE_DIRS ${CRASHPAD_INCLUDE_DIRS}) list(APPEND OLIVE_LIBRARIES ${CRASHPAD_LIBRARIES}) else() message(" Automatic crash reporting will be disabled.") if (APPLE) # Enables use of special functions for slider dragging, only linked if Crashpad isn't found # because Crashpad links it itself and will cause duplicate references if we also link it list(APPEND OLIVE_LIBRARIES "-framework ApplicationServices") endif() endif() if (WIN32) list(APPEND OLIVE_DEFINITIONS "-DUNICODE -D_UNICODE") elseif (APPLE) list(APPEND OLIVE_LIBRARIES "-framework IOKit") elseif(UNIX) list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus) endif() # Generate Git hash set(PROJECT_LONG_VERSION ${PROJECT_VERSION}) if(EXISTS "${CMAKE_SOURCE_DIR}/.git") find_package(Git) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short=8 HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) set(PROJECT_LONG_VERSION ${PROJECT_VERSION}-${GIT_HASH}) endif() endif() # Optional: Find Doxygen if requested if(BUILD_DOXYGEN) find_package(Doxygen) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext) add_subdirectory(ext) add_subdirectory(app) if (BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Olive Thank you for your interest in contributing to Olive! ## Reporting issues Bug reports help to make the software more stable and usable. Please read the pinned [issue #1175](https://github.com/olive-editor/olive/issues/1175) for guidelines before you create a new issue. ## Writing code Code contributions are welcome. Note that the code base is rapidly changing in the current stage of development however. There is some documentation in the form of code comments, including Javadoc in header files. Feel free to reach out via an issue or pull request if you have questions about the architecture or implementation details. ### Code Standards In order to keep the code as readable and maintainable as possible, code submitted should abide by the following standards: * The code style generally follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) including, but not limited to: * Indentation is 2 spaces wide, spaces only (no tabs) * `lowercase_underscored_variable_names` * `lowercase_underscored_functions()` or `SentenceCaseFunctions()` * `class SentenceCaseClassesAndStructs {}` * `kSentenceCaseConstants` prepended with a lowercase `k` * `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions * `class_member_variables_` end with a `_` * 100 column limit (where it doesn't impair readability) * Unix line endings (only LF no CRLF) * Javadoc documentation where appropriate ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # Olive Video Editor [![Build status](https://github.com/olive-editor/olive/workflows/CI/badge.svg?branch=master)](https://github.com/olive-editor/olive/actions?query=branch%3Amaster) Olive is a free non-linear video editor for Windows, macOS, and Linux. ![screen](https://olivevideoeditor.org/img/020-2.png) **Discover more:** [Website](https://www.olivevideoeditor.org/) | [Binaries](https://olivevideoeditor.org/download) | [Patreon](https://www.patreon.com/olivevideoeditor) | [Wiki](https://github.com/olive-editor/olive/wiki/Overview-Guide) | [Community Discord (Unofficial)](https://discord.gg/4Ae9KZn) **NOTE: Olive is alpha software and is considered highly unstable. While we highly appreciate users testing and providing usage information, please use at your own risk.** ## Binaries - [0.1.0 alpha](https://github.com/olive-editor/olive/releases/tag/0.1.0) - [0.2.0 unstable development build](https://github.com/olive-editor/olive/releases/tag/0.2.0-nightly) ## Support Olive Please consider supporting Olive: The Patreon logo ## Compiling from Source: Compiling instructions for Windows, macOS, and Linux can be found [on the main site](https://olivevideoeditor.org/compile). ================================================ FILE: app/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Set Olive sources and resources set(OLIVE_SOURCES core.h core.cpp ) #set(OLIVE_RESOURCES) # Add subdirectories, which will populate the above variables add_subdirectory(audio) add_subdirectory(cli) add_subdirectory(codec) add_subdirectory(common) add_subdirectory(config) add_subdirectory(dialog) add_subdirectory(node) add_subdirectory(packaging) add_subdirectory(panel) add_subdirectory(render) add_subdirectory(shaders) add_subdirectory(task) add_subdirectory(timeline) add_subdirectory(ts) add_subdirectory(tool) add_subdirectory(ui) add_subdirectory(undo) add_subdirectory(widget) add_subdirectory(window) # Add translations qt_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES}) set(QRC_BODY "") foreach(QM_FILE ${OLIVE_QM_FILES}) get_filename_component(QM_FILENAME_COMPONENT ${QM_FILE} NAME_WE) string(APPEND QRC_BODY "${QM_FILE}\n") endforeach() configure_file(ts/translations.qrc.in ts/translations.qrc @ONLY) set(OLIVE_RESOURCES ${OLIVE_RESOURCES} ${CMAKE_CURRENT_BINARY_DIR}/ts/translations.qrc ) # Add version object add_library(olive-version-obj OBJECT version.cpp version.h ) target_link_libraries(olive-version-obj PRIVATE Qt${QT_VERSION_MAJOR}::Core) target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" ) # Add main library add_library(libolive-editor OBJECT ${OLIVE_SOURCES} ${OLIVE_RESOURCES} ) # Remove prefix - prevents CMake calling it "liblibolive-editor" set_target_properties(libolive-editor PROPERTIES PREFIX "") # Add application add_executable(olive-editor main.cpp $ $ ) # Create docs if doxygen was found if(DOXYGEN_FOUND) set(DOXYGEN_PROJECT_NAME "Olive") set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs") set(DOXYGEN_EXTRACT_ALL "YES") set(DOXYGEN_EXTRACT_PRIVATE "YES") doxygen_add_docs(docs ALL ${OLIVE_SOURCES}) endif() # Platform-specific deployment preferences if (WIN32) # Set Windows application icon target_sources(olive-editor PRIVATE packaging/windows/resources.rc) # Preserve folder structure in visual studio source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES}) elseif(APPLE) # Set Mac application icon set(OLIVE_ICON packaging/macos/olive.icns) target_sources(olive-editor PRIVATE ${OLIVE_ICON}) # Set Mac bundle properties set_target_properties(olive-editor PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/MacOSXBundleInfo.plist.in MACOSX_BUNDLE_GUI_IDENTIFIER org.olivevideoeditor.Olive MACOSX_BUNDLE_ICON_FILE olive.icns MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION} MACOSX_BUNDLE_BUNDLE_NAME "Olive" MACOSX_BUNDLE_INFO_STRING "Olive ${PROJECT_LONG_VERSION}" MACOSX_BUNDLE_COPYRIGHT "©2018-2021 Olive Studios LLC and others. Published under the GNU General Public License version 3.0." RESOURCE "${OLIVE_ICON}" OUTPUT_NAME "Olive" ) elseif(UNIX) # Set Linux-specific properties for application install(TARGETS olive-editor RUNTIME DESTINATION bin) endif() # Set link libraries target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES}) target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES}) # Set compile options target_compile_options(olive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS}) target_compile_options(libolive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS}) # Set global definitions target_compile_definitions(olive-editor PRIVATE ${OLIVE_DEFINITIONS}) target_compile_definitions(libolive-editor PRIVATE ${OLIVE_DEFINITIONS}) # Set include dirs target_include_directories(olive-editor PRIVATE ${OLIVE_INCLUDE_DIRS}) target_include_directories(libolive-editor PRIVATE ${OLIVE_INCLUDE_DIRS}) # Add crash handler if (GoogleCrashpad_FOUND AND Qt${QT_VERSION_MAJOR}Network_FOUND) add_subdirectory(crashhandler) endif() ================================================ FILE: app/audio/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} audio/audiomanager.cpp audio/audiomanager.h audio/audioprocessor.cpp audio/audioprocessor.h audio/audiovisualwaveform.cpp audio/audiovisualwaveform.h PARENT_SCOPE ) ================================================ FILE: app/audio/audiomanager.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "audiomanager.h" #ifdef PA_HAS_JACK #include #endif #include #include "config/config.h" namespace olive { AudioManager* AudioManager::instance_ = nullptr; void AudioManager::CreateInstance() { if (instance_ == nullptr) { instance_ = new AudioManager(); } } void AudioManager::DestroyInstance() { delete instance_; instance_ = nullptr; } AudioManager *AudioManager::instance() { return instance_; } void AudioManager::SetOutputNotifyInterval(int n) { output_buffer_->set_notify_interval(n); } int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { PreviewAudioDevice *device = static_cast(userData); qint64 max_read = frameCount * device->bytes_per_frame(); qint64 read_count = device->read(reinterpret_cast(output), max_read); if (read_count < max_read) { memset(reinterpret_cast(output) + read_count, 0, max_read - read_count); } return paContinue; } int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { FFmpegEncoder *f = static_cast(userData); AudioParams our_params = f->params().audio_params(); our_params.set_format(f->params().audio_params().format().to_packed_equivalent()); f->WriteAudioData(our_params, reinterpret_cast(&input), frameCount); return paContinue; } bool AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples, QString *error) { if (output_device_ == paNoDevice) { if (error) *error = tr("No output device is set"); return false; } if (output_params_ != params || output_stream_ == nullptr) { output_params_ = params; CloseOutputStream(); PaStreamParameters p = GetPortAudioParams(params, output_device_); PaError r = Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); if (r != paNoError) { // Unhandled error //qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r); if (error) *error = Pa_GetErrorText(r); return false; } output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1)); } output_buffer_->write(samples); if (!Pa_IsStreamActive(output_stream_)) { Pa_StartStream(output_stream_); } return true; } void AudioManager::ClearBufferedOutput() { output_buffer_->clear(); } PaSampleFormat AudioManager::GetPortAudioSampleFormat(SampleFormat fmt) { switch (fmt) { case SampleFormat::U8: case SampleFormat::U8P: return paUInt8; case SampleFormat::S16: case SampleFormat::S16P: return paInt16; case SampleFormat::S32: case SampleFormat::S32P: return paInt32; case SampleFormat::F32: case SampleFormat::F32P: return paFloat32; case SampleFormat::S64: case SampleFormat::S64P: case SampleFormat::F64: case SampleFormat::F64P: case SampleFormat::INVALID: case SampleFormat::COUNT: break; } return 0; } void AudioManager::CloseOutputStream() { if (output_stream_) { if (Pa_IsStreamActive(output_stream_)) { StopOutput(); } Pa_CloseStream(output_stream_); output_stream_ = nullptr; } } void AudioManager::StopOutput() { // Abort the stream so playback stops immediately if (output_stream_) { Pa_AbortStream(output_stream_); ClearBufferedOutput(); } } void AudioManager::SetOutputDevice(PaDeviceIndex device) { if (device == paNoDevice) { qInfo() << "No output device found"; } else { qInfo() << "Setting output audio device to" << Pa_GetDeviceInfo(device)->name; } output_device_ = device; CloseOutputStream(); } void AudioManager::SetInputDevice(PaDeviceIndex device) { if (device == paNoDevice) { qInfo() << "No input device found"; } else { qInfo() << "Setting input audio device to" << Pa_GetDeviceInfo(device)->name; } input_device_ = device; } void AudioManager::HardReset() { CloseOutputStream(); Pa_Terminate(); Pa_Initialize(); } bool AudioManager::StartRecording(const EncodingParams ¶ms, QString *error_str) { if (input_device_ == paNoDevice) { return false; } input_encoder_ = new FFmpegEncoder(params); if (!input_encoder_->Open()) { qCritical() << "Failed to open encoder for recording"; return false; } PaStreamParameters p = GetPortAudioParams(params.audio_params(), input_device_); PaError r = Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_); if (r == paNoError) { //const PaStreamInfo* info = Pa_GetStreamInfo(input_stream_); r = Pa_StartStream(input_stream_); if (r == paNoError) { return true; } } if (error_str) { *error_str = Pa_GetErrorText(r); } StopRecording(); return false; } void AudioManager::StopRecording() { if (input_stream_) { if (Pa_IsStreamActive(input_stream_)) { Pa_StopStream(input_stream_); } Pa_CloseStream(input_stream_); input_stream_ = nullptr; } if (input_encoder_) { input_encoder_->Close(); delete input_encoder_; input_encoder_ = nullptr; } } PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device) { QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput"); return FindDeviceByName(OLIVE_CONFIG_STR(entry).toString(), is_output_device); } PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device) { if (!s.isEmpty()) { for (PaDeviceIndex i=0, end=Pa_GetDeviceCount(); imaxOutputChannels) || (!is_output_device && device->maxInputChannels)) && !s.compare(device->name)) { return i; } } } return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice(); } PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms, PaDeviceIndex device) { PaStreamParameters p; p.channelCount = params.channel_count(); p.device = device; p.hostApiSpecificStreamInfo = nullptr; p.sampleFormat = GetPortAudioSampleFormat(params.format()); p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency; return p; } AudioManager::AudioManager() : output_stream_(nullptr), input_stream_(nullptr), input_encoder_(nullptr) { #ifdef PA_HAS_JACK // PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not // a QString converted to UTF-8) PaJack_SetClientName("Olive"); #endif Pa_Initialize(); // Get device from config PaDeviceIndex output_device = FindConfigDeviceByName(true); PaDeviceIndex input_device = FindConfigDeviceByName(false); SetOutputDevice(output_device); SetInputDevice(input_device); output_buffer_ = new PreviewAudioDevice(this); output_buffer_->open(PreviewAudioDevice::ReadWrite); connect(output_buffer_, &PreviewAudioDevice::Notify, this, &AudioManager::OutputNotify); } AudioManager::~AudioManager() { CloseOutputStream(); Pa_Terminate(); } } ================================================ FILE: app/audio/audiomanager.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AUDIOMANAGER_H #define AUDIOMANAGER_H #include #include #include #include #include "audiovisualwaveform.h" #include "audio/audioprocessor.h" #include "common/define.h" #include "codec/ffmpeg/ffmpegencoder.h" #include "render/audioplaybackcache.h" #include "render/previewaudiodevice.h" namespace olive { /** * @brief Audio input and output management class * * Wraps around a QAudioOutput and AudioHybridDevice, connecting them together and exposing audio functionality to * the rest of the system. */ class AudioManager : public QObject { Q_OBJECT public: static void CreateInstance(); static void DestroyInstance(); static AudioManager* instance(); void SetOutputNotifyInterval(int n); bool PushToOutput(const AudioParams ¶ms, const QByteArray& samples, QString *error = nullptr); void ClearBufferedOutput(); void StopOutput(); PaDeviceIndex GetOutputDevice() const { return output_device_; } PaDeviceIndex GetInputDevice() const { return input_device_; } void SetOutputDevice(PaDeviceIndex device); void SetInputDevice(PaDeviceIndex device); void HardReset(); bool StartRecording(const EncodingParams ¶ms, QString *error_str = nullptr); void StopRecording(); static PaDeviceIndex FindConfigDeviceByName(bool is_output_device); static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device); static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device); signals: void OutputNotify(); void OutputParamsChanged(); private: AudioManager(); virtual ~AudioManager() override; static PaSampleFormat GetPortAudioSampleFormat(SampleFormat fmt); void CloseOutputStream(); static AudioManager* instance_; PaDeviceIndex output_device_; PaStream *output_stream_; AudioParams output_params_; PreviewAudioDevice *output_buffer_; PaDeviceIndex input_device_; PaStream *input_stream_; FFmpegEncoder *input_encoder_; }; } #endif // AUDIOMANAGER_H ================================================ FILE: app/audio/audioprocessor.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "audioprocessor.h" extern "C" { #include #include } #include #include "common/ffmpegutils.h" namespace olive { AudioProcessor::AudioProcessor() { filter_graph_ = nullptr; in_frame_ = nullptr; out_frame_ = nullptr; } AudioProcessor::~AudioProcessor() { Close(); } bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double tempo) { if (filter_graph_) { qWarning() << "Tried to open a processor that was already open"; return false; } filter_graph_ = avfilter_graph_alloc(); if (!filter_graph_) { qCritical() << "Failed to allocate filter graph"; return false; } from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format()); to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format()); // Set up audio buffer args char filter_args[200]; snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64, 1, from.sample_rate(), from.sample_rate(), from_fmt_, from.channel_layout()); int r; // Create buffersrc (input) r = avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_); if (r < 0) { qCritical() << "Failed to create buffersrc:" << r; Close(); return false; } // Store "previous" filter for linking AVFilterContext *previous_filter = buffersrc_ctx_; // Create tempo bool create_tempo; if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) { // Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside // those boundaries, we need to daisychain more than one together. double base = (tempo > 1.0) ? 2.0 : 0.5; double speed_log = log(tempo) / log(base); // This is the number of how many 0.5 or 2.0 tempos we need to daisychain int whole = std::floor(speed_log); // Set speed_log to the remainder speed_log -= whole; for (int i=0;i<=whole;i++) { double filter_tempo = (i == whole) ? std::pow(base, speed_log) : base; if (qFuzzyCompare(filter_tempo, 1.0)) { // This filter would do nothing continue; } previous_filter = CreateTempoFilter(filter_graph_, previous_filter, filter_tempo); if (!previous_filter) { qCritical() << "Failed to create audio tempo filter"; Close(); return false; } } } // Create conversion filter if (from.sample_rate() != to.sample_rate() || from.channel_layout() != to.channel_layout() || from.format() != to.format() || (to.format().is_planar() && create_tempo)) { // Tempo processor automatically converts to packed, // so if the desired output is planar, it'll need // to be converted snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64, av_get_sample_fmt_name(to_fmt_), to.sample_rate(), to.channel_layout()); AVFilterContext *c; r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), "fmt", filter_args, nullptr, filter_graph_); if (r < 0) { qCritical() << "Failed to create format conversion filter:" << r << filter_args; Close(); return false; } r = avfilter_link(previous_filter, 0, c, 0); if (r < 0) { qCritical() << "Failed to link filters:" << r; Close(); return false; } previous_filter = c; } // Create buffersink (output) r = avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_); if (r < 0) { qCritical() << "Failed to create buffersink:" << r; Close(); return false; } r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0); if (r < 0) { qCritical() << "Failed to link filters:" << r; Close(); return false; } r = avfilter_graph_config(filter_graph_, nullptr); if (r < 0) { qCritical() << "Failed to configure graph:" << r; Close(); return false; } in_frame_ = av_frame_alloc(); if (in_frame_) { in_frame_->sample_rate = from.sample_rate(); in_frame_->format = from_fmt_; in_frame_->channel_layout = from.channel_layout(); in_frame_->channels = from.channel_count(); in_frame_->pts = 0; } else { qCritical() << "Failed to allocate input frame"; Close(); return false; } out_frame_ = av_frame_alloc(); if (!out_frame_) { qCritical() << "Failed to allocate output frame"; Close(); return false; } from_ = from; to_ = to; return true; } void AudioProcessor::Close() { if (filter_graph_) { avfilter_graph_free(&filter_graph_); filter_graph_ = nullptr; buffersrc_ctx_ = nullptr; buffersink_ctx_ = nullptr; } if (in_frame_) { av_frame_free(&in_frame_); in_frame_ = nullptr; } if (out_frame_) { av_frame_free(&out_frame_); out_frame_ = nullptr; } } int AudioProcessor::Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output) { if (!IsOpen()) { qCritical() << "Tried to convert on closed processor"; return -1; } int r = 0; if (in && nb_in_samples) { // Set frame parameters in_frame_->nb_samples = nb_in_samples; for (int i=0; idata[i] = reinterpret_cast(in[i]); in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples); } r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, AV_BUFFERSRC_FLAG_KEEP_REF); if (r < 0) { qCritical() << "Failed to add frame to buffersrc:" << r; return r; } } if (output) { int nb_channels = to_.channel_count(); if (to_.format().is_packed()) { nb_channels = 1; } AudioProcessor::Buffer &result = *output; result.resize(nb_channels); int byte_offset = 0; while (true) { av_frame_unref(out_frame_); r = av_buffersink_get_frame(buffersink_ctx_, out_frame_); if (r < 0) { if (r == AVERROR(EAGAIN)) { r = 0; } else { // Handle unexpected error qCritical() << "Failed to pull from buffersink:" << r; } break; } int nb_bytes = out_frame_->nb_samples * to_.bytes_per_sample_per_channel(); if (to_.format().is_packed()) { nb_bytes *= to_.channel_count(); } for (int i=0; idata[i], nb_bytes); } byte_offset += nb_bytes; } av_frame_unref(out_frame_); } return r; } void AudioProcessor::Flush() { int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF); if (r < 0) { qCritical() << "Failed to flush:" << r; } } AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo) { // Set up tempo param, which is taken as a C string char speed_param[20]; snprintf(speed_param, 20, "%f", tempo); AVFilterContext* tempo_ctx = nullptr; if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0 && avfilter_link(link, 0, tempo_ctx, 0) == 0) { return tempo_ctx; } return nullptr; } } ================================================ FILE: app/audio/audioprocessor.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AUDIOPROCESSOR_H #define AUDIOPROCESSOR_H #include #include #include extern "C" { #include } #include "common/define.h" namespace olive { using namespace core; class AudioProcessor { public: AudioProcessor(); ~AudioProcessor(); DISABLE_COPY_MOVE(AudioProcessor) bool Open(const AudioParams &from, const AudioParams &to, double tempo = 1.0); void Close(); bool IsOpen() const { return filter_graph_; } using Buffer = QVector; int Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output); void Flush(); const AudioParams &from() const { return from_; } const AudioParams &to() const { return to_; } private: static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo); AVFilterGraph* filter_graph_; AVFilterContext* buffersrc_ctx_; AVFilterContext* buffersink_ctx_; AudioParams from_; AVSampleFormat from_fmt_; AudioParams to_; AVSampleFormat to_fmt_; AVFrame *in_frame_; AVFrame *out_frame_; }; } #endif // AUDIOPROCESSOR_H ================================================ FILE: app/audio/audiovisualwaveform.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "audiovisualwaveform.h" #include #include #include "config/config.h" namespace olive { const rational AudioVisualWaveform::kMinimumSampleRate = rational(1, 8); const rational AudioVisualWaveform::kMaximumSampleRate = 1024; AudioVisualWaveform::AudioVisualWaveform() : channels_(0) { for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) { mipmapped_data_.insert({i, Sample()}); } } void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, size_t &start_index, size_t &samples_length) { start_index = time_to_samples(start, target_rate); samples_length = time_to_samples(static_cast(samples.sample_count()) / static_cast(sample_rate), target_rate); size_t end_index = start_index + samples_length; if (data.size() < end_index) { data.resize(end_index); } double chunk_size = double(sample_rate) / double(target_rate); for (size_t i=0; i(input_length / channels_) / input_sample_rate, output_rate); size_t end_index = start_index + samples_length; if (output_data.size() < end_index) { output_data.resize(end_index); } // We guarantee mipmaps are powers of two so integer division should be perfectly accurate here size_t chunk_size = input_sample_rate / output_rate; for (size_t i=0; i new_start) { TrimIn(new_start - virtual_start_); } } void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start) { if (!channels_) { qWarning() << "Failed to write samples - channel count is zero"; return; } ValidateVirtualStart(start); // Process the largest mipmap directly for the samples auto current_mipmap = mipmapped_data_.rbegin(); size_t input_start, input_length; OverwriteSamplesFromBuffer(samples, sample_rate, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length); while (true) { // For each smaller mipmap, we just process from the mipmap before it, making each one // exponentially faster to create auto previous_mipmap = current_mipmap; current_mipmap++; if (current_mipmap == mipmapped_data_.rend()) { break; } OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(), input_start, input_length, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second); } rational sample_length(samples.sample_count(), sample_rate); length_ = qMax(length_, start + sample_length); } void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length) { ValidateVirtualStart(dest); for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; Sample& our_arr = it->second; const Sample& their_arr = sums.mipmapped_data_.at(rate); double rate_dbl = rate.toDouble(); // Get our destination sample size_t our_start_index = time_to_samples(dest - virtual_start_, rate_dbl); // Get our source sample size_t their_start_index = time_to_samples(offset, rate_dbl); if (their_start_index >= their_arr.size()) { continue; } // Determine how much we're copying size_t copy_len = their_arr.size() - their_start_index; if (!length.isNull()) { copy_len = qMin(copy_len, time_to_samples(length, rate_dbl)); if (copy_len == 0) { continue; } } // Determine end index of our array size_t end_index = our_start_index + copy_len; if (our_arr.size() < end_index) { our_arr.resize(end_index); } memcpy(reinterpret_cast(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), reinterpret_cast(their_arr.data()) + their_start_index * sizeof(SamplePerChannel), copy_len * sizeof(SamplePerChannel)); } length_ = qMax(length_, dest + ((length.isNull()) ? sums.length() - offset : length)); } void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length) { ValidateVirtualStart(start); for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; Sample& our_arr = it->second; double rate_dbl = rate.toDouble(); // Get our destination sample size_t our_start_index = time_to_samples(start - virtual_start_, rate_dbl); size_t our_length_index = time_to_samples(length, rate_dbl); size_t our_end_index = our_start_index + our_length_index; if (our_arr.size() < our_end_index) { our_arr.resize(our_end_index); } memset(reinterpret_cast(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel)); } length_ = qMax(length_, start + length); } void AudioVisualWaveform::TrimIn(rational length) { if (length == 0) { return; } virtual_start_ += length; bool negative = (length < 0); if (negative) { length = -length; } for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; double rate_dbl = rate.toDouble(); Sample& data = it->second; size_t chop_length = time_to_samples(length, rate_dbl); if (chop_length == 0) { continue; } if (!negative) { data = Sample(data.begin() + chop_length, data.end()); } else { data.insert(data.begin(), chop_length, SamplePerChannel()); } } length_ = qMax(rational(0), length_ - length); } AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const { AudioVisualWaveform mid = *this; mid.TrimIn(offset - virtual_start_); return mid; } AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const rational &length) const { AudioVisualWaveform mid = *this; mid.TrimRange(offset - virtual_start_, length); return mid; } void AudioVisualWaveform::Resize(const rational &length) { if (length_ == length) { return; } for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; double rate_dbl = rate.toDouble(); Sample& data = it->second; size_t chop_length = time_to_samples(length, rate_dbl); data.resize(chop_length); } length_ = length; } void AudioVisualWaveform::TrimRange(const rational &in, const rational &length) { TrimIn(in); Resize(length); } AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const { // Find mipmap that requires auto using_mipmap = GetMipmapForScale(length.flipped().toDouble()); double rate_dbl = using_mipmap->first.toDouble(); size_t start_sample = time_to_samples(start - virtual_start_, rate_dbl); size_t sample_length = time_to_samples(length, rate_dbl); const Sample &mipmap_data = using_mipmap->second; // Determine if the array actually has this sample sample_length = qMin(sample_length, mipmap_data.size() - start_sample); // Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the // array and nothing can be returned. if (sample_length > 0) { return ReSumSamples(&mipmap_data.data()[start_sample], sample_length, channels_); } // Return null samples return AudioVisualWaveform::Sample(channel_count(), {0, 0}); } void ExpandMinMaxChannel(const float *a, size_t length, float &min_val, float &max_val) { #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) // SSE optimized // load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits) __m128 max = _mm_loadu_ps(a); __m128 min = _mm_loadu_ps(a); // loop over 'a' and compare current elements with min and max 4 by 4. // we need to make sure we don't read out of boundaries should 'a' length be not mod. 4 for(size_t i = 4; i < length-4; i+=4) { __m128 cur = _mm_loadu_ps(a + i); max = _mm_max_ps(max, cur); min = _mm_min_ps(min, cur); } // so we read the last 4 (or less) elements in a safe manner. __m128 cur = _mm_loadu_ps(a + length - 4); max = _mm_max_ps(max, cur); min = _mm_min_ps(min, cur); // this potentially overlaps up to the last 3 elements but it's not an issue. // min and max will contain 4 min and max. To get the absolute min and max // we need to compare the 4 values over themselves by shuffling each time. for (size_t i = 0; i < 3; i++) { max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93)); } // now min and max contain 4 identical items each representing min and max value respectively. // and we store the first one into a float variable. _mm_store_ss(&max_val, max); _mm_store_ss(&min_val, min); // I bet you don't find annotated low level code very often. #else // Standard unoptimized function for (size_t i=0; idata(i%channels)[i]); // } return summed_samples; } AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples, size_t nb_samples, int nb_channels) { AudioVisualWaveform::Sample summed_samples(nb_channels); for (size_t i=0;i summed_samples[j].max) { summed_samples[j].max = sample.max; } } } return summed_samples; } template inline int round_away_from_zero(T t) { return (t < 0) ? std::floor(t) : std::ceil(t); } void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample& sample, int x, int y, int height, bool rectified) { if (sample.empty()) { return; } int channel_height = height / sample.size(); int channel_half_height = channel_height / 2; for (size_t i=0;idrawLine(x, channel_bottom - diff, x, channel_bottom); } else { int channel_mid = y + channel_height * i + channel_half_height; // We subtract the sample so that positive Y values go up on the screen rather than down, // which is how waveforms are usually rendered painter->drawLine(x, channel_mid - round_away_from_zero(min * static_cast(channel_half_height)), x, channel_mid - round_away_from_zero(max * static_cast(channel_half_height))); } } } void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples, const rational& start_time) { if (samples.mipmapped_data_.empty()) { return; } auto using_mipmap = samples.GetMipmapForScale(scale); rational rate = using_mipmap->first; double rate_dbl = rate.toDouble(); const Sample& arr = using_mipmap->second; size_t start_sample_index = samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl); if (start_sample_index >= arr.size()) { return; } size_t next_sample_index = start_sample_index; size_t sample_index; Sample summary; size_t summary_index = -1; const QRect& viewport = painter->viewport(); QPoint top_left = painter->transform().map(viewport.topLeft()); size_t start = qMax(rect.x(), -top_left.x()); size_t end = qMin(rect.right(), -top_left.x() + viewport.width()); bool rectified = OLIVE_CONFIG("RectifiedWaveforms").toBool(); for (size_t i=start;i(i - rect.x() + 1) / scale) * samples.channel_count())); if (summary_index != sample_index) { summary = AudioVisualWaveform::ReSumSamples(&arr.at(sample_index), qMax(size_t(samples.channel_count()), next_sample_index - sample_index), samples.channel_count()); summary_index = sample_index; } DrawSample(painter, summary, i, rect.y(), rect.height(), rectified); } } size_t AudioVisualWaveform::time_to_samples(const rational &time, double sample_rate) const { return time_to_samples(time.toDouble(), sample_rate); } size_t AudioVisualWaveform::time_to_samples(const double &time, double sample_rate) const { return std::floor(time * sample_rate) * channels_; } std::map::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const { // Find largest mipmap for this scale (or the largest if we don't find one sufficient) for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) { if (it->first.toDouble() >= scale) { return it; } } // We don't have a mipmap large enough for this scale, so just return the largest we have return std::prev(mipmapped_data_.cend()); } } ================================================ FILE: app/audio/audiovisualwaveform.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SUMSAMPLES_H #define SUMSAMPLES_H #include #include #include namespace olive { using namespace core; /** * @brief A buffer of data used to store a visual representation of audio * * This differs from a SampleBuffer as the data in an AudioVisualWaveform has been reduced * significantly and optimized for visual display. */ class AudioVisualWaveform { public: AudioVisualWaveform(); struct SamplePerChannel { float min; float max; }; using Sample = std::vector; int channel_count() const { return channels_; } void set_channel_count(int channels) { channels_ = channels; } const rational& length() const { return length_; } /** * @brief Writes samples into the visual waveform buffer * * Starting at `start`, writes samples over anything in the buffer, expanding it if necessary. */ void OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational& start = 0); /** * @brief Replaces sums at a certain range in this visual waveform * * @param sums * * The sums to write over our current ones with. * * @param dest * * Where in this visual waveform these sums should START being written to. * * @param offset * * Where in the `sums` parameter this should start reading from. Defaults to 0. * * @param length * * Maximum length of `sums` to overwrite with. */ void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0); void OverwriteSilence(const rational &start, const rational &length); void TrimIn(rational length); AudioVisualWaveform Mid(const rational &offset) const; AudioVisualWaveform Mid(const rational &offset, const rational &length) const; void Resize(const rational &length); void TrimRange(const rational &in, const rational &length); Sample GetSummaryFromTime(const rational& start, const rational& length) const; static Sample SumSamples(const SampleBuffer &samples, size_t start_index, size_t length); static Sample ReSumSamples(const SamplePerChannel *samples, size_t nb_samples, int nb_channels); static void DrawSample(QPainter* painter, const Sample &sample, int x, int y, int height, bool rectified); static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples, const rational &start_time); // Must be a power of 2 static const rational kMinimumSampleRate; static const rational kMaximumSampleRate; private: void OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational& start, double target_rate, Sample &data, size_t &start_index, size_t &samples_length); void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, size_t &input_start, size_t &input_length, const rational& start, double output_rate, Sample &output_data); size_t time_to_samples(const rational& time, double sample_rate) const; size_t time_to_samples(const double& time, double sample_rate) const; std::map::const_iterator GetMipmapForScale(double scale) const; void ValidateVirtualStart(const rational &new_start); rational virtual_start_; int channels_; std::map mipmapped_data_; rational length_; }; } Q_DECLARE_METATYPE(olive::AudioVisualWaveform) #endif // SUMSAMPLES_H ================================================ FILE: app/cli/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(cliprogress) add_subdirectory(clitask) set(OLIVE_SOURCES ${OLIVE_SOURCES} PARENT_SCOPE ) ================================================ FILE: app/cli/cliexport/cliexportmanager.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "cliexportmanager.h" namespace olive { CLIExportManager::CLIExportManager() { } } ================================================ FILE: app/cli/cliexport/cliexportmanager.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CLIEXPORTMANAGER_H #define CLIEXPORTMANAGER_H #include "task/export/export.h" namespace olive { class CLIExportManager : public QObject { public: CLIExportManager(); }; } #endif // CLIEXPORTMANAGER_H ================================================ FILE: app/cli/cliprogress/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} cli/cliprogress/cliprogressdialog.h cli/cliprogress/cliprogressdialog.cpp PARENT_SCOPE ) ================================================ FILE: app/cli/cliprogress/cliprogressdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "cliprogressdialog.h" #include namespace olive { CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) : QObject(parent), title_(title), progress_(-1), drawn_(false) { SetProgress(0); } void CLIProgressDialog::Update() { if (drawn_) { // We've been here before, do a carriage return back to the start of the terminal line std::cout << "\r"; } else { drawn_ = true; } // FIXME: Get real column count int columns = 80; int title_columns = columns / 2 - 1; // Print "title" text QString sized_title = title_; if (title_.size() > title_columns) { sized_title = title_.left(title_columns - 3).append(QStringLiteral("...")); } else { sized_title = title_; } std::cout << sized_title.toUtf8().constData(); // Pad out the rest of the title area if necessary for (int i=sized_title.size(); i. ***/ #ifndef CLIPROGRESSDIALOG_H #define CLIPROGRESSDIALOG_H #include #include #include "common/define.h" namespace olive { class CLIProgressDialog : public QObject { public: CLIProgressDialog(const QString &title, QObject* parent = nullptr); public slots: void SetProgress(double p); private: void Update(); QString title_; double progress_; bool drawn_; }; } #endif // CLIPROGRESSDIALOG_H ================================================ FILE: app/cli/clitask/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} cli/clitask/clitaskdialog.h cli/clitask/clitaskdialog.cpp PARENT_SCOPE ) ================================================ FILE: app/cli/clitask/clitaskdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "clitaskdialog.h" namespace olive { CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) : CLIProgressDialog(task->GetTitle(), parent), task_(task) { connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress); } bool CLITaskDialog::Run() { return task_->Start(); } } ================================================ FILE: app/cli/clitask/clitaskdialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CLITASKDIALOG_H #define CLITASKDIALOG_H #include "cli/cliprogress/cliprogressdialog.h" #include "task/task.h" namespace olive { class CLITaskDialog : public CLIProgressDialog { Q_OBJECT public: CLITaskDialog(Task *task, QObject* parent = nullptr); bool Run(); private: Task* task_; }; } #endif // CLITASKDIALOG_H ================================================ FILE: app/codec/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(ffmpeg) add_subdirectory(oiio) set(OLIVE_SOURCES ${OLIVE_SOURCES} codec/conformmanager.cpp codec/conformmanager.h codec/decoder.cpp codec/decoder.h codec/encoder.cpp codec/encoder.h codec/exportcodec.cpp codec/exportcodec.h codec/exportformat.cpp codec/exportformat.h codec/frame.cpp codec/frame.h codec/planarfiledevice.cpp codec/planarfiledevice.h PARENT_SCOPE ) ================================================ FILE: app/codec/conformmanager.cpp ================================================ #include "conformmanager.h" #include #include "task/taskmanager.h" namespace olive { ConformManager *ConformManager::instance_ = nullptr; ConformManager::Conform ConformManager::GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms, bool wait) { // Mutex because we'll need to check the status of a conform task QMutexLocker locker(&mutex_); // Return existing conform if exists QVector filenames = GetConformedFilename(cache_path, stream, params); if (AllConformsExist(filenames)) { return {kConformExists, filenames, nullptr}; } ConformTask *conforming_task = nullptr; foreach (const ConformData &data, conforming_) { if (data.stream == stream && data.params == params) { // Already creating conform in a task conforming_task = data.task; break; } } if (!conforming_task) { // Not conforming yet, create a task to do so // We conform to a different filename until it's done to make it clear even across sessions // whether this conform is ready or not QVector working_filenames = filenames; for (int i=0; imoveToThread(TaskManager::instance()->thread()); QMetaObject::invokeMethod(TaskManager::instance(), "AddTask", Qt::QueuedConnection, Q_ARG(Task *, conforming_task)); conforming_.append({stream, params, conforming_task, working_filenames, filenames}); } if (wait) { do { conform_done_condition_.wait(&mutex_); } while (!AllConformsExist(filenames)); return {kConformExists, filenames, nullptr}; } return {kConformGenerating, QVector(), conforming_task}; } QVector ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms) { QVector filenames(params.channel_count()); for (int i=0; i &filenames) { foreach (const QString &fn, filenames) { if (!QFileInfo::exists(fn)) { return false; } } return true; } void ConformManager::ConformTaskFinished(Task *task, bool succeeded) { QMutexLocker locker(&mutex_); ConformData data; // Remove conform data from list for (int i=0; i #include #include "decoder.h" #include "task/conform/conform.h" namespace olive { class ConformManager : public QObject { Q_OBJECT public: static void CreateInstance() { if (!instance_) { instance_ = new ConformManager(); } } static void DestroyInstance() { delete instance_; instance_ = nullptr; } static ConformManager *instance() { return instance_; } enum ConformState { kConformExists, kConformGenerating }; struct Conform { ConformState state; QVector filenames; ConformTask *task; }; /** * @brief Get conform state, and start conforming if no conform exists * * Thread-safe. */ Conform GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms, bool wait); signals: void ConformReady(); private: ConformManager() = default; static ConformManager *instance_; QMutex mutex_; QWaitCondition conform_done_condition_; struct ConformData { Decoder::CodecStream stream; AudioParams params; ConformTask *task; QVector working_filename; QVector finished_filename; }; QVector conforming_; /** * @brief Get the destination filename of an audio stream conformed to a set of parameters */ static QVector GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms); static bool AllConformsExist(const QVector &filenames); private slots: void ConformTaskFinished(Task *task, bool succeeded); }; } #endif // CONFORMMANAGER_H ================================================ FILE: app/codec/decoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "decoder.h" #include #include #include #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/planarfiledevice.h" #include "codec/oiio/oiiodecoder.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "conformmanager.h" #include "node/project.h" #include "task/taskmanager.h" namespace olive { const rational Decoder::kAnyTimecode = RATIONAL_MIN; Decoder::Decoder() : cached_texture_(nullptr) { UpdateLastAccessed(); } void Decoder::IncrementAccessTime(qint64 t) { last_accessed_ += t; } bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (stream_.IsValid()) { // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not. if (stream_ == stream) { return true; } else { qWarning() << "Tried to open a decoder that was already open with another stream"; return false; } } else { // Stream was not open, try opening it now if (!stream.IsValid()) { // Cannot open null stream qCritical() << "Decoder attempted to open null stream"; return false; } if (!stream.Exists()) { // Cannot open file that doesn't exist qCritical() << "Decoder attempted to open file that doesn't exist"; return false; } // Set stream stream_ = stream; // Try open internal if (OpenInternal()) { return true; } else { // Unset stream qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream(); CloseInternal(); stream_.Reset(); return false; } } } TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (!stream_.IsValid()) { qCritical() << "Can't retrieve video on a closed decoder"; return nullptr; } if (!SupportsVideo()) { qCritical() << "Decoder doesn't support video"; return nullptr; } if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } if (cached_texture_ && cached_time_ == p.time && cached_divider_ == p.divider) { return cached_texture_; } cached_texture_ = RetrieveVideoInternal(p); cached_time_ = p.time; cached_divider_ = p.divider; return cached_texture_; } Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); UpdateLastAccessed(); if (!stream_.IsValid()) { qCritical() << "Can't retrieve audio on a closed decoder"; return kInvalid; } if (!SupportsAudio()) { qCritical() << "Decoder doesn't support audio"; return kInvalid; } // Get conform state from ConformManager ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, params, (mode == RenderMode::kOnline)); if (conform.state == ConformManager::kConformGenerating) { // If we need the task, it's available in `conform.task` return kWaitingForConform; } // See if we got the conform if (RetrieveAudioFromConform(dest, conform.filenames, range, loop_mode, params)) { return kOK; } else { return kUnknownError; } } qint64 Decoder::GetLastAccessedTime() { return last_accessed_; } void Decoder::Close() { QMutexLocker locker(&mutex_); UpdateLastAccessed(); cached_texture_ = nullptr; if (stream_.IsValid()) { CloseInternal(); stream_.Reset(); } else { qWarning() << "Tried to close a decoder that wasn't open"; } } bool Decoder::ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled) { return ConformAudioInternal(output_filenames, params, cancelled); } /* * DECODER STATIC PUBLIC MEMBERS */ QVector Decoder::ReceiveListOfAllDecoders() { QVector decoders; // The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last, // since it supports so many formats and we presumably want to override those formats with a more specific decoder. decoders.append(std::make_shared()); decoders.append(std::make_shared()); return decoders; } DecoderPtr Decoder::CreateFromID(const QString &id) { if (id.isEmpty()) { return nullptr; } // Create list to iterate through QVector decoder_list = ReceiveListOfAllDecoders(); foreach (DecoderPtr d, decoder_list) { if (d->id() == id) { return d; } } return nullptr; } void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) { if (duration != AV_NOPTS_VALUE && duration != 0) { emit IndexProgress(static_cast(ts) / static_cast(duration)); } } QString Decoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number) { int digit_count = GetImageSequenceDigitCount(filename); QFileInfo file_info(filename); QString original_basename = file_info.completeBaseName(); QString new_basename = original_basename.left(original_basename.size() - digit_count) .append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0'))); return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename)); } int Decoder::GetImageSequenceDigitCount(const QString &filename) { QString basename = QFileInfo(filename).completeBaseName(); // See if basename contains a number at the end int digit_count = 0; for (int i=basename.size()-1;i>=0;i--) { if (basename.at(i).isDigit()) { digit_count++; } else { break; } } return digit_count; } int64_t Decoder::GetImageSequenceIndex(const QString &filename) { int digit_count = GetImageSequenceDigitCount(filename); QFileInfo file_info(filename); QString original_basename = file_info.completeBaseName(); QString number_only = original_basename.mid(original_basename.size() - digit_count); return number_only.toLongLong(); } TexturePtr Decoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { Q_UNUSED(p) return nullptr; } bool Decoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, CancelAtom *cancelled) { Q_UNUSED(filenames) Q_UNUSED(cancelled) Q_UNUSED(params) return false; } bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { // Offset range by audio start offset range -= GetAudioStartOffset(); qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 write_index = 0; const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); while (write_index < buffer_length_in_bytes) { if (loop_mode == LoopMode::kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); } while (read_index < 0) { read_index += input.size(); } } qint64 write_count = 0; if (read_index < 0) { // Reading before 0, write silence here until audio data would actually start write_count = qMin(-read_index, buffer_length_in_bytes); sample_buffer.silence_bytes(write_index, write_index + write_count); } else if (read_index >= input.size()) { // Reading after data length, write silence until the end of the buffer write_count = buffer_length_in_bytes - write_index; sample_buffer.silence_bytes(write_index, write_index + write_count); } else { write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index); input.seek(read_index); input.read(reinterpret_cast(sample_buffer.to_raw_ptrs().data()), write_count, write_index); } read_index += write_count; write_index += write_count; } input.close(); return true; } return false; } void Decoder::UpdateLastAccessed() { last_accessed_ = QDateTime::currentMSecsSinceEpoch(); } uint qHash(Decoder::CodecStream stream, uint seed) { return qHash(stream.filename(), seed) ^ ::qHash(stream.stream(), seed) ^ qHash(stream.block(), seed); } } ================================================ FILE: app/codec/decoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef DECODER_H #define DECODER_H extern "C" { #include } #include #include #include #include #include #include "node/block/block.h" #include "node/project/footage/footagedescription.h" #include "render/cancelatom.h" #include "render/rendermodes.h" namespace olive { class Decoder; using DecoderPtr = std::shared_ptr; #define DECODER_DEFAULT_DESTRUCTOR(x) virtual ~x() override {CloseInternal();} /** * @brief A decoder's is the main class for bringing external media into Olive * * Its responsibilities are to serve as * abstraction from codecs/decoders and provide complete frames. These frames can be video or audio data and are * provided as Frame objects in shared pointers to alleviate the responsibility of memory handling. * * The main function in a decoder is Retrieve() which should return complete image/audio data. A decoder should * alleviate all the complexities of codec compression from the rest of the application (i.e. a decoder should never * return a partial frame or require other parts of the system to interface directly with the codec). Often this will * necessitate pre-emptively caching, indexing, or even fully transcoding media before using it which can be implemented * through the Analyze() function. * * A decoder does NOT perform any pixel/sample format conversion. Frames should pass through the PixelService * to be utilized in the rest of the rendering pipeline. */ class Decoder : public QObject { Q_OBJECT public: enum RetrieveState { kReady, kFailedToOpen, kIndexUnavailable }; Decoder(); /** * @brief Unique decoder ID */ virtual QString id() const = 0; virtual bool SupportsVideo(){return false;} virtual bool SupportsAudio(){return false;} void IncrementAccessTime(qint64 t); class CodecStream { public: CodecStream() : stream_(-1), block_(nullptr) { } CodecStream(const QString& filename, int stream, Block *block) : filename_(filename), stream_(stream), block_(block) { } bool IsValid() const { return !filename_.isEmpty() && stream_ >= 0; } bool Exists() const { return QFileInfo::exists(filename_); } void Reset() { *this = CodecStream(); } bool operator==(const CodecStream& rhs) const { return filename_ == rhs.filename_ && stream_ == rhs.stream_; } const QString& filename() const { return filename_; } int stream() const { return stream_; } Block *block() const { return block_; } private: QString filename_; int stream_; Block *block_; }; /** * @brief Open stream for decoding * * This function is thread safe. * * Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is * already open and the stream == the stream provided. Returns FALSE if the stream couldn't * be opened OR if already open and the stream is NOT the same. */ bool Open(const CodecStream& stream); static const rational kAnyTimecode; struct RetrieveVideoParams { Renderer *renderer = nullptr; rational time; int divider = 1; PixelFormat maximum_format = PixelFormat::INVALID; CancelAtom *cancelled = nullptr; VideoParams::ColorRange force_range = VideoParams::kColorRangeDefault; VideoParams::Interlacing src_interlacing = VideoParams::kInterlaceNone; }; /** * @brief Retrieves a video frame from footage * * This function will always return a valid frame unless a fatal error occurs (in such case, * nullptr will return). If the timecode is before the start of the footage, this function should * return the first frame. Likewise, if it is after the timecode, this function should return the * last frame. * * This function is thread safe and can only run while the decoder is open. \see Open() */ TexturePtr RetrieveVideo(const RetrieveVideoParams& p); enum RetrieveAudioStatus { kInvalid = -1, kOK, kWaitingForConform, kUnknownError }; /** * @brief Retrieve audio data from footage * * This function will always return a sample buffer unless a fatal error occurs (in such case, * nullptr will return). The SampleBuffer should always have enough audio for the range provided. * * This function is thread safe and can only run while the decoder is open. \see Open() */ RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode); /** * @brief Determine the last time this decoder instance was used in any way */ qint64 GetLastAccessedTime(); /** * @brief Generate a Footage object from a file * * If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it * will return nullptr. * * For sub-classes, this function should be effectively static. We can't do virtual static * functions in C++, but it should hold and access no state during its run. * * This function is re-entrant. */ virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const = 0; /** * @brief Closes media/deallocates memory * * This function is thread safe and can only run while the decoder is open. \see Open() */ void Close(); /** * @brief Conform audio stream */ bool ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled = nullptr); /** * @brief Create a Decoder instance using a Decoder ID * * @return * * A Decoder instance or nullptr if a Decoder with this ID does not exist */ static DecoderPtr CreateFromID(const QString& id); static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number); static int GetImageSequenceDigitCount(const QString& filename); static int64_t GetImageSequenceIndex(const QString& filename); static QVector ReceiveListOfAllDecoders(); protected: /** * @brief Internal open function * * Sub-classes must override this function. Function will already be mutexed, so there is no need * to worry about thread safety. Also many other sanity checks will be done before this, so * sub-classes only need to worry about their own opening functions. It is guaranteed that the * decoder is not open yet and that the footage stream was from that sub-classes probe function. * * Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise, * return FALSE. If this function returns false, Decoder will call CloseInternal to clean any * memory allocated during OpenInternal. */ virtual bool OpenInternal() = 0; /** * @brief Internal close function * * Sub-classes must override this function. Function should be able to safely clear all allocated * memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called. */ virtual void CloseInternal() = 0; /** * @brief Internal frame retrieval function * * Sub-classes must override this function IF they support video. Function is already mutexed * so sub-classes don't need to worry about thread safety. */ virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p); virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled); void SignalProcessingProgress(int64_t ts, int64_t duration); /** * @brief Return currently open stream * * This function is NOT thread safe and should therefore only be called by thread safe functions. */ const CodecStream& stream() const { return stream_; } virtual rational GetAudioStartOffset() const { return 0; } signals: /** * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if * available */ void IndexProgress(double); private: void UpdateLastAccessed(); bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; QMutex mutex_; std::atomic_int64_t last_accessed_; TexturePtr cached_texture_; rational cached_time_; int cached_divider_; }; uint qHash(Decoder::CodecStream stream, uint seed = 0); } Q_DECLARE_METATYPE(olive::Decoder::RetrieveState) #endif // DECODER_H ================================================ FILE: app/codec/encoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "encoder.h" #include #include "common/xmlutils.h" #include "ffmpeg/ffmpegencoder.h" #include "oiio/oiioencoder.h" namespace olive { const QRegularExpression Encoder::kImageSequenceContainsDigits = QRegularExpression(QStringLiteral("\\[[#]+\\]")); const QRegularExpression Encoder::kImageSequenceRemoveDigits = QRegularExpression(QStringLiteral("[\\-\\.\\ \\_]?\\[[#]+\\]")); Encoder::Encoder(const EncodingParams ¶ms) : params_(params) { } const EncodingParams &Encoder::params() const { return params_; } QString Encoder::GetFilenameForFrame(const rational &frame) { if (params().video_is_image_sequence()) { // Transform! int64_t frame_index = Timecode::time_to_timestamp(frame, params().video_params().frame_rate_as_time_base()); int digits = GetImageSequencePlaceholderDigitCount(params().filename()); QString frame_index_str = QStringLiteral("%1").arg(frame_index, digits, 10, QChar('0')); QString f = params_.filename(); f.replace(kImageSequenceContainsDigits, frame_index_str); return f; } else { // Keep filename return params_.filename(); } } int Encoder::GetImageSequencePlaceholderDigitCount(const QString &filename) { int start = filename.indexOf(kImageSequenceContainsDigits); int digit_count = 0; for (int i=start+1; iname() == QStringLiteral("export")) { int version = 0; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("version")) { version = attr.value().toInt(); } } switch (version) { case 1: return LoadV1(reader); } } else { reader->skipCurrentElement(); } } return false; } bool EncodingParams::Load(QIODevice *device) { QXmlStreamReader reader(device); return Load(&reader); } void EncodingParams::Save(QIODevice *device) const { QXmlStreamWriter writer(device); Save(&writer); } void EncodingParams::Save(QXmlStreamWriter *writer) const { writer->writeStartDocument(); writer->writeStartElement(QStringLiteral("export")); writer->writeAttribute(QStringLiteral("version"), QString::number(kEncoderParamsVersion)); writer->writeTextElement(QStringLiteral("filename"), filename_); writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); writer->writeTextElement(QStringLiteral("customrangein"), QString::fromStdString(custom_range_.in().toString())); writer->writeTextElement(QStringLiteral("customrangeout"), QString::fromStdString(custom_range_.out().toString())); writer->writeStartElement(QStringLiteral("video")); writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_)); if (video_enabled_) { writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_)); writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width())); writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height())); writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format())); writer->writeTextElement(QStringLiteral("pixelaspect"), QString::fromStdString(video_params_.pixel_aspect_ratio().toString())); writer->writeTextElement(QStringLiteral("timebase"), QString::fromStdString(video_params_.time_base().toString())); writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider())); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_)); writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_min_bit_rate_)); writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_)); writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_)); writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_)); writer->writeTextElement(QStringLiteral("pixfmt"), video_pix_fmt_); writer->writeTextElement(QStringLiteral("imgseq"), QString::number(video_is_image_sequence_)); writer->writeStartElement(QStringLiteral("color")); writer->writeTextElement(QStringLiteral("output"), color_transform_.output()); writer->writeEndElement(); // colortransform writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); if (!video_opts_.isEmpty()) { writer->writeStartElement(QStringLiteral("opts")); QHash::const_iterator i; for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) { writer->writeStartElement(QStringLiteral("entry")); writer->writeTextElement(QStringLiteral("key"), i.key()); writer->writeTextElement(QStringLiteral("value"), i.value()); writer->writeEndElement(); // entry } writer->writeEndElement(); // opts } } writer->writeEndElement(); // video writer->writeStartElement(QStringLiteral("audio")); writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_)); if (audio_enabled_) { writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_)); writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate())); writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout())); writer->writeTextElement(QStringLiteral("format"), QString::fromStdString(audio_params_.format().to_string())); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(audio_bit_rate_)); } writer->writeStartElement(QStringLiteral("subtitles")); writer->writeAttribute(QStringLiteral("enabled"), QString::number(subtitles_enabled_)); if (subtitles_enabled_) { writer->writeTextElement(QStringLiteral("sidecar"), QString::number(subtitles_are_sidecar_)); writer->writeTextElement(QStringLiteral("sidecarformat"), QString::number(subtitle_sidecar_fmt_)); writer->writeTextElement(QStringLiteral("codec"), QString::number(subtitles_codec_)); } writer->writeEndElement(); // subtitles writer->writeEndElement(); // audio writer->writeEndElement(); // export writer->writeEndDocument(); } Encoder* Encoder::CreateFromID(Type id, const EncodingParams& params) { switch (id) { case kEncoderTypeNone: break; case kEncoderTypeFFmpeg: return new FFmpegEncoder(params); case kEncoderTypeOIIO: return new OIIOEncoder(params); } return nullptr; } Encoder::Type Encoder::GetTypeFromFormat(ExportFormat::Format f) { switch (f) { case ExportFormat::kFormatDNxHD: case ExportFormat::kFormatMatroska: case ExportFormat::kFormatQuickTime: case ExportFormat::kFormatMPEG4Video: case ExportFormat::kFormatMPEG4Audio: case ExportFormat::kFormatWAV: case ExportFormat::kFormatAIFF: case ExportFormat::kFormatMP3: case ExportFormat::kFormatFLAC: case ExportFormat::kFormatOgg: case ExportFormat::kFormatWebM: case ExportFormat::kFormatSRT: return kEncoderTypeFFmpeg; case ExportFormat::kFormatOpenEXR: case ExportFormat::kFormatPNG: case ExportFormat::kFormatTIFF: return kEncoderTypeOIIO; case ExportFormat::kFormatCount: break; } return kEncoderTypeNone; } Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms) { return CreateFromID(GetTypeFromFormat(f), params); } Encoder *Encoder::CreateFromParams(const EncodingParams ¶ms) { return CreateFromFormat(params.format(), params); } QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const { return QStringList(); } std::vector Encoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const { return std::vector(); } QMatrix4x4 EncodingParams::GenerateMatrix(EncodingParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height) { QMatrix4x4 preview_matrix; if (method == EncodingParams::kStretch) { return preview_matrix; } float export_ar = static_cast(dest_width) / static_cast(dest_height); float source_ar = static_cast(source_width) / static_cast(source_height); if (qFuzzyCompare(export_ar, source_ar)) { return preview_matrix; } if ((export_ar > source_ar) == (method == EncodingParams::kFit)) { preview_matrix.scale(source_ar / export_ar, 1.0F); } else { preview_matrix.scale(1.0F, export_ar / source_ar); } return preview_matrix; } bool EncodingParams::LoadV1(QXmlStreamReader *reader) { rational custom_range_in, custom_range_out; while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("filename")) { filename_ = reader->readElementText(); } else if (reader->name() == QStringLiteral("format")) { format_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("range")) { has_custom_range_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("customrangein")) { custom_range_in = rational::fromString(reader->readElementText().toStdString()); } else if (reader->name() == QStringLiteral("customrangeout")) { custom_range_out = rational::fromString(reader->readElementText().toStdString()); } else if (reader->name() == QStringLiteral("video")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { video_enabled_ = attr.value().toInt(); } } while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("codec")) { video_codec_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("width")) { video_params_.set_width(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("height")) { video_params_.set_height(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("format")) { video_params_.set_format(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("pixelaspect")) { video_params_.set_pixel_aspect_ratio(rational::fromString(reader->readElementText().toStdString())); } else if (reader->name() == QStringLiteral("timebase")) { video_params_.set_time_base(rational::fromString(reader->readElementText().toStdString())); } else if (reader->name() == QStringLiteral("divider")) { video_params_.set_divider(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("bitrate")) { video_bit_rate_ = reader->readElementText().toLongLong(); } else if (reader->name() == QStringLiteral("minbitrate")) { video_min_bit_rate_ = reader->readElementText().toLongLong(); } else if (reader->name() == QStringLiteral("maxbitrate")) { video_max_bit_rate_ = reader->readElementText().toLongLong(); } else if (reader->name() == QStringLiteral("bufsize")) { video_buffer_size_ = reader->readElementText().toLongLong(); } else if (reader->name() == QStringLiteral("threads")) { video_threads_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("pixfmt")) { video_pix_fmt_ = reader->readElementText(); } else if (reader->name() == QStringLiteral("imgseq")) { video_is_image_sequence_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("color")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("output")) { color_transform_ = reader->readElementText(); } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("vscale")) { video_scaling_method_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("opts")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("entry")) { QString key, value; while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("key")) { key = reader->readElementText(); } else if (reader->name() == QStringLiteral("value")) { value = reader->readElementText(); } else { reader->skipCurrentElement(); } } set_video_option(key, value); } else { reader->skipCurrentElement(); } } } else { reader->skipCurrentElement(); } } // HACK: Resolve bug where I forgot to serialize pixel aspect ratio if (video_params_.pixel_aspect_ratio().isNull()) { video_params_.set_pixel_aspect_ratio(1); } } else if (reader->name() == QStringLiteral("audio")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { audio_enabled_ = attr.value().toInt(); } } while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("codec")) { audio_codec_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("samplerate")) { audio_params_.set_sample_rate(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("channellayout")) { audio_params_.set_channel_layout(reader->readElementText().toULongLong()); } else if (reader->name() == QStringLiteral("format")) { audio_params_.set_format(SampleFormat::from_string(reader->readElementText().toStdString())); } else if (reader->name() == QStringLiteral("bitrate")) { audio_bit_rate_ = reader->readElementText().toLongLong(); } else { reader->skipCurrentElement(); } } // HACK: Resolve bug where I forgot to serialize the audio bit rate if (!audio_bit_rate_) { audio_bit_rate_ = 320000; } } else if (reader->name() == QStringLiteral("subtitles")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { subtitles_enabled_ = attr.value().toInt(); } } while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("sidecar")) { subtitles_are_sidecar_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("sidecarformat")) { subtitle_sidecar_fmt_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("codec")) { subtitles_codec_ = static_cast(reader->readElementText().toInt()); } else { reader->skipCurrentElement(); } } } else { reader->skipCurrentElement(); } } return true; } } ================================================ FILE: app/codec/encoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef ENCODER_H #define ENCODER_H #include #include #include #include #include "codec/exportcodec.h" #include "codec/exportformat.h" #include "codec/frame.h" #include "node/block/subtitle/subtitle.h" #include "render/colortransform.h" #include "render/subtitleparams.h" #include "render/videoparams.h" namespace olive { class Encoder; using EncoderPtr = std::shared_ptr; class EncodingParams { public: enum VideoScalingMethod { kFit, kStretch, kCrop }; EncodingParams(); static QDir GetPresetPath(); static QStringList GetListOfPresets(); bool IsValid() const { return video_enabled_ || audio_enabled_ || subtitles_enabled_; } void SetFilename(const QString& filename) { filename_ = filename; } void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec); void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec); void EnableSubtitles(const ExportCodec::Codec &scodec); void EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec); void DisableVideo(); void DisableAudio(); void DisableSubtitles(); const ExportFormat::Format &format() const { return format_; } void set_format(const ExportFormat::Format &format) { format_ = format; } void set_video_option(const QString& key, const QString& value) { video_opts_.insert(key, value); } void set_video_bit_rate(const int64_t& rate) { video_bit_rate_ = rate; } void set_video_min_bit_rate(const int64_t& rate) { video_min_bit_rate_ = rate; } void set_video_max_bit_rate(const int64_t& rate) { video_max_bit_rate_ = rate; } void set_video_buffer_size(const int64_t& sz) { video_buffer_size_ = sz; } void set_video_threads(const int& threads) { video_threads_ = threads; } void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; } void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; } void set_color_transform(const ColorTransform& color_transform) { color_transform_ = color_transform; } const QString& filename() const { return filename_; } bool video_enabled() const { return video_enabled_; } const ExportCodec::Codec& video_codec() const { return video_codec_; } const VideoParams& video_params() const { return video_params_; } const QHash& video_opts() const { return video_opts_; } QString video_option(const QString &key) const { return video_opts_.value(key); } bool has_video_opt(const QString &key) const { return video_opts_.contains(key); } const int64_t& video_bit_rate() const { return video_bit_rate_; } const int64_t& video_min_bit_rate() const { return video_min_bit_rate_; } const int64_t& video_max_bit_rate() const { return video_max_bit_rate_; } const int64_t& video_buffer_size() const { return video_buffer_size_; } const int& video_threads() const { return video_threads_; } const QString& video_pix_fmt() const { return video_pix_fmt_; } bool video_is_image_sequence() const { return video_is_image_sequence_; } const ColorTransform& color_transform() const { return color_transform_; } bool audio_enabled() const { return audio_enabled_; } const ExportCodec::Codec &audio_codec() const { return audio_codec_; } const AudioParams& audio_params() const { return audio_params_; } const int64_t& audio_bit_rate() const { return audio_bit_rate_; } void set_audio_bit_rate(const int64_t& b) { audio_bit_rate_ = b; } bool subtitles_enabled() const { return subtitles_enabled_; } bool subtitles_are_sidecar() const { return subtitles_are_sidecar_; } ExportFormat::Format subtitle_sidecar_fmt() const { return subtitle_sidecar_fmt_; } ExportCodec::Codec subtitles_codec() const { return subtitles_codec_; } const rational& GetExportLength() const { return export_length_; } void SetExportLength(const rational& export_length) { export_length_ = export_length; } bool Load(QIODevice *device); bool Load(QXmlStreamReader *reader); void Save(QIODevice *device) const; void Save(QXmlStreamWriter* writer) const; bool has_custom_range() const { return has_custom_range_; } const TimeRange& custom_range() const { return custom_range_; } void set_custom_range(const TimeRange& custom_range) { has_custom_range_ = true; custom_range_ = custom_range; } const VideoScalingMethod& video_scaling_method() const { return video_scaling_method_; } void set_video_scaling_method(const VideoScalingMethod& video_scaling_method) { video_scaling_method_ = video_scaling_method; } static QMatrix4x4 GenerateMatrix(VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height); private: static const int kEncoderParamsVersion = 1; bool LoadV1(QXmlStreamReader *reader); QString filename_; ExportFormat::Format format_; bool video_enabled_; ExportCodec::Codec video_codec_; VideoParams video_params_; QHash video_opts_; int64_t video_bit_rate_; int64_t video_min_bit_rate_; int64_t video_max_bit_rate_; int64_t video_buffer_size_; int video_threads_; QString video_pix_fmt_; bool video_is_image_sequence_; ColorTransform color_transform_; bool audio_enabled_; ExportCodec::Codec audio_codec_; AudioParams audio_params_; int64_t audio_bit_rate_; bool subtitles_enabled_; bool subtitles_are_sidecar_; ExportFormat::Format subtitle_sidecar_fmt_; ExportCodec::Codec subtitles_codec_; rational export_length_; VideoScalingMethod video_scaling_method_; bool has_custom_range_; TimeRange custom_range_; }; class Encoder : public QObject { Q_OBJECT public: Encoder(const EncodingParams& params); enum Type { kEncoderTypeNone = -1, kEncoderTypeFFmpeg, kEncoderTypeOIIO }; /** * @brief Create a Encoder instance using a Encoder ID * * @return * * A Encoder instance or nullptr if a Decoder with this ID does not exist */ static Encoder *CreateFromID(Type id, const EncodingParams ¶ms); static Type GetTypeFromFormat(ExportFormat::Format f); static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms); static Encoder *CreateFromParams(const EncodingParams ¶ms); virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const; virtual std::vector GetSampleFormatsForCodec(ExportCodec::Codec c) const; const EncodingParams& params() const; virtual PixelFormat GetDesiredPixelFormat() const { return PixelFormat::INVALID; } const QString& GetError() const { return error_; } QString GetFilenameForFrame(const rational& frame); static int GetImageSequencePlaceholderDigitCount(const QString& filename); static bool FilenameContainsDigitPlaceholder(const QString &filename); static QString FilenameRemoveDigitPlaceholder(QString filename); static const QRegularExpression kImageSequenceContainsDigits; static const QRegularExpression kImageSequenceRemoveDigits; public slots: virtual bool Open() = 0; virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) = 0; virtual bool WriteAudio(const olive::SampleBuffer &audio) = 0; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0; virtual void Close() = 0; protected: void SetError(const QString& err) { error_ = err; } private: EncodingParams params_; QString error_; }; } #endif // ENCODER_H ================================================ FILE: app/codec/exportcodec.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportcodec.h" extern "C" { #include #include } namespace olive { QString ExportCodec::GetCodecName(ExportCodec::Codec c) { switch (c) { case kCodecDNxHD: return tr("DNxHD"); case kCodecH264: return tr("H.264"); case kCodecH264rgb: return tr("H.264 RGB"); case kCodecH265: return tr("H.265"); case kCodecOpenEXR: return tr("OpenEXR"); case kCodecPNG: return tr("PNG"); case kCodecProRes: return tr("ProRes"); case kCodecCineform: return tr("Cineform"); case kCodecTIFF: return tr("TIFF"); case kCodecMP2: return tr("MP2"); case kCodecMP3: return tr("MP3"); case kCodecAAC: return tr("AAC"); case kCodecPCM: return tr("PCM (Uncompressed)"); case kCodecFLAC: return tr("FLAC"); case kCodecOpus: return tr("Opus"); case kCodecVorbis: return tr("Vorbis"); case kCodecVP9: return tr("VP9"); case kCodecAV1: return tr("AV1"); case kCodecSRT: return tr("SubRip SRT"); case kCodecCount: break; } return tr("Unknown"); } bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c) { switch (c) { case kCodecDNxHD: case kCodecH264: case kCodecH264rgb: case kCodecH265: case kCodecProRes: case kCodecCineform: case kCodecMP2: case kCodecMP3: case kCodecAAC: case kCodecPCM: case kCodecVorbis: case kCodecOpus: case kCodecFLAC: case kCodecVP9: case kCodecAV1: case kCodecSRT: return false; case kCodecOpenEXR: case kCodecPNG: case kCodecTIFF: return true; case kCodecCount: break; } return false; } bool ExportCodec::IsCodecLossless(Codec c) { switch (c) { case kCodecPCM: case kCodecFLAC: return true; case kCodecDNxHD: case kCodecH264: case kCodecH264rgb: case kCodecH265: case kCodecProRes: case kCodecCineform: case kCodecMP2: case kCodecMP3: case kCodecAAC: case kCodecVorbis: case kCodecOpus: case kCodecVP9: case kCodecAV1: case kCodecSRT: case kCodecOpenEXR: case kCodecPNG: case kCodecTIFF: case kCodecCount: break; } return false; } } ================================================ FILE: app/codec/exportcodec.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTCODEC_H #define EXPORTCODEC_H #include #include #include "common/define.h" #include "render/subtitleparams.h" namespace olive { class ExportCodec : public QObject { Q_OBJECT public: // Only append to this list (never insert) because indexes are used in serialized files enum Codec { kCodecDNxHD, kCodecH264, kCodecH264rgb, kCodecH265, kCodecOpenEXR, kCodecPNG, kCodecProRes, kCodecCineform, kCodecTIFF, kCodecVP9, kCodecMP2, kCodecMP3, kCodecAAC, kCodecPCM, kCodecOpus, kCodecVorbis, kCodecFLAC, kCodecSRT, kCodecAV1, kCodecCount }; static QString GetCodecName(Codec c); static bool IsCodecAStillImage(Codec c); static bool IsCodecLossless(Codec c); }; } #endif // EXPORTCODEC_H ================================================ FILE: app/codec/exportformat.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportformat.h" #include "encoder.h" namespace olive { QString ExportFormat::GetName(olive::ExportFormat::Format f) { switch (f) { case kFormatDNxHD: return tr("DNxHD"); case kFormatMatroska: return tr("Matroska Video"); case kFormatMPEG4Video: return tr("MPEG-4 Video"); case kFormatMPEG4Audio: return tr("MPEG-4 Audio"); case kFormatOpenEXR: return tr("OpenEXR"); case kFormatPNG: return tr("PNG"); case kFormatTIFF: return tr("TIFF"); case kFormatQuickTime: return tr("QuickTime"); case kFormatWAV: return tr("Wave Audio"); case kFormatAIFF: return tr("AIFF"); case kFormatMP3: return tr("MP3"); case kFormatFLAC: return tr("FLAC"); case kFormatOgg: return tr("Ogg"); case kFormatWebM: return tr("WebM"); case kFormatSRT: return tr("SubRip SRT"); case kFormatCount: break; } return tr("Unknown"); } QString ExportFormat::GetExtension(ExportFormat::Format f) { switch (f) { case kFormatDNxHD: return QStringLiteral("mxf"); case kFormatMatroska: return QStringLiteral("mkv"); case kFormatMPEG4Video: return QStringLiteral("mp4"); case kFormatMPEG4Audio: return QStringLiteral("m4a"); case kFormatOpenEXR: return QStringLiteral("exr"); case kFormatPNG: return QStringLiteral("png"); case kFormatTIFF: return QStringLiteral("tiff"); case kFormatQuickTime: return QStringLiteral("mov"); case kFormatWAV: return QStringLiteral("wav"); case kFormatAIFF: return QStringLiteral("aiff"); case kFormatMP3: return QStringLiteral("mp3"); case kFormatFLAC: return QStringLiteral("flac"); case kFormatOgg: return QStringLiteral("ogg"); case kFormatWebM: return QStringLiteral("webm"); case kFormatSRT: return QStringLiteral("srt"); case kFormatCount: break; } return QString(); } QList ExportFormat::GetVideoCodecs(ExportFormat::Format f) { switch (f) { case kFormatDNxHD: return {ExportCodec::kCodecDNxHD}; case kFormatMatroska: return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecVP9}; case kFormatMPEG4Video: return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265}; case kFormatOpenEXR: return {ExportCodec::kCodecOpenEXR}; case kFormatPNG: return {ExportCodec::kCodecPNG}; case kFormatTIFF: return {ExportCodec::kCodecTIFF}; case kFormatQuickTime: return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes, ExportCodec::kCodecCineform}; case kFormatWebM: return {ExportCodec::kCodecAV1, ExportCodec::kCodecVP9}; case kFormatOgg: case kFormatWAV: case kFormatMPEG4Audio: case kFormatAIFF: case kFormatMP3: case kFormatFLAC: case kFormatSRT: case kFormatCount: break; } return {}; } QList ExportFormat::GetAudioCodecs(ExportFormat::Format f) { switch (f) { // Video/audio formats case kFormatDNxHD: return {ExportCodec::kCodecPCM}; case kFormatMatroska: return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus, ExportCodec::kCodecFLAC}; case kFormatMPEG4Video: case kFormatMPEG4Audio: return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3}; case kFormatQuickTime: return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM}; case kFormatWebM: return {ExportCodec::kCodecOpus, ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis}; // Audio only formats case kFormatWAV: return {ExportCodec::kCodecPCM}; case kFormatAIFF: return {ExportCodec::kCodecPCM}; case kFormatMP3: return {ExportCodec::kCodecMP3}; case kFormatFLAC: return {ExportCodec::kCodecFLAC}; case kFormatOgg: return {ExportCodec::kCodecOpus, ExportCodec::kCodecVorbis, ExportCodec::kCodecPCM}; // Video only formats case kFormatOpenEXR: case kFormatPNG: case kFormatTIFF: case kFormatSRT: case kFormatCount: break; } return {}; } QList ExportFormat::GetSubtitleCodecs(Format f) { switch (f) { case kFormatDNxHD: case kFormatMPEG4Video: case kFormatMPEG4Audio: case kFormatOpenEXR: case kFormatQuickTime: case kFormatPNG: case kFormatTIFF: case kFormatWAV: case kFormatAIFF: case kFormatMP3: case kFormatFLAC: case kFormatOgg: case kFormatWebM: case kFormatCount: break; case kFormatMatroska: case kFormatSRT: return {ExportCodec::kCodecSRT}; } return {}; } QStringList ExportFormat::GetPixelFormatsForCodec(ExportFormat::Format f, ExportCodec::Codec c) { Encoder* e = Encoder::CreateFromFormat(f, EncodingParams()); QStringList list; if (e) { list = e->GetPixelFormatsForCodec(c); delete e; } return list; } std::vector ExportFormat::GetSampleFormatsForCodec(Format format, ExportCodec::Codec c) { std::vector f; Encoder *e = Encoder::CreateFromFormat(format, EncodingParams()); if (e) { f = e->GetSampleFormatsForCodec(c); delete e; } return f; } } ================================================ FILE: app/codec/exportformat.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTFORMAT_H #define EXPORTFORMAT_H #include #include #include "common/define.h" #include "exportcodec.h" namespace olive { class ExportFormat : public QObject { Q_OBJECT public: // Only append to this list (never insert) because indexes are used in serialized files enum Format { kFormatDNxHD, kFormatMatroska, kFormatMPEG4Video, kFormatOpenEXR, kFormatQuickTime, kFormatPNG, kFormatTIFF, kFormatWAV, kFormatAIFF, kFormatMP3, kFormatFLAC, kFormatOgg, kFormatWebM, kFormatSRT, kFormatMPEG4Audio, kFormatCount }; static QString GetName(Format f); static QString GetExtension(Format f); static QList GetVideoCodecs(ExportFormat::Format f); static QList GetAudioCodecs(ExportFormat::Format f); static QList GetSubtitleCodecs(ExportFormat::Format f); static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c); static std::vector GetSampleFormatsForCodec(Format f, ExportCodec::Codec c); }; } #endif // EXPORTFORMAT_H ================================================ FILE: app/codec/ffmpeg/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} codec/ffmpeg/ffmpegdecoder.cpp codec/ffmpeg/ffmpegdecoder.h codec/ffmpeg/ffmpegencoder.cpp codec/ffmpeg/ffmpegencoder.h PARENT_SCOPE ) ================================================ FILE: app/codec/ffmpeg/ffmpegdecoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "ffmpegdecoder.h" extern "C" { #include #include #include #include #include #include #include } #include #include #include #include #include #include #include #include #include "codec/planarfiledevice.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "render/renderer.h" #include "render/subtitleparams.h" namespace olive { QVariant Yuv2RgbShader; QVariant DeinterlaceShader; FFmpegDecoder::FFmpegDecoder() : sws_ctx_(nullptr), working_packet_(nullptr), cache_at_zero_(false), cache_at_eof_(false) { } bool FFmpegDecoder::OpenInternal() { if (instance_.Open(stream().filename().toUtf8(), stream().stream())) { AVStream* s = instance_.avstream(); // Store one second in the source's timebase second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); working_packet_ = av_packet_alloc(); return true; } return false; } TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original) { // Determine native format AVPixelFormat ideal_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(f->format)); PixelFormat native_fmt = GetNativePixelFormat(ideal_fmt); int native_channels = GetNativeChannelCount(ideal_fmt); // Set up video params VideoParams vp(original->width, original->height, native_fmt, native_channels, av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), VideoParams::kInterlaceNone, p.divider); // Create texture TexturePtr tex = p.renderer->CreateTexture(vp); switch (f->format) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: case AV_PIX_FMT_YUV420P10LE: case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV444P10LE: case AV_PIX_FMT_YUV420P12LE: case AV_PIX_FMT_YUV422P12LE: case AV_PIX_FMT_YUV444P12LE: { // Run through YUV to RGB shader if (Yuv2RgbShader.isNull()) { // Compile shader Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); if (Yuv2RgbShader.isNull()) { return nullptr; } } int px_size; int bits_per_pixel; switch (f->format) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: default: px_size = 1; bits_per_pixel = 8; break; case AV_PIX_FMT_YUV420P10LE: case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV444P10LE: px_size = 2; bits_per_pixel = 10; break; case AV_PIX_FMT_YUV420P12LE: case AV_PIX_FMT_YUV422P12LE: case AV_PIX_FMT_YUV444P12LE: px_size = 2; bits_per_pixel = 12; break; } AVFrame *hw_in = f.get(); VideoParams plane_params = vp; plane_params.set_channel_count(1); plane_params.set_format(native_fmt); TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); switch (f->format) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV420P10LE: case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV420P12LE: case AV_PIX_FMT_YUV422P12LE: plane_params.set_width(plane_params.width()/2); break; } switch (f->format) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV420P10LE: case AV_PIX_FMT_YUV420P12LE: plane_params.set_height(plane_params.height()/2); break; } TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->linesize[2] / px_size); ShaderJob job; job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG)); const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace)); job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0)); job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0)); job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0)); job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0)); tex = p.renderer->CreateTexture(vp); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); break; } case AV_PIX_FMT_RGBA: case AV_PIX_FMT_RGBA64LE: // RGBA can be uploaded directly to the texture tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel()); break; } // Deinterlace if necessary if (p.src_interlacing != VideoParams::kInterlaceNone) { if (DeinterlaceShader.isNull()) { // Compile shader DeinterlaceShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace2.frag")))); if (DeinterlaceShader.isNull()) { return nullptr; } } rational frame_rate_tb = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), original.get()); // Double frame rate for interlaced fields frame_rate_tb *= 2; // Flip frame rate so it can be used as a timebase frame_rate_tb.flip(); int64_t req = Timecode::time_to_timestamp(p.time + rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE), frame_rate_tb); int64_t frm = Timecode::rescale_timestamp(original->pts, instance_.avstream()->time_base, frame_rate_tb); bool first = (req == frm); bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); int interlacing = (first == top_first) ? 1 : 2; TexturePtr deinterlaced = p.renderer->CreateTexture(tex->params()); ShaderJob job; job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, tex)); job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, original->height)); p.renderer->BlitToTexture(DeinterlaceShader, job, deinterlaced.get(), false); tex = deinterlaced; } return tex; } TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } AVFramePtr original = f; // Disregard "JPEG" pixel formats because we allow the user to override that f->format = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast(f->format)); // Force frame's color range to whatever it's set to in Olive f->color_range = p.force_range == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; // Perform any CPU processing required f = PreProcessFrame(f, p); if (!f) { // Error occurred while software scaling return nullptr; } // Finally, perform any GPU processing required return ProcessFrameIntoTexture(f, p, original); } return nullptr; } void FFmpegDecoder::CloseInternal() { if (working_packet_) { av_packet_free(&working_packet_); working_packet_ = nullptr; } ClearFrameCache(); FreeScaler(); instance_.Close(); } rational FFmpegDecoder::GetAudioStartOffset() const { auto f = instance_.fmt_ctx(); if (f) { rational fmt_start = rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE); rational str_start = rational(instance_.avstream()->time_base) * instance_.avstream()->start_time; return str_start - fmt_start; } else { return 0; } } QString FFmpegDecoder::id() const { return QStringLiteral("ffmpeg"); } FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *cancelled) const { // Return value FootageDescription desc(id()); // Variable for receiving errors from FFmpeg int error_code; // Convert QString to a C string QByteArray filename_c = filename.toUtf8(); // Open file in a format context AVFormatContext* fmt_ctx = nullptr; error_code = avformat_open_input(&fmt_ctx, filename_c, nullptr, nullptr); // Handle format context error if (error_code == 0) { // Retrieve metadata about the media avformat_find_stream_info(fmt_ctx, nullptr); int64_t footage_duration = fmt_ctx->duration; bool duration_guessed_from_bitrate = (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE); if (duration_guessed_from_bitrate) { qWarning() << "Unreliable duration detected - we will manually determine it ourselves (this may take some time)"; } // Dump it into the Footage object int video_streams = 0, audio_streams = 0, still_streams = 0; for (unsigned int i=0;inb_streams;i++) { // FFmpeg AVStream AVStream* avstream = fmt_ctx->streams[i]; // Find decoder for this stream, if it exists we can proceed const AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id); if (decoder && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) { if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { AVPixelFormat compatible_pix_fmt = AV_PIX_FMT_NONE; bool image_is_still = false; rational pixel_aspect_ratio; rational frame_rate; VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone; { // Read at least two frames to get more information about this video stream AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); { Instance instance; instance.Open(filename_c, avstream->index); // Read first frame and retrieve some metadata if (instance.GetFrame(pkt, frame) >= 0) { // Check if video is interlaced and what field dominance it has if so if (frame->interlaced_frame) { if (frame->top_field_first) { interlacing = VideoParams::kInterlacedTopFirst; } else { interlacing = VideoParams::kInterlacedBottomFirst; } } pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(), instance.avstream(), frame); frame_rate = av_guess_frame_rate(instance.fmt_ctx(), instance.avstream(), frame); compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)); } // Read second frame int ret = instance.GetFrame(pkt, frame); if (ret >= 0) { // Check if we need a manual duration if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { // Manually read through file for duration int64_t new_dur; do { new_dur = frame->best_effort_timestamp; } while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled())); avstream->duration = new_dur; } else { // Fallback to footage duration avstream->duration = Timecode::rescale_timestamp_ceil(footage_duration, rational(1, AV_TIME_BASE), avstream->time_base); } } } else if (ret == AVERROR_EOF) { // Video has only one frame in it, treat it like a still image image_is_still = true; } instance.Close(); } av_frame_free(&frame); av_packet_free(&pkt); } VideoParams stream; stream.set_stream_index(i); stream.set_width(avstream->codecpar->width); stream.set_height(avstream->codecpar->height); stream.set_video_type((image_is_still) ? VideoParams::kVideoTypeStill : VideoParams::kVideoTypeVideo); stream.set_format(GetNativePixelFormat(compatible_pix_fmt)); stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); stream.set_interlacing(interlacing); stream.set_pixel_aspect_ratio(pixel_aspect_ratio); stream.set_frame_rate(frame_rate); stream.set_start_time(avstream->start_time); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG ? VideoParams::kColorRangeFull : VideoParams::kColorRangeLimited); // Defaults to false, requires user intervention if incorrect stream.set_premultiplied_alpha(false); desc.AddVideoStream(stream); if (image_is_still) { still_streams++; } else { video_streams++; } } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // Create an audio stream object uint64_t channel_layout = avstream->codecpar->channel_layout; if (!channel_layout) { channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { // Loop through stream until we get the whole duration if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { Instance instance; instance.Open(filename_c, avstream->index); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); int64_t new_dur; do { new_dur = frame->best_effort_timestamp; } while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled())); avstream->duration = new_dur; av_frame_free(&frame); av_packet_free(&pkt); instance.Close(); } else { avstream->duration = Timecode::rescale_timestamp_ceil(footage_duration, rational(1, AV_TIME_BASE), avstream->time_base); } } AudioParams stream; stream.set_stream_index(i); stream.set_channel_layout(channel_layout); stream.set_sample_rate(avstream->codecpar->sample_rate); stream.set_format(FFmpegUtils::GetNativeSampleFormat(static_cast(avstream->codecpar->format))); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); desc.AddAudioStream(stream); audio_streams++; } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { // Limit to SRT for now... if (avstream->codecpar->codec_id == AV_CODEC_ID_SUBRIP) { SubtitleParams sub; AVPacket* pkt = av_packet_alloc(); { Instance instance; instance.Open(filename_c, avstream->index); while (instance.GetPacket(pkt) >= 0) { TimeRange time(Timecode::timestamp_to_time(pkt->pts, avstream->time_base), Timecode::timestamp_to_time(pkt->pts + pkt->duration, avstream->time_base)); QString text = QString::fromUtf8((const char *) pkt->data, pkt->size); sub.push_back(Subtitle(time, text)); } instance.Close(); } av_packet_free(&pkt); desc.AddSubtitleStream(sub); } } } } desc.SetStreamCount(fmt_ctx->nb_streams); if (video_streams == 0 && audio_streams > 0 && still_streams > 0) { // This footage has no video streams, but has audio and image streams. We've probably // imported a song with embedded album art that most people don't care about. We'll keep the // stills referenced in case users do, but we'll default them to disabled so they're // easier to work with. for (VideoParams &vp : desc.GetVideoStreams()) { vp.set_enabled(false); } } } // Free all memory avformat_close_input(&fmt_ctx); return desc; } QString FFmpegDecoder::FFmpegError(int error_code) { char err[1024]; av_strerror(error_code, err, 512); return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, CancelAtom *cancelled) { // Iterate through each audio frame and extract the PCM data // Seek to starting point instance_.Seek(0); // Handle NULL channel layout uint64_t channel_layout = ValidateChannelLayout(instance_.avstream()); if (!channel_layout) { qCritical() << "Failed to determine channel layout of audio file, could not conform"; return false; } // Create resampling context SwrContext* resampler = swr_alloc_set_opts(nullptr, params.channel_layout(), FFmpegUtils::GetFFmpegSampleFormat(params.format()), params.sample_rate(), channel_layout, static_cast(instance_.avstream()->codecpar->format), instance_.avstream()->codecpar->sample_rate, 0, nullptr); swr_init(resampler); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); int ret; bool success = false; int64_t duration = instance_.avstream()->duration; if (duration == 0 || duration == AV_NOPTS_VALUE) { duration = instance_.fmt_ctx()->duration; if (!(duration == 0 || duration == AV_NOPTS_VALUE)) { // Rescale from AVFormatContext timebase to AVStream timebase duration = av_rescale_q_rnd(duration, {1, AV_TIME_BASE}, instance_.avstream()->time_base, AV_ROUND_UP); } } PlanarFileDevice wave_out; if (wave_out.open(filenames, QFile::WriteOnly)) { int nb_channels = params.channel_count(); SampleBuffer data; data.set_audio_params(params); while (true) { // Check if we have a `cancelled` ptr and its value if (cancelled && cancelled->IsCancelled()) { break; } ret = instance_.GetFrame(pkt, frame); if (ret < 0) { if (ret == AVERROR_EOF) { success = true; } else { char err_str[512]; av_strerror(ret, err_str, 512); qWarning() << "Failed to conform:" << ret << err_str; } break; } // Allocate buffers int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); int nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; data.set_sample_count(nb_bytes_per_channel); data.allocate(); // Resample audio to our destination parameters nb_samples = swr_convert(resampler, reinterpret_cast(data.to_raw_ptrs().data()), nb_samples, const_cast(frame->data), frame->nb_samples); // If no error, write to files if (nb_samples > 0) { // Update byte count for the number of samples we actually received nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; // Write to files wave_out.write(const_cast(reinterpret_cast(data.to_raw_ptrs().data())), nb_bytes_per_channel); } // Free buffer data.destroy(); // Handle error now after freeing if (nb_samples < 0) { char err_str[512]; av_strerror(nb_samples, err_str, 512); qWarning() << "libswresample failed with error:" << nb_samples << err_str; break; } SignalProcessingProgress(frame->best_effort_timestamp, duration); } wave_out.close(); } else { qWarning() << "Failed to open WAVE output for indexing"; } swr_free(&resampler); av_frame_free(&frame); av_packet_free(&pkt); return success; } PixelFormat FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_RGB24: case AV_PIX_FMT_RGBA: return PixelFormat::U8; case AV_PIX_FMT_RGB48: case AV_PIX_FMT_RGBA64: return PixelFormat::U16; default: return PixelFormat::INVALID; } } int FFmpegDecoder::GetNativeChannelCount(AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_RGB24: case AV_PIX_FMT_RGB48: return VideoParams::kRGBChannelCount; case AV_PIX_FMT_RGBA: case AV_PIX_FMT_RGBA64: return VideoParams::kRGBAChannelCount; default: return 0; } } uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) { if (stream->codecpar->channel_layout) { return stream->codecpar->channel_layout; } return av_get_default_channel_layout(stream->codecpar->channels); } const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing) { if (interlacing == VideoParams::kInterlacedTopFirst) { return "tff"; } else { return "bff"; } } bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f) { // NOTE: We don't include RGB24 or RGB48 here because those are slow on the GPU and performance // should be better if we convert to RGBA on the CPU beforehand switch (f) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: case AV_PIX_FMT_YUV420P10LE: case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV444P10LE: case AV_PIX_FMT_YUV420P12LE: case AV_PIX_FMT_YUV422P12LE: case AV_PIX_FMT_YUV444P12LE: case AV_PIX_FMT_RGBA: case AV_PIX_FMT_RGBA64LE: return true; default: return false; } } void FFmpegDecoder::ClearFrameCache() { if (!cached_frames_.empty()) { cached_frames_.clear(); cache_at_eof_ = false; cache_at_zero_ = false; } } AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p) { // In pre-processing, we try to achieve the following: // - If a divider is being used, scale down the image // - If a pixel format is not compatible with the GLSL shader, convert it to RGBA ourselves if (p.divider == 1 && IsPixelFormatGLSLCompatible(static_cast(f->format))) { // No CPU processing required, the user wants this in full resolution and the pixel format can // be converted on the GPU return f; } // Some scaling and/or format conversion needs to be done AVFramePtr dest = CreateAVFramePtr(); dest->width = f->width; dest->height = f->height; dest->format = f->format; dest->color_range = f->color_range; dest->colorspace = f->colorspace; if (p.divider > 1) { dest->width = VideoParams::GetScaledDimension(dest->width, p.divider); dest->height = VideoParams::GetScaledDimension(dest->height, p.divider); } if (!IsPixelFormatGLSLCompatible(static_cast(dest->format))) { dest->format = FFmpegUtils::GetCompatiblePixelFormat(static_cast(dest->format), p.maximum_format); } int r = av_frame_get_buffer(dest.get(), 0); if (r < 0) { FFmpegError(r); return nullptr; } if (!sws_ctx_ || sws_src_width_ != f->width || sws_src_height_ != f->height || sws_src_format_ != f->format || sws_dst_width_ != dest->width || sws_dst_height_ != dest->height || sws_dst_format_ != dest->format || sws_colrange_ != dest->color_range || sws_colspace_ != dest->colorspace) { // SwsContext must be recreated, destroy current if it exists FreeScaler(); // Cache info sws_src_width_ = f->width; sws_src_height_ = f->height; sws_src_format_ = static_cast(f->format); sws_dst_width_ = dest->width; sws_dst_height_ = dest->height; sws_dst_format_ = static_cast(dest->format); sws_colrange_ = dest->color_range; sws_colspace_ = dest->colorspace; // Create new scaler sws_ctx_ = sws_getContext(sws_src_width_, sws_src_height_, sws_src_format_, sws_dst_width_, sws_dst_height_, sws_dst_format_, SWS_POINT, nullptr, nullptr, nullptr); // Set swscale's colorspace details sws_setColorspaceDetails(sws_ctx_, sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)), dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)), dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, 0, 0x10000, 0x10000); } r = sws_scale(sws_ctx_, f->data, f->linesize, 0, f->height, dest->data, dest->linesize); if (r < 0) { FFmpegError(r); return nullptr; } return dest; } AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled) { int64_t target_ts = Timecode::time_to_timestamp(time, instance_.avstream()->time_base); if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base); } const int64_t min_seek = 0; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; if (time != kAnyTimecode) { // If the frame wasn't in the frame cache, see if this frame cache is too old to use if (cached_frames_.empty() || (target_ts < cached_frames_.front()->pts || target_ts > cached_frames_.back()->pts + 2*second_ts_)) { ClearFrameCache(); instance_.Seek(seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } still_seeking = true; } else { // Search cache for frame AVFramePtr cached_frame = GetFrameFromCache(target_ts); if (cached_frame) { return cached_frame; } } } int ret; AVFramePtr return_frame = nullptr; AVFramePtr filtered = nullptr; while (true) { // Break out of loop if we've cancelled if (cancelled && cancelled->IsCancelled()) { break; } if (!filtered) { filtered = CreateAVFramePtr(); } // Pull from the decoder ret = instance_.GetFrame(working_packet_, filtered.get()); if (cancelled && cancelled->IsCancelled()) { break; } // Handle any errors that aren't EOF (EOF is handled later on) if (ret < 0 && ret != AVERROR_EOF) { qCritical() << "Failed to retrieve frame:" << ret; break; } if (still_seeking) { // Handle a failure to seek (occurs on some media) // We'll only be here if the frame cache was emptied earlier if (!cache_at_zero_ && (ret == AVERROR_EOF || filtered->best_effort_timestamp > target_ts)) { seek_ts = qMax(min_seek, seek_ts - second_ts_); instance_.Seek(seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } continue; } else { still_seeking = false; } } if (ret == AVERROR_EOF) { // Handle an "expected" EOF by using the last frame of our cache cache_at_eof_ = true; if (cached_frames_.empty()) { qCritical() << "Unexpected codec EOF - unable to retrieve frame"; } else { return_frame = cached_frames_.back(); } break; } else { // Cut down to thread count - 1 before we acquire a new frame if (cached_frames_.size() > size_t(MaximumQueueSize())) { RemoveFirstFrame(); } // Store frame before just in case AVFramePtr previous; if (cached_frames_.empty()) { previous = nullptr; } else { previous = cached_frames_.back(); } // Append this frame and signal to other threads that a new frame has arrived cached_frames_.push_back(filtered); // If this is a valid frame, see if this or the frame before it are the one we need if (filtered->pts == target_ts || time == kAnyTimecode) { return_frame = filtered; break; } else if (filtered->pts > target_ts) { if (!previous && cache_at_zero_) { return_frame = filtered; break; } else { return_frame = previous; break; } } } filtered = nullptr; } av_packet_unref(working_packet_); return return_frame; } void FFmpegDecoder::FreeScaler() { if (sws_ctx_) { sws_freeContext(sws_ctx_); sws_ctx_ = nullptr; } } AVFramePtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const { if (t < cached_frames_.front()->pts) { if (cache_at_zero_) { return cached_frames_.front(); } } else if (t > cached_frames_.back()->pts) { if (cache_at_eof_) { return cached_frames_.back(); } } else { // We already have this frame in the cache, find it for (auto it=cached_frames_.cbegin(); it!=cached_frames_.cend(); it++) { AVFramePtr this_frame = *it; auto next = it; next++; if (this_frame->pts == t // Test for an exact match || (next != cached_frames_.cend() && (*next)->pts > t)) { // Or for this frame to be the "closest" return this_frame; } } } return nullptr; } void FFmpegDecoder::RemoveFirstFrame() { cached_frames_.pop_front(); cache_at_zero_ = false; } int FFmpegDecoder::MaximumQueueSize() { // Fairly arbitrary size. This used to need to be the number of current threads to ensure any // thread that arrived would have its frame available, but if we only have one render thread, // that's no longer a concern. Now, this value could technically be 1, but some memory cache // may be useful for reversing. This value may be tweaked over time. return 2; } FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), avstream_(nullptr), opts_(nullptr) { } bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index) { // Open file in a format context int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr); // Handle format context error if (error_code != 0) { qCritical() << "Failed to open input:" << filename << FFmpegError(error_code); return false; } // Get stream information from format error_code = avformat_find_stream_info(fmt_ctx_, nullptr); // Handle get stream information error if (error_code < 0) { qCritical() << "Failed to find stream info:" << FFmpegError(error_code); return false; } // Get reference to correct AVStream avstream_ = fmt_ctx_->streams[stream_index]; // Find decoder const AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id); // Handle failure to find decoder if (codec == nullptr) { qCritical() << "Failed to find appropriate decoder for this codec:" << filename << stream_index << avstream_->codecpar->codec_id; return false; } // Allocate context for the decoder codec_ctx_ = avcodec_alloc_context3(codec); if (codec_ctx_ == nullptr) { qCritical() << "Failed to allocate codec context"; return false; } // Copy parameters from the AVStream to the AVCodecContext error_code = avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar); // Handle failure to copy parameters if (error_code < 0) { qCritical() << "Failed to copy parameters from AVStream to AVCodecContext"; return false; } // Set multithreading setting error_code = av_dict_set(&opts_, "threads", "auto", 0); // Handle failure to set multithreaded decoding if (error_code < 0) { qCritical() << "Failed to set codec options, performance may suffer"; } // Open codec error_code = avcodec_open2(codec_ctx_, codec, &opts_); if (error_code < 0) { char buf[512]; av_strerror(error_code, buf, 512); qCritical() << "Failed to open codec" << codec->id << error_code << buf; return false; } return true; } void FFmpegDecoder::Instance::Close() { if (opts_) { av_dict_free(&opts_); opts_ = nullptr; } if (codec_ctx_) { avcodec_free_context(&codec_ctx_); codec_ctx_ = nullptr; } if (fmt_ctx_) { avformat_close_input(&fmt_ctx_); fmt_ctx_ = nullptr; } } int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) { bool eof = false; int ret; // Clear any previous frames av_frame_unref(frame); while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) { // Find next packet in the correct stream index ret = GetPacket(pkt); if (ret == AVERROR_EOF) { // Don't break so that receive gets called again, but don't try to read again eof = true; // Send a null packet to signal end of avcodec_send_packet(codec_ctx_, nullptr); } else if (ret < 0) { // Handle other error by breaking loop and returning the code we received break; } else { // Successful read, send the packet ret = avcodec_send_packet(codec_ctx_, pkt); // We don't need the packet anymore, so free it av_packet_unref(pkt); if (ret < 0) { break; } } } return ret; } const char *FFmpegDecoder::Instance::GetSubtitleHeader() const { return reinterpret_cast(codec_ctx_->subtitle_header); } int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) { int ret = GetPacket(pkt); if (ret >= 0) { int got_sub; ret = avcodec_decode_subtitle2(codec_ctx_, sub, &got_sub, pkt); if (!got_sub) { ret = -1; } } return ret; } int FFmpegDecoder::Instance::GetPacket(AVPacket *pkt) { int ret; do { av_packet_unref(pkt); ret = av_read_frame(fmt_ctx_, pkt); } while (pkt->stream_index != avstream_->index && ret >= 0); return ret; } void FFmpegDecoder::Instance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); } } ================================================ FILE: app/codec/ffmpeg/ffmpegdecoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FFMPEGDECODER_H #define FFMPEGDECODER_H // Fixes weird define issue when including #include extern "C" { #include #include #include #include #include } #include #include #include #include "codec/decoder.h" #include "common/ffmpegutils.h" namespace olive { /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder */ class FFmpegDecoder : public Decoder { Q_OBJECT public: // Constructor FFmpegDecoder(); // Destructor DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder) virtual QString id() const override; virtual bool SupportsVideo() override{return true;} virtual bool SupportsAudio() override{return true;} virtual FootageDescription Probe(const QString &filename, CancelAtom *cancelled) const override; protected: virtual bool OpenInternal() override; virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; virtual void CloseInternal() override; virtual rational GetAudioStartOffset() const override; private: class Instance { public: Instance(); ~Instance() { Close(); } bool Open(const char* filename, int stream_index); bool IsOpen() const { return fmt_ctx_; } void Close(); /** * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) * * @return * * An FFmpeg error code, or >= 0 on success */ int GetFrame(AVPacket* pkt, AVFrame* frame); const char *GetSubtitleHeader() const; int GetSubtitle(AVPacket* pkt, AVSubtitle* sub); int GetPacket(AVPacket *pkt); void Seek(int64_t timestamp); AVFormatContext* fmt_ctx() const { return fmt_ctx_; } AVStream* avstream() const { return avstream_; } private: AVFormatContext* fmt_ctx_; AVCodecContext* codec_ctx_; AVStream* avstream_; AVDictionary* opts_; }; /** * @brief Handle an FFmpeg error code * * Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this * function also automatically closes the Decoder. * * @param error_code */ static QString FFmpegError(int error_code); void FreeScaler(); static PixelFormat GetNativePixelFormat(AVPixelFormat pix_fmt); static int GetNativeChannelCount(AVPixelFormat pix_fmt); static uint64_t ValidateChannelLayout(AVStream *stream); static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing); static bool IsPixelFormatGLSLCompatible(AVPixelFormat f); AVFramePtr GetFrameFromCache(const int64_t &t) const; void ClearFrameCache(); AVFramePtr PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p); TexturePtr ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original); AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled); void RemoveFirstFrame(); static int MaximumQueueSize(); SwsContext *sws_ctx_; int sws_src_width_; int sws_src_height_; AVPixelFormat sws_src_format_; int sws_dst_width_; int sws_dst_height_; AVPixelFormat sws_dst_format_; AVColorRange sws_colrange_; AVColorSpace sws_colspace_; AVPacket *working_packet_; int64_t second_ts_; std::list cached_frames_; bool cache_at_zero_; bool cache_at_eof_; Instance instance_; }; } #endif // FFMPEGDECODER_H ================================================ FILE: app/codec/ffmpeg/ffmpegencoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "ffmpegencoder.h" extern "C" { #include #include #include } #include #include "common/ffmpegutils.h" namespace olive { FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : Encoder(params), fmt_ctx_(nullptr), video_stream_(nullptr), video_codec_ctx_(nullptr), video_scale_ctx_(nullptr), video_buffersrc_ctx_(nullptr), video_buffersink_ctx_(nullptr), audio_stream_(nullptr), audio_codec_ctx_(nullptr), audio_resample_ctx_(nullptr), audio_frame_(nullptr), open_(false) { } QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const { QStringList pix_fmts; const AVCodec* codec_info = GetEncoder(c, SampleFormat::INVALID); if (codec_info) { for (int i=0; codec_info->pix_fmts[i]!=-1; i++) { if (FFmpegUtils::ConvertJPEGSpaceToRegularSpace(codec_info->pix_fmts[i]) != codec_info->pix_fmts[i]) { // This is a deprecated "JPEG" space, skip it continue; } const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]); pix_fmts.append(pix_fmt_name); } } return pix_fmts; } std::vector FFmpegEncoder::GetSampleFormatsForCodec(ExportCodec::Codec c) const { std::vector f; if (c == ExportCodec::kCodecPCM) { // FFmpeg lists these as separate codecs so we need custom functionality here // We list signed 16 first because ExportDialog will always use the first element by default // (because first element is the "default" in tFFmpeg) f = { SampleFormat::S16, SampleFormat::U8, SampleFormat::S32, SampleFormat::S64, SampleFormat::F32, SampleFormat::F64 }; } else { const AVCodec* codec_info = GetEncoder(c, SampleFormat::INVALID); if (codec_info && codec_info->sample_fmts) { for (int i=0; codec_info->sample_fmts[i]!=-1; i++) { SampleFormat this_format = FFmpegUtils::GetNativeSampleFormat(static_cast(codec_info->sample_fmts[i])); if (this_format != SampleFormat::INVALID) { f.push_back(this_format); } } } } return f; } bool FFmpegEncoder::Open() { if (open_) { return true; } int error_code; // Convert QString to C string that FFmpeg expects QByteArray filename_bytes = params().filename().toUtf8(); const char* filename_c_str = filename_bytes.constData(); // Create output format context error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str); // Check error code if (error_code < 0) { FFmpegError(tr("Failed to allocate output context"), error_code); return false; } // Initialize a video stream if it's enabled if (params().video_enabled()) { if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) { return false; } // This is the format we will expect frames received in Write() to be in PixelFormat native_pixel_fmt = params().video_params().format(); // This is the format we will need to convert the frame to for swscale to understand it video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt); // This is the equivalent pixel format above as an AVPixelFormat that swscale can understand AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, VideoParams::kRGBAChannelCount); AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, VideoParams::kRGBChannelCount); if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) { SetError(tr("Failed to find suitable pixel format for this buffer")); return false; } // This is the pixel format the encoder wants to encode to AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt; video_scale_ctx_ = avfilter_graph_alloc(); if (!video_scale_ctx_) { return false; } static const int FILTER_ARG_SZ = 1024; char filter_args[FILTER_ARG_SZ]; snprintf(filter_args, FILTER_ARG_SZ, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", params().video_params().effective_width(), params().video_params().effective_height(), src_alpha_pix_fmt, params().video_params().time_base().numerator(), params().video_params().time_base().denominator(), params().video_params().pixel_aspect_ratio().numerator(), params().video_params().pixel_aspect_ratio().denominator()); avfilter_graph_create_filter(&video_buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, video_scale_ctx_); avfilter_graph_create_filter(&video_buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, video_scale_ctx_); AVFilterContext *last_filter = video_buffersrc_ctx_; { // Set color range AVFilterContext* range_filter; snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s", params().video_params().color_range() == VideoParams::kColorRangeFull ? "full" : "limited"); avfilter_graph_create_filter(&range_filter, avfilter_get_by_name("scale"), "range", filter_args, nullptr, video_scale_ctx_); avfilter_link(last_filter, 0, range_filter, 0); last_filter = range_filter; } if (src_alpha_pix_fmt != encoder_pix_fmt) { // Transform pixel format AVFilterContext* format_filter; snprintf(filter_args, FILTER_ARG_SZ, "pix_fmts=%u", encoder_pix_fmt); avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, video_scale_ctx_); avfilter_link(last_filter, 0, format_filter, 0); last_filter = format_filter; } avfilter_link(last_filter, 0, video_buffersink_ctx_, 0); if (avfilter_graph_config(video_scale_ctx_, nullptr) < 0) { SetError(tr("Failed to configure filter graph")); return false; } } // Initialize an audio stream if it's enabled if (params().audio_enabled()) { if (!InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) { return false; } } // Initialize a subtitle stream if it's enabled if (params().subtitles_enabled()) { if (!InitializeStream(AVMEDIA_TYPE_SUBTITLE, &subtitle_stream_, &subtitle_codec_ctx_, params().subtitles_codec())) { return false; } } av_dump_format(fmt_ctx_, 0, filename_c_str, 1); // Open output file for writing error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE); if (error_code < 0) { FFmpegError(tr("Failed to open IO context"), error_code); return false; } // Write header error_code = avformat_write_header(fmt_ctx_, nullptr); if (error_code < 0) { FFmpegError(tr("Failed to write format header"), error_code); return false; } open_ = true; return true; } bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) { // We may need to convert this frame to a frame that swscale will understand if (frame->format() != video_conversion_fmt_) { frame = frame->convert(video_conversion_fmt_); } // Use swscale context to convert formats/linesizes AVFramePtr input_frame = CreateAVFramePtr(av_frame_alloc()); input_frame->width = frame->width(); input_frame->height = frame->height(); input_frame->format = FFmpegUtils::GetFFmpegPixelFormat(frame->format(), frame->channel_count()); input_frame->data[0] = reinterpret_cast(frame->data()); input_frame->linesize[0] = frame->linesize_bytes(); input_frame->color_primaries = video_codec_ctx_->color_primaries; input_frame->color_trc = video_codec_ctx_->color_trc; input_frame->colorspace = video_codec_ctx_->colorspace; input_frame->color_range = video_codec_ctx_->color_range; int r; r = av_buffersrc_add_frame_flags(video_buffersrc_ctx_, input_frame.get(), AV_BUFFERSRC_FLAG_KEEP_REF); if (r < 0) { FFmpegError(tr("Failed to add frame to filter graph"), r); return false; } AVFramePtr encoded_frame = CreateAVFramePtr(av_frame_alloc()); r = av_buffersink_get_frame(video_buffersink_ctx_, encoded_frame.get()); if (r < 0) { FFmpegError(tr("Failed to retrieve frame from buffer sink"), r); return false; } encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base)); return WriteAVFrame(encoded_frame.get(), video_codec_ctx_, video_stream_); } bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) { if (!audio.is_allocated()) { return true; } bool result = true; size_t start = 0; size_t end = audio.sample_count(); const size_t max_frame = 48000; while (result && start < end) { // Create input buffer uint8_t** input_data = nullptr; size_t input_sample_count = std::min(end - start, max_frame); int input_linesize; int r = av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(), input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0); if (r < 0) { FFmpegError(tr("Failed to allocate sample array"), r); return false; } else { int bpsc = audio.audio_params().bytes_per_sample_per_channel(); for (int i=0; i(input_data), input_sample_count); if (input_data) { av_freep(&input_data[0]); av_freep(&input_data); } } return result; } bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, const uint8_t **input_data, int input_sample_count) { if (!InitializeResampleContext(audio_params)) { qCritical() << "Failed to initialize resample context"; return false; } bool result = true; // Create output buffer int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400; uint8_t** output_data = nullptr; int output_linesize; av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->channels, output_sample_count, static_cast(audio_stream_->codecpar->format), 0); // Perform conversion int converted = swr_convert(audio_resample_ctx_, output_data, output_sample_count, const_cast(input_data), input_sample_count); if (converted > 0) { // Split sample buffer into frames for (int i=0; idata, output_data, audio_frame_offset_, i, copy_length, audio_frame_->channels, static_cast(audio_frame_->format)); audio_frame_offset_ += copy_length; i += copy_length; if (audio_frame_offset_ == audio_max_samples_ || (i == converted && !input_data)) { // Got all the samples we needed, write the frame audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base); WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); audio_write_count_ += audio_frame_offset_; audio_frame_offset_ = 0; } } } else if (converted < 0) { FFmpegError(tr("Failed to resample audio"), converted); result = false; } if (!input_data && audio_frame_offset_ > 0) { audio_frame_->nb_samples = audio_frame_offset_; audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base); WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); } // Free buffers created if (output_data) { av_freep(&output_data[0]); av_freep(&output_data); } return result; } QString GetAssTime(const rational &time) { int64_t total_centiseconds = qRound64(time.toDouble() * 100); int64_t cs = total_centiseconds % 100; int64_t ss = (total_centiseconds / 100) % 60; int64_t mm = (total_centiseconds / 6000) % 60; int64_t hh = total_centiseconds / 360000; return QStringLiteral("%1:%2:%3.%4").arg( QString::number(hh), QStringLiteral("%1").arg(mm, 2, 10, QLatin1Char('0')), QStringLiteral("%1").arg(ss, 2, 10, QLatin1Char('0')), QStringLiteral("%1").arg(cs, 2, 10, QLatin1Char('0')) ); } bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) { QByteArray utf8_sub = sub_block->GetText().toUtf8(); AVPacket *pkt = av_packet_alloc(); pkt->stream_index = subtitle_stream_->index; pkt->data = (uint8_t *) utf8_sub.data(); pkt->size = utf8_sub.size(); pkt->pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor); pkt->duration = av_rescale_q(qRound64(sub_block->length().toDouble() * 1000), {1, 1000}, subtitle_codec_ctx_->time_base); pkt->dts = pkt->pts; av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, subtitle_stream_->time_base); int err = av_interleaved_write_frame(fmt_ctx_, pkt); bool ret = true; if (err < 0) { FFmpegError(tr("Failed to write interleaved packet"), err); ret = false; } av_packet_free(&pkt); return ret; } /* void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file) { // Keep track of sample count to use as each frame's timebase int sample_counter = 0; while (true) { // Calculate how many samples should input this frame int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()), audio_codec_ctx_->sample_rate, pcm_info.sample_rate(), AV_ROUND_UP); // Calculate how many bytes this is int max_read = pcm_info.samples_to_bytes(samples_needed); // Read bytes from PCM QByteArray input_data = file->read(max_read); // Use swresample to convert the data into the correct format const char* input_data_array = input_data.constData(); int converted = swr_convert(swr_ctx, // output data frame->data, // output sample count (maximum amount of samples in output) maximum_frame_samples, // input data reinterpret_cast(&input_data_array), // input sample count (maximum amount of samples we read from pcm file) pcm_info.bytes_to_samples(input_data.size())); // Update the frame's number of samples to the amount we actually received frame->nb_samples = converted; // Update frame timestamp frame->pts = sample_counter; // Increment timestamp for the next frame by the amount of samples in this one sample_counter += converted; // Write the frame if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) { qCritical() << "Failed to write audio AVFrame"; break; } // Break if we've reached the end point if (file->atEnd()) { break; } } } */ void FFmpegEncoder::Close() { if (open_) { // Flush encoders FlushEncoders(); // We've written a header, so we'll write a trailer av_write_trailer(fmt_ctx_); avio_closep(&fmt_ctx_->pb); open_ = false; } if (audio_resample_ctx_) { swr_init(audio_resample_ctx_); audio_resample_ctx_ = nullptr; } if (audio_frame_) { av_frame_free(&audio_frame_); audio_frame_ = nullptr; } if (video_scale_ctx_) { avfilter_graph_free(&video_scale_ctx_); video_scale_ctx_ = nullptr; video_buffersrc_ctx_ = nullptr; video_buffersink_ctx_ = nullptr; } if (video_codec_ctx_) { avcodec_free_context(&video_codec_ctx_); video_codec_ctx_ = nullptr; } if (audio_codec_ctx_) { avcodec_free_context(&audio_codec_ctx_); audio_codec_ctx_ = nullptr; } if (fmt_ctx_) { // NOTE: This also frees video_stream_ and audio_stream_ avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; video_stream_ = nullptr; audio_stream_ = nullptr; } } void FFmpegEncoder::FFmpegError(const QString& context, int error_code) { char err[1024]; av_strerror(error_code, err, 1024); QString formatted_err = tr("%1: %2 %3").arg(context, err, QString::number(error_code)); qDebug() << formatted_err; SetError(formatted_err); } bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVStream* stream) { // Send raw frame to the encoder int error_code = avcodec_send_frame(codec_ctx, frame); if (error_code < 0) { FFmpegError(tr("Failed to send frame to encoder"), error_code); return false; } bool succeeded = false; AVPacket* pkt = av_packet_alloc(); // Retrieve packets from encoder while (error_code >= 0) { error_code = avcodec_receive_packet(codec_ctx, pkt); // EAGAIN just means the encoder wants another frame before encoding if (error_code == AVERROR(EAGAIN)) { break; } else if (error_code < 0) { FFmpegError(tr("Failed to receive packet from decoder"), error_code); goto fail; } // Set packet stream index pkt->stream_index = stream->index; av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); // Write packet to file error_code = av_interleaved_write_frame(fmt_ctx_, pkt); if (error_code < 0) { FFmpegError(tr("Failed to write interleaved packet"), error_code); goto fail; } // Unref packet in case we're getting another av_packet_unref(pkt); } succeeded = true; fail: av_packet_free(&pkt); return succeeded; } bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const ExportCodec::Codec& codec) { if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && type != AVMEDIA_TYPE_SUBTITLE) { SetError(tr("Cannot initialize a stream that is not a video, audio, or subtitle type")); return false; } // Find encoder const AVCodec* encoder = GetEncoder(codec, params().audio_params().format()); if (!encoder) { SetError(tr("Failed to find codec for 0x%1").arg(codec, 16)); return false; } if (encoder->type != type) { SetError(tr("Retrieved unexpected codec type %1 for codec %2").arg(QString::number(encoder->type), QString::number(codec))); return false; } if (!InitializeCodecContext(stream_ptr, codec_ctx_ptr, encoder)) { return false; } // Set codec parameters AVCodecContext* codec_ctx = *codec_ctx_ptr; AVStream* stream = *stream_ptr; if (type == AVMEDIA_TYPE_VIDEO) { codec_ctx->width = params().video_params().width(); codec_ctx->height = params().video_params().height(); codec_ctx->sample_aspect_ratio = params().video_params().pixel_aspect_ratio().toAVRational(); codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational(); codec_ctx->framerate = params().video_params().frame_rate().toAVRational(); codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); codec_ctx->color_range = params().video_params().color_range() == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; if (params().video_params().interlacing() != VideoParams::kInterlaceNone) { // FIXME: I actually don't know what these flags do, the documentation helpfully doesn't // explain them at all. I hope using both of them is the right thing to do. codec_ctx->flags |= AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME; if (params().video_params().interlacing() == VideoParams::kInterlacedTopFirst) { codec_ctx->field_order = AV_FIELD_TT; } else { codec_ctx->field_order = AV_FIELD_BB; if (codec == ExportCodec::kCodecH264 || codec == ExportCodec::kCodecH264rgb) { // For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves av_opt_set(codec_ctx->priv_data, "x264opts", "bff=1", AV_OPT_SEARCH_CHILDREN); } } } // Set custom options { for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) { if (!i.key().startsWith(QStringLiteral("ove_"))) { av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); } } if (params().video_bit_rate() > 0) { codec_ctx->bit_rate = params().video_bit_rate(); } if (params().video_min_bit_rate() > 0) { codec_ctx->rc_min_rate = params().video_min_bit_rate(); } if (params().video_max_bit_rate() > 0) { codec_ctx->rc_max_rate = params().video_max_bit_rate(); } if (params().video_buffer_size() > 0) { codec_ctx->rc_buffer_size = static_cast(params().video_buffer_size()); } // nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7 // ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov" if (params().color_transform().output().contains(QStringLiteral("sRGB"), Qt::CaseInsensitive)) { codec_ctx->color_primaries = AVCOL_PRI_BT709; codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1; codec_ctx->colorspace = AVCOL_SPC_BT709; } else { // Assume Rec.709 codec_ctx->color_primaries = AVCOL_PRI_BT709; codec_ctx->color_trc = AVCOL_TRC_BT709; codec_ctx->colorspace = AVCOL_SPC_BT709; } } } else if (type == AVMEDIA_TYPE_AUDIO) { // Assume audio stream codec_ctx->sample_rate = params().audio_params().sample_rate(); codec_ctx->channel_layout = params().audio_params().channel_layout(); codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout); codec_ctx->sample_fmt = FFmpegUtils::GetFFmpegSampleFormat(params().audio_params().format()); codec_ctx->time_base = {1, codec_ctx->sample_rate}; if (params().audio_bit_rate() > 0) { codec_ctx->bit_rate = params().audio_bit_rate(); } } else if (type == AVMEDIA_TYPE_SUBTITLE) { codec_ctx->time_base = av_get_time_base_q(); QByteArray ass_header = SubtitleParams::GenerateASSHeader().toUtf8(); codec_ctx->subtitle_header = new uint8_t[ass_header.size()]; memcpy(codec_ctx->subtitle_header, ass_header.constData(), ass_header.size()); codec_ctx->subtitle_header_size = ass_header.size(); } if (!SetupCodecContext(stream, codec_ctx, encoder)) { return false; } return true; } bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, const AVCodec* codec) { *stream = avformat_new_stream(fmt_ctx_, nullptr); if (!(*stream)) { SetError(tr("Failed to allocate AVStream")); return false; } // Allocate a codec context *codec_ctx = avcodec_alloc_context3(codec); if (!(*codec_ctx)) { SetError(tr("Failed to allocate AVCodecContext")); return false; } return true; } bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, const AVCodec* codec) { int error_code; if (fmt_ctx_->oformat->flags & AVFMT_GLOBALHEADER) { codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } AVDictionary* codec_opts = nullptr; // Set thread count if (params().video_threads() == 0) { av_dict_set(&codec_opts, "threads", "auto", 0); } else { QString thread_val = QString::number(params().video_threads()); av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0); } // Try to open encoder error_code = avcodec_open2(codec_ctx, codec, &codec_opts); if (error_code < 0) { FFmpegError(tr("Failed to open encoder"), error_code); return false; } // Copy context settings to codecpar object error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx); if (error_code < 0) { FFmpegError(tr("Failed to copy codec parameters to stream"), error_code); return false; } if (codec->type == AVMEDIA_TYPE_VIDEO) { stream->avg_frame_rate = codec_ctx->framerate; } return true; } void FFmpegEncoder::FlushEncoders() { if (video_codec_ctx_) { FlushCodecCtx(video_codec_ctx_, video_stream_); } if (audio_codec_ctx_) { WriteAudio(SampleBuffer()); FlushCodecCtx(audio_codec_ctx_, audio_stream_); } if (fmt_ctx_) { if (fmt_ctx_->oformat->flags & AVFMT_ALLOW_FLUSH) { int r = av_interleaved_write_frame(fmt_ctx_, nullptr); if (r < 0) { FFmpegError(tr("Failed to write interleaved packet"), r); } } } } void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream) { avcodec_send_frame(codec_ctx, nullptr); AVPacket* pkt = av_packet_alloc(); int error_code; do { error_code = avcodec_receive_packet(codec_ctx, pkt); if (error_code < 0) { break; } pkt->stream_index = stream->index; av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); int r = av_interleaved_write_frame(fmt_ctx_, pkt); if (r < 0) { FFmpegError(tr("Failed to write interleaved packet"), r); break; } av_packet_unref(pkt); } while (error_code >= 0); av_packet_free(&pkt); } bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio) { if (audio_resample_ctx_) { return true; } // Create resample context audio_resample_ctx_ = swr_alloc_set_opts(nullptr, static_cast(audio_codec_ctx_->channel_layout), audio_codec_ctx_->sample_fmt, audio_codec_ctx_->sample_rate, static_cast(audio.channel_layout()), FFmpegUtils::GetFFmpegSampleFormat(audio.format()), audio.sample_rate(), 0, nullptr); if (!audio_resample_ctx_) { return false; } int err = swr_init(audio_resample_ctx_); if (err < 0) { FFmpegError(tr("Failed to create resampling context"), err); return false; } audio_max_samples_ = audio_codec_ctx_->frame_size; if (!audio_max_samples_) { // If not, use another frame size if (params().video_enabled()) { // If we're encoding video, use enough samples to cover roughly one frame of video audio_max_samples_ = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base()); } else { // If no video, just use an arbitrary number audio_max_samples_ = 256; } } audio_frame_ = av_frame_alloc(); if (!audio_frame_) { return false; } audio_frame_->channel_layout = audio_codec_ctx_->channel_layout; audio_frame_->format = audio_codec_ctx_->sample_fmt; audio_frame_->nb_samples = audio_max_samples_; err = av_frame_get_buffer(audio_frame_, 0); if (err < 0) { FFmpegError(tr("Failed to create audio frame"), err); return false; } audio_frame_offset_ = 0; audio_write_count_ = 0; return true; } const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c, SampleFormat aformat) { switch (c) { case ExportCodec::kCodecH264: return avcodec_find_encoder_by_name("libx264"); case ExportCodec::kCodecH264rgb: return avcodec_find_encoder_by_name("libx264rgb"); case ExportCodec::kCodecDNxHD: return avcodec_find_encoder(AV_CODEC_ID_DNXHD); case ExportCodec::kCodecProRes: return avcodec_find_encoder(AV_CODEC_ID_PRORES); case ExportCodec::kCodecCineform: return avcodec_find_encoder(AV_CODEC_ID_CFHD); case ExportCodec::kCodecH265: return avcodec_find_encoder(AV_CODEC_ID_HEVC); case ExportCodec::kCodecVP9: return avcodec_find_encoder(AV_CODEC_ID_VP9); case ExportCodec::kCodecAV1: { const AVCodec *encoder = avcodec_find_encoder_by_name("libsvtav1"); if(!encoder) encoder = avcodec_find_encoder(AV_CODEC_ID_AV1); return encoder; } case ExportCodec::kCodecOpenEXR: return avcodec_find_encoder(AV_CODEC_ID_EXR); case ExportCodec::kCodecPNG: return avcodec_find_encoder(AV_CODEC_ID_PNG); case ExportCodec::kCodecTIFF: return avcodec_find_encoder(AV_CODEC_ID_TIFF); case ExportCodec::kCodecMP2: return avcodec_find_encoder(AV_CODEC_ID_MP2); case ExportCodec::kCodecMP3: return avcodec_find_encoder(AV_CODEC_ID_MP3); case ExportCodec::kCodecAAC: return avcodec_find_encoder(AV_CODEC_ID_AAC); case ExportCodec::kCodecPCM: switch (aformat) { case SampleFormat::INVALID: case SampleFormat::COUNT: case SampleFormat::U8P: case SampleFormat::S16P: case SampleFormat::S32P: case SampleFormat::S64P: case SampleFormat::F32P: case SampleFormat::F64P: break; case SampleFormat::U8: return avcodec_find_encoder(AV_CODEC_ID_PCM_U8); case SampleFormat::S16: return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); case SampleFormat::S32: return avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE); case SampleFormat::S64: return avcodec_find_encoder(AV_CODEC_ID_PCM_S64LE); case SampleFormat::F32: return avcodec_find_encoder(AV_CODEC_ID_PCM_F32LE); case SampleFormat::F64: return avcodec_find_encoder(AV_CODEC_ID_PCM_F64LE); } break; case ExportCodec::kCodecFLAC: return avcodec_find_encoder(AV_CODEC_ID_FLAC); case ExportCodec::kCodecOpus: return avcodec_find_encoder(AV_CODEC_ID_OPUS); case ExportCodec::kCodecVorbis: return avcodec_find_encoder(AV_CODEC_ID_VORBIS); case ExportCodec::kCodecSRT: return avcodec_find_encoder(AV_CODEC_ID_SUBRIP); case ExportCodec::kCodecCount: // These are audio or invalid codecs and therefore have no pixel formats break; } return nullptr; } } ================================================ FILE: app/codec/ffmpeg/ffmpegencoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FFMPEGENCODER_H #define FFMPEGENCODER_H extern "C" { #include #include #include #include #include } #include "codec/encoder.h" namespace olive { class FFmpegEncoder : public Encoder { Q_OBJECT public: FFmpegEncoder(const EncodingParams ¶ms); virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const override; virtual std::vector GetSampleFormatsForCodec(ExportCodec::Codec c) const override; virtual bool Open() override; virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) override; virtual bool WriteAudio(const olive::SampleBuffer &audio) override; bool WriteAudioData(const AudioParams &audio_params, const uint8_t **data, int input_sample_count); virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override; virtual void Close() override; virtual PixelFormat GetDesiredPixelFormat() const override { return video_conversion_fmt_; } private: /** * @brief Handle an FFmpeg error code * * Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this * function also automatically closes the Decoder. * * @param error_code */ void FFmpegError(const QString &context, int error_code); bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream); bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec); bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, const AVCodec* codec); bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, const AVCodec *codec); void FlushEncoders(); void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream); bool InitializeResampleContext(const AudioParams &audio); static const AVCodec *GetEncoder(ExportCodec::Codec c, SampleFormat aformat); AVFormatContext* fmt_ctx_; AVStream* video_stream_; AVCodecContext* video_codec_ctx_; AVFilterGraph *video_scale_ctx_; AVFilterContext *video_buffersrc_ctx_; AVFilterContext *video_buffersink_ctx_; PixelFormat video_conversion_fmt_; AVStream* audio_stream_; AVCodecContext* audio_codec_ctx_; SwrContext* audio_resample_ctx_; AVFrame* audio_frame_; int audio_max_samples_; int audio_frame_offset_; int audio_write_count_; AVStream* subtitle_stream_; AVCodecContext* subtitle_codec_ctx_; bool open_; }; } #endif // FFMPEGENCODER_H ================================================ FILE: app/codec/frame.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "frame.h" #include #include #include #include #include "common/oiioutils.h" #include "render/framemanager.h" namespace olive { Frame::Frame() : data_(nullptr), data_size_(0), timestamp_(0) { } Frame::~Frame() { destroy(); } FramePtr Frame::Create() { return std::make_shared(); } const VideoParams &Frame::video_params() const { return params_; } void Frame::set_video_params(const VideoParams ¶ms) { params_ = params; linesize_ = generate_linesize_bytes(width(), params_.format(), params_.channel_count()); linesize_pixels_ = linesize_ / params_.GetBytesPerPixel(); } FramePtr Frame::Interlace(FramePtr top, FramePtr bottom) { if (top->video_params() != bottom->video_params()) { qCritical() << "Tried to interlace two frames that had incompatible parameters"; return nullptr; } FramePtr interlaced = Frame::Create(); interlaced->set_video_params(top->video_params()); interlaced->allocate(); int linesize = interlaced->linesize_bytes(); for (int i=0; iheight(); i++) { FramePtr which = (i%2 == 0) ? top : bottom; memcpy(interlaced->data() + i*linesize, which->const_data() + i*linesize, linesize); } return interlaced; } int Frame::generate_linesize_bytes(int width, PixelFormat format, int channel_count) { // Align to 32 bytes (not sure if this is necessary?) return VideoParams::GetBytesPerPixel(format, channel_count) * ((width + 31) & ~31); } Color Frame::get_pixel(int x, int y) const { if (!contains_pixel(x, y)) { return Color(); } int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); return Color(reinterpret_cast(data_ + byte_offset), video_params().format(), video_params().channel_count()); } bool Frame::contains_pixel(int x, int y) const { return (is_allocated() && x >= 0 && x < width() && y >= 0 && y < height()); } void Frame::set_pixel(int x, int y, const Color &c) { if (!contains_pixel(x, y)) { return; } int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); c.toData(reinterpret_cast(data_ + byte_offset), video_params().format(), video_params().channel_count()); } bool Frame::allocate() { // Assume this frame is intended to be a video frame if (!params_.is_valid()) { qWarning() << "Tried to allocate a frame with invalid parameters"; return false; } if (is_allocated()) { // Already allocated return true; } data_size_ = linesize_ * height(); data_ = FrameManager::Allocate(data_size_); return true; } void Frame::destroy() { if (is_allocated()) { FrameManager::Deallocate(data_size_, data_); data_size_ = 0; data_ = nullptr; } } FramePtr Frame::convert(PixelFormat format) const { // Create new params with destination format VideoParams params = params_; params.set_format(format); // Create new frame FramePtr converted = Frame::Create(); converted->set_video_params(params); converted->set_timestamp(timestamp_); converted->allocate(); // Do the conversion through OIIO for convenience OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(), channel_count(), OIIOUtils::GetOIIOBaseTypeFromFormat(this->format()))); OIIOUtils::FrameToBuffer(this, &src); OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(), channel_count(), OIIOUtils::GetOIIOBaseTypeFromFormat(format))); if (dst.copy_pixels(src)) { OIIOUtils::BufferToFrame(&dst, converted.get()); return converted; } else { return nullptr; } } } ================================================ FILE: app/codec/frame.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FRAME_H #define FRAME_H #include #include #include #include "common/define.h" #include "render/videoparams.h" namespace olive { class Frame; using FramePtr = std::shared_ptr; /** * @brief Video frame data or audio sample data from a Decoder */ class Frame { public: Frame(); ~Frame(); DISABLE_COPY_MOVE(Frame) static FramePtr Create(); const VideoParams& video_params() const; void set_video_params(const VideoParams& params); static FramePtr Interlace(FramePtr top, FramePtr bottom); static int generate_linesize_bytes(int width, PixelFormat format, int channel_count); int linesize_pixels() const { return linesize_pixels_; } int linesize_bytes() const { return linesize_; } int width() const { return params_.effective_width(); } int height() const { return params_.effective_height(); } PixelFormat format() const { return params_.format(); } int channel_count() const { return params_.channel_count(); } Color get_pixel(int x, int y) const; bool contains_pixel(int x, int y) const; void set_pixel(int x, int y, const Color& c); /** * @brief Get frame's timestamp. * * This timestamp is always a rational that will equate to the time in seconds. */ const rational& timestamp() const { return timestamp_; } void set_timestamp(const rational& timestamp) { timestamp_ = timestamp; } /** * @brief Get the data buffer of this frame */ char* data() { return data_; } /** * @brief Get the const data buffer of this frame */ const char* const_data() const { return data_; } /** * @brief Allocate memory buffer to store data based on parameters * * For video frames, the width(), height(), and format() must be set for this function to work. * * If a memory buffer has been previously allocated without destroying, this function will destroy it. */ bool allocate(); /** * @brief Return whether the frame is allocated or not */ bool is_allocated() const { return data_; } /** * @brief Destroy a memory buffer allocated with allocate() */ void destroy(); /** * @brief Returns the size of the array returned in data() in bytes * * Returns 0 if nothing is allocated. */ int allocated_size() const { return data_size_; } FramePtr convert(PixelFormat format) const; private: VideoParams params_; char* data_; int data_size_; rational timestamp_; int linesize_; int linesize_pixels_; }; } Q_DECLARE_METATYPE(olive::FramePtr) #endif // FRAME_H ================================================ FILE: app/codec/oiio/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} codec/oiio/oiiodecoder.cpp codec/oiio/oiiodecoder.h codec/oiio/oiioencoder.cpp codec/oiio/oiioencoder.h PARENT_SCOPE ) ================================================ FILE: app/codec/oiio/oiiodecoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "oiiodecoder.h" #include #include #include #include #include "common/define.h" #include "common/oiioutils.h" #include "config/config.h" #include "core.h" #include "render/renderer.h" namespace olive { QStringList OIIODecoder::supported_formats_; OIIODecoder::OIIODecoder() : image_(nullptr) { } QString OIIODecoder::id() const { return QStringLiteral("oiio"); } FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cancelled) const { Q_UNUSED(cancelled) FootageDescription desc(id()); // Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying // to open a file that it can't if it's given one if (!FileTypeIsSupported(filename)) { return desc; } std::string std_filename = filename.toStdString(); auto in = OIIO::ImageInput::open(std_filename); if (!in) { return desc; } // Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle // it better if (!strcmp(in->format_name(), "FFmpeg movie")) { return desc; } bool stream_enabled = true; int i; for (i=0; in->seek_subimage(i, 0); i++) { OIIO::ImageSpec spec = in->spec(); VideoParams video_params = GetVideoParamsFromImageSpec(spec); video_params.set_stream_index(i); if (i > 1) { // This is a multilayer image and this image might have an offset OIIO::ImageSpec root_spec = in->spec(0); float norm_x = spec.x + float(spec.width)*0.5f - float(root_spec.width)*0.5f; float norm_y = spec.y + float(spec.height)*0.5f - float(root_spec.height)*0.5f; video_params.set_x(norm_x); video_params.set_y(norm_y); } // By default, only enable the first subimage (presumably the combined image). Later we will // ask the user if they want to enable the layers instead. video_params.set_enabled(stream_enabled); stream_enabled = false; // OIIO automatically premultiplies alpha // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this // likely reduces the fidelity? video_params.set_premultiplied_alpha(true); desc.AddVideoStream(video_params); } desc.SetStreamCount(i); // If we're here, we have a successful image open in->close(); return desc; } bool OIIODecoder::OpenInternal() { // If we can open the filename provided, assume everything is working return OpenImageHandler(stream().filename(), stream().stream()); } TexturePtr OIIODecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { VideoParams vp = GetVideoParamsFromImageSpec(image_->spec()); vp.set_divider(p.divider); if (!buffer_.is_allocated() || last_params_.divider != p.divider) { last_params_ = p; buffer_.destroy(); buffer_.set_video_params(vp); buffer_.allocate(); if (p.divider == 1) { // Just upload straight to the buffer image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); } else { OIIO::ImageBuf buf(image_->spec()); image_->read_image(image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride()); // Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here) int px_sz = vp.GetBytesPerPixel(); for (int dst_y=0; dst_y(buf.localpixels()) + buf.scanline_stride() * src_y + px_sz * src_x, px_sz); } } } } return p.renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels()); } void OIIODecoder::CloseInternal() { CloseImageHandle(); } bool OIIODecoder::FileTypeIsSupported(const QString& fn) { // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG) // will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's // "extension_list" attribute and match it with the extension of the file. // Check if we've created the supported formats list, create it if not if (supported_formats_.isEmpty()) { QStringList extension_list = QString::fromStdString(OIIO::get_string_attribute("extension_list")).split(';'); // The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions foreach (const QString& ext, extension_list) { QStringList format_and_ext = ext.split(':'); supported_formats_.append(format_and_ext.at(1).split(',')); } } if (!supported_formats_.contains(QFileInfo(fn).suffix(), Qt::CaseInsensitive)) { return false; } return true; } bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage) { image_ = OIIO::ImageInput::open(fn.toStdString()); if (!image_) { return false; } if (!image_->seek_subimage(subimage, 0)) { return false; } // Check if we can work with this pixel format const OIIO::ImageSpec& spec = image_->spec(); // We use RGBA frames because that tends to be the native format of GPUs pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype)); if (pix_fmt_ == PixelFormat::INVALID) { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; return false; } oiio_pix_fmt_ = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_); if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) { qCritical() << "Failed to determine appropriate OIIO basetype from native format"; return false; } return true; } void OIIODecoder::CloseImageHandle() { if (image_) { image_->close(); image_ = nullptr; } buffer_.destroy(); } VideoParams OIIODecoder::GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec) { VideoParams video_params; video_params.set_width(spec.width); video_params.set_height(spec.height); video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype))); video_params.set_channel_count(spec.nchannels); video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec)); video_params.set_video_type(VideoParams::kVideoTypeStill); return video_params; } } ================================================ FILE: app/codec/oiio/oiiodecoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OIIODECODER_H #define OIIODECODER_H #include #include #include "codec/decoder.h" namespace olive { class OIIODecoder : public Decoder { Q_OBJECT public: OIIODecoder(); DECODER_DEFAULT_DESTRUCTOR(OIIODecoder) virtual QString id() const override; virtual bool SupportsVideo() override{return true;} virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const override; protected: virtual bool OpenInternal() override; virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual void CloseInternal() override; private: std::unique_ptr image_; static bool FileTypeIsSupported(const QString& fn); bool OpenImageHandler(const QString& fn, int subimage); void CloseImageHandle(); static VideoParams GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec); PixelFormat pix_fmt_; OIIO::TypeDesc::BASETYPE oiio_pix_fmt_; Frame buffer_; RetrieveVideoParams last_params_; static QStringList supported_formats_; }; } #endif // OIIODECODER_H ================================================ FILE: app/codec/oiio/oiioencoder.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "oiioencoder.h" #include "common/oiioutils.h" namespace olive { OIIOEncoder::OIIOEncoder(const EncodingParams ¶ms) : Encoder(params) { } bool OIIOEncoder::Open() { return true; } bool OIIOEncoder::WriteFrame(FramePtr frame, rational time) { std::string filename = GetFilenameForFrame(time).toStdString(); auto output = OIIO::ImageOutput::create(filename); if (!output) { return false; } OIIO::TypeDesc type = OIIOUtils::GetOIIOBaseTypeFromFormat(frame->format()); OIIO::ImageSpec spec(frame->width(), frame->height(), frame->channel_count(), type); if (!output->open(filename, spec)) { return false; } if (!output->write_image(type, frame->data(), OIIO::AutoStride, frame->linesize_bytes())) { return false; } if (!output->close()) { return false; } return true; } bool OIIOEncoder::WriteAudio(const SampleBuffer &audio) { // Do nothing return false; } bool OIIOEncoder::WriteSubtitle(const SubtitleBlock *sub_block) { return false; } void OIIOEncoder::Close() { // Do nothing } } ================================================ FILE: app/codec/oiio/oiioencoder.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OIIOENCODER_H #define OIIOENCODER_H #include "codec/encoder.h" namespace olive { class OIIOEncoder : public Encoder { Q_OBJECT public: OIIOEncoder(const EncodingParams ¶ms); public slots: virtual bool Open() override; virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) override; virtual bool WriteAudio(const SampleBuffer &audio) override; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override; virtual void Close() override; }; } #endif // OIIOENCODER_H ================================================ FILE: app/codec/planarfiledevice.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "planarfiledevice.h" namespace olive { PlanarFileDevice::PlanarFileDevice(QObject *parent) : QObject(parent) { } PlanarFileDevice::~PlanarFileDevice() { close(); } bool PlanarFileDevice::open(const QVector &filenames, QIODevice::OpenMode mode) { if (isOpen()) { // Already open return false; } files_.resize(filenames.size()); files_.fill(nullptr); for (int i=0; iopen(mode)) { close(); return false; } } return true; } qint64 PlanarFileDevice::read(char **data, qint64 bytes_per_channel, qint64 offset) { qint64 ret = -1; if (isOpen()) { for (int i=0; iread(data[i] + offset, bytes_per_channel); } } return ret; } qint64 PlanarFileDevice::write(const char **data, qint64 bytes_per_channel, qint64 offset) { qint64 ret = -1; if (isOpen()) { for (int i=0; iwrite(data[i] + offset, bytes_per_channel); } } return ret; } qint64 PlanarFileDevice::size() const { if (isOpen()) { return files_.first()->size(); } else { return 0; } } bool PlanarFileDevice::seek(qint64 pos) { bool ret = true; for (int i=0; iseek(pos) & ret; } return ret; } void PlanarFileDevice::close() { for (int i=0; iisOpen()) { f->close(); } delete f; } } files_.clear(); } } ================================================ FILE: app/codec/planarfiledevice.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PLANARFILEDEVICE_H #define PLANARFILEDEVICE_H #include #include #include namespace olive { using namespace core; class PlanarFileDevice : public QObject { Q_OBJECT public: PlanarFileDevice(QObject *parent = nullptr); virtual ~PlanarFileDevice() override; bool isOpen() const { return !files_.isEmpty(); } bool open(const QVector &filenames, QIODevice::OpenMode mode); qint64 read(char **data, qint64 bytes_per_channel, qint64 offset = 0); qint64 write(const char **data, qint64 bytes_per_channel, qint64 offset = 0); qint64 size() const; bool seek(qint64 pos); void close(); private: QVector files_; }; } #endif // PLANARFILEDEVICE_H ================================================ FILE: app/common/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} common/cancelableobject.h common/channellayout.h common/commandlineparser.cpp common/commandlineparser.h common/crashpadinterface.cpp common/crashpadinterface.h common/crashpadutils.h common/debug.cpp common/debug.h common/decibel.h common/define.h common/ffmpegutils.cpp common/ffmpegutils.h common/filefunctions.cpp common/filefunctions.h common/html.cpp common/html.h common/jobtime.cpp common/jobtime.h common/lerp.h common/memorypool.h common/ocioutils.cpp common/ocioutils.h common/oiioutils.cpp common/oiioutils.h common/otioutils.h common/qtutils.cpp common/qtutils.h common/range.h common/ratiodialog.cpp common/ratiodialog.h common/threadsafemap.h common/tohex.h common/util.h common/xmlutils.cpp common/xmlutils.h PARENT_SCOPE ) ================================================ FILE: app/common/autoscroll.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AUTOSCROLL_H #define AUTOSCROLL_H #include "common/define.h" namespace olive { class AutoScroll { public: enum Method { kNone, kPage, kSmooth }; }; } #endif // AUTOSCROLL_H ================================================ FILE: app/common/cancelableobject.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CANCELABLEOBJECT_H #define CANCELABLEOBJECT_H #include "common/define.h" #include "render/cancelatom.h" namespace olive { class CancelableObject { public: CancelableObject() { } void Cancel() { cancel_.Cancel(); CancelEvent(); } CancelAtom *GetCancelAtom() { return &cancel_; } bool IsCancelled() { return cancel_.IsCancelled(); } protected: virtual void CancelEvent(){} private: CancelAtom cancel_; }; } #endif // CANCELABLEOBJECT_H ================================================ FILE: app/common/channellayout.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CHANNELLAYOUT_H #define CHANNELLAYOUT_H /** * Channel Layouts header * * We don't do much here at the moment, audio is a much simpler beast than video nowadays and FFmpeg seems to cover it * fairly well. */ extern "C" { #include } #endif // CHANNELLAYOUT_H ================================================ FILE: app/common/commandlineparser.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "commandlineparser.h" #include #include CommandLineParser::~CommandLineParser() { foreach (const KnownOption& o, options_) { delete o.option; } foreach (const KnownPositionalArgument& a, positional_args_) { delete a.option; } } const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description, bool takes_arg, const QString &arg_placeholder, bool hidden) { Option* o = new Option(); options_.append({strings, description, o, takes_arg, arg_placeholder, hidden}); return o; } const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArgument(const QString &name, const QString &description, bool required) { PositionalArgument* a = new PositionalArgument(); positional_args_.append({name, description, a, required}); return a; } void CommandLineParser::Process(const QVector &argv) { int positional_index = 0; for (int i=1; iSet(); if (o.takes_arg && i+1 < argv.size()) { o.option->SetSetting(argv[i+1]); i++; } matched_known = true; goto found_flag; } } } found_flag: if (!matched_known) { qWarning() << "Unknown parameter:" << argv[i]; } } else { // Must be a positional flag if (positional_index < positional_args_.size()) { positional_args_[positional_index].option->SetSetting(argv[i]); positional_index++; } else { qWarning() << "Unknown parameter:" << argv[i]; } } } } void CommandLineParser::PrintHelp(const char* filename) { printf("%s %s\n", QCoreApplication::applicationName().toUtf8().constData(), QCoreApplication::applicationVersion().toUtf8().constData()); printf("Copyright (C) 2018-2022 Olive Team\n"); QString positional_args; for (int i=0; i 0) { positional_args.append(' '); } positional_args.append('['); positional_args.append(positional_args_.at(i).name); positional_args.append(']'); } const char* basename; #ifdef Q_OS_WINDOWS basename = strrchr(filename, '\\'); if (!basename) { basename = strrchr(filename, '/'); } #else basename = strrchr(filename, '/'); #endif if (basename) { // Slash found, increment pointer to avoid showing the slash itself basename++; } else { // If no slashes are found, assume string is already a basename basename = filename; } printf("Usage: %s [options] %s\n\n", basename, positional_args.toUtf8().constData()); foreach (const KnownOption& o, options_) { if (o.hidden) { continue; } QString all_args; for (int i=0; i 0) { all_args.append(QStringLiteral(", ")); } const QString& this_arg = o.args.at(i); all_args.append('-'); all_args.append(this_arg); } if (o.arg_placeholder.isEmpty()) { printf(" %s\n", all_args.toUtf8().constData()); } else { printf(" %s <%s>\n", all_args.toUtf8().constData(), o.arg_placeholder.toUtf8().constData()); } printf(" %s\n\n", o.description.toUtf8().constData()); } printf("\n"); } ================================================ FILE: app/common/commandlineparser.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef COMMANDLINEPARSER_H #define COMMANDLINEPARSER_H #include #include #include "common/define.h" /** * @brief Command-line argument parser * * You may be wondering why we don't use QCommandLineParser instead of a custom implementation like * this. The reason why is because QCommandLineParser requires a QApplication object of some kind * to already have been created before it can parse anything, but we need to be able to control * whether a QApplication (GUI-mode) or a QCoreApplication (CLI-mode) is created which is set by * the user as a command line argument. Therefore we needed a custom implementation that could * parse arguments without the need for a Q(Core)Application to be present already. */ class CommandLineParser { public: ~CommandLineParser(); DISABLE_COPY_MOVE(CommandLineParser) class PositionalArgument { public: PositionalArgument() = default; const QString& GetSetting() const { return setting_; } void SetSetting(const QString& s) { setting_ = s; } private: QString setting_; }; class Option : public PositionalArgument { public: Option() { is_set_ = false; } bool IsSet() const { return is_set_; } void Set() { is_set_ = true; } private: bool is_set_; }; CommandLineParser() = default; const Option* AddOption(const QStringList& strings, const QString& description, bool takes_arg = false, const QString& arg_placeholder = QString(), bool hidden = false); const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false); void Process(const QVector &argv); void PrintHelp(const char* filename); private: struct KnownOption { QStringList args; QString description; Option* option; bool takes_arg; QString arg_placeholder; bool hidden; }; struct KnownPositionalArgument { QString name; QString description; PositionalArgument* option; bool required; }; QVector options_; QVector positional_args_; }; #endif // COMMANDLINEPARSER_H ================================================ FILE: app/common/crashpadinterface.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "crashpadinterface.h" #ifdef USE_CRASHPAD #include #include #include #include #include #include #include "crashpadutils.h" #include "filefunctions.h" #if BUILDFLAG(IS_WIN) #include #endif crashpad::CrashpadClient *client; bool InitializeCrashpad() { QString report_path = QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("reports")); QString handler_fn = olive::FileFunctions::GetFormattedExecutableForPlatform(QStringLiteral("crashpad_handler")); // Generate absolute path QString handler_abs_path = QDir(QCoreApplication::applicationDirPath()).filePath(handler_fn); bool status = false; if (QFileInfo::exists(handler_abs_path)) { base::FilePath handler(QSTRING_TO_BASE_STRING(handler_abs_path)); base::FilePath reports_dir(QSTRING_TO_BASE_STRING(report_path)); base::FilePath metrics_dir(QSTRING_TO_BASE_STRING(QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("metrics")))); // Metadata that will be posted to the server with the crash report map std::map annotations; // Disable crashpad rate limiting so that all crashes have dmp files std::vector arguments; arguments.push_back("--no-rate-limit"); arguments.push_back("--no-upload-gzip"); // Initialize Crashpad database std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(reports_dir); if (database == NULL) return false; // Disable automated crash uploads crashpad::Settings *settings = database->GetSettings(); if (settings == NULL) return false; settings->SetUploadsEnabled(false); // Start crash handler client = new crashpad::CrashpadClient(); status = client->StartHandler(handler, reports_dir, metrics_dir, "https://olivevideoeditor.org/crashpad/report.php", annotations, arguments, true, true); } // Override Crashpad exception filter with our own if (!status) { qWarning() << "Failed to start Crashpad, automatic crash reporting will be disabled"; } return status; } #endif // USE_CRASHPAD ================================================ FILE: app/common/crashpadinterface.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CRASHPAD_INTERFACE_H #define CRASHPAD_INTERFACE_H #ifdef USE_CRASHPAD #include #include bool InitializeCrashpad(); #endif // USE_CRASHPAD #endif // CRASHPAD_INTERFACE_H ================================================ FILE: app/common/crashpadutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CRASHPADUTILS_H #define CRASHPADUTILS_H #include // Copied from base::FilePath to match its macro #if BUILDFLAG(IS_POSIX) // On most platforms, native pathnames are char arrays, and the encoding // may or may not be specified. On Mac OS X, native pathnames are encoded // in UTF-8. #define QSTRING_TO_BASE_STRING(x) x.toStdString() #define BASE_STRING_TO_QSTRING(x) QString::fromStdString(x) #elif BUILDFLAG(IS_WIN) // On Windows, for Unicode-aware applications, native pathnames are wchar_t // arrays encoded in UTF-16. #define QSTRING_TO_BASE_STRING(x) x.toStdWString() #define BASE_STRING_TO_QSTRING(x) QString::fromStdWString(x) #endif // BUILDFLAG(IS_WIN) #endif // CRASHPADUTILS_H ================================================ FILE: app/common/debug.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "debug.h" namespace olive { void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); const char* msg_type = "UNKNOWN"; switch (type) { case QtDebugMsg: msg_type = "DEBUG"; break; case QtInfoMsg: msg_type = "INFO"; break; case QtWarningMsg: msg_type = "WARNING"; break; case QtCriticalMsg: msg_type = "ERROR"; break; case QtFatalMsg: msg_type = "FATAL"; break; } //fprintf(stderr, "[%s] %s (%s:%u)\n", msg_type, localMsg.constData(), context.function, context.line); fprintf(stderr, "[%s] %s\n", msg_type, localMsg.constData()); #ifdef Q_OS_WINDOWS // Windows still seems to buffer stderr and we want to see debug messages immediately, so here we make sure each line // is flushed fflush(stderr); #endif } } ================================================ FILE: app/common/debug.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef DEBUG_H #define DEBUG_H #include #include "common/define.h" namespace olive { void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); } #endif // DEBUG_H ================================================ FILE: app/common/decibel.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef DECIBEL_H #define DECIBEL_H #include #include //#define ALLOW_RETURNING_INFINITY namespace olive { class Decibel { public: // In basically all circumstances, this should calculate to 0.0 linear static constexpr double MINIMUM = -200.0; static double fromLinear(double linear) { double v = double(20.0) * std::log10(linear); #ifndef ALLOW_RETURNING_INFINITY if (std::isinf(v)) { return MINIMUM; } #endif return v; } static double toLinear(double decibel) { double to_linear = std::pow(double(10.0), decibel / double(20.0)); // Minimum threshold that we figure is close enough to 0 that we may as well just return 0 if (to_linear < 0.000001) { return 0; } else { return to_linear; } } static double fromLogarithmic(double logarithmic) { if (logarithmic < 0.001) #ifdef ALLOW_RETURNING_INFINITY return std::numeric_limits::infinity(); #else return MINIMUM; #endif else if (logarithmic > 0.99) return 0; else return 20.0 * std::log10(-std::log(1 - logarithmic) / LOG100); } static double toLogarithmic(double decibel) { if (qFuzzyIsNull(decibel)) { return 1; } else { return 1 - std::exp(-std::pow(10.0, decibel / 20.0) * LOG100); } } static double LinearToLogarithmic(double linear) { return 1 - std::exp(-linear * LOG100); } static double LogarithmicToLinear(double logarithmic) { if (logarithmic > 0.99) { return 1; } else { return -std::log(1 - logarithmic) / LOG100; } } private: static constexpr double LOG100 = 4.60517018599; }; } #endif // DECIBEL_H ================================================ FILE: app/common/define.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OLIVECOMMONDEFINE_H #define OLIVECOMMONDEFINE_H namespace olive { /// The minimum size an icon in ProjectExplorer can be const int kProjectIconSizeMinimum = 16; /// The maximum size an icon in ProjectExplorer can be const int kProjectIconSizeMaximum = 256; /// The default size an icon in ProjectExplorer can be const int kProjectIconSizeDefault = 64; const int kBytesInGigabyte = 1073741824; } #define MACRO_NAME_AS_STR(s) #s #define MACRO_VAL_AS_STR(s) MACRO_NAME_AS_STR(s) #define OLIVE_NS_CONST_ARG(x, y) QArgument("const " MACRO_VAL_AS_STR(olive) "::" #x, y) #define OLIVE_NS_ARG(x, y) QArgument(MACRO_VAL_AS_STR(olive) "::" #x, y) #define OLIVE_NS_RETURN_ARG(x, y) QReturnArgument(MACRO_VAL_AS_STR(olive) "::" #x, y) /** * Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we * use our own functions for portability. */ #define DISABLE_COPY(Class) \ Class(const Class &) = delete;\ Class &operator=(const Class &) = delete; #define DISABLE_MOVE(Class) \ Class(Class &&) = delete; \ Class &operator=(Class &&) = delete; #define DISABLE_COPY_MOVE(Class) \ DISABLE_COPY(Class) \ DISABLE_MOVE(Class) #endif // OLIVECOMMONDEFINE_H ================================================ FILE: app/common/digit.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef DIGIT_H #define DIGIT_H #include namespace olive { inline int64_t GetDigitCount(int64_t input) { input = std::abs(input); int64_t lim = 10; int64_t digit = 1; while (input >= lim) { lim *= 10; digit++; } return digit; } } #endif // DIGIT_H ================================================ FILE: app/common/ffmpegutils.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "common/ffmpegutils.h" namespace olive { AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, PixelFormat maximum) { AVPixelFormat possible_pix_fmts[3]; possible_pix_fmts[0] = AV_PIX_FMT_RGBA; if (maximum == PixelFormat::U8) { possible_pix_fmts[1] = AV_PIX_FMT_NONE; } else { possible_pix_fmts[1] = AV_PIX_FMT_RGBA64; possible_pix_fmts[2] = AV_PIX_FMT_NONE; } return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt, 1, nullptr); } SampleFormat FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) { switch (smp_fmt) { case AV_SAMPLE_FMT_U8: return SampleFormat::U8; case AV_SAMPLE_FMT_S16: return SampleFormat::S16; case AV_SAMPLE_FMT_S32: return SampleFormat::S32; case AV_SAMPLE_FMT_S64: return SampleFormat::S64; case AV_SAMPLE_FMT_FLT: return SampleFormat::F32; case AV_SAMPLE_FMT_DBL: return SampleFormat::F64; case AV_SAMPLE_FMT_U8P : return SampleFormat::U8P; case AV_SAMPLE_FMT_S16P: return SampleFormat::S16P; case AV_SAMPLE_FMT_S32P: return SampleFormat::S32P; case AV_SAMPLE_FMT_S64P: return SampleFormat::S64P; case AV_SAMPLE_FMT_FLTP: return SampleFormat::F32P; case AV_SAMPLE_FMT_DBLP: return SampleFormat::F64P; case AV_SAMPLE_FMT_NONE: case AV_SAMPLE_FMT_NB: break; } return SampleFormat::INVALID; } AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) { switch (smp_fmt) { case SampleFormat::U8: return AV_SAMPLE_FMT_U8; case SampleFormat::S16: return AV_SAMPLE_FMT_S16; case SampleFormat::S32: return AV_SAMPLE_FMT_S32; case SampleFormat::S64: return AV_SAMPLE_FMT_S64; case SampleFormat::F32: return AV_SAMPLE_FMT_FLT; case SampleFormat::F64: return AV_SAMPLE_FMT_DBL; case SampleFormat::U8P: return AV_SAMPLE_FMT_U8P; case SampleFormat::S16P: return AV_SAMPLE_FMT_S16P; case SampleFormat::S32P: return AV_SAMPLE_FMT_S32P; case SampleFormat::S64P: return AV_SAMPLE_FMT_S64P; case SampleFormat::F32P: return AV_SAMPLE_FMT_FLTP; case SampleFormat::F64P: return AV_SAMPLE_FMT_DBLP; case SampleFormat::INVALID: case SampleFormat::COUNT: break; } return AV_SAMPLE_FMT_NONE; } int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs) { switch (cs) { case AVCOL_SPC_BT709: return SWS_CS_ITU709; case AVCOL_SPC_FCC: return SWS_CS_FCC; case AVCOL_SPC_BT470BG: return SWS_CS_ITU624; case AVCOL_SPC_SMPTE170M: return SWS_CS_SMPTE170M; case AVCOL_SPC_SMPTE240M: return SWS_CS_SMPTE240M; case AVCOL_SPC_BT2020_NCL: return SWS_CS_BT2020; default: break; } return SWS_CS_DEFAULT; } AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f) { switch (f) { case AV_PIX_FMT_YUVJ420P: return AV_PIX_FMT_YUV420P; case AV_PIX_FMT_YUVJ422P: return AV_PIX_FMT_YUV422P; case AV_PIX_FMT_YUVJ444P: return AV_PIX_FMT_YUV444P; case AV_PIX_FMT_YUVJ440P: return AV_PIX_FMT_YUV440P; case AV_PIX_FMT_YUVJ411P: return AV_PIX_FMT_YUV411P; default: break; } return f; } AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, int channel_layout) { if (channel_layout == VideoParams::kRGBChannelCount) { switch (pix_fmt) { case PixelFormat::U8: return AV_PIX_FMT_RGB24; case PixelFormat::U16: return AV_PIX_FMT_RGB48; case PixelFormat::F16: case PixelFormat::F32: case PixelFormat::INVALID: case PixelFormat::COUNT: break; } } else if (channel_layout == VideoParams::kRGBAChannelCount) { switch (pix_fmt) { case PixelFormat::U8: return AV_PIX_FMT_RGBA; case PixelFormat::U16: return AV_PIX_FMT_RGBA64; case PixelFormat::F16: case PixelFormat::F32: case PixelFormat::INVALID: case PixelFormat::COUNT: break; } } return AV_PIX_FMT_NONE; } PixelFormat FFmpegUtils::GetCompatiblePixelFormat(const PixelFormat &pix_fmt) { switch (pix_fmt) { case PixelFormat::U8: return PixelFormat::U8; case PixelFormat::U16: case PixelFormat::F16: case PixelFormat::F32: return PixelFormat::U16; case PixelFormat::INVALID: case PixelFormat::COUNT: break; } return PixelFormat::INVALID; } } ================================================ FILE: app/common/ffmpegutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FFMPEGABSTRACTION_H #define FFMPEGABSTRACTION_H extern "C" { #include #include #include } #include #include "render/videoparams.h" namespace olive { using namespace core; class FFmpegUtils { public: /** * @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss */ static AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt, PixelFormat maximum = PixelFormat::INVALID); /** * @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss */ static PixelFormat GetCompatiblePixelFormat(const PixelFormat& pix_fmt); /** * @brief Returns an FFmpeg pixel format for a given native pixel format */ static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat& pix_fmt, int channel_layout); /** * @brief Returns a native sample format type for a given AVSampleFormat */ static SampleFormat GetNativeSampleFormat(const AVSampleFormat& smp_fmt); /** * @brief Returns an FFmpeg sample format type for a given native type */ static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat &smp_fmt); /** * @brief Returns an SWS_CS_* macro from an AVColorSpace enum member * * Why aren't these the same thing anyway? And for that matter, why doesn't FFmpeg provide a * convenience function to do this conversion for us? Who knows, but here we are. */ static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs); /** * @brief Convert "JPEG"/full-range colorspace to its regular counterpart * * "JPEG "spaces are deprecated in favor of the regular space and setting `color_range`. For the * time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range * aware), we use this function. */ static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f); }; using AVFramePtr = std::shared_ptr; inline AVFramePtr CreateAVFramePtr(AVFrame *f) { return std::shared_ptr(f, [](AVFrame *g){ av_frame_free(&g); }); } inline AVFramePtr CreateAVFramePtr() { return CreateAVFramePtr(av_frame_alloc()); } } #endif // FFMPEGABSTRACTION_H ================================================ FILE: app/common/filefunctions.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "filefunctions.h" #include #include #include #include #include #include #include "config/config.h" namespace olive { QString FileFunctions::GetUniqueFileIdentifier(const QString &filename) { QFileInfo info(filename); if (!info.exists()) { return QString(); } QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(info.absoluteFilePath().toUtf8()); hash.addData(QString::number(info.lastModified().toMSecsSinceEpoch()).toUtf8()); QByteArray result = hash.result(); return QString(result.toHex()); } QString FileFunctions::GetConfigurationLocation() { if (IsPortable()) { return GetApplicationPath(); } else { QString s = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir(s).mkpath("."); return s; } } bool FileFunctions::IsPortable() { return QFileInfo::exists(QDir(GetApplicationPath()).filePath("portable")); } QString FileFunctions::GetApplicationPath() { return QCoreApplication::applicationDirPath(); } QString FileFunctions::GetTempFilePath() { QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)) .filePath(QCoreApplication::organizationName())) .filePath(QCoreApplication::applicationName()); // Ensure it exists QDir(temp_path).mkpath("."); return temp_path; } bool FileFunctions::CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest) { QFileInfoList info_list = QDir(source).entryInfoList(); foreach (const QFileInfo& info, info_list) { // QDir::NoDotAndDotDot continues to not work, so we have to check manually if (info.fileName() == QStringLiteral(".") || info.fileName() == QStringLiteral("..")) { continue; } QString dest_equivalent = QDir(dest).filePath(info.fileName()); if (info.isDir()) { if (!CanCopyDirectoryWithoutOverwriting(info.absoluteFilePath(), dest_equivalent)) { return false; } } else if (QFileInfo::exists(dest_equivalent)) { return false; } } return true; } void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bool overwrite) { QDir d(source); if (!d.exists()) { qCritical() << "Failed to copy directory, source" << source << "didn't exist"; return; } QDir dest_dir(dest); if (!dest_dir.mkpath(QStringLiteral("."))) { qCritical() << "Failed to create destination directory" << dest; return; } QFileInfoList l = d.entryInfoList(); foreach (const QFileInfo& info, l) { // QDir::NoDotAndDotDot continues to not work, so we have to check manually if (info.fileName() == QStringLiteral(".") || info.fileName() == QStringLiteral("..")) { continue; } QString dest_file_path = dest_dir.filePath(info.fileName()); if (info.isDir()) { // Copy dir CopyDirectory(info.absoluteFilePath(), dest_file_path, overwrite); } else { // Copy file if (overwrite && QFile::exists(dest_file_path)) { QFile file(dest_file_path); file.setPermissions(file.permissions() | QFileDevice::WriteOwner | QFileDevice::WriteUser | QFileDevice::WriteGroup | QFileDevice::WriteOther); file.remove(); } QFile::copy(info.absoluteFilePath(), dest_file_path); } } } bool FileFunctions::DirectoryIsValid(const QDir &d, bool try_to_create_if_not_exists) { // Return whether the directory exists, or whether it could be created if it doesn't return d.exists() || d.mkpath(QStringLiteral(".")); } QString FileFunctions::EnsureFilenameExtension(QString fn, const QString &extension) { // No-op if either input is empty if (!fn.isEmpty() && !extension.isEmpty()) { QString extension_with_dot; extension_with_dot.append('.'); extension_with_dot.append(extension); if (!fn.endsWith(extension_with_dot, Qt::CaseInsensitive)) { fn.append(extension_with_dot); } } return fn; } QString FileFunctions::ReadFileAsString(const QString &filename) { QFile f(filename); QString file_data; if (f.open(QFile::ReadOnly | QFile::Text)) { QTextStream text_stream(&f); file_data = text_stream.readAll(); f.close(); } return file_data; } QString FileFunctions::GetSafeTemporaryFilename(const QString &original) { int counter = 0; QFileInfo original_info(original); QString basename = original_info.baseName(); QString complete_suffix = original_info.completeSuffix(); // If we have a complete suffix, make sure there's a period in it if (!complete_suffix.isEmpty()) { complete_suffix.prepend('.'); } QString temp_abs_path; do { temp_abs_path = original_info.dir().filePath( QStringLiteral("%1.tmp%2%3").arg(basename, QString::number(counter), complete_suffix)); counter++; } while (QFileInfo::exists(temp_abs_path)); return temp_abs_path; } bool FileFunctions::RenameFileAllowOverwrite(const QString &from, const QString &to) { if (QFileInfo::exists(to) && !QFile::remove(to)) { qCritical() << "Couldn't remove existing file" << to << "for overwrite"; return false; } // By this point, we can assume `to` either never existed or has now been deleted if (!QFile::rename(from, to)) { qCritical() << "Failed to rename file" << from << "to" << to; return false; } return true; } QString FileFunctions::GetAutoRecoveryRoot() { return QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(QStringLiteral("autorecovery")); } } ================================================ FILE: app/common/filefunctions.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FILEFUNCTIONS_H #define FILEFUNCTIONS_H #include #include #include "common/define.h" namespace olive { /** * @brief A collection of static file and directory functions */ class FileFunctions { public: /** * @brief Returns true if the application is running in portable mode * * In portable mode, any persistent configuration files should be made in a path relative to the application rather * than in the user's home folder. */ static bool IsPortable(); static QString GetUniqueFileIdentifier(const QString& filename); static QString GetConfigurationLocation(); static QString GetApplicationPath(); static QString GetTempFilePath(); static bool CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest); static void CopyDirectory(const QString& source, const QString& dest, bool overwrite = false); static bool DirectoryIsValid(const QDir& dir, bool try_to_create_if_not_exists = true); /** * @brief Ensures a given filename has a certain extension * * Checks if the filename has the extension provided and appends it if not. The extension is * checked case-insensitive. The extension should be provided with no dot (e.g. "ove" rather than * ".ove"). * @return The filename provided either untouched or with the extension appended to it. */ static QString EnsureFilenameExtension(QString fn, const QString& extension); static QString ReadFileAsString(const QString& filename); /** * @brief Returns a temporary filename that can be used while writing rather than the original * * If overwriting a file, it's safest to write to a new file first and then only replace it at * the end so that if the program crashes or the user cancels the save half way through, the * original file is still intact. * * This function returns a slight variant of the filename provided that's guaranteed to not exist * and therefore won't overwrite anything important. */ static QString GetSafeTemporaryFilename(const QString& original); /** * @brief Renames a file from `from` to `to`, deleting `to` if such a file already exists first */ static bool RenameFileAllowOverwrite(const QString& from, const QString& to); inline static QString GetFormattedExecutableForPlatform(QString unformatted) { #ifdef Q_OS_WINDOWS unformatted.append(QStringLiteral(".exe")); #endif return unformatted; } static QString GetAutoRecoveryRoot(); }; } #endif // FILEFUNCTIONS_H ================================================ FILE: app/common/html.cpp ================================================ #include "html.h" #include #include #include "xmlutils.h" namespace olive { const QVector Html::kBlockTags = { QStringLiteral("p"), QStringLiteral("div") }; inline bool StrEquals(const QStringView &a, const QStringView &b) { return !a.compare(b, Qt::CaseInsensitive); } QString Html::DocToHtml(const QTextDocument *doc) { QString html; QXmlStreamWriter writer(&html); //writer.setAutoFormatting(true); for (auto it=doc->begin(); it!=doc->end(); it=it.next()) { WriteBlock(&writer, it); } return html; } struct HtmlNode { QString tag; QTextCharFormat format; }; QTextCharFormat MergeHtmlFormats(const QVector &stack) { QTextCharFormat f; for (int i=0; iclear(); bool inside_block = true; // Create cursor, which appears to be Qt's official way of inserting blocks and fragments QTextCursor c(doc); QString wrapped = QStringLiteral("").append(html).append(""); QXmlStreamReader reader(wrapped); QVector fmt_stack; QTextCharFormat default_fmt; default_fmt.setFontWeight(QFont::Normal); fmt_stack.append({QStringLiteral("html"), default_fmt}); QTextCharFormat current_fmt; while (!reader.atEnd()) { reader.readNext(); if (reader.tokenType() == QXmlStreamReader::StartElement) { QString tag = reader.name().toString().toLower(); fmt_stack.append({tag, ReadCharFormat(reader.attributes())}); current_fmt = MergeHtmlFormats(fmt_stack); if (kBlockTags.contains(tag)) { QTextBlockFormat block_fmt = ReadBlockFormat(reader.attributes()); if (inside_block) { c.setBlockFormat(block_fmt); c.setBlockCharFormat(current_fmt); } else { c.insertBlock(block_fmt, current_fmt); inside_block = true; } } } else if (reader.tokenType() == QXmlStreamReader::Characters) { QString characters = reader.text().toString(); c.insertText(characters, current_fmt); } else if (reader.tokenType() == QXmlStreamReader::EndElement) { QString tag = reader.name().toString().toLower(); for (int i=fmt_stack.size()-1; i>=0; i--) { if (fmt_stack.at(i).tag == tag) { fmt_stack.removeAt(i); current_fmt = MergeHtmlFormats(fmt_stack); if (kBlockTags.contains(tag)) { inside_block = false; } break; } } } } if (reader.error()) { qCritical() << "Failed to parse HTML:" << reader.errorString(); } } void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block) { writer->writeStartElement(QStringLiteral("p")); const QTextBlockFormat &fmt = block.blockFormat(); // Write block alignment if (!(fmt.alignment() & Qt::AlignLeft)) { if (fmt.alignment() & Qt::AlignRight) { writer->writeAttribute(QStringLiteral("align"), QStringLiteral("right")); } else if (fmt.alignment() & Qt::AlignHCenter) { writer->writeAttribute(QStringLiteral("align"), QStringLiteral("center")); } else if (fmt.alignment() & Qt::AlignJustify) { writer->writeAttribute(QStringLiteral("align"), QStringLiteral("justify")); } } // RTL support if (block.textDirection() == Qt::RightToLeft) { writer->writeAttribute(QStringLiteral("dir"), QStringLiteral("rtl")); } // Write CSS attributes QString style; if (fmt.lineHeightType() != QTextBlockFormat::SingleHeight) { WriteCSSProperty(&style, QStringLiteral("line-height"), QStringLiteral("%1%").arg(fmt.lineHeight())); } WriteCharFormat(&style, block.charFormat()); if (!style.isEmpty()) { writer->writeAttribute(QStringLiteral("style"), style); } auto it = block.begin(); if (it != block.end()) { for (; it!=block.end(); it++) { WriteFragment(writer, it.fragment()); } } writer->writeEndElement(); // p } void Html::WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment) { const QTextCharFormat &fmt = fragment.charFormat(); writer->writeStartElement(QStringLiteral("span")); // Write CSS attributes QString style; WriteCharFormat(&style, fmt); if (!style.isEmpty()) { writer->writeAttribute(QStringLiteral("style"), style); } QStringList lines = fragment.text().split(QChar::LineSeparator); bool first_line = true; foreach (const QString &l, lines) { if (first_line) { first_line = false; } else { writer->writeEmptyElement(QStringLiteral("br")); } writer->writeCharacters(l); } writer->writeEndElement(); // span } void Html::WriteCSSProperty(QString *style, const QString &key, const QStringList &values) { QString value; foreach (QString v, values) { if (v.contains(' ')) { v = QStringLiteral("'%1'").arg(v); } AppendStringAutoSpace(&value, v); } AppendStringAutoSpace(style, QStringLiteral("%1: %2;").arg(key, value)); } void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt) { QStringList families = fmt.fontFamilies().toStringList(); if (!families.isEmpty()) { WriteCSSProperty(style, QStringLiteral("font-family"), families.first()); } if (fmt.hasProperty(QTextFormat::FontPointSize)) { WriteCSSProperty(style, QStringLiteral("font-size"), QStringLiteral("%1pt").arg(QString::number(fmt.fontPointSize()))); } if (fmt.hasProperty(QTextFormat::FontWeight)) { WriteCSSProperty(style, QStringLiteral("font-weight"), QString::number(fmt.fontWeight() * 8)); } if (fmt.hasProperty(QTextFormat::FontItalic)) { WriteCSSProperty(style, QStringLiteral("font-style"), fmt.fontItalic() ? QStringLiteral("italic") : QStringLiteral("normal")); } if (fmt.hasProperty(QTextFormat::FontStyleName)) { WriteCSSProperty(style, QStringLiteral("-ove-font-style"), fmt.fontStyleName().toString()); } QStringList deco; if (fmt.fontUnderline()) { deco.append(QStringLiteral("underline")); } if (fmt.fontStrikeOut()) { deco.append(QStringLiteral("line-through")); } if (fmt.fontOverline()) { deco.append(QStringLiteral("overline")); } if (!deco.isEmpty()) { WriteCSSProperty(style, QStringLiteral("text-decoration"), deco); } if (fmt.foreground().style() != Qt::NoBrush) { const QColor &color = fmt.foreground().color(); QString cs; if (color.alpha() == 255) { cs = color.name(); } else if (color.alpha()) { cs = QStringLiteral("rgba(%1, %2, %3, %4)").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()), QString::number(color.alphaF())); } WriteCSSProperty(style, QStringLiteral("color"), cs); } if (fmt.fontCapitalization() != QFont::MixedCase) { if (fmt.fontCapitalization() == QFont::SmallCaps) { WriteCSSProperty(style, QStringLiteral("font-variant"), QStringLiteral("small-caps")); // TODO: Add others } } if (fmt.fontLetterSpacing() != 0.0) { WriteCSSProperty(style, QStringLiteral("letter-spacing"), QStringLiteral("%1%").arg(QString::number(fmt.fontLetterSpacing()))); } if (fmt.fontStretch() != 0) { WriteCSSProperty(style, QStringLiteral("font-stretch"), QStringLiteral("%1%").arg(QString::number(fmt.fontStretch()))); } } QTextCharFormat Html::ReadCharFormat(const QXmlStreamAttributes &attributes) { QTextCharFormat fmt; foreach (const QXmlStreamAttribute &attr, attributes) { if (StrEquals(attr.name(), QStringLiteral("style"))) { auto css = GetCSSFromStyle(attr.value().toString()); for (auto it=css.begin(); it!=css.end(); it++) { const QString &first_val = it.value().first(); if (it.key() == QStringLiteral("font-family")) { fmt.setFontFamilies({first_val}); } else if (it.key() == QStringLiteral("font-size")) { if (first_val.endsWith(QStringLiteral("pt"), Qt::CaseInsensitive)) { fmt.setFontPointSize(first_val.chopped(2).toDouble()); } } else if (it.key() == QStringLiteral("font-weight")) { fmt.setFontWeight(first_val.toInt()/8); } else if (it.key() == QStringLiteral("font-style")) { fmt.setFontItalic(StrEquals(first_val, QStringLiteral("italic"))); } else if (it.key() == QStringLiteral("text-decoration")) { foreach (const QString &v, it.value()) { if (StrEquals(v, QStringLiteral("underline"))) { fmt.setFontUnderline(true); } else if (StrEquals(v, QStringLiteral("line-through"))) { fmt.setFontStrikeOut(true); } else if (StrEquals(v, QStringLiteral("overline"))) { fmt.setFontOverline(true); } } } else if (it.key() == QStringLiteral("color")) { if (first_val.startsWith(QStringLiteral("rgba"), Qt::CaseInsensitive)) { QString vals_only = first_val; vals_only.remove(QStringLiteral("rgba")); vals_only.remove(QStringLiteral("(")); vals_only.remove(QStringLiteral(")")); QStringList rgba = vals_only.split(','); if (rgba.size() == 4) { QColor c; c.setRedF(rgba.at(0).toDouble()); c.setGreenF(rgba.at(1).toDouble()); c.setBlueF(rgba.at(2).toDouble()); c.setAlphaF(rgba.at(3).toDouble()); fmt.setForeground(c); } } else { fmt.setForeground(QColor(first_val)); } } else if (it.key() == QStringLiteral("font-variant")) { if (StrEquals(first_val, QStringLiteral("small-caps"))) { fmt.setFontCapitalization(QFont::SmallCaps); } } else if (it.key() == QStringLiteral("letter-spacing")) { if (first_val.contains(QChar('%'))) { fmt.setFontLetterSpacing(first_val.chopped(1).toDouble()); } } else if (it.key() == QStringLiteral("font-stretch")) { if (first_val.contains(QChar('%'))) { fmt.setFontStretch(first_val.chopped(1).toInt()); } } else if (it.key() == QStringLiteral("-ove-font-style")) { fmt.setFontStyleName(first_val); } } } } return fmt; } QTextBlockFormat Html::ReadBlockFormat(const QXmlStreamAttributes &attributes) { QTextBlockFormat block_fmt; foreach (const QXmlStreamAttribute &attr, attributes) { if (StrEquals(attr.name(), QStringLiteral("align"))) { if (StrEquals(attr.value(), QStringLiteral("right"))) { block_fmt.setAlignment(Qt::AlignRight); } else if (StrEquals(attr.value(), QStringLiteral("center"))) { block_fmt.setAlignment(Qt::AlignHCenter); } else if (StrEquals(attr.value(), QStringLiteral("justify"))) { block_fmt.setAlignment(Qt::AlignJustify); } } else if (StrEquals(attr.name(), QStringLiteral("dir"))) { if (StrEquals(attr.value(), QStringLiteral("rtl"))) { block_fmt.setLayoutDirection(Qt::RightToLeft); } } else if (StrEquals(attr.name(), QStringLiteral("style"))) { auto css = GetCSSFromStyle(attr.value().toString()); for (auto it=css.begin(); it!=css.end(); it++) { if (it.key() == QStringLiteral("line-height")) { const QString &first_val = it.value().constFirst(); if (first_val.contains(QChar('%'))) { block_fmt.setLineHeight(first_val.chopped(1).toDouble(), QTextBlockFormat::ProportionalHeight); } } } } } return block_fmt; } void Html::AppendStringAutoSpace(QString *s, const QString &append) { if (!s->isEmpty()) { s->append(QChar(' ')); } s->append(append); } QMap Html::GetCSSFromStyle(const QString &s) { QMap map; QStringList list = s.split(QChar(';')); foreach (const QString &a, list) { QStringList kv = a.split(QChar(':')); if (kv.size() != 2) { continue; } // I'm sure there's regex that could do this, but I couldn't figure it out. It needs to split // by space EXCEPT within quotes OR double-quotes, and said quotes should be EXCLUDED from each // match. Also commas should be filtered out. QStringList values; const QString &val = kv.at(1); QChar in_quote(0); QString current_str; for (int i=0; i #include #include #include namespace olive { /** * @brief Functions for converting HTML to QTextDocument and vice versa * * Qt does contain its own functions for this, however they have some limitations. Some things that * we want to support (e.g. kerning/spacing and font stretch) are not implemented in Qt's * QTextHtmlExporter and QTextHtmlParser. Additionally, since these functions are not part of Qt's * public API, and make many references to other parts of Qt that are not part of the public API, * there is no way to subclass or extend their functionality without forking Qt as a whole. * * Therefore, it became necessary to write a custom class for the conversion so that we can * ensure support for the features we need. * * If someone wishes to extend this class for more feature support, feel free to open a pull * request. But this is NOT intended to be an exhaustive HTML implementation, and is primarily * designed to store rich text in a standard format for the purpose of text formatting for video. */ class Html { public: static QString DocToHtml(const QTextDocument *doc); static void HtmlToDoc(QTextDocument *doc, const QString &html); private: static void WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block); static void WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment); static void WriteCSSProperty(QString *style, const QString &key, const QStringList &value); static void WriteCSSProperty(QString *style, const QString &key, const QString &value) { WriteCSSProperty(style, key, QStringList({value})); } static void WriteCharFormat(QString *style, const QTextCharFormat &fmt); static QTextCharFormat ReadCharFormat(const QXmlStreamAttributes &attributes); static QTextBlockFormat ReadBlockFormat(const QXmlStreamAttributes &attributes); static void AppendStringAutoSpace(QString *s, const QString &append); static QMap GetCSSFromStyle(const QString &s); static const QVector kBlockTags; }; } #endif // HTML_H ================================================ FILE: app/common/jobtime.cpp ================================================ #include "jobtime.h" #include namespace olive { uint64_t job_time_index = 0; QMutex job_time_mutex; JobTime::JobTime() { Acquire(); } void JobTime::Acquire() { job_time_mutex.lock(); value_ = job_time_index; job_time_index++; job_time_mutex.unlock(); } } QDebug operator<<(QDebug debug, const olive::JobTime& r) { return debug.space() << r.value(); } ================================================ FILE: app/common/jobtime.h ================================================ #ifndef JOBTIME_H #define JOBTIME_H #include #include namespace olive { class JobTime { public: JobTime(); void Acquire(); uint64_t value() const { return value_; } bool operator==(const JobTime &rhs) const { return value_ == rhs.value_; } bool operator!=(const JobTime &rhs) const { return value_ != rhs.value_; } bool operator<(const JobTime &rhs) const { return value_ < rhs.value_; } bool operator>(const JobTime &rhs) const { return value_ > rhs.value_; } bool operator<=(const JobTime &rhs) const { return value_ <= rhs.value_; } bool operator>=(const JobTime &rhs) const { return value_ >= rhs.value_; } private: uint64_t value_; }; } QDebug operator<<(QDebug debug, const olive::JobTime& r); Q_DECLARE_METATYPE(olive::JobTime) #endif // JOBTIME_H ================================================ FILE: app/common/lerp.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef LERP_H #define LERP_H template /** * @brief Linearly interpolate a value between a and b using t * * t should be a number between 0.0 and 1.0. 0.0 will return a, 1.0 will return b, and between will return a value * in between a and b at that point linearly. */ T lerp(T a, T b, double t) { return (a * (1.0 - t)) + (b * t); } template T lerp(T a, T b, float t) { return (a * (1.0f - t)) + (b * t); } #endif // LERP_H ================================================ FILE: app/common/memorypool.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef MEMORYPOOL_H #define MEMORYPOOL_H #include #include #include #include #include #include #include #include "common/define.h" namespace olive { /** * @brief MemoryPool base class * * A custom memory system that allocates can allocate several objects in a large chunk (as opposed to several small * allocations). Improves performance and memory consumption. * * As a class, this base is usable by setting the template to an object of your choosing. The pool will then allocate * `(element_count * sizeof(T))` per arena. Arenas are allocated and destroyed on the fly - when an arena fills up, * another is allocated. * * `Get()` will return an ElementPtr. The original desired data can be accessed through ElementPtr::data(). This data * will belong to the caller until ElementPtr goes out of scope and the memory is freed back into the pool. */ class MemoryPool : public QObject { Q_OBJECT public: /** * @brief Constructor * @param element_count * * Number of elements per arena */ MemoryPool(int element_count) { element_count_ = element_count; clear_timer_ = new QTimer(); clear_timer_->setInterval(kMaxEmptyArenaLife); clear_timer_->moveToThread(qApp->thread()); connect(clear_timer_, &QTimer::timeout, this, &MemoryPool::ClearEmptyArenas, Qt::DirectConnection); QMetaObject::invokeMethod(clear_timer_, "start", Qt::QueuedConnection); } /** * @brief Destructor * * Deletes all arenas. */ virtual ~MemoryPool() { Clear(); clear_timer_->deleteLater(); } DISABLE_COPY_MOVE(MemoryPool) /** * @brief Clears all arenas, freeing all of their memory * * Note that this function is not safe, any elements that are still out there will be invalid * and accessing them will cause a crash. You'll need to make sure all elements are already * relinquished before then. */ void Clear() { qDeleteAll(arenas_); arenas_.clear(); } /** * @brief Returns whether any arenas are successfully allocated */ inline bool IsAllocated() const { return !arenas_.empty(); } /** * @brief Returns current number of allocated arenas */ inline int GetArenaCount() const { return arenas_.size(); } class Arena; /** * @brief A handle for a chunk of memory in an arena * * Calling Get() on the pool or arena will return a shared pointer to an element which will contain a pointer to * the desired object/data in data(). When Element is destroyed (i.e. when ElementPtr goes out of scope), the memory * is released back into the pool so it can be used by another class. */ class Element { public: /** * @brief Element Constructor * * There is no need to use this outside of the memory pool's internal functions. */ Element(Arena* parent, uint8_t* data) { parent_ = parent; data_ = data; accessed_ = QDateTime::currentMSecsSinceEpoch(); } /** * @brief Element Destructor * * Automatically releases this element's memory back to the arena it was retrieved from. */ ~Element() { release(); } DISABLE_COPY_MOVE(Element) /** * @brief Access data represented in the pool */ inline uint8_t* data() const { return data_; } inline const int64_t& timestamp() const { return timestamp_; } inline void set_timestamp(const int64_t& timestamp) { timestamp_ = timestamp; } /** * @brief Register that this element has been accessed * * \see last_accessed() */ inline void access() { accessed_ = QDateTime::currentMSecsSinceEpoch(); } /** * @brief Returns the last time `access()` was called on this function * * Useful for determining the relative age of an element (i.e. if it hasn't been accessed for a certain amount of * time, it can probably be freed back into the pool). This requires all usages to call `access()`. */ inline const int64_t& last_accessed() const { return accessed_; } void release() { if (data_) { parent_->Release(this); data_ = nullptr; } } private: Arena* parent_; uint8_t* data_; int64_t timestamp_; int64_t accessed_; }; using ElementPtr = std::shared_ptr; /** * @brief A memory pool arena - a subsection of memory * * The pool itself does not store memory, it stores "arenas". This is so that the pool can handle the situation of * an arena becoming full with no more memory to lend. A pool can automatically allocate another arena and continue * providing memory (and freeing arenas when they're no longer in use). */ class Arena { public: Arena(MemoryPool* parent) { parent_ = parent; data_ = nullptr; allocated_sz_ = 0; empty_time_ = QDateTime::currentMSecsSinceEpoch(); } ~Arena() { std::list copy = lent_elements_; foreach (Element* e, copy) { e->release(); } delete [] data_; } DISABLE_COPY_MOVE(Arena) /** * @brief Returns an element if there is free memory to do so */ ElementPtr Get() { QMutexLocker locker(&lock_); for (int i=0;i(this, reinterpret_cast(data_ + i * element_sz_)); lent_elements_.push_back(e.get()); return e; } } return nullptr; } /** * @brief Releases an element back into the pool for use elsewhere */ void Release(Element* e) { QMutexLocker locker(&lock_); quintptr diff = reinterpret_cast(e->data()) - reinterpret_cast(data_); int index = diff / element_sz_; available_.replace(index, true); lent_elements_.remove(e); if (lent_elements_.empty()) { empty_time_ = QDateTime::currentMSecsSinceEpoch(); } } int GetUsageCount() { QMutexLocker locker(&lock_); return lent_elements_.size(); } bool Allocate(size_t ele_sz, size_t nb_elements) { if (IsAllocated()) { return true; } element_sz_ = ele_sz; allocated_sz_ = element_sz_ * nb_elements; if ((data_ = new uint8_t[allocated_sz_])) { available_.resize(nb_elements); available_.fill(true); return true; } else { available_.clear(); return false; } } inline int GetElementCount() const { return available_.size(); } inline bool IsAllocated() const { return data_; } inline qint64 GetTimeArenaWasMadeEmpty() { QMutexLocker locker(&lock_); return empty_time_; } private: MemoryPool* parent_; uint8_t* data_; size_t allocated_sz_; QVector available_; QMutex lock_; size_t element_sz_; std::list lent_elements_; qint64 empty_time_; }; /** * @brief Retrieves an element from an available arena */ ElementPtr Get() { QMutexLocker locker(&lock_); // Attempt to get an element from an arena foreach (Arena* a, arenas_) { ElementPtr e = a->Get(); if (e) { return e; } } // All arenas were empty, we'll need to create a new one if (arenas_.empty()) { qDebug() << "No arenas, creating new..."; } else { qDebug() << "All arenas are full, creating new..."; } size_t ele_sz = GetElementSize(); if (!ele_sz) { qCritical() << "Failed to create arena, element size was 0"; return nullptr; } if (element_count_ <= 0) { qCritical() << "Failed to create arena, element count was invalid:" << element_count_; return nullptr; } Arena* a = new Arena(this); if (!a->Allocate(ele_sz, element_count_)) { qCritical() << "Failed to create arena, allocation failed. Out of memory?"; delete a; return nullptr; } arenas_.push_back(a); return a->Get(); } protected: /** * @brief The size of each element * * Override this to use a custom size (e.g. a char array where T = char but the element size is > 1) */ virtual size_t GetElementSize() { return sizeof(uint8_t); } private: int element_count_; std::list arenas_; QMutex lock_; QTimer *clear_timer_; static const qint64 kMaxEmptyArenaLife = 5000; private slots: void ClearEmptyArenas() { QMutexLocker locker(&lock_); const qint64 min_time = QDateTime::currentMSecsSinceEpoch() - kMaxEmptyArenaLife; for (auto it=arenas_.begin(); it!=arenas_.end(); ) { Arena* arena = (*it); if (arena->GetUsageCount() == 0 && arena->GetTimeArenaWasMadeEmpty() <= min_time) { qDebug() << "Removing an empty arena"; delete arena; it = arenas_.erase(it); } else { it++; } } } }; } #endif // MEMORYPOOL_H ================================================ FILE: app/common/ocioutils.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "ocioutils.h" namespace olive { OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(PixelFormat format) { switch (format) { case PixelFormat::U8: return OCIO::BIT_DEPTH_UINT8; case PixelFormat::U16: return OCIO::BIT_DEPTH_UINT16; break; case PixelFormat::F16: return OCIO::BIT_DEPTH_F16; break; case PixelFormat::F32: return OCIO::BIT_DEPTH_F32; break; case PixelFormat::INVALID: case PixelFormat::COUNT: break; } return OCIO::BIT_DEPTH_UNKNOWN; } } ================================================ FILE: app/common/ocioutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OCIOUTILS_H #define OCIOUTILS_H #include namespace OCIO = OCIO_NAMESPACE; #include "render/videoparams.h" namespace olive { class OCIOUtils { public: static OCIO::BitDepth GetOCIOBitDepthFromPixelFormat(PixelFormat format); }; } #endif // OCIOUTILS_H ================================================ FILE: app/common/oiioutils.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "oiioutils.h" #include namespace olive { void OIIOUtils::FrameToBuffer(const Frame* frame, OIIO::ImageBuf *buf) { buf->set_pixels(OIIO::ROI(), buf->spec().format, frame->const_data(), OIIO::AutoStride, frame->linesize_bytes()); } void OIIOUtils::BufferToFrame(OIIO::ImageBuf *buf, Frame* frame) { buf->get_pixels(OIIO::ROI(), buf->spec().format, frame->data(), OIIO::AutoStride, frame->linesize_bytes()); } rational OIIOUtils::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec) { return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1)); } PixelFormat OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type) { switch (type) { case OIIO::TypeDesc::UNKNOWN: case OIIO::TypeDesc::NONE: break; case OIIO::TypeDesc::INT8: case OIIO::TypeDesc::INT16: case OIIO::TypeDesc::INT32: case OIIO::TypeDesc::UINT32: case OIIO::TypeDesc::INT64: case OIIO::TypeDesc::UINT64: case OIIO::TypeDesc::STRING: case OIIO::TypeDesc::PTR: case OIIO::TypeDesc::LASTBASE: case OIIO::TypeDesc::DOUBLE: qDebug() << "Tried to use unknown OIIO base type"; break; case OIIO::TypeDesc::UINT8: return PixelFormat::U8; case OIIO::TypeDesc::UINT16: return PixelFormat::U16; case OIIO::TypeDesc::HALF: return PixelFormat::F16; case OIIO::TypeDesc::FLOAT: return PixelFormat::F32; } return PixelFormat::INVALID; } } ================================================ FILE: app/common/oiioutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OIIOUTILS_H #define OIIOUTILS_H #include #include #include "codec/frame.h" #include "render/videoparams.h" namespace olive { class OIIOUtils { public: static OIIO::TypeDesc::BASETYPE GetOIIOBaseTypeFromFormat(PixelFormat format) { switch (format) { case PixelFormat::U8: return OIIO::TypeDesc::UINT8; case PixelFormat::U16: return OIIO::TypeDesc::UINT16; case PixelFormat::F16: return OIIO::TypeDesc::HALF; case PixelFormat::F32: return OIIO::TypeDesc::FLOAT; case PixelFormat::INVALID: case PixelFormat::COUNT: break; } return OIIO::TypeDesc::UNKNOWN; } static void FrameToBuffer(const Frame *frame, OIIO::ImageBuf* buf); static void BufferToFrame(OIIO::ImageBuf* buf, Frame* frame); static PixelFormat GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type); static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec); }; } #endif // OIIOUTILS_H ================================================ FILE: app/common/otioutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OTIOUTILS_H #define OTIOUTILS_H #ifdef USE_OTIO #include namespace OTIO = opentimelineio::OPENTIMELINEIO_VERSION; #endif #endif // OTIOUTILS ================================================ FILE: app/common/power.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef POWER_H #define POWER_H #include #include "common/define.h" namespace olive { uint32_t ceil_to_power_of_2(uint32_t v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } uint32_t floor_to_power_of_2(uint32_t x) { x = x | (x >> 1); x = x | (x >> 2); x = x | (x >> 4); x = x | (x >> 8); x = x | (x >> 16); return x - (x >> 1); } } #endif // POWER_H ================================================ FILE: app/common/qtutils.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "qtutils.h" #include namespace olive { int QtUtils::QFontMetricsWidth(QFontMetrics fm, const QString& s) { #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) return fm.width(s); #else return fm.horizontalAdvance(s); #endif } QFrame *QtUtils::CreateHorizontalLine() { QFrame* horizontal_line = new QFrame(); horizontal_line->setFrameShape(QFrame::HLine); horizontal_line->setFrameShadow(QFrame::Sunken); return horizontal_line; } QFrame *QtUtils::CreateVerticalLine() { QFrame *l = CreateHorizontalLine(); l->setFrameShape(QFrame::VLine); return l; } int QtUtils::MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons) { QMessageBox b(parent); b.setIcon(icon); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(title); b.setText(message); uint mask = QMessageBox::FirstButton; while (mask <= QMessageBox::LastButton) { uint sb = buttons & mask; if (sb) { b.addButton(static_cast(sb)); } mask <<= 1; } return b.exec(); } QDateTime QtUtils::GetCreationDate(const QFileInfo &info) { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) return info.created(); #else QDateTime t = info.birthTime(); if (!t.isValid()) { t = info.metadataChangeTime(); } return t; #endif } QString QtUtils::GetFormattedDateTime(const QDateTime &dt) { return dt.toString(Qt::TextDate); } QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width) { QStringList list; QStringList lines = s.split('\n'); // Iterate every line for (int i=0; i 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) { int old_size = this_line.size(); int hard_break = -1; for (int j=this_line.size()-1; j>=0; j--) { const QChar &char_test = this_line.at(j); if (char_test.isSpace() || char_test == '-') { if (QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) { if (!char_test.isSpace()) { j++; } QString chopped = this_line.left(j); list.append(chopped); while (j < this_line.size() && this_line.at(j).isSpace()) { j++; } this_line.remove(0, j); break; } } else if (hard_break == -1 && QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) { // In case we can't find a better place to split, split at the earliest time the line // goes under the width limit hard_break = j; } } if (old_size == this_line.size()) { if (hard_break != -1) { list.append(this_line.left(hard_break)); this_line.remove(0, hard_break); } else { qWarning() << "Failed to find anywhere to wrap. Returning full line."; break; } } } if (!this_line.isEmpty()) { list.append(this_line); } } return list; } Qt::KeyboardModifiers QtUtils::FlipControlAndShiftModifiers(Qt::KeyboardModifiers e) { if (e & Qt::ControlModifier & Qt::ShiftModifier) { return e; } if (e & Qt::ShiftModifier) { e |= Qt::ControlModifier; e &= ~Qt::ShiftModifier; } else if (e & Qt::ControlModifier) { e |= Qt::ShiftModifier; e &= ~Qt::ControlModifier; } return e; } void QtUtils::SetComboBoxData(QComboBox *cb, int data) { for (int i=0; icount(); i++) { if (cb->itemData(i).toInt() == data) { cb->setCurrentIndex(i); break; } } } void QtUtils::SetComboBoxData(QComboBox *cb, const QString &data) { for (int i=0; icount(); i++) { if (cb->itemData(i).toString() == data) { cb->setCurrentIndex(i); break; } } } QColor QtUtils::toQColor(const core::Color &i) { QColor c; // QColor only supports values from 0.0 to 1.0 and are only used for UI representations c.setRedF(std::clamp(i.red(), 0.0f, 1.0f)); c.setGreenF(std::clamp(i.green(), 0.0f, 1.0f)); c.setBlueF(std::clamp(i.blue(), 0.0f, 1.0f)); c.setAlphaF(std::clamp(i.alpha(), 0.0f, 1.0f)); return c; } namespace core { uint qHash(const core::rational &r, uint seed) { return ::qHash(r.toDouble(), seed); } uint qHash(const core::TimeRange &r, uint seed) { return qHash(r.in(), seed) ^ qHash(r.out(), seed); } } } ================================================ FILE: app/common/qtutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef QTVERSIONABSTRACTION_H #define QTVERSIONABSTRACTION_H #include #include #include #include #include #include #include namespace olive { class QtUtils { public: /** * @brief Retrieves the width of a string according to certain QFontMetrics * * QFontMetrics::width() has been deprecatd in favor of QFontMetrics::horizontalAdvance(), but the * latter was only introduced in 5.11+. This function wraps the latter for 5.11+ and the former for * earlier. */ static int QFontMetricsWidth(QFontMetrics fm, const QString& s); static QFrame* CreateHorizontalLine(); static QFrame* CreateVerticalLine(); static int MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok); static QDateTime GetCreationDate(const QFileInfo &info); static QString GetFormattedDateTime(const QDateTime &dt); static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); static Qt::KeyboardModifiers FlipControlAndShiftModifiers(Qt::KeyboardModifiers e); static void SetComboBoxData(QComboBox *cb, int data); static void SetComboBoxData(QComboBox *cb, const QString &data); template static T *GetParentOfType(const QObject *child) { QObject *t = child->parent(); while (t) { if (T *p = dynamic_cast(t)) { return p; } t = t->parent(); } return nullptr; } static QColor toQColor(const core::Color &c); /** * @brief Convert a pointer to a value that can be sent between NodeParams */ static QVariant PtrToValue(void* ptr) { return reinterpret_cast(ptr); } /** * @brief Convert a NodeParam value to a pointer of any kind */ template static T* ValueToPtr(const QVariant &ptr) { return reinterpret_cast(ptr.value()); } }; namespace core { uint qHash(const core::rational& r, uint seed = 0); uint qHash(const core::TimeRange& r, uint seed = 0); } } Q_DECLARE_METATYPE(olive::core::rational) Q_DECLARE_METATYPE(olive::core::Color) Q_DECLARE_METATYPE(olive::core::TimeRange) Q_DECLARE_METATYPE(olive::core::Bezier) Q_DECLARE_METATYPE(olive::core::AudioParams) Q_DECLARE_METATYPE(olive::core::SampleBuffer) #endif // QTVERSIONABSTRACTION_H ================================================ FILE: app/common/range.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef RANGE_H #define RANGE_H template bool InRange(T a, T b, T range) { return (a >= b - range && a <= b + range); } #endif // RANGE_H ================================================ FILE: app/common/ratiodialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "ratiodialog.h" #include #include #include namespace olive { double GetFloatRatioFromUser(QWidget* parent, const QString& title, bool* ok_in) { QString s; forever { bool ok; s = QInputDialog::getText(parent, title, QCoreApplication::translate("RatioDialog", "Enter custom ratio (e.g. \"4:3\", \"16/9\", etc.):"), QLineEdit::Normal, s, &ok); if (!ok) { // User cancelled dialog, do nothing if (ok_in) { *ok_in = false; } return qSNaN(); } QStringList ratio_components = s.split(QRegularExpression(QStringLiteral(":|;|\\/"))); if (ratio_components.size() == 1) { bool float_ok; double flt = ratio_components.at(0).toDouble(&float_ok); if (float_ok && flt > 0) { if (ok_in) { *ok_in = true; } return flt; } } else if (ratio_components.size() == 2) { bool numer_ok, denom_ok; double num = ratio_components.at(0).toDouble(&numer_ok); double den = ratio_components.at(1).toDouble(&denom_ok); if (numer_ok && denom_ok && num > 0 && den > 0) { // Exit loop and set this ratio if (ok_in) { *ok_in = true; } return num / den; } } QMessageBox::warning(parent, QCoreApplication::translate("RatioDialog", "Invalid custom ratio"), QCoreApplication::translate("RatioDialog", "Failed to parse \"%1\" into an aspect ratio. Please format a " "rational fraction with a ':' or a '/' separator.").arg(s), QMessageBox::Ok); } } } ================================================ FILE: app/common/ratiodialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef RATIODIALOG_H #define RATIODIALOG_H #include namespace olive { double GetFloatRatioFromUser(QWidget* parent, const QString& title, bool* ok_in); } #endif // RATIODIALOG_H ================================================ FILE: app/common/threadsafemap.h ================================================ #ifndef THREADSAFEMAP_H #define THREADSAFEMAP_H #include #include template class ThreadSafeMap { public: ThreadSafeMap() = default; void insert(K key, V value) { mutex_.lock(); map_.insert(key, value); mutex_.unlock(); } private: QMutex mutex_; QMap map_; }; #endif // THREADSAFEMAP_H ================================================ FILE: app/common/tohex.h ================================================ #ifndef TOHEX_H #define TOHEX_H #include #include #include "common/define.h" namespace olive { inline QString ToHex(quint64 t) { return QStringLiteral("%1").arg(t, 0, 16); } } #endif // TOHEX_H ================================================ FILE: app/common/util.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef UTIL_H #define UTIL_H template inline T mid(T a, T b) { return (a + b) * 0.5; } #endif // UTIL_H ================================================ FILE: app/common/xmlutils.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "xmlutils.h" #include "node/block/block.h" #include "node/factory.h" namespace olive { bool XMLReadNextStartElement(QXmlStreamReader *reader, CancelAtom *cancel_atom) { QXmlStreamReader::TokenType token; while ((token = reader->readNext()) != QXmlStreamReader::Invalid && token != QXmlStreamReader::EndDocument && (!cancel_atom || !cancel_atom->IsCancelled())) { if (reader->isEndElement()) { return false; } else if (reader->isStartElement()) { return true; } } return false; } } ================================================ FILE: app/common/xmlutils.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef XMLREADLOOP_H #define XMLREADLOOP_H #include #include "node/param.h" #include "render/cancelatom.h" #include "undo/undocommand.h" namespace olive { class Block; class Node; class NodeInput; class NodeGroup; #define XMLAttributeLoop(reader, item) \ foreach (const QXmlStreamAttribute& item, reader->attributes()) /** * @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document * * Since Qt's default function doesn't exit at the end of the document, it ends up consistently * throwing a "premature end of document" error. We have our own function here that does essentially * the same thing but fixes that issue. * * See also: https://stackoverflow.com/questions/46346450/qt-qxmlstreamreader-always-returns-premature-end-of-document-error */ bool XMLReadNextStartElement(QXmlStreamReader* reader, CancelAtom *cancel_atom = nullptr); } #endif // XMLREADLOOP_H ================================================ FILE: app/config/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} config/config.h config/config.cpp PARENT_SCOPE ) ================================================ FILE: app/config/config.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "config.h" #include #include #include #include #include #include #include "codec/exportformat.h" #include "common/autoscroll.h" #include "common/filefunctions.h" #include "common/xmlutils.h" #include "core.h" #include "timeline/timelinecommon.h" #include "ui/colorcoding.h" #include "ui/style/style.h" #include "window/mainwindow/mainwindow.h" namespace olive { Config Config::current_config_; Config::Config() { SetDefaults(); } void Config::SetEntryInternal(const QString &key, NodeValue::Type type, const QVariant &data) { config_map_[key] = {type, data}; } QString Config::GetConfigFilePath() { return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("config.xml")); } Config &Config::Current() { return current_config_; } void Config::SetDefaults() { config_map_.clear(); SetEntryInternal(QStringLiteral("Style"), NodeValue::kText, StyleManager::kDefaultStyle); SetEntryInternal(QStringLiteral("TimecodeDisplay"), NodeValue::kInt, Timecode::kTimecodeDropFrame); SetEntryInternal(QStringLiteral("DefaultStillLength"), NodeValue::kRational, QVariant::fromValue(rational(2))); SetEntryInternal(QStringLiteral("HoverFocus"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AudioScrubbing"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("AutorecoveryEnabled"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("AutorecoveryInterval"), NodeValue::kInt, 1); SetEntryInternal(QStringLiteral("AutorecoveryMaximum"), NodeValue::kInt, 20); SetEntryInternal(QStringLiteral("DiskCacheSaveInterval"), NodeValue::kInt, 10000); SetEntryInternal(QStringLiteral("Language"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("ScrollZooms"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("EnableSeekToImport"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("EditToolAlsoSeeks"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("EditToolSelectsLinks"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("EnableDragFilesToTimeline"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("InvertTimelineScrollAxes"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PasteSeeks"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("SeekAlsoSelects"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AutoSeekToBeginning"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("DropFileOnMediaToReplace"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AddDefaultEffectsToClips"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("AutoscaleByDefault"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("Autoscroll"), NodeValue::kInt, AutoScroll::kPage); SetEntryInternal(QStringLiteral("AutoSelectDivider"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeValue::kInt, ImportTool::kDWSAsk); SetEntryInternal(QStringLiteral("Loop"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("SplitClipsCopyNodes"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("UseGradients"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("AutoMergeTracks"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("UseSliderLadders"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("ShowWelcomeDialog"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("ShowClipWhileDragging"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailInOut); SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); SetEntryInternal(QStringLiteral("DefaultSubtitleSize"), NodeValue::kInt, 48); SetEntryInternal(QStringLiteral("DefaultSubtitleFamily"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("DefaultSubtitleWeight"), NodeValue::kInt, QFont::Bold); SetEntryInternal(QStringLiteral("AntialiasSubtitles"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed); SetEntryInternal(QStringLiteral("CatColor1"), NodeValue::kInt, ColorCoding::kMaroon); SetEntryInternal(QStringLiteral("CatColor2"), NodeValue::kInt, ColorCoding::kOrange); SetEntryInternal(QStringLiteral("CatColor3"), NodeValue::kInt, ColorCoding::kBrown); SetEntryInternal(QStringLiteral("CatColor4"), NodeValue::kInt, ColorCoding::kYellow); SetEntryInternal(QStringLiteral("CatColor5"), NodeValue::kInt, ColorCoding::kOlive); SetEntryInternal(QStringLiteral("CatColor6"), NodeValue::kInt, ColorCoding::kLime); SetEntryInternal(QStringLiteral("CatColor7"), NodeValue::kInt, ColorCoding::kGreen); SetEntryInternal(QStringLiteral("CatColor8"), NodeValue::kInt, ColorCoding::kCyan); SetEntryInternal(QStringLiteral("CatColor9"), NodeValue::kInt, ColorCoding::kTeal); SetEntryInternal(QStringLiteral("CatColor10"), NodeValue::kInt, ColorCoding::kBlue); SetEntryInternal(QStringLiteral("CatColor11"), NodeValue::kInt, ColorCoding::kNavy); SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); SetEntryInternal(QStringLiteral("AudioOutputSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV); SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM); SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("AudioRecordingChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); SetEntryInternal(QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); SetEntryInternal(QStringLiteral("AudioRecordingBitRate"), NodeValue::kInt, 320); SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0))); SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeValue::kRational, QVariant::fromValue(rational(60))); SetEntryInternal(QStringLiteral("DefaultSequenceWidth"), NodeValue::kInt, 1920); SetEntryInternal(QStringLiteral("DefaultSequenceHeight"), NodeValue::kInt, 1080); SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeValue::kRational, QVariant::fromValue(rational(1))); SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeValue::kRational, QVariant::fromValue(rational(1001, 30000))); SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeValue::kInt, VideoParams::kInterlaceNone); SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache2"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, PixelFormat::F32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, PixelFormat::F16); SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kLime); } void Config::Load() { QFile config_file(GetConfigFilePath()); if (!config_file.exists()) { return; } if (!config_file.open(QFile::ReadOnly)) { qWarning() << "Failed to load application settings. This session will use defaults."; return; } // Reset to defaults current_config_.SetDefaults(); QXmlStreamReader reader(&config_file); QString config_version; while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("Configuration")) { while (XMLReadNextStartElement(&reader)) { QString key = reader.name().toString(); QString value = reader.readElementText(); if (key == QStringLiteral("Version")) { config_version = value; if (!value.contains(".")) { qDebug() << "CONFIG: This is a 0.1.x config file, upconvert"; } } else if (key == QStringLiteral("DefaultSequenceFrameRate") && !config_version.contains('.')) { // 0.1.x stored this value as a float while we now use rationals, we'll use a heuristic to find the closest // supported rational qDebug() << " CONFIG: Finding closest match to" << value; double config_fr = value.toDouble(); const QVector& supported_frame_rates = VideoParams::kSupportedFrameRates; rational match = supported_frame_rates.first(); double match_diff = qAbs(match.toDouble() - config_fr); for (int i=1;imain_window(), QCoreApplication::translate("Config", "Error loading settings"), QCoreApplication::translate("Config", "Failed to load application settings. This session will " "use defaults.\n\n%1").arg(reader.errorString()), QMessageBox::Ok); current_config_.SetDefaults(); } config_file.close(); } void Config::Save() { QString real_filename = GetConfigFilePath(); QString temp_filename = FileFunctions::GetSafeTemporaryFilename(real_filename); QFile config_file(temp_filename); if (!config_file.open(QFile::WriteOnly)) { QMessageBox::critical(Core::instance()->main_window(), QCoreApplication::translate("Config", "Error saving settings"), QCoreApplication::translate("Config", "Failed to save application settings. The application " "may lack write permissions for this location."), QMessageBox::Ok); return; } QXmlStreamWriter writer(&config_file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("Configuration"); // Anything after the hyphen is considered "unimportant" information writer.writeTextElement("Version", QCoreApplication::applicationVersion().split('-').first()); QMapIterator iterator(current_config_.config_map_); while (iterator.hasNext()) { iterator.next(); QString value = NodeValue::ValueToString(iterator.value().type, iterator.value().data, false); if (iterator.value().type == NodeValue::kNone) { qWarning() << "Config key" << iterator.key() << "had null type and was discarded"; } else { writer.writeTextElement(iterator.key(), value); } } writer.writeEndElement(); // Configuration writer.writeEndDocument(); config_file.close(); if (!FileFunctions::RenameFileAllowOverwrite(temp_filename, real_filename)) { qWarning() << QStringLiteral("Failed to overwrite \"%1\". Config has been saved as \"%2\" instead.") .arg(real_filename, temp_filename); } } QVariant Config::operator[](const QString &key) const { return config_map_[key].data; } QVariant &Config::operator[](const QString &key) { return config_map_[key].data; } NodeValue::Type Config::GetConfigEntryType(const QString &key) const { return config_map_[key].type; } } ================================================ FILE: app/config/config.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CONFIG_H #define CONFIG_H #include #include #include #include "node/value.h" namespace olive { #define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)] #define OLIVE_CONFIG_STR(x) Config::Current()[x] class Config { public: static Config& Current(); void SetDefaults(); static void Load(); static void Save(); QVariant operator[](const QString&) const; QVariant& operator[](const QString&); NodeValue::Type GetConfigEntryType(const QString& key) const; private: Config(); struct ConfigEntry { NodeValue::Type type; QVariant data; }; void SetEntryInternal(const QString& key, NodeValue::Type type, const QVariant& data); QMap config_map_; static Config current_config_; static QString GetConfigFilePath(); }; } #endif // CONFIG_H ================================================ FILE: app/core.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "core.h" #include #include #include #include #include #include #include #include #include #include "window/mainwindow/mainwindowundo.h" #ifdef Q_OS_WINDOWS #include #endif #include "audio/audiomanager.h" #include "cli/clitask/clitaskdialog.h" #include "codec/conformmanager.h" #include "common/filefunctions.h" #include "common/xmlutils.h" #include "config/config.h" #include "dialog/about/about.h" #include "dialog/autorecovery/autorecoverydialog.h" #include "dialog/export/export.h" #include "dialog/footagerelink/footagerelinkdialog.h" #ifdef USE_OTIO #include "dialog/otioproperties/otiopropertiesdialog.h" #endif #include "dialog/projectproperties/projectproperties.h" #include "dialog/sequence/sequence.h" #include "dialog/task/task.h" #include "dialog/preferences/preferences.h" #include "node/color/colormanager/colormanager.h" #include "node/factory.h" #include "node/nodeundo.h" #include "node/project/serializer/serializer.h" #include "panel/panelmanager.h" #include "panel/project/project.h" #include "panel/viewer/viewer.h" #include "render/diskmanager.h" #include "render/framemanager.h" #include "render/rendermanager.h" #ifdef USE_OTIO #include "task/project/loadotio/loadotio.h" #include "task/project/saveotio/saveotio.h" #endif #include "task/project/import/import.h" #include "task/project/import/importerrordialog.h" #include "task/project/load/load.h" #include "task/project/save/save.h" #include "task/taskmanager.h" #include "ui/style/style.h" #include "undo/undostack.h" #include "widget/menu/menushared.h" #include "widget/taskview/taskviewitem.h" #include "widget/viewer/viewer.h" #include "window/mainwindow/mainstatusbar.h" #include "window/mainwindow/mainwindow.h" namespace olive { Core* Core::instance_ = nullptr; Core::Core(const CoreParams& params) : main_window_(nullptr), open_project_(nullptr), tool_(Tool::kPointer), addable_object_(Tool::kAddableEmpty), snapping_(true), core_params_(params), magic_(false), pixel_sampling_users_(0), shown_cache_full_warning_(false) { // Store reference to this object, making the assumption that Core will only ever be made in // main(). This will obviously break if not. instance_ = this; translator_ = new QTranslator(this); } Core *Core::instance() { return instance_; } void Core::DeclareTypesForQt() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void Core::Start() { // Load application config Config::Load(); // Set locale based on either startup arg, config, or auto-detect SetStartupLocale(); // Declare custom types for Qt signal/slot system DeclareTypesForQt(); // Set up node factory/library NodeFactory::Initialize(); // Set up color manager's default config ColorManager::SetUpDefaultConfig(); // Initialize task manager TaskManager::CreateInstance(); // Initialize ConformManager ConformManager::CreateInstance(); // Initialize RenderManager RenderManager::CreateInstance(); // Initialize FrameManager FrameManager::CreateInstance(); // Initialize project serializers ProjectSerializer::Initialize(); // // Start application // qInfo() << "Using Qt version:" << qVersion(); switch (core_params_.run_mode()) { case CoreParams::kRunNormal: // Start GUI StartGUI(core_params_.fullscreen()); // If we have a startup QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection); break; case CoreParams::kHeadlessExport: qInfo() << "Headless export is not fully implemented yet"; break; case CoreParams::kHeadlessPreCache: qInfo() << "Headless pre-cache is not fully implemented yet"; break; } // Manual crash triggering if (core_params_.crash_on_startup()) { const int interval = 5000; qInfo() << "Manual crash was triggered. Application will crash in" << interval << "ms"; QTimer *crash_timer = new QTimer(this); crash_timer->setInterval(interval); connect(crash_timer, &QTimer::timeout, this, []{ abort(); }); crash_timer->start(); } } void Core::Stop() { // Assume all projects have closed gracefully and no auto-recovery is necessary autorecovered_projects_.clear(); SaveUnrecoveredList(); // Save Config Config::Save(); ProjectSerializer::Destroy(); ConformManager::DestroyInstance(); FrameManager::DestroyInstance(); RenderManager::DestroyInstance(); MenuShared::DestroyInstance(); TaskManager::DestroyInstance(); PanelManager::DestroyInstance(); AudioManager::DestroyInstance(); DiskManager::DestroyInstance(); NodeFactory::Destroy(); delete main_window_; main_window_ = nullptr; } MainWindow *Core::main_window() { return main_window_; } UndoStack *Core::undo_stack() { return &undo_stack_; } void Core::ImportFiles(const QStringList &urls, Folder* parent) { if (urls.isEmpty()) { QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import")); return; } ProjectImportTask* pim = new ProjectImportTask(parent, urls); if (!pim->GetFileCount()) { // No files to import delete pim; return; } TaskDialog* task_dialog = new TaskDialog(pim, tr("Importing..."), main_window()); connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::ImportTaskComplete); task_dialog->open(); } const Tool::Item &Core::tool() const { return tool_; } const Tool::AddableObject &Core::GetSelectedAddableObject() const { return addable_object_; } const QString &Core::GetSelectedTransition() const { return selected_transition_; } void Core::SetSelectedAddableObject(const Tool::AddableObject &obj) { addable_object_ = obj; emit AddableObjectChanged(addable_object_); } void Core::SetSelectedTransitionObject(const QString &obj) { selected_transition_ = obj; } void Core::ClearOpenRecentList() { recent_projects_.clear(); SaveRecentProjectsList(); emit OpenRecentListChanged(); } void Core::CreateNewProject() { // If we already have an empty/new project, switch to it if (CloseProject(false)) { Project *p = new Project(); p->Initialize(); AddOpenProject(p); } } const bool &Core::snapping() const { return snapping_; } const QStringList &Core::GetRecentProjects() const { return recent_projects_; } void Core::SetTool(const Tool::Item &tool) { tool_ = tool; emit ToolChanged(tool_); } void Core::SetSnapping(const bool &b) { snapping_ = b; emit SnappingChanged(snapping_); } void Core::DialogAboutShow() { AboutDialog a(false, main_window_); a.exec(); } void Core::DialogImportShow() { // Open dialog for user to select files QStringList files = QFileDialog::getOpenFileNames(main_window_, tr("Import footage...")); // Check if the user actually selected files to import if (!files.isEmpty()) { // Locate the most recently focused Project panel (assume that's the panel the user wants to import into) ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); Project* active_project; if (active_project_panel == nullptr // Check that we found a Project panel || (active_project = active_project_panel->project()) == nullptr) { // and that we could find an active Project QMessageBox::critical(main_window_, tr("Failed to import footage"), tr("Failed to find active Project panel")); return; } // Get the selected folder in this panel Folder* folder = active_project_panel->GetSelectedFolder(); ImportFiles(files, folder); } } void Core::DialogPreferencesShow() { PreferencesDialog pd(main_window_); pd.exec(); } void Core::DialogProjectPropertiesShow() { Project *proj = GetActiveProject(); if (proj) { ProjectPropertiesDialog ppd(proj, main_window_); ppd.exec(); } else { QMessageBox::critical(main_window_, tr("No Active Project"), tr("No project is currently open to set the properties for"), QMessageBox::Ok); } } void Core::DialogExportShow() { if (ViewerOutput* viewer = GetSequenceToExport()) { OpenExportDialogForViewer(viewer, false); } } #ifdef USE_OTIO bool Core::DialogImportOTIOShow(const QList& sequences) { Project* active_project = GetActiveProject(); OTIOPropertiesDialog opd(sequences, active_project); return opd.exec() == QDialog::Accepted; } #endif void Core::CreateNewFolder() { // Locate the most recently focused Project panel (assume that's the panel the user wants to import into) ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); Project* active_project; if (active_project_panel == nullptr // Check that we found a Project panel || (active_project = active_project_panel->project()) == nullptr) { // and that we could find an active Project QMessageBox::critical(main_window_, tr("Failed to create new folder"), tr("Failed to find active project")); return; } // Get the selected folder in this panel Folder* folder = active_project_panel->GetSelectedFolder(); // Create new folder Folder* new_folder = new Folder(); // Set a default name new_folder->SetLabel(tr("New Folder")); // Create an undoable command MultiUndoCommand* command = new MultiUndoCommand(); command->add_child(new NodeAddCommand(active_project, new_folder)); command->add_child(new FolderAddChild(folder, new_folder)); Core::instance()->undo_stack()->push(command, tr("Created New Folder")); // Trigger an automatic rename so users can enter the folder name active_project_panel->Edit(new_folder); } void Core::CreateNewSequence() { Project* active_project = GetActiveProject(); if (!active_project) { QMessageBox::critical(main_window_, tr("Failed to create new sequence"), tr("Failed to find active project")); return; } // Create new sequence Sequence* new_sequence = CreateNewSequenceForProject(active_project); SequenceDialog sd(new_sequence, SequenceDialog::kNew, main_window_); // Make sure SequenceDialog doesn't make an undo command for editing the sequence, since we make an undo command for // adding it later on sd.SetUndoable(false); if (sd.exec() == QDialog::Accepted) { // Create an undoable command MultiUndoCommand* command = new MultiUndoCommand(); command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position())); command->add_child(new OpenSequenceCommand(new_sequence)); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); Core::instance()->undo_stack()->push(command, tr("Created New Sequence")); } else { // If the dialog was accepted, ownership goes to the AddItemCommand. But if we get here, just delete delete new_sequence; } } void Core::AddOpenProject(Project* p, bool add_to_recents) { // Ensure project is not open at the moment if (open_project_ == p) { return; } // If we currently have an empty project, close it first if (open_project_) { CloseProject(false); } SetActiveProject(p); if (!p->filename().isEmpty() && add_to_recents) { PushRecentlyOpenedProject(p->filename()); } } bool Core::AddOpenProjectFromTask(Task *task, bool add_to_recents) { ProjectLoadBaseTask* load_task = static_cast(task); if (!load_task->IsCancelled()) { Project* project = load_task->GetLoadedProject(); if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) { AddOpenProject(project, add_to_recents); main_window_->LoadLayout(load_task->GetLoadedLayout()); return true; } else { delete project; CreateNewProject(); } } return false; } void Core::SetActiveProject(Project *p) { if (open_project_) { disconnect(open_project_, &Project::ModifiedChanged, this, &Core::ProjectWasModified); } open_project_ = p; RenderManager::instance()->SetProject(p); main_window_->SetProject(p); if (open_project_) { connect(open_project_, &Project::ModifiedChanged, this, &Core::ProjectWasModified); } } void Core::ImportTaskComplete(Task* task) { ProjectImportTask* import_task = static_cast(task); MultiUndoCommand *command = import_task->GetCommand(); foreach (Footage *f, import_task->GetImportedFootage()) { // Look for multi-layer images if (f->GetAudioStreamCount() == 0 && f->GetVideoStreamCount() > 1) { bool all_stills = true; for (int i=0; iGetVideoStreamCount(); i++) { const VideoParams &vs = f->GetVideoParams(i); if (!(vs.video_type() == VideoParams::kVideoTypeStill && vs.enabled() == (i == 0))) { all_stills = false; } } if (all_stills) { QMessageBox d(main_window()); d.setIcon(QMessageBox::Question); d.setWindowTitle(tr("Multi-Layer Image")); d.setText(tr("The file '%1' has multiple layers. Would you like these layers to be " "separated across multiple tracks or merged into a single image?").arg(f->filename())); auto multi_btn = d.addButton(tr("Multiple Layers"), QMessageBox::YesRole); auto single_btn = d.addButton(tr("Single Layer"), QMessageBox::NoRole); auto cancel_btn = d.addButton(QMessageBox::Cancel); d.exec(); if (d.clickedButton() == multi_btn) { for (int i=0; iGetVideoStreamCount(); i++) { VideoParams vs = f->GetVideoParams(i); vs.set_enabled(!vs.enabled()); f->SetVideoParams(vs, i); } } else if (d.clickedButton() == single_btn) { // Do nothing, footage will already be set up this way } else if (d.clickedButton() == cancel_btn) { // Cancel import delete command; return; } } } } if (import_task->HasInvalidFiles()) { ProjectImportErrorDialog d(import_task->GetInvalidFiles(), main_window_); d.exec(); } undo_stack_.push(command, tr("Imported %1 File(s)").arg(import_task->GetImportedFootage().size())); main_window_->SelectFootage(import_task->GetImportedFootage()); } bool Core::ConfirmImageSequence(const QString& filename) { QMessageBox mb(main_window_); mb.setIcon(QMessageBox::Question); mb.setWindowTitle(tr("Possible image sequence detected")); mb.setText(tr("The file '%1' looks like it might be part of an image " "sequence. Would you like to import it as such?").arg(filename)); mb.addButton(QMessageBox::Yes); mb.addButton(QMessageBox::No); return (mb.exec() == QMessageBox::Yes); } void Core::ProjectWasModified(bool e) { main_window_->setWindowModified(e); } bool Core::StartHeadlessExport() { const QString& startup_project = core_params_.startup_project(); if (startup_project.isEmpty()) { qCritical().noquote() << tr("You must specify a project file to export"); return false; } if (!QFileInfo::exists(startup_project)) { qCritical().noquote() << tr("Specified project does not exist"); return false; } // Start a load task and try running it ProjectLoadTask plm(startup_project); CLITaskDialog task_dialog(&plm); /* if (task_dialog.Run()) { std::unique_ptr p = std::unique_ptr(plm.GetLoadedProject()); QVector items = p->get_items_of_type(Item::kSequence); // Check if this project contains sequences if (items.isEmpty()) { qCritical().noquote() << tr("Project contains no sequences, nothing to export"); return false; } Sequence* sequence = nullptr; // Check if this project contains multiple sequences if (items.size() > 1) { qInfo().noquote() << tr("This project has multiple sequences. Which do you wish to export?"); for (int i=0;iGetLabel().toStdString(); } QTextStream stream(stdin); QString sequence_read; int sequence_index = -1; QString quit_code = QStringLiteral("q"); std::string prompt = tr("Enter number (or %1 to cancel): ").arg(quit_code).toStdString(); forever { std::cout << prompt; stream.readLineInto(&sequence_read); if (!QString::compare(sequence_read, quit_code, Qt::CaseInsensitive)) { return false; } bool ok; sequence_index = sequence_read.toInt(&ok); if (ok && sequence_index >= 0 && sequence_index < items.size()) { break; } else { qCritical().noquote() << tr("Invalid sequence number"); } } sequence = static_cast(items.at(sequence_index)); } else { sequence = static_cast(items.first()); } ExportParams params; ExportTask export_task(sequence->viewer_output(), p->color_manager(), params); CLITaskDialog export_dialog(&export_task); if (export_dialog.Run()) { qInfo().noquote() << tr("Export succeeded"); return true; } else { qInfo().noquote() << tr("Export failed: %1").arg(export_task.GetError()); return false; } } else { qCritical().noquote() << tr("Project failed to load: %1").arg(plm.GetError()); return false; } */ return false; } void Core::OpenStartupProject() { const QString& startup_project = core_params_.startup_project(); bool startup_project_exists = !startup_project.isEmpty() && QFileInfo::exists(startup_project); // Load startup project if (!startup_project_exists && !startup_project.isEmpty()) { QMessageBox::warning(main_window_, tr("Failed to open startup file"), tr("The project \"%1\" doesn't exist. " "A new project will be started instead.").arg(startup_project), QMessageBox::Ok); } if (startup_project_exists) { // If a startup project was set and exists, open it now OpenProjectInternal(startup_project); } else { // If no load project is set, create a new one on open CreateNewProject(); } } void Core::AddRecoveryProjectFromTask(Task *task) { if (AddOpenProjectFromTask(task, false)) { ProjectLoadBaseTask* load_task = static_cast(task); Project* project = load_task->GetLoadedProject(); // Clearing the filename will force the user to re-save it somewhere else project->set_filename(QString()); // Forcing a UUID regeneration will prevent it from saving auto-recoveries in the same place // the original project did project->RegenerateUuid(); // Setting modified will ensure that the program doesn't close and lose the project without // prompting the user first project->set_modified(true); } } void Core::StartGUI(bool full_screen) { // Set UI style StyleManager::Init(); // Set up shared menus MenuShared::CreateInstance(); // Since we're starting GUI mode, create a PanelFocusManager (auto-deletes with QObject) PanelManager::CreateInstance(); // Initialize audio service AudioManager::CreateInstance(); // Initialize disk service DiskManager::CreateInstance(); // Connect the PanelFocusManager to the application's focus change signal connect(qApp, &QApplication::focusChanged, PanelManager::instance(), &PanelManager::FocusChanged); // Set KDDockWidgets flags auto &config = KDDockWidgets::Config::self(); auto flags = config.flags(); flags |= KDDockWidgets::Config::Flag_TabsHaveCloseButton; flags |= KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible; flags |= KDDockWidgets::Config::Flag_AlwaysShowTabs; flags |= KDDockWidgets::Config::Flag_AllowReorderTabs; config.setFlags(flags); config.setAbsoluteWidgetMinSize(QSize(1, 1)); // Create main window and open it main_window_ = new MainWindow(); if (full_screen) { main_window_->showFullScreen(); } else { main_window_->showMaximized(); } #ifdef Q_OS_WINDOWS // Workaround for Qt bug where menus don't appear in full screen mode // See: https://doc.qt.io/qt-5/windows-issues.html QWindowsWindowFunctions::setHasBorderInFullScreen(main_window_->windowHandle(), true); #endif // Start autorecovery timer using the config value as its interval SetAutorecoveryInterval(OLIVE_CONFIG("AutorecoveryInterval").toInt()); connect(&autorecovery_timer_, &QTimer::timeout, this, &Core::SaveAutorecovery); autorecovery_timer_.start(); // Load recently opened projects list { QFile recent_projects_file(GetRecentProjectsFilePath()); if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) { QString r = QString::fromUtf8(recent_projects_file.readAll()); if (!r.isEmpty()) { recent_projects_ = r.split('\n'); } recent_projects_file.close(); } emit OpenRecentListChanged(); } } void Core::SaveProjectInternal(const QString& override_filename) { // Create save manager Task* psm; if (open_project_->filename().endsWith(QStringLiteral(".otio"), Qt::CaseInsensitive)) { #ifdef USE_OTIO psm = new SaveOTIOTask(open_project_); #else QMessageBox::critical(main_window_, tr("Missing OpenTimelineIO Libraries"), tr("This build was compiled without OpenTimelineIO and therefore " "cannot open OpenTimelineIO files.")); return; #endif } else { bool use_compression = !open_project_->filename().endsWith(QStringLiteral(".ovexml"), Qt::CaseInsensitive); psm = new ProjectSaveTask(open_project_, use_compression); static_cast(psm)->SetLayout(main_window_->SaveLayout()); if (!override_filename.isEmpty()) { // Set override filename if provided static_cast(psm)->SetOverrideFilename(override_filename); } } // We don't use a TaskDialog here because a model save dialog is annoying, particularly when // saving auto-recoveries that the user can't anticipate. Doing this in the main thread will // cause a brief (but often unnoticeable) pause in the GUI, which, while not ideal, is not that // different from what already happened (modal dialog preventing use of the GUI) and in many ways // less annoying (doesn't disrupt any current actions or pull focus from elsewhere). // // Ideally we could do this in a background thread and show progress in the status bar like // Microsoft Word, but that would be far more complex. If it becomes necessary in the future, // we will look into an approach like that. if (psm->Start()) { if (override_filename.isEmpty()) { ProjectSaveSucceeded(psm); } } psm->deleteLater(); } ViewerOutput *Core::GetSequenceToExport() { // First try the most recently focused time based window TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused(); // If that fails try defaulting to the first timeline (i.e. if a project has just been loaded). if (!time_panel->GetConnectedViewer()) { // Safe to assume there will always be one timeline. time_panel = PanelManager::instance()->GetPanelsOfType().first(); } if (time_panel && time_panel->GetConnectedViewer()) { if (time_panel->GetConnectedViewer()->GetLength() == 0) { QMessageBox::critical(main_window_, tr("Error"), tr("This Sequence is empty. There is nothing to export."), QMessageBox::Ok); } else { return time_panel->GetConnectedViewer(); } } else { QMessageBox::critical(main_window_, tr("Error"), tr("No valid sequence detected.\n\nMake sure a sequence is loaded and it has a connected Viewer node."), QMessageBox::Ok); } return nullptr; } QString Core::GetAutoRecoveryIndexFilename() { return QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(QStringLiteral("unrecovered")); } void Core::SaveUnrecoveredList() { QFile autorecovery_index(GetAutoRecoveryIndexFilename()); if (autorecovered_projects_.isEmpty()) { // Recovery list is empty, delete file if exists if (autorecovery_index.exists()) { autorecovery_index.remove(); } } else if (autorecovery_index.open(QFile::WriteOnly)) { // Overwrite recovery list with current list QTextStream ts(&autorecovery_index); bool first = true; foreach (const QUuid& uuid, autorecovered_projects_) { if (first) { first = false; } else { ts << QStringLiteral("\n"); } ts << uuid.toString(); } autorecovery_index.close(); } else { qWarning() << "Failed to save unrecovered list"; } } bool Core::RevertProjectInternal(bool by_opening_existing) { if (open_project_->filename().isEmpty()) { QMessageBox::critical(main_window_, tr("Revert"), tr("This project has not yet been saved, therefore there is no last saved state to revert to.")); } else { QString msg; if (by_opening_existing) { msg = tr("The project \"%1\" is already open. By re-opening it, the project will revert to " "its last saved state. Any unsaved changes will be lost. Do you wish to continue?").arg(open_project_->filename()); } else { msg = tr("This will revert the project \"%1\" back to its last saved state. " "All unsaved changes will be lost. Do you wish to continue?").arg(open_project_->name()); } if (QMessageBox::question(main_window_, tr("Revert"), msg, QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { // Copy filename because CloseProject is going to delete `p` QString filename = open_project_->filename(); // Close project without prompting to save it CloseProject(false, true); // NOTE: `open_project_` will be deleted now, so don't try accessing it // Re-open project at the filename OpenProjectInternal(filename); return true; } } return false; } void Core::SaveRecentProjectsList() { // Save recently opened projects QFile recent_projects_file(GetRecentProjectsFilePath()); if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { recent_projects_file.write(recent_projects_.join('\n').toUtf8()); recent_projects_file.close(); } } void Core::SaveAutorecovery() { if (OLIVE_CONFIG("AutorecoveryEnabled").toBool()) { if (open_project_ && !open_project_->has_autorecovery_been_saved()) { QDir project_autorecovery_dir(QDir(FileFunctions::GetAutoRecoveryRoot()).filePath(open_project_->GetUuid().toString())); if (FileFunctions::DirectoryIsValid(project_autorecovery_dir)) { QString this_autorecovery_path = project_autorecovery_dir.filePath(QStringLiteral("%1.ove").arg(QString::number(QDateTime::currentSecsSinceEpoch()))); SaveProjectInternal(this_autorecovery_path); open_project_->set_autorecovery_saved(true); // Keep track of projects that where the "newest" save is the recovery project if (!autorecovered_projects_.contains(open_project_->GetUuid())) { autorecovered_projects_.append(open_project_->GetUuid()); } qDebug() << "Saved auto-recovery to:" << this_autorecovery_path; // Write human-readable real name so it's not just a UUID { QFile realname_file(project_autorecovery_dir.filePath(QStringLiteral("realname.txt"))); realname_file.open(QFile::WriteOnly); realname_file.write(open_project_->pretty_filename().toUtf8()); realname_file.close(); } int64_t max_recoveries_per_file = OLIVE_CONFIG("AutorecoveryMaximum").toLongLong(); // Since we write an extra file, increment total allowed files by 1 max_recoveries_per_file++; // Delete old entries QStringList recovery_files = project_autorecovery_dir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); while (recovery_files.size() > max_recoveries_per_file) { bool deleted = false; for (int i=0; i(task)->GetProject(); PushRecentlyOpenedProject(p->filename()); p->set_modified(false); autorecovered_projects_.removeOne(p->GetUuid()); SaveUnrecoveredList(); ShowStatusBarMessage(tr("Saved to \"%1\" successfully").arg(p->filename())); } Project* Core::GetActiveProject() const { return open_project_; } Folder *Core::GetSelectedFolderInActiveProject() const { ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); if (active_project_panel) { return active_project_panel->GetSelectedFolder(); } else { return nullptr; } } Timecode::Display Core::GetTimecodeDisplay() const { return static_cast(OLIVE_CONFIG("TimecodeDisplay").toInt()); } void Core::SetTimecodeDisplay(Timecode::Display d) { OLIVE_CONFIG("TimecodeDisplay") = d; emit TimecodeDisplayChanged(d); } void Core::SetAutorecoveryInterval(int minutes) { // Convert minutes to milliseconds autorecovery_timer_.setInterval(minutes * 60000); } void Core::CopyStringToClipboard(const QString &s) { QGuiApplication::clipboard()->setText(s); } QString Core::PasteStringFromClipboard() { return QGuiApplication::clipboard()->text(); } QString Core::GetProjectFilter(bool include_any_filter) { static const QVector< QPair > FILTERS = { // Standard compressed Olive project {tr("Olive Project"), QStringLiteral("ove")}, // Uncompressed XML Olive project {tr("Olive Project (Uncompressed XML)"), QStringLiteral("ovexml")}, // OpenTimelineIO project, if available #ifdef USE_OTIO {tr("OpenTimelineIO"), QStringLiteral("otio")} #endif }; QStringList filters; filters.reserve(FILTERS.size() + 1); if (include_any_filter) { QStringList combined; for (auto it=FILTERS.cbegin(); it!=FILTERS.cend(); it++) { combined.append(QStringLiteral("*.%1").arg(it->second)); } filters.append(QStringLiteral("%1 (%2)").arg(tr("All Supported Projects"), combined.join(' '))); } for (auto it=FILTERS.cbegin(); it!=FILTERS.cend(); it++) { filters.append(QStringLiteral("%1 (*.%2)").arg(it->first, it->second)); } return filters.join(QStringLiteral(";;")); } QString Core::GetRecentProjectsFilePath() { return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("recent")); } void Core::SetStartupLocale() { // Set language if (!core_params_.startup_language().isEmpty()) { if (translator_->load(core_params_.startup_language()) && QApplication::installTranslator(translator_)) { return; } else { qWarning() << "Failed to load translation file. Falling back to defaults."; } } QString use_locale = OLIVE_CONFIG("Language").toString(); if (use_locale.isEmpty()) { // No configured locale, auto-detect the system's locale use_locale = QLocale::system().name(); } if (!SetLanguage(use_locale)) { qWarning() << "Trying to use locale" << use_locale << "but couldn't find a translation for it"; } } bool Core::SaveProject() { if (open_project_->filename().isEmpty()) { return SaveProjectAs(); } else { SaveProjectInternal(); return true; } } void Core::ShowStatusBarMessage(const QString &s, int timeout) { main_window_->statusBar()->showMessage(s, timeout); } void Core::ClearStatusBarMessage() { main_window_->statusBar()->clearMessage(); } void Core::OpenRecoveryProject(const QString &filename) { OpenProjectInternal(filename, true); } void Core::OpenNodeInViewer(ViewerOutput *viewer) { main_window_->OpenNodeInViewer(viewer); } void Core::OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image) { ExportDialog* ed = new ExportDialog(viewer, start_still_image, main_window_); connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); ed->open(); connect(ed, &ExportDialog::RequestImportFile, this, &Core::ImportSingleFile); } void Core::CheckForAutoRecoveries() { QFile autorecovery_index(GetAutoRecoveryIndexFilename()); if (autorecovery_index.exists()) { // Uh-oh, we have auto-recoveries to prompt if (autorecovery_index.open(QFile::ReadOnly)) { QStringList recovery_filenames = QString::fromUtf8(autorecovery_index.readAll()).split('\n'); AutoRecoveryDialog ard(tr("The following projects had unsaved changes when Olive " "forcefully quit. Would you like to load them?"), recovery_filenames, true, main_window_); ard.exec(); autorecovery_index.close(); // Delete recovery index since we don't need it anymore QFile::remove(GetAutoRecoveryIndexFilename()); } else { QMessageBox::critical(main_window_, tr("Auto-Recovery Error"), tr("Found auto-recoveries but failed to load the auto-recovery index. " "Auto-recover projects will have to be opened manually.\n\n" "Your recoverable projects are still available at: %1").arg(FileFunctions::GetAutoRecoveryRoot())); } } } void Core::BrowseAutoRecoveries() { // List all auto-recovery entries AutoRecoveryDialog ard(tr("The following project versions have been auto-saved:"), QDir(FileFunctions::GetAutoRecoveryRoot()).entryList(QDir::Dirs | QDir::NoDotAndDotDot), false, main_window_); ard.exec(); } void Core::RequestPixelSamplingInViewers(bool e) { if (e) { if (pixel_sampling_users_ == 0) { // Signal to start pixel sampling emit ColorPickerEnabled(true); } pixel_sampling_users_++; } else { pixel_sampling_users_--; if (pixel_sampling_users_ == 0) { // Signal to end pixel sampling emit ColorPickerEnabled(false); } } } void Core::WarnCacheFull() { if (!shown_cache_full_warning_ && main_window_) { shown_cache_full_warning_ = true; QMessageBox::warning(main_window_, tr("Disk Cache Full"), tr("The disk cache is currently full and Olive is having to delete old " "frames to keep it within the limits set in the Disk preferences. This " "will result in SIGNIFICANTLY reduced cache performance.\n\n" "To remedy this, please do one of the following:\n\n" "1. Manually clear the disk cache in Disk preferences.\n" "2. Increase the maximum disk cache size in Disk preferences.\n" "3. Reduce usage of the disk cache (e.g. disable auto-cache or only cache specific sections of your sequence).")); } } bool Core::SaveProjectAs() { QFileDialog fd(main_window_, tr("Save Project As")); fd.setAcceptMode(QFileDialog::AcceptSave); fd.setNameFilter(GetProjectFilter(false)); if (fd.exec() == QDialog::Accepted) { QString fn = fd.selectedFiles().first(); // Somewhat hacky method of extracting the extension from the name filter const QString& name_filter = fd.selectedNameFilter(); int ext_index = name_filter.indexOf(QStringLiteral("(*.")) + 3; QString extension = name_filter.mid(ext_index, name_filter.size() - ext_index - 1); fn = FileFunctions::EnsureFilenameExtension(fn, extension); open_project_->set_filename(fn); SaveProjectInternal(); return true; } return false; } void Core::RevertProject() { RevertProjectInternal(false); } void Core::PushRecentlyOpenedProject(const QString& s) { if (s.isEmpty()) { return; } int existing_index = recent_projects_.indexOf(s); if (existing_index >= 0) { recent_projects_.move(existing_index, 0); } else { recent_projects_.prepend(s); const int kMaximumRecentProjects = 10; while (recent_projects_.size() > kMaximumRecentProjects) { recent_projects_.removeLast(); } } SaveRecentProjectsList(); emit OpenRecentListChanged(); } void Core::OpenProjectInternal(const QString &filename, bool recovery_project) { if (open_project_) { // Comparing QFileInfos will handle case insensitivity and both slash directions on platforms // where this is necessary (not naming any names *cough* Windows) if (QFileInfo(open_project_->filename()) == QFileInfo(filename)) { // This project is already open bool reverted = RevertProjectInternal(true); if (!reverted) { // Calling this will focus attention to the project that the user just tried to re-open AddOpenProject(open_project_); } // Don't do anything else return; } } Task* load_task; if (filename.endsWith(QStringLiteral(".otio"), Qt::CaseInsensitive)) { // Load OpenTimelineIO project #ifdef USE_OTIO load_task = new LoadOTIOTask(filename); #else QMessageBox::critical(main_window_, tr("Missing OpenTimelineIO Libraries"), tr("This build was compiled without OpenTimelineIO and therefore " "cannot open OpenTimelineIO files.")); return; #endif } else { // Fallback to regular OVE project load_task = new ProjectLoadTask(filename); } TaskDialog* task_dialog = new TaskDialog(load_task, tr("Load Project"), main_window()); if (recovery_project) { connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddRecoveryProjectFromTask); } else { connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTaskAndAddToRecents); } task_dialog->open(); } void Core::ImportSingleFile(const QString &f) { if (Project *p = GetActiveProject()) { ImportFiles({f}, p->root()); } } int Core::CountFilesInFileList(const QFileInfoList &filenames) { int file_count = 0; foreach (const QFileInfo& f, filenames) { // For some reason QDir::NoDotAndDotDot doesn't work with entryInfoList, so we have to check manually if (f.fileName() == "." || f.fileName() == "..") { continue; } else if (f.isDir()) { QFileInfoList info_list = QDir(f.absoluteFilePath()).entryInfoList(); file_count += CountFilesInFileList(info_list); } else { file_count++; } } return file_count; } bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { return false; } bool ok; QString start_label = nodes.first()->GetLabel(); for (int i=1; iGetLabel() != start_label) { // Not all the nodes share the same name, so we'll start with a blank one start_label.clear(); break; } } QString s = QInputDialog::getText(main_window_, tr("Label Node"), tr("Set node label"), QLineEdit::Normal, start_label, &ok); if (ok) { NodeRenameCommand* rename_command = new NodeRenameCommand(); foreach (Node* n, nodes) { rename_command->AddNode(n, s); } if (parent) { parent->add_child(rename_command); } else { undo_stack_.push(rename_command, tr("Renamed %1 Node(s)").arg(nodes.size())); } return true; } return false; } Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project) { Sequence* new_sequence = new Sequence(); // Get default name for this sequence (in the format "Sequence N", the first that doesn't exist) int sequence_number = 1; QString sequence_name; do { sequence_name = format.arg(sequence_number); sequence_number++; } while (project->root()->ChildExistsWithName(sequence_name)); new_sequence->SetLabel(sequence_name); return new_sequence; } void Core::OpenProjectFromRecentList(int index) { const QString& open_fn = recent_projects_.at(index); if (QFileInfo::exists(open_fn)) { OpenProjectInternal(open_fn); } else if (QMessageBox::information(main_window(), tr("Cannot open recent project"), tr("The project \"%1\" doesn't exist. Would you like to remove this file from the recent list?").arg(open_fn), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { recent_projects_.removeAt(index); SaveRecentProjectsList(); emit OpenRecentListChanged(); } } bool Core::CloseProject(bool auto_open_new, bool ignore_modified) { if (open_project_) { if (open_project_->is_modified() && !ignore_modified) { QMessageBox mb(main_window_); mb.setWindowModality(Qt::WindowModal); mb.setIcon(QMessageBox::Question); mb.setWindowTitle(tr("Unsaved Changes")); mb.setText(tr("The project '%1' has unsaved changes. Would you like to save them?") .arg(open_project_->name())); QPushButton* yes_btn = mb.addButton(tr("Save"), QMessageBox::YesRole); mb.addButton(tr("Don't Save"), QMessageBox::NoRole); QPushButton* cancel_btn = mb.addButton(QMessageBox::Cancel); mb.exec(); if (mb.clickedButton() == cancel_btn) { // Stop closing projects if the user clicked cancel return false; } if (mb.clickedButton() == yes_btn && !SaveProject()) { // The save failed, stop closing projects return false; } } // For safety, the undo stack is cleared so no commands try to affect a freed project undo_stack_.clear(); Project *tmp = open_project_; SetActiveProject(nullptr); delete tmp; } // Ensure a project is always active if (auto_open_new) { CreateNewProject(); } return true; } void Core::CacheActiveSequence(bool in_out_only) { TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused(); if (p && p->GetConnectedViewer()) { // Hacky but works for now // Find Viewer attached to this TimeBasedPanel QList all_viewers = PanelManager::instance()->GetPanelsOfType(); ViewerPanel* found_panel = nullptr; foreach (ViewerPanel* viewer, all_viewers) { if (viewer->GetConnectedViewer() == p->GetConnectedViewer()) { found_panel = viewer; break; } } if (found_panel) { if (in_out_only) { found_panel->CacheSequenceInOut(); } else { found_panel->CacheEntireSequence(); } } else { QMessageBox::critical(main_window_, tr("Failed to cache sequence"), tr("No active viewer found with this sequence."), QMessageBox::Ok); } } } QString StripWindowsDriveLetter(QString s) { // HACK: On Windows, absolute paths are saved with a drive letter (e.g. "C:\video.mp4"). Below, // we use Qt's relative path system to resolve when an entire project may be in a different // folder, but the files are all in the same place relatively to the project. Unfortunately, // Qt chooses not to understand paths from Windows on non-Windows platforms, which causes // this to break when a project is moving from Windows to non-Windows. To resolve that, if // we're on a non-Windows platform and we detect a Windows path (i.e. a path with a drive // letter at the start), we strip it off. We also convert any back-slashes to forward-slashes // because on Windows they are interchangeable and on non-Windows they are not. #ifndef Q_OS_WINDOWS if (s.size() >= 2) { if (s.at(0).isLetter() && s.at(1) == ':') { s = s.mid(2); s.replace('\\', '/'); } } #endif return s; } bool Core::ValidateFootageInLoadedProject(Project* project, const QString& project_saved_url) { QVector footage_we_couldnt_validate; for (Node *n : project->nodes()) { if (Footage *footage = dynamic_cast(n)) { QString footage_fn = StripWindowsDriveLetter(footage->filename()); QString project_fn = StripWindowsDriveLetter(project_saved_url); if (!QFileInfo::exists(footage_fn) && !project_saved_url.isEmpty()) { // If the footage doesn't exist, it might have moved with the project const QString& project_current_url = project->filename(); if (project_current_url != project_fn) { // Project has definitely moved, try to resolve relative paths QDir saved_dir(QFileInfo(project_fn).dir()); QDir true_dir(QFileInfo(project_current_url).dir()); QString relative_filename = saved_dir.relativeFilePath(footage_fn); QString transformed_abs_filename = true_dir.filePath(relative_filename); if (QFileInfo::exists(transformed_abs_filename)) { // Use this file instead qInfo() << "Resolved" << footage_fn << "relatively to" << transformed_abs_filename; footage->set_filename(transformed_abs_filename); } } } if (QFileInfo::exists(footage->filename())) { // Assume valid footage->SetValid(); } else { footage_we_couldnt_validate.append(footage); } } } if (!footage_we_couldnt_validate.isEmpty()) { FootageRelinkDialog frd(footage_we_couldnt_validate, main_window_); if (frd.exec() == QDialog::Rejected) { return false; } } return true; } bool Core::SetLanguage(const QString &locale) { QApplication::removeTranslator(translator_); QString resource_path = QStringLiteral(":/ts/%1").arg(locale); if (translator_->load(resource_path) && QApplication::installTranslator(translator_)) { return true; } return false; } void Core::OpenProject() { QString file = QFileDialog::getOpenFileName(main_window_, tr("Open Project"), QString(), GetProjectFilter(true)); if (!file.isEmpty()) { OpenProjectInternal(file); } } Core::CoreParams::CoreParams() : mode_(kRunNormal), run_fullscreen_(false), crash_(false) { } } ================================================ FILE: app/core.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CORE_H #define CORE_H #include #include #include #include #include #include "node/project/footage/footage.h" #include "node/project.h" #include "node/project/sequence/sequence.h" #include "task/task.h" #include "tool/tool.h" #include "undo/undostack.h" #include "widget/projectexplorer/projectviewmodel.h" namespace olive { class MainWindow; /** * @brief The main central Olive application instance * * This runs both in GUI and CLI modes (and handles what to init based on that). * It also contains various global functions/variables for use throughout Olive. * * The "public slots" are usually user-triggered actions and can be connected to UI elements (e.g. creating a folder, * opening the import dialog, etc.) */ class Core : public QObject { Q_OBJECT public: class CoreParams { public: CoreParams(); enum RunMode { kRunNormal, kHeadlessExport, kHeadlessPreCache }; bool fullscreen() const { return run_fullscreen_; } void set_fullscreen(bool e) { run_fullscreen_ = e; } RunMode run_mode() const { return mode_; } void set_run_mode(RunMode m) { mode_ = m; } const QString startup_project() const { return startup_project_; } void set_startup_project(const QString& p) { startup_project_ = p; } const QString& startup_language() const { return startup_language_; } void set_startup_language(const QString& s) { startup_language_ = s; } bool crash_on_startup() const { return crash_; } void set_crash_on_startup(bool e) { crash_ = true; } private: RunMode mode_; QString startup_project_; QString startup_language_; bool run_fullscreen_; bool crash_; }; /** * @brief Core Constructor * * Currently empty */ Core(const CoreParams& params); /** * @brief Core object accessible from anywhere in the code * * Use this to access Core functions. */ static Core* instance(); const CoreParams& core_params() const { return core_params_; } /** * @brief Start Olive Core * * Main application launcher. Parses command line arguments and constructs main window (if entering a GUI mode). */ void Start(); /** * @brief Stop Olive Core * * Ends all threads and frees all memory ready for the application to exit. */ void Stop(); /** * @brief Retrieve main window instance * * @return * * Pointer to the olive::MainWindow object, or nullptr if running in CLI mode. */ MainWindow* main_window(); /** * @brief Retrieve UndoStack object */ UndoStack* undo_stack(); /** * @brief Import a list of files * * FIXME: I kind of hate this, it needs a model to update correctly. Is there a way that Items can signal enough to * make passing references to the model unnecessary? * * @param urls */ void ImportFiles(const QStringList& urls, Folder *parent); /** * @brief Get the currently active tool */ const Tool::Item& tool() const; /** * @brief Get the currently selected object that the add tool should make (if the add tool is active) */ const Tool::AddableObject& GetSelectedAddableObject() const; /** * @brief Get the currently selected node that the transition tool should make (if the transition tool is active) */ const QString& GetSelectedTransition() const; /** * @brief Get current snapping value */ const bool& snapping() const; /** * @brief Returns a list of the most recently opened/saved projects */ const QStringList& GetRecentProjects() const; /** * @brief Get the currently active project * * Uses the UI/Panel system to determine which Project was the last focused on and assumes this is the active Project * that the user wishes to work on. * * @return * * The active Project file, or nullptr if the heuristic couldn't find one. */ Project* GetActiveProject() const; Folder* GetSelectedFolderInActiveProject() const; /** * @brief Gets current timecode display mode */ Timecode::Display GetTimecodeDisplay() const; /** * @brief Sets current timecode display mode */ void SetTimecodeDisplay(Timecode::Display d); /** * @brief Set how frequently an autorecovery should be saved (if the project has changed, see SetProjectModified()) */ void SetAutorecoveryInterval(int minutes); static void CopyStringToClipboard(const QString& s); static QString PasteStringFromClipboard(); /** * @brief Recursively count files in a file/directory list */ static int CountFilesInFileList(const QFileInfoList &filenames); /** * @brief Show a dialog to the user to rename a set of nodes */ bool LabelNodes(const QVector &nodes, MultiUndoCommand *parent = nullptr); /** * @brief Create a new sequence named appropriately for the active project */ static Sequence* CreateNewSequenceForProject(const QString &format, Project *project); static Sequence* CreateNewSequenceForProject(Project *project) { return CreateNewSequenceForProject(tr("Sequence %1"), project); } /** * @brief Opens a project from the recently opened list */ void OpenProjectFromRecentList(int index); /** * @brief Closes a project */ bool CloseProject(bool auto_open_new, bool ignore_modified = false); /** * @brief Runs a modal cache task on the currently active sequence */ void CacheActiveSequence(bool in_out_only); /** * @brief Check each footage object for whether it still exists or has changed */ bool ValidateFootageInLoadedProject(Project* project, const QString &project_saved_url); /** * @brief Changes the current language */ bool SetLanguage(const QString& locale); /** * @brief Show message in main window's status bar * * Shorthand for Core::instance()->main_window()->statusBar()->showMessage(); */ void ShowStatusBarMessage(const QString& s, int timeout = 0); void ClearStatusBarMessage(); void OpenRecoveryProject(const QString& filename); void OpenNodeInViewer(ViewerOutput* viewer); void OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image); bool IsMagicEnabled() const { return magic_; } public slots: /** * @brief Starts an open file dialog to load a project from file */ void OpenProject(); /** * @brief Saves the current project */ bool SaveProject(); /** * @brief Performs a "save as" on the current project */ bool SaveProjectAs(); void RevertProject(); /** * @brief Set the current application-wide tool * * @param tool */ void SetTool(const Tool::Item& tool); /** * @brief Set the current snapping setting */ void SetSnapping(const bool& b); /** * @brief Show an About dialog */ void DialogAboutShow(); /** * @brief Open the import footage dialog and import the files selected (runs ImportFiles()) */ void DialogImportShow(); /** * @brief Show Preferences dialog */ void DialogPreferencesShow(); /** * @brief Show Project Properties dialog */ void DialogProjectPropertiesShow(); /** * @brief Show Export dialog */ void DialogExportShow(); /** * @brief Show OTIO import dialog */ #ifdef USE_OTIO bool DialogImportOTIOShow(const QList& sequences); #endif /** * @brief Create a new folder in the currently active project */ void CreateNewFolder(); /** * @brief Create a new sequence in the currently active project */ void CreateNewSequence(); /** * @brief Set the currently selected object that the add tool should make */ void SetSelectedAddableObject(const Tool::AddableObject& obj); /** * @brief Set the currently selected object that the add tool should make */ void SetSelectedTransitionObject(const QString& obj); /** * @brief Clears the list of recently opened/saved projects */ void ClearOpenRecentList(); /** * @brief Creates a new empty project and opens it */ void CreateNewProject(); void CheckForAutoRecoveries(); void BrowseAutoRecoveries(); void RequestPixelSamplingInViewers(bool e); void WarnCacheFull(); void SetMagic(bool e) { magic_ = e; } signals: /** * @brief Signal emitted when the tool is changed from somewhere */ void ToolChanged(const Tool::Item& tool); /** * @brief Signal emitted when addable object changes through SetSelectedAddableObject */ void AddableObjectChanged(Tool::AddableObject o); /** * @brief Signal emitted when the snapping setting is changed */ void SnappingChanged(const bool& b); /** * @brief Signal emitted when the default timecode display mode changed */ void TimecodeDisplayChanged(Timecode::Display d); /** * @brief Signal emitted when a change is made to the open recent list */ void OpenRecentListChanged(); /** * @brief Enable mouse color sampling functionality on all viewers * * This can be slow, so we only turn it on when we need it. */ void ColorPickerEnabled(bool e); /** * @brief A viewer with color picked enabled has emitted a color */ void ColorPickerColorEmitted(const Color &reference, const Color &display); private: /** * @brief Get the file filter than can be used with QFileDialog to open and save compatible projects */ static QString GetProjectFilter(bool include_any_filter); /** * @brief Returns the filename where the recently opened/saved projects should be stored */ static QString GetRecentProjectsFilePath(); /** * @brief Called only on startup to set the locale */ void SetStartupLocale(); /** * @brief Adds a filename to the top of the recently opened projects list (or moves it if it already exists) */ void PushRecentlyOpenedProject(const QString &s); /** * @brief Declare custom types/classes for Qt's signal/slot system * * Qt's signal/slot system requires types to be declared. In the interest of doing this only at startup, we contain * them all in a function here. */ void DeclareTypesForQt(); /** * @brief Start GUI portion of Olive * * Starts services and objects required for the GUI of Olive. It's guaranteed that running without this function will * create an application instance that is completely valid minus the UI (e.g. for CLI modes). */ void StartGUI(bool full_screen); /** * @brief Internal function for saving a project to a file */ void SaveProjectInternal(const QString &override_filename = QString()); /** * @brief Retrieves the currently most active sequence for exporting */ ViewerOutput *GetSequenceToExport(); static QString GetAutoRecoveryIndexFilename(); void SaveUnrecoveredList(); bool RevertProjectInternal(bool by_opening_existing); void SaveRecentProjectsList(); /** * @brief Adds a project to the "open projects" list */ void AddOpenProject(olive::Project* p, bool add_to_recents = false); bool AddOpenProjectFromTask(Task* task, bool add_to_recents); void SetActiveProject(Project *p); /** * @brief Internal main window object */ MainWindow* main_window_; /** * @brief List of currently open projects */ Project *open_project_; /** * @brief Currently active tool */ Tool::Item tool_; /** * @brief Currently active addable object */ Tool::AddableObject addable_object_; /** * @brief Currently selected transition */ QString selected_transition_; /** * @brief Current snapping setting */ bool snapping_; /** * @brief Internal timer for saving autorecovery files */ QTimer autorecovery_timer_; /** * @brief Application-wide undo stack instance */ UndoStack undo_stack_; /** * @brief List of most recently opened/saved projects */ QStringList recent_projects_; /** * @brief Parameters set up in main() determining how the program should run */ CoreParams core_params_; /** * @brief Static singleton core instance */ static Core* instance_; /** * @brief Internal translator */ QTranslator* translator_; /** * @brief List of projects that are unsaved but have autorecovery projects */ QVector autorecovered_projects_; /** * @brief Do something debug related */ bool magic_; /** * @brief How many widgets currently need pixel sampling access */ int pixel_sampling_users_; bool shown_cache_full_warning_; private slots: void SaveAutorecovery(); void ProjectSaveSucceeded(Task *task); bool AddOpenProjectFromTaskAndAddToRecents(Task* task) { return AddOpenProjectFromTask(task, true); } void ImportTaskComplete(Task *task); bool ConfirmImageSequence(const QString &filename); void ProjectWasModified(bool e); bool StartHeadlessExport(); void OpenStartupProject(); void AddRecoveryProjectFromTask(Task* task); /** * @brief Internal project open */ void OpenProjectInternal(const QString& filename, bool recovery_project = false); void ImportSingleFile(const QString &f); }; } #endif // CORE_H ================================================ FILE: app/crashhandler/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Create crash handler executable add_executable( olive-crashhandler crashhandler.cpp crashhandler.h $ ) # Disable console appearing on crash handler dialog set_target_properties(olive-crashhandler PROPERTIES WIN32_EXECUTABLE TRUE ) # Set crash handler includes target_include_directories( olive-crashhandler PRIVATE ${CMAKE_SOURCE_DIR}/app ${CRASHPAD_INCLUDE_DIRS} ) # Set crash handler libs target_link_libraries( olive-crashhandler PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network ${CRASHPAD_LIBRARIES} ) set(CRASHPAD_HANDLER "crashpad_handler${CMAKE_EXECUTABLE_SUFFIX}") set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}") if(APPLE) # Move crash handler executables inside Mac app bundle add_custom_command(TARGET olive-crashhandler POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different olive-crashhandler $ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} $ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} $ ) elseif(UNIX) install(TARGETS olive-crashhandler RUNTIME DESTINATION bin) install(PROGRAMS ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} DESTINATION bin) install(PROGRAMS ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} DESTINATION bin) endif() target_compile_definitions(olive-crashhandler PRIVATE ${OLIVE_DEFINITIONS}) ================================================ FILE: app/crashhandler/crashhandler.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "crashhandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/crashpadutils.h" #include "common/filefunctions.h" #include "version.h" namespace olive { CrashHandlerDialog::CrashHandlerDialog(const QString& report_path) { setWindowTitle(tr("Olive")); setWindowFlags(Qt::WindowStaysOnTopHint); report_filename_ = report_path; waiting_for_upload_ = false; QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(new QLabel(tr("We're sorry, Olive has crashed. Please help us fix it by " "sending an error report."))); QSplitter* splitter = new QSplitter(Qt::Vertical); splitter->setChildrenCollapsible(false); layout->addWidget(splitter); summary_edit_ = new QTextEdit(); summary_edit_->setPlaceholderText(tr("Describe what you were doing in as much detail as " "possible. If you can, provide steps to reproduce this crash.")); splitter->addWidget(summary_edit_); QWidget* crash_widget = new QWidget(); QVBoxLayout* crash_widget_layout = new QVBoxLayout(crash_widget); crash_widget_layout->setMargin(0); crash_widget_layout->addWidget(new QLabel(tr("Crash Report:"))); crash_report_ = new QTextEdit(); crash_report_->setReadOnly(true); crash_report_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); crash_widget_layout->addWidget(crash_report_); splitter->addWidget(crash_widget); QHBoxLayout* btn_layout = new QHBoxLayout(); btn_layout->setMargin(0); btn_layout->addStretch(); send_report_btn_ = new QPushButton(tr("Send Error Report")); connect(send_report_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::SendErrorReport); btn_layout->addWidget(send_report_btn_); dont_send_btn_ = new QPushButton(tr("Don't Send")); connect(dont_send_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::reject); btn_layout->addWidget(dont_send_btn_); layout->addLayout(btn_layout); crash_report_->setEnabled(false); send_report_btn_->setEnabled(false); crash_report_->setText(tr("Waiting for crash report to be generated...")); GenerateReport(); } void CrashHandlerDialog::SetGUIObjectsEnabled(bool e) { summary_edit_->setEnabled(e); crash_report_->setEnabled(e); send_report_btn_->setEnabled(e); dont_send_btn_->setEnabled(e); } QString CrashHandlerDialog::GetSymbolPath() { QDir app_path(qApp->applicationDirPath()); QString symbols_path; #if BUILDFLAG(IS_WIN) symbols_path = app_path.filePath(QStringLiteral("symbols")); #elif BUILDFLAG(IS_LINUX) app_path.cdUp(); symbols_path = app_path.filePath(QStringLiteral("share/olive-editor/symbols")); #elif BUILDFLAG(IS_APPLE) app_path.cdUp(); symbols_path = app_path.filePath(QStringLiteral("Resources/symbols")); #endif return symbols_path; } void CrashHandlerDialog::GenerateReport() { QProcess* p = new QProcess(); connect(p, QOverload::of(&QProcess::finished), this, &CrashHandlerDialog::ReadProcessFinished); connect(p, &QProcess::readyReadStandardOutput, this, &CrashHandlerDialog::ReadProcessHasData); QString stackwalk_filename = FileFunctions::GetFormattedExecutableForPlatform(QStringLiteral("minidump_stackwalk")); QString stackwalk_bin = QDir(qApp->applicationDirPath()).filePath(stackwalk_filename); p->start(stackwalk_bin, {report_filename_, GetSymbolPath()}); crash_report_->setText(QStringLiteral("Trying to run: %1").arg(stackwalk_bin)); } void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) { waiting_for_upload_ = false; if (reply->error() == QNetworkReply::NoError) { // Close dialog QDialog::accept(); } else { QMessageBox b(this); b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Upload Failed")); b.setText(tr("Failed to send error report (%1). Please try again later.").arg(QString::number(reply->error()))); b.addButton(QMessageBox::Ok); b.exec(); SetGUIObjectsEnabled(true); } } void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList &se) { QStringList errors; for (const QSslError &err : se) { errors.append(err.errorString()); } QMessageBox b(this); b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("SSL Error")); b.setText(tr("Encountered the following SSL errors:\n\n%1").arg(errors.join('\n'))); b.addButton(QMessageBox::Ok); b.exec(); } void CrashHandlerDialog::AttemptToFindReport() { // If we found it, use it, otherwise wait a second and try again if (report_filename_.isEmpty()) { // Couldn't find report, try again in one second QTimer::singleShot(500, this, &CrashHandlerDialog::AttemptToFindReport); } else { GenerateReport(); } } void CrashHandlerDialog::ReadProcessHasData() { report_data_.append(static_cast(sender())->readAllStandardOutput()); } void CrashHandlerDialog::ReadProcessFinished() { SetGUIObjectsEnabled(true); crash_report_->setText(report_data_); delete sender(); } void CrashHandlerDialog::SendErrorReport() { if (summary_edit_->document()->isEmpty()) { QMessageBox b(this); b.setIcon(QMessageBox::Question); b.setWindowModality(Qt::WindowModal); b.setText(tr("You must write a description to submit this crash report.")); b.addButton(QMessageBox::Ok); b.exec(); return; } QNetworkAccessManager* manager = new QNetworkAccessManager(); connect(manager, &QNetworkAccessManager::finished, this, &CrashHandlerDialog::ReplyFinished); connect(manager, &QNetworkAccessManager::sslErrors, this, &CrashHandlerDialog::HandleSslErrors); QNetworkRequest request; request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); request.setUrl(QStringLiteral("https://olivevideoeditor.org/crashpad/report.php")); // Create HTTP form QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); // Create description section QHttpPart desc_part; desc_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8")); desc_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"description\"")); desc_part.setBody(summary_edit_->toPlainText().toUtf8()); multipart->append(desc_part); // Create report section QHttpPart report_part; report_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8")); report_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"report\"")); report_part.setBody(report_data_); multipart->append(report_part); // Create commit section QHttpPart commit_part; commit_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8")); commit_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"commit\"")); commit_part.setBody(kAppVersionLong.toUtf8()); multipart->append(commit_part); // Create dump section QHttpPart dump_part; dump_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream")); dump_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"dump\"; filename=\"%1\"") .arg(QFileInfo(report_filename_).fileName())); QFile* dump_file = new QFile(report_filename_); dump_file->open(QFile::ReadOnly); dump_part.setBodyDevice(dump_file); dump_file->setParent(multipart); // Delete file with multipart multipart->append(dump_part); // Find symbol file QDir symbol_dir(GetSymbolPath()); QString symbol_bin_name; #if BUILDFLAG(IS_WIN) symbol_bin_name = QStringLiteral("olive-editor.pdb"); #elif BUILDFLAG(IS_APPLE) symbol_bin_name = QStringLiteral("Olive"); #else symbol_bin_name = QStringLiteral("olive-editor"); #endif symbol_dir = QDir(symbol_dir.filePath(symbol_bin_name)); QStringList folders_in_symbol_path = symbol_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (folders_in_symbol_path.size() > 0) { symbol_dir = QDir(symbol_dir.filePath(folders_in_symbol_path.first())); } else { QMessageBox b(this); b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Failed to send report")); b.setText(tr("Failed to find symbols necessary to send report. " "This is a packaging issue. Please notify " "the maintainers of this package.")); b.addButton(QMessageBox::Ok); b.exec(); return; } // Create sym section QString symbol_filename; #if BUILDFLAG(IS_APPLE) symbol_filename = QStringLiteral("Olive.sym"); #else symbol_filename = QStringLiteral("olive-editor.sym"); #endif QString symbol_full_path = symbol_dir.filePath(symbol_filename); QHttpPart sym_part; sym_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream")); sym_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"sym\"; filename=\"%1\"") .arg(symbol_filename)); QFile sym_file(symbol_full_path); if (!sym_file.open(QFile::ReadOnly)) { QMessageBox b(this); b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Failed to send report")); b.setText(tr("Failed to open symbol file. You may not have " "permission to access it.")); b.addButton(QMessageBox::Ok); b.exec(); return; } QByteArray symbol_data = qCompress(sym_file.readAll(), 9); sym_file.close(); sym_part.setBody(symbol_data); multipart->append(sym_part); manager->post(request, multipart); SetGUIObjectsEnabled(false); waiting_for_upload_ = true; } void CrashHandlerDialog::closeEvent(QCloseEvent* e) { QMessageBox b(this); b.setIcon(QMessageBox::Warning); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(tr("Confirm Close")); b.setText(tr("Crash report is still uploading. Closing now may result in no " "report being sent. Are you sure you wish to close?")); b.addButton(QMessageBox::Ok); b.addButton(QMessageBox::Cancel); if (waiting_for_upload_ && b.exec() == QMessageBox::Cancel) { e->ignore(); } else { e->accept(); } } } int main(int argc, char *argv[]) { QString report; #ifdef Q_OS_WINDOWS int num_args; LPWSTR *args = CommandLineToArgvW(GetCommandLineW(), &num_args); if (num_args < 2) { LocalFree(args); return 1; } report = QString::fromWCharArray(args[1]); LocalFree(args); #else if (argc < 2) { return 1; } report = argv[1]; #endif QApplication a(argc, argv); olive::CrashHandlerDialog chd(report); chd.open(); return a.exec(); } ================================================ FILE: app/crashhandler/crashhandler.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CRASHHANDLERDIALOG_H #define CRASHHANDLERDIALOG_H #include #include #include #include #include #include #include "common/define.h" namespace olive { class CrashHandlerDialog : public QDialog { Q_OBJECT public: CrashHandlerDialog(const QString& report_path); private: void SetGUIObjectsEnabled(bool e); void GenerateReport(); static QString GetSymbolPath(); QTextEdit* summary_edit_; QTextEdit* crash_report_; QPushButton* send_report_btn_; QPushButton* dont_send_btn_; QString report_filename_; QByteArray report_data_; bool waiting_for_upload_; protected: virtual void closeEvent(QCloseEvent* e) override; private slots: void ReplyFinished(QNetworkReply *reply); void HandleSslErrors(QNetworkReply *reply, const QList &errors); void AttemptToFindReport(); void ReadProcessHasData(); void ReadProcessFinished(); void SendErrorReport(); }; } #endif // CRASHHANDLERDIALOG_H ================================================ FILE: app/dialog/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(about) add_subdirectory(actionsearch) add_subdirectory(autorecovery) add_subdirectory(color) add_subdirectory(configbase) add_subdirectory(diskcache) add_subdirectory(export) add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) add_subdirectory(markerproperties) if(OpenTimelineIO_FOUND) add_subdirectory(otioproperties) endif() add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(projectproperties) add_subdirectory(rendercancel) add_subdirectory(sequence) add_subdirectory(speedduration) add_subdirectory(task) add_subdirectory(text) set(OLIVE_SOURCES ${OLIVE_SOURCES} PARENT_SCOPE ) ================================================ FILE: app/dialog/about/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/about/about.cpp dialog/about/about.h dialog/about/patreon.h dialog/about/scrollinglabel.cpp dialog/about/scrollinglabel.h PARENT_SCOPE ) ================================================ FILE: app/dialog/about/about.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "about.h" #include #include #include #include #include "common/qtutils.h" #include "config/config.h" #include "patreon.h" #include "scrollinglabel.h" namespace olive { AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : QDialog(parent) { if (welcome_dialog) { setWindowTitle(tr("Welcome to %1").arg(QApplication::applicationName())); } else { setWindowTitle(tr("About %1").arg(QApplication::applicationName())); } QFontMetrics fm = fontMetrics(); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height()); QHBoxLayout *horiz_layout = new QHBoxLayout(); horiz_layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height()); horiz_layout->setSpacing(fm.height()*2); QLabel* icon = new QLabel(QStringLiteral("")); icon->setAlignment(Qt::AlignCenter); horiz_layout->addWidget(icon); // Construct About text QLabel* label = new QLabel(QStringLiteral("" "

%1 %2

" // AppName (version identifier) "

" "https://www.olivevideoeditor.org/" "

" "

%3

" // First statement "").arg(QApplication::applicationName(), QApplication::applicationVersion(), tr("Olive is a free open source non-linear video editor. " "This software is licensed under the GNU GPL Version 3."))); // Set text formatting label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); label->setWordWrap(true); label->setOpenExternalLinks(true); label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); label->setCursor(Qt::IBeamCursor); horiz_layout->addWidget(label); layout->addLayout(horiz_layout); // Patrons where possible layout->addWidget(new QLabel()); QString opening_statement; if (welcome_dialog || patrons.isEmpty()) { opening_statement = tr("Olive relies on support from the community to continue its development."); } else { opening_statement = tr("Olive wouldn't be possible without the support of gracious donations from the following people."); } QLabel* support_lbl = new QLabel(tr("%1 " "If you like this project, please consider making a " "one-time donation or " "pledging monthly to " "support its development.").arg(opening_statement)); support_lbl->setWordWrap(true); support_lbl->setAlignment(Qt::AlignCenter); support_lbl->setOpenExternalLinks(true); layout->addWidget(support_lbl); if (!patrons.isEmpty()) { ScrollingLabel* scroll = new ScrollingLabel(patrons); scroll->StartAnimating(); layout->addWidget(scroll); } layout->addWidget(new QLabel()); QHBoxLayout *btn_layout = new QHBoxLayout(); btn_layout->setContentsMargins(0, 0, 0, 0); btn_layout->setSpacing(0); if (welcome_dialog) { dont_show_again_checkbox_ = new QCheckBox(tr("Don't show this message again")); btn_layout->addWidget(dont_show_again_checkbox_); } else { dont_show_again_checkbox_ = nullptr; } QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); if (!welcome_dialog) { buttons->setCenterButtons(true); } btn_layout->addWidget(buttons); layout->addLayout(btn_layout); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); setFixedSize(sizeHint()); } void AboutDialog::accept() { if (dont_show_again_checkbox_ && dont_show_again_checkbox_->isChecked()) { OLIVE_CONFIG("ShowWelcomeDialog") = false; } QDialog::accept(); } } ================================================ FILE: app/dialog/about/about.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H #include #include #include "common/define.h" namespace olive { /** * @brief The AboutDialog class * * The About dialog (accessible through Help > About). Contains license and version information. This can be run from * anywhere */ class AboutDialog : public QDialog { Q_OBJECT public: /** * @brief AboutDialog Constructor * * Creates About dialog. * * @param parent * * QWidget parent object. Usually this will be MainWindow. */ explicit AboutDialog(bool welcome_dialog, QWidget *parent = nullptr); public slots: virtual void accept() override; private: QCheckBox *dont_show_again_checkbox_; }; } #endif // ABOUTDIALOG_H ================================================ FILE: app/dialog/about/patreon.h ================================================ #ifndef PATREON_H #define PATREON_H #include QStringList patrons; #endif // PATREON_H ================================================ FILE: app/dialog/about/patreon.py ================================================ import json import requests import os url = 'https://www.patreon.com/api/oauth2/v2/campaigns/1478705/members?include=currently_entitled_tiers&fields%5Bmember%5D=full_name' name_list = '' while True: member_data = requests.get(url, headers = {"authorization": "Bearer " + os.environ.get('PATREON_KEY')}) member_data_decoded = json.loads(member_data.text) for member in member_data_decoded["data"]: if len(member["relationships"]["currently_entitled_tiers"]["data"]) > 0: if member["relationships"]["currently_entitled_tiers"]["data"][0]["id"] == "3952333": if len(name_list) > 0: name_list += ',\n' name = member["attributes"]["full_name"] name_list += " QStringLiteral(\"" name_list += name.translate(str.maketrans({ "\"": "\\\"", "\\": "\\\\" })) name_list += "\")" if "links" in member_data_decoded: url = member_data_decoded["links"]["next"] else: break text_file = open("patreon.h", "w", encoding="utf-8") text_file.write("#ifndef PATREON_H\n#define PATREON_H\n\n#include \n\nQStringList patrons = {\n%s\n};\n\n#endif // PATREON_H\n" % name_list) text_file.close() ================================================ FILE: app/dialog/about/scrollinglabel.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "scrollinglabel.h" #include #include "common/qtutils.h" namespace olive { const int ScrollingLabel::kMinLineHeight = 10; ScrollingLabel::ScrollingLabel(QWidget *parent) : QWidget(parent), animate_(0) { timer_.setInterval(50); connect(&timer_, &QTimer::timeout, this, &ScrollingLabel::AnimationUpdate); } ScrollingLabel::ScrollingLabel(const QStringList &text, QWidget *parent) : ScrollingLabel(parent) { SetText(text); } void ScrollingLabel::SetText(const QStringList &text) { text_ = text; QFontMetrics fm = fontMetrics(); text_height_ = fm.height(); int width = 0; foreach (const QString& s, text_) { width = qMax(width, QtUtils::QFontMetricsWidth(fm, s)); } setMinimumSize(width, text_height_ * kMinLineHeight); } void ScrollingLabel::paintEvent(QPaintEvent *e) { QImage map(width(), height(), QImage::Format_RGBA8888_Premultiplied); map.fill(Qt::transparent); { QPainter p(&map); p.setPen(palette().text().color()); QFontMetrics fm = p.fontMetrics(); int half_width = width(); for (int i=0; i= height()) { continue; } const QString& s = text_.at(i); int width = QtUtils::QFontMetricsWidth(fm, s); p.drawText(half_width/2 - width/2, text_y, s); } for (int y=0; y= height() + text_.size() * text_height_) { animate_ = 0; } update(); } } ================================================ FILE: app/dialog/about/scrollinglabel.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SCROLLINGLABEL_H #define SCROLLINGLABEL_H #include #include namespace olive { class ScrollingLabel : public QWidget { Q_OBJECT public: ScrollingLabel(QWidget* parent = nullptr); ScrollingLabel(const QStringList& text, QWidget* parent = nullptr); void SetText(const QStringList& text); void StartAnimating() { timer_.start(); } void StopAnimating() { timer_.stop(); } protected: virtual void paintEvent(QPaintEvent* e) override; private: static void SetOpacityOfScanLine(uchar* scan_line, int width, int channels, double mul); static const int kMinLineHeight; QStringList text_; int text_height_; QTimer timer_; int animate_; private slots: void AnimationUpdate(); }; } #endif // SCROLLINGLABEL_H ================================================ FILE: app/dialog/actionsearch/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/actionsearch/actionsearch.h dialog/actionsearch/actionsearch.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/actionsearch/actionsearch.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "actionsearch.h" #include #include #include #include namespace olive { ActionSearch::ActionSearch(QWidget *parent) : QDialog(parent), menu_bar_(nullptr) { // ActionSearch requires a parent widget Q_ASSERT(parent != nullptr); // Set styling (object name is required for CSS specific to this object) setObjectName("ASDiag"); setStyleSheet("#ASDiag{border: 2px solid #808080;}"); // Size proportionally to the parent (usually MainWindow). resize(parent->width()/3, parent->height()/3); // Show dialog as a "popup", which will make the dialog close if the user clicks out of it. setWindowFlags(Qt::Popup); QVBoxLayout* layout = new QVBoxLayout(this); // Construct the main entry text field. ActionSearchEntry* entry_field = new ActionSearchEntry(this); // Set the main entry field font size to 1.2x its standard font size. QFont entry_field_font = entry_field->font(); entry_field_font.setPointSize(qRound(entry_field_font.pointSize()*1.2)); entry_field->setFont(entry_field_font); // Set placeholder text for the main entry field entry_field->setPlaceholderText(tr("Search for action...")); // Connect signals/slots connect(entry_field, SIGNAL(textChanged(const QString&)), this, SLOT(search_update(const QString &))); connect(entry_field, SIGNAL(returnPressed()), this, SLOT(perform_action())); // moveSelectionUp() and moveSelectionDown() are emitted when the user pressed up or down on the text field. // We override it here to select the upper or lower item in the list. connect(entry_field, SIGNAL(moveSelectionUp()), this, SLOT(move_selection_up())); connect(entry_field, SIGNAL(moveSelectionDown()), this, SLOT(move_selection_down())); layout->addWidget(entry_field); // Construct list of actions list_widget = new ActionSearchList(this); // Set list's font to 1.2x its standard font size QFont list_widget_font = list_widget->font(); list_widget_font.setPointSize(qRound(list_widget_font.pointSize()*1.2)); list_widget->setFont(list_widget_font); layout->addWidget(list_widget); connect(list_widget, SIGNAL(dbl_click()), this, SLOT(perform_action())); // Instantly focus on the entry field to allow for fully keyboard operation (if this popup was initiated by keyboard // shortcut for example). entry_field->setFocus(); } void ActionSearch::SetMenuBar(QMenuBar *menu_bar) { menu_bar_ = menu_bar; } void ActionSearch::search_update(const QString &s, const QString &p, QMenu *parent) { // Do nothing if there's no menu bar to work with if (menu_bar_ == nullptr) { return; } // This function is recursive, using the `parent` parameter to loop through a menu's items. It functions in two // modes - the parent being NULL, meaning it'll get MainWindow's menubar and loop over its menus, and the parent // referring to a menu at which point it'll loop over its actions (and call itself recursively if it finds any // submenus). if (parent == nullptr) { // If parent is NULL, we'll pull from the MainWindow's menubar and call this recursively on all of its submenus // (and their submenus). // We'll clear all the current items in the list since if we're here, we're just starting. list_widget->clear(); QList menus = menu_bar_->actions(); // Loop through all menus from the menubar and run this function on each one. for (int i=0;imenu(); search_update(s, p, menu); } // Once we're here, all the recursion/item retrieval is complete. We auto-select the first item for better // keyboard-exclusive functionality. if (list_widget->count() > 0) { list_widget->item(0)->setSelected(true); } } else { // Parent was not NULL, so we loop over the actions in the menu we were given in `parent`. // The list shows a '>' delimited hierarchy of the menus in which this action came from. We construct it here by // adding the current menu's text to the existing hierarchy (passed in `p`). QString menu_text; if (!p.isEmpty()) menu_text += p + " > "; menu_text += parent->title().replace("&", ""); // Strip out any &s used in menu action names // Loop over the menu's actions QList actions = parent->actions(); for (int i=0;iisSeparator()) { if (a->menu() != nullptr) { // If the action is a menu, run this function recursively on it search_update(s, menu_text, a->menu()); } else { // This is a valid non-separator non-menu action, so check it against the currently entered string. // Strip out all &s from the action's name QString comp = a->text().replace("&", ""); // See if the action's name contains any of the currently entered string if (comp.contains(s, Qt::CaseInsensitive)) { // If so, we add it to the list widget. QListWidgetItem* item = new QListWidgetItem(QStringLiteral("%1\n(%2)").arg(comp, menu_text), list_widget); // Add a pointer to the original QAction in the item's data item->setData(Qt::UserRole+1, reinterpret_cast(a)); list_widget->addItem(item); } } } } } } void ActionSearch::perform_action() { // Loop over all the items in the list and if we find one that's selected, we trigger it. QList selected_items = list_widget->selectedItems(); if (list_widget->count() > 0 && selected_items.size() > 0) { QListWidgetItem* item = selected_items.at(0); // Get QAction pointer from item's data QAction* a = reinterpret_cast(item->data(Qt::UserRole+1).value()); a->trigger(); } // Close this popup accept(); } void ActionSearch::move_selection_up() { // Here we loop over all the items to find the currently selected one, and then select the one above it. We start // iterating at 1 (instead of 0) to efficiently ignore the first item (since the selection can't go below the very // bottom item). int lim = list_widget->count(); for (int i=1;iitem(i)->isSelected()) { list_widget->item(i-1)->setSelected(true); list_widget->scrollToItem(list_widget->item(i-1)); break; } } } void ActionSearch::move_selection_down() { // Here we loop over all the items to find the currently selected one, and then select the one below it. We limit it // one entry before count() to efficiently ignore the item at the end (since the selection can't go below the very // bottom item). int lim = list_widget->count()-1; for (int i=0;iitem(i)->isSelected()) { list_widget->item(i+1)->setSelected(true); list_widget->scrollToItem(list_widget->item(i+1)); break; } } } ActionSearchEntry::ActionSearchEntry(QWidget *parent) : QLineEdit(parent) {} bool ActionSearchEntry::event(QEvent *e) { switch (e->type()) { case QEvent::ShortcutOverride: switch (static_cast(e)->key()) { case Qt::Key_Up: case Qt::Key_Down: e->accept(); return true; } break; case QEvent::KeyPress: // Listen for up/down, otherwise pass the key event to the base class. switch (static_cast(e)->key()) { case Qt::Key_Up: e->accept(); emit moveSelectionUp(); return true; case Qt::Key_Down: e->accept(); emit moveSelectionDown(); return true; } break; default: break; } return QLineEdit::event(e); } ActionSearchList::ActionSearchList(QWidget *parent) : QListWidget(parent) {} void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *) { // Indiscriminately emit a signal on any double click emit dbl_click(); } } ================================================ FILE: app/dialog/actionsearch/actionsearch.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef ACTIONSEARCH_H #define ACTIONSEARCH_H #include #include #include #include #include #include "common/define.h" namespace olive { class ActionSearchList; /** * @brief The ActionSearch class * * A popup window (accessible through Help > Action Search) that allows users to search for a menu command by typing * rather than browsing through the menu bar. This can be created from anywhere provided olive::MainWindow is valid. */ class ActionSearch : public QDialog { Q_OBJECT public: /** * @brief ActionSearch Constructor * * Create ActionSearch popup. * * @param parent * * QWidget parent. Usually MainWindow. */ ActionSearch(QWidget* parent); /** * @brief Set the menu bar to use in this action search */ void SetMenuBar(QMenuBar* menu_bar); private slots: /** * @brief Update the list of actions according to a search query * * This function adds/removes actions in the action list according to a given search query entered by the user. * * To loop over the menubar and all of its menus and submenus, this function will call itself recursively. As such * some of its parameters do not need to be set externally, as these will be set by the function itself as it calls * itself. * * @param s * * The search text. This is the only parameter that should be set externally. * * @param p * * The current parent hierarchy. In most cases, this should be left as nullptr when called externally. * search_update() will fill this automatically as it needs while calling itself recursively. * * @param parent * * The current menu to loop over. In most cases, this should be left as nullptr when called externally. * search_update() will fill this automatically as it needs while calling itself recursively. */ void search_update(const QString& s, const QString &p = nullptr, QMenu *parent = nullptr); /** * @brief Perform the currently selected action * * Usually triggered by pressing Enter on the ActionSearchEntry field, this will trigger whatever action is currently * highlighted and then close this popup. If no entries are highlighted (i.e. the list is empty), no action is * triggered and the popup closes anyway. */ void perform_action(); /** * @brief Move selection up * * A slot for pressing up on the ActionSearchEntry field. Moves the selection in the list up once. If the * selection is already at the top of the list, this is a no-op. */ void move_selection_up(); /** * @brief Move selection down * * A slot for pressing down on the ActionSearchEntry field. Moves the selection in the list down once. If the * selection is already at the bottom of the list, this is a no-op. */ void move_selection_down(); private: /** * @brief Main widget that shows the list of commands */ ActionSearchList* list_widget; /** * @brief Attached menu bar object */ QMenuBar* menu_bar_; }; /** * @brief The ActionSearchList class * * Simple wrapper around QListWidget that emits a signal when an item is double clicked that ActionSearch connects * to a slot that triggers the currently selected action. */ class ActionSearchList : public QListWidget { Q_OBJECT public: /** * @brief ActionSearchList Constructor * @param parent * * Usually ActionSearch. */ ActionSearchList(QWidget* parent); protected: /** * @brief Override of QListWidget's double click event that emits a signal. */ void mouseDoubleClickEvent(QMouseEvent *); signals: /** * @brief Signal emitted when a QListWidget item is double clicked. */ void dbl_click(); }; /** * @brief The ActionSearchEntry class * * Simple wrapper around QLineEdit that emits signals when the up or down arrow keys are pressed so that ActionSearch * can connect them to moving the current selection up or down. */ class ActionSearchEntry : public QLineEdit { Q_OBJECT public: /** * @brief ActionSearchEntry * @param parent * * Usually ActionSearch. */ ActionSearchEntry(QWidget* parent); protected: /** * @brief Override of QLineEdit's key press event that listens for up/down key presses. * @param event */ virtual bool event(QEvent *e) override; signals: /** * @brief Emitted when the user presses the up arrow key. */ void moveSelectionUp(); /** * @brief Emitted when the user presses the down arrow key. */ void moveSelectionDown(); }; } #endif // ACTIONSEARCH_H ================================================ FILE: app/dialog/autorecovery/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/autorecovery/autorecoverydialog.h dialog/autorecovery/autorecoverydialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/autorecovery/autorecoverydialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "autorecoverydialog.h" #include #include #include #include #include #include #include "core.h" namespace olive { #define super QDialog AutoRecoveryDialog::AutoRecoveryDialog(const QString &message, const QStringList &recoveries, bool autocheck_latest, QWidget* parent) : QDialog(parent) { Init(message); PopulateTree(recoveries, autocheck_latest); } void AutoRecoveryDialog::accept() { foreach (QTreeWidgetItem* checkable, checkable_items_) { if (checkable->checkState(0) == Qt::Checked) { QString filename = checkable->data(0, kFilenameRole).toString(); Core::instance()->OpenRecoveryProject(filename); } } super::accept(); } void AutoRecoveryDialog::Init(const QString& header_text) { QVBoxLayout* layout = new QVBoxLayout(this); setWindowTitle(tr("Auto-Recovery")); layout->addWidget(new QLabel(header_text)); tree_widget_ = new QTreeWidget(); tree_widget_->setHeaderHidden(true); layout->addWidget(tree_widget_); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->button(QDialogButtonBox::Ok)->setText(tr("Load")); connect(buttons, &QDialogButtonBox::accepted, this, &AutoRecoveryDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &AutoRecoveryDialog::reject); layout->addWidget(buttons); } void AutoRecoveryDialog::PopulateTree(const QStringList& recoveries, bool autocheck_latest) { // Each entry in `recoveries` is a directory with 1+ recovery projects in it QDir autorecovery_root(FileFunctions::GetAutoRecoveryRoot()); foreach (const QString& recovery_folder, recoveries) { QDir recovery_dir(autorecovery_root.filePath(recovery_folder)); QString pretty_name; { // Retrieve pretty name QFile pretty_name_file(recovery_dir.filePath(QStringLiteral("realname.txt"))); if (pretty_name_file.open(QFile::ReadOnly)) { // Read pretty name that we should have written in the autorecovery process pretty_name = QString::fromUtf8(pretty_name_file.readAll()); pretty_name_file.close(); } if (pretty_name.isEmpty()) { // Fallback to just the UUID. While it won't mean much to the user, it's better than nothing. pretty_name = recovery_dir.dirName(); } } QTreeWidgetItem* top_level = new QTreeWidgetItem(tree_widget_); top_level->setText(0, pretty_name); { // Populate with recoveries QStringList entries = recovery_dir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed); for (int i=0; isetText(0, entry_name); entry_item->setData(0, kFilenameRole, recovery_dir.filePath(entry)); // Allow to be checked, auto-checking the first entry entry_item->setCheckState(0, (autocheck_latest && top_level->childCount() == 1) ? Qt::Checked : Qt::Unchecked); checkable_items_.append(entry_item); } } } } } } ================================================ FILE: app/dialog/autorecovery/autorecoverydialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AUTORECOVERYDIALOG_H #define AUTORECOVERYDIALOG_H #include #include #include "common/define.h" namespace olive { class AutoRecoveryDialog : public QDialog { Q_OBJECT public: AutoRecoveryDialog(const QString& message, const QStringList& recoveries, bool autocheck_latest, QWidget* parent); public slots: virtual void accept() override; private: void Init(const QString &header_text); void PopulateTree(const QStringList &recoveries, bool autocheck); QTreeWidget* tree_widget_; QVector checkable_items_; enum DataRole { kFilenameRole = Qt::UserRole }; }; } #endif // AUTORECOVERYDIALOG_H ================================================ FILE: app/dialog/color/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/color/colordialog.h dialog/color/colordialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/color/colordialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "colordialog.h" #include #include #include #include "common/qtutils.h" namespace olive { ColorDialog::ColorDialog(ColorManager* color_manager, const ManagedColor& start, QWidget *parent) : QDialog(parent), color_manager_(color_manager) { setWindowTitle(tr("Select Color")); QVBoxLayout* layout = new QVBoxLayout(this); QSplitter* splitter = new QSplitter(Qt::Horizontal); splitter->setChildrenCollapsible(false); layout->addWidget(splitter); QWidget* graphics_area = new QWidget(); splitter->addWidget(graphics_area); QVBoxLayout *graphics_layout = new QVBoxLayout(graphics_area); QHBoxLayout* wheel_layout = new QHBoxLayout(); graphics_layout->addLayout(wheel_layout); color_wheel_ = new ColorWheelWidget(); wheel_layout->addWidget(color_wheel_); hsv_value_gradient_ = new ColorGradientWidget(Qt::Vertical); hsv_value_gradient_->setFixedWidth(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("HHH"))); wheel_layout->addWidget(hsv_value_gradient_); QHBoxLayout *swatch_layout = new QHBoxLayout(); graphics_layout->addLayout(swatch_layout); swatch_layout->addStretch(); swatch_ = new ColorSwatchChooser(color_manager_); swatch_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); swatch_layout->addWidget(swatch_); swatch_layout->addStretch(); QWidget* value_area = new QWidget(); QVBoxLayout* value_layout = new QVBoxLayout(value_area); value_layout->setSpacing(0); splitter->addWidget(value_area); color_values_widget_ = new ColorValuesWidget(color_manager_); color_values_widget_->IgnorePickFrom(this); value_layout->addWidget(color_values_widget_); chooser_ = new ColorSpaceChooser(color_manager_); value_layout->addWidget(chooser_); // Split window 50/50 splitter->setSizes({INT_MAX, INT_MAX}); connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, color_values_widget_, &ColorValuesWidget::SetColor); connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); connect(color_wheel_, &ColorWheelWidget::SelectedColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, color_values_widget_, &ColorValuesWidget::SetColor); connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, color_wheel_, &ColorWheelWidget::SetSelectedColor); connect(hsv_value_gradient_, &ColorGradientWidget::SelectedColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); connect(color_values_widget_, &ColorValuesWidget::ColorChanged, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); connect(color_values_widget_, &ColorValuesWidget::ColorChanged, color_wheel_, &ColorWheelWidget::SetSelectedColor); connect(color_values_widget_, &ColorValuesWidget::ColorChanged, swatch_, &ColorSwatchChooser::SetCurrentColor); connect(swatch_, &ColorSwatchChooser::ColorClicked, hsv_value_gradient_, &ColorGradientWidget::SetSelectedColor); connect(swatch_, &ColorSwatchChooser::ColorClicked, color_wheel_, &ColorWheelWidget::SetSelectedColor); connect(swatch_, &ColorSwatchChooser::ColorClicked, color_values_widget_, &ColorValuesWidget::SetColor); connect(color_wheel_, &ColorWheelWidget::DiameterChanged, hsv_value_gradient_, &ColorGradientWidget::setFixedHeight); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(buttons); SetColor(start); connect(chooser_, &ColorSpaceChooser::ColorSpaceChanged, this, &ColorDialog::ColorSpaceChanged); ColorSpaceChanged(chooser_->input(), chooser_->output()); // Set default size ratio to 2:1 resize(sizeHint().height() * 2, sizeHint().height()); } void ColorDialog::SetColor(const ManagedColor &start) { chooser_->set_input(start.color_input()); chooser_->set_output(start.color_output()); Color managed_start; if (start.color_input().isEmpty()) { managed_start = start; } else { // Convert reference color to the input space ColorProcessorPtr linear_to_input = ColorProcessor::Create(color_manager_, color_manager_->GetReferenceColorSpace(), start.color_input()); managed_start = linear_to_input->ConvertColor(start); } color_wheel_->SetSelectedColor(managed_start); hsv_value_gradient_->SetSelectedColor(managed_start); color_values_widget_->SetColor(managed_start); swatch_->SetCurrentColor(managed_start); } ManagedColor ColorDialog::GetSelectedColor() const { ManagedColor selected = color_wheel_->GetSelectedColor(); // Convert to linear and return a linear color if (input_to_ref_processor_) { selected = input_to_ref_processor_->ConvertColor(selected); } selected.set_color_input(GetColorSpaceInput()); selected.set_color_output(GetColorSpaceOutput()); return selected; } QString ColorDialog::GetColorSpaceInput() const { return chooser_->input(); } ColorTransform ColorDialog::GetColorSpaceOutput() const { return chooser_->output(); } void ColorDialog::ColorSpaceChanged(const QString &input, const ColorTransform &output) { input_to_ref_processor_ = ColorProcessor::Create(color_manager_, input, color_manager_->GetReferenceColorSpace()); ColorProcessorPtr ref_to_display = ColorProcessor::Create(color_manager_, color_manager_->GetReferenceColorSpace(), output); ColorProcessorPtr ref_to_input = ColorProcessor::Create(color_manager_, color_manager_->GetReferenceColorSpace(), input); // FIXME: For some reason, using OCIO::TRANSFORM_DIR_INVERSE (wrapped by ColorProcessor::kInverse) causes OCIO to // crash. We've disabled that functionality for now (also disabling display_tab_ in ColorValuesWidget) /*ColorProcessorPtr display_to_ref = ColorProcessor::Create(color_manager_->GetConfig(), color_manager_->GetReferenceColorSpace(), display, view, look, ColorProcessor::kInverse);*/ color_wheel_->SetColorProcessor(input_to_ref_processor_, ref_to_display); hsv_value_gradient_->SetColorProcessor(input_to_ref_processor_, ref_to_display); color_values_widget_->SetColorProcessor(input_to_ref_processor_, ref_to_display, nullptr, ref_to_input); } } ================================================ FILE: app/dialog/color/colordialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef COLORDIALOG_H #define COLORDIALOG_H #include #include "node/color/colormanager/colormanager.h" #include "render/managedcolor.h" #include "widget/colorwheel/colorgradientwidget.h" #include "widget/colorwheel/colorspacechooser.h" #include "widget/colorwheel/colorswatchchooser.h" #include "widget/colorwheel/colorvalueswidget.h" #include "widget/colorwheel/colorwheelwidget.h" namespace olive { class ColorDialog : public QDialog { Q_OBJECT public: /** * @brief ColorDialog Constructor * * @param color_manager * * The ColorManager to use for color management. This must be valid. * * @param start * * The color to start with. This must be in the color_manager's reference space * * @param input_cs * * The input range that the user should see. The start color will be converted to this for UI object. * * @param parent * * QWidget parent. */ ColorDialog(ColorManager* color_manager, const ManagedColor &start = Color(1.0f, 1.0f, 1.0f), QWidget* parent = nullptr); /** * @brief Retrieves the color selected by the user * * The color is always returned in the ColorManager's reference space (usually scene linear). */ ManagedColor GetSelectedColor() const; QString GetColorSpaceInput() const; ColorTransform GetColorSpaceOutput() const; public slots: void SetColor(const ManagedColor &c); private: ColorManager* color_manager_; ColorWheelWidget* color_wheel_; ColorValuesWidget* color_values_widget_; ColorGradientWidget* hsv_value_gradient_; ColorProcessorPtr input_to_ref_processor_; ColorSpaceChooser* chooser_; ColorSwatchChooser *swatch_; private slots: void ColorSpaceChanged(const QString& input, const ColorTransform &output); }; } #endif // COLORDIALOG_H ================================================ FILE: app/dialog/configbase/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/configbase/configdialogbase.cpp dialog/configbase/configdialogbase.h dialog/configbase/configdialogbasetab.cpp dialog/configbase/configdialogbasetab.h PARENT_SCOPE ) ================================================ FILE: app/dialog/configbase/configdialogbase.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "configdialogbase.h" #include #include #include #include "core.h" namespace olive { ConfigDialogBase::ConfigDialogBase(QWidget* parent) : QDialog(parent) { QVBoxLayout* layout = new QVBoxLayout(this); QSplitter* splitter = new QSplitter(); splitter->setChildrenCollapsible(false); layout->addWidget(splitter); list_widget_ = new QListWidget(); preference_pane_stack_ = new QStackedWidget(this); splitter->addWidget(list_widget_); splitter->addWidget(preference_pane_stack_); QDialogButtonBox* button_box = new QDialogButtonBox(this); button_box->setOrientation(Qt::Horizontal); button_box->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); layout->addWidget(button_box); connect(button_box, &QDialogButtonBox::accepted, this, &ConfigDialogBase::accept); connect(button_box, &QDialogButtonBox::rejected, this, &ConfigDialogBase::reject); connect(list_widget_, &QListWidget::currentRowChanged, preference_pane_stack_, &QStackedWidget::setCurrentIndex); } void ConfigDialogBase::accept() { foreach (ConfigDialogBaseTab* tab, tabs_) { if (!tab->Validate()) { return; } } MultiUndoCommand* command = new MultiUndoCommand(); foreach (ConfigDialogBaseTab* tab, tabs_) { tab->Accept(command); } Core::instance()->undo_stack()->push(command, tr("Set Configuration")); AcceptEvent(); QDialog::accept(); } void ConfigDialogBase::AddTab(ConfigDialogBaseTab *tab, const QString &title) { list_widget_->addItem(title); preference_pane_stack_->addWidget(tab); tabs_.append(tab); } } ================================================ FILE: app/dialog/configbase/configdialogbase.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CONFIGBASE_H #define CONFIGBASE_H #include #include #include #include "configdialogbasetab.h" namespace olive { class ConfigDialogBase : public QDialog { Q_OBJECT public: ConfigDialogBase(QWidget* parent = nullptr); private slots: /** * @brief Override of accept to save preferences to Config. */ virtual void accept() override; protected: void AddTab(ConfigDialogBaseTab* tab, const QString& title); virtual void AcceptEvent(){} private: QListWidget* list_widget_; QStackedWidget* preference_pane_stack_; QList tabs_; }; } #endif // CONFIGBASE_H ================================================ FILE: app/dialog/configbase/configdialogbasetab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "configdialogbasetab.h" namespace olive { bool ConfigDialogBaseTab::Validate() { return true; } } ================================================ FILE: app/dialog/configbase/configdialogbasetab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESTAB_H #define PREFERENCESTAB_H #include #include "config/config.h" #include "undo/undocommand.h" namespace olive { class ConfigDialogBaseTab : public QWidget { public: ConfigDialogBaseTab() = default; virtual bool Validate(); virtual void Accept(MultiUndoCommand *parent) = 0; }; } #endif // PREFERENCESTAB_H ================================================ FILE: app/dialog/diskcache/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/diskcache/diskcachedialog.h dialog/diskcache/diskcachedialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/diskcache/diskcachedialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "diskcachedialog.h" #include #include #include #include #include "config/config.h" #include "core.h" namespace olive { DiskCacheDialog::DiskCacheDialog(DiskCacheFolder *folder, QWidget* parent) : QDialog(parent), folder_(folder) { QGridLayout* layout = new QGridLayout(this); int row = 0; layout->addWidget(new QLabel(tr("Disk Cache: %1").arg(folder->GetPath())), row, 0, 1, 2); setWindowTitle(tr("Disk Cache Settings")); row++; layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0); maximum_cache_slider_ = new FloatSlider(); maximum_cache_slider_->SetFormat(tr("%1 GB")); maximum_cache_slider_->SetMinimum(1.0); maximum_cache_slider_->SetValue(static_cast(folder->GetLimit()) / static_cast(kBytesInGigabyte)); layout->addWidget(maximum_cache_slider_, row, 1); row++; clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache")); connect(clear_cache_btn_, &QPushButton::clicked, this, static_cast(&DiskCacheDialog::ClearDiskCache)); layout->addWidget(clear_cache_btn_, row, 1); row++; clear_disk_cache_ = new QCheckBox(tr("Automatically clear disk cache on close")); clear_disk_cache_->setChecked(folder->GetClearOnClose()); layout->addWidget(clear_disk_cache_, row, 1); row++; QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &DiskCacheDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &DiskCacheDialog::reject); layout->addWidget(buttons, row, 0, 1, 2); } void DiskCacheDialog::accept() { qint64 new_disk_cache_limit = qRound64(maximum_cache_slider_->GetValue() * kBytesInGigabyte); if (new_disk_cache_limit != folder_->GetLimit()) { folder_->SetLimit(new_disk_cache_limit); } if (folder_->GetClearOnClose() != clear_disk_cache_->isChecked()) { folder_->SetClearOnClose(clear_disk_cache_->isChecked()); } QDialog::accept(); } void DiskCacheDialog::ClearDiskCache() { ClearDiskCache(folder_->GetPath(), this, clear_cache_btn_); } void DiskCacheDialog::ClearDiskCache(const QString &path, QWidget *parent, QPushButton *clear_btn) { if (QMessageBox::question(parent, tr("Clear Disk Cache"), tr("Are you sure you want to clear the disk cache in '%1'?").arg(path), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { if (clear_btn) clear_btn->setEnabled(false); if (DiskManager::instance()->ClearDiskCache(path)) { if (clear_btn) clear_btn->setText(tr("Disk Cache Cleared")); } else { QMessageBox::information(parent, tr("Clear Disk Cache"), tr("Disk cache failed to fully clear. You may have to delete the cache files manually."), QMessageBox::Ok); if (clear_btn) clear_btn->setText(tr("Disk Cache Partially Cleared")); } } } } ================================================ FILE: app/dialog/diskcache/diskcachedialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef DISKCACHEDIALOG_H #define DISKCACHEDIALOG_H #include #include #include #include "render/diskmanager.h" #include "widget/slider/floatslider.h" namespace olive { class DiskCacheDialog : public QDialog { Q_OBJECT public: DiskCacheDialog(DiskCacheFolder* folder, QWidget* parent = nullptr); static void ClearDiskCache(const QString &path, QWidget *parent, QPushButton *clear_btn = nullptr); public slots: virtual void accept() override; private: DiskCacheFolder* folder_; FloatSlider* maximum_cache_slider_; QCheckBox* clear_disk_cache_; QPushButton* clear_cache_btn_; private slots: void ClearDiskCache(); }; } #endif // DISKCACHEDIALOG_H ================================================ FILE: app/dialog/export/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(codec) set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/export/export.cpp dialog/export/export.h dialog/export/exportadvancedvideodialog.cpp dialog/export/exportadvancedvideodialog.h dialog/export/exportaudiotab.cpp dialog/export/exportaudiotab.h dialog/export/exportformatcombobox.cpp dialog/export/exportformatcombobox.h dialog/export/exportsavepresetdialog.cpp dialog/export/exportsavepresetdialog.h dialog/export/exportsubtitlestab.cpp dialog/export/exportsubtitlestab.h dialog/export/exportvideotab.cpp dialog/export/exportvideotab.h PARENT_SCOPE ) ================================================ FILE: app/dialog/export/codec/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/export/codec/av1section.cpp dialog/export/codec/av1section.h dialog/export/codec/cineformsection.cpp dialog/export/codec/cineformsection.h dialog/export/codec/codecsection.cpp dialog/export/codec/codecsection.h dialog/export/codec/codecstack.cpp dialog/export/codec/codecstack.h dialog/export/codec/h264section.cpp dialog/export/codec/h264section.h dialog/export/codec/imagesection.cpp dialog/export/codec/imagesection.h PARENT_SCOPE ) ================================================ FILE: app/dialog/export/codec/av1section.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "av1section.h" #include #include #include #include #include "common/qtutils.h" #include "widget/slider/integerslider.h" namespace olive { AV1Section::AV1Section(QWidget *parent) : AV1Section(AV1CRFSection::kDefaultAV1CRF, parent) { } AV1Section::AV1Section(int default_crf, QWidget *parent) : CodecSection(parent) { QGridLayout* layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Preset:")), row, 0); preset_combobox_ = new QComboBox(); preset_combobox_->setToolTip(tr("This parameter governs the efficiency/encode-time trade-off.\n" "Lower presets will result in an output with better quality for a given file size, but will take longer to encode.\n" "Higher presets can result in a very fast encode, but will make some compromises on visual quality for a given crf value.")); for (int i = 0; i <= 13; i++) preset_combobox_->addItem(QString::number(i)); preset_combobox_->setCurrentIndex(8); layout->addWidget(preset_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Compression Method:")), row, 0); QComboBox* compression_box = new QComboBox(); compression_box->setToolTip(tr("This parameter governs the quality/size trade-off.\n" "Higher CRF values will result in a final output that takes less space, but begins to lose detail.\n" "Lower CRF values retain more detail at the cost of larger file sizes.\n" "The possible range of CRF in SVT-AV1 is 1-63.")); // These items must correspond to the CompressionMethod enum compression_box->addItem(tr("Constant Rate Factor")); layout->addWidget(compression_box, row, 1); row++; compression_method_stack_ = new QStackedWidget(); layout->addWidget(compression_method_stack_, row, 0, 1, 2); crf_section_ = new AV1CRFSection(default_crf); compression_method_stack_->addWidget(crf_section_); connect(compression_box, static_cast(&QComboBox::currentIndexChanged), compression_method_stack_, &QStackedWidget::setCurrentIndex); } void AV1Section::AddOpts(EncodingParams *params) { CompressionMethod method = static_cast(compression_method_stack_->currentIndex()); if (method == kConstantRateFactor) { // Set Quantizer value params->set_video_option(QStringLiteral("qp"), QString::number(crf_section_->GetValue())); } params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex())); } AV1CRFSection::AV1CRFSection(int default_crf, QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); crf_slider_ = new QSlider(Qt::Horizontal); crf_slider_->setMinimum(kMinimumCRF); crf_slider_->setMaximum(kMaximumCRF); crf_slider_->setValue(default_crf); layout->addWidget(crf_slider_); IntegerSlider* crf_input = new IntegerSlider(); crf_input->setMaximumWidth(QtUtils::QFontMetricsWidth(crf_input->fontMetrics(), QStringLiteral("HHHH"))); crf_input->SetMinimum(kMinimumCRF); crf_input->SetMaximum(kMaximumCRF); crf_input->SetValue(default_crf); crf_input->SetDefaultValue(default_crf); layout->addWidget(crf_input); connect(crf_slider_, &QSlider::valueChanged, crf_input, &IntegerSlider::SetValue); connect(crf_input, &IntegerSlider::ValueChanged, crf_slider_, &QSlider::setValue); } int AV1CRFSection::GetValue() const { return crf_slider_->value(); } } ================================================ FILE: app/dialog/export/codec/av1section.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AV1SECTION_H #define AV1SECTION_H #include #include #include #include "codecsection.h" #include "widget/slider/floatslider.h" namespace olive { class AV1CRFSection : public QWidget { Q_OBJECT public: AV1CRFSection(int default_crf, QWidget* parent = nullptr); int GetValue() const; static const int kDefaultAV1CRF = 30; private: static const int kMinimumCRF = 0; static const int kMaximumCRF = 63; QSlider* crf_slider_; }; class AV1Section : public CodecSection { Q_OBJECT public: enum CompressionMethod { kConstantRateFactor, }; AV1Section(QWidget* parent = nullptr); AV1Section(int default_crf, QWidget* parent); virtual void AddOpts(EncodingParams* params) override; private: QStackedWidget* compression_method_stack_; AV1CRFSection* crf_section_; QComboBox *preset_combobox_; }; } #endif // AV1SECTION_H ================================================ FILE: app/dialog/export/codec/cineformsection.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "cineformsection.h" #include #include namespace olive { CineformSection::CineformSection(QWidget *parent) : CodecSection(parent) { QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Quality:")), row, 0); quality_combobox_ = new QComboBox(); /* Correspond to the following indexes for FFmpeg * * -quality E..V....... set quality (from 0 to 12) (default film3+) * film3+ 0 E..V....... * film3 1 E..V....... * film2+ 2 E..V....... * film2 3 E..V....... * film1.5 4 E..V....... * film1+ 5 E..V....... * film1 6 E..V....... * high+ 7 E..V....... * high 8 E..V....... * medium+ 9 E..V....... * medium 10 E..V....... * low+ 11 E..V....... * low 12 E..V....... * */ quality_combobox_->addItem(tr("Film Scan 3+")); quality_combobox_->addItem(tr("Film Scan 3")); quality_combobox_->addItem(tr("Film Scan 2+")); quality_combobox_->addItem(tr("Film Scan 2")); quality_combobox_->addItem(tr("Film Scan 1.5")); quality_combobox_->addItem(tr("Film Scan 1+")); quality_combobox_->addItem(tr("Film Scan 1")); quality_combobox_->addItem(tr("High+")); quality_combobox_->addItem(tr("High")); quality_combobox_->addItem(tr("Medium+")); quality_combobox_->addItem(tr("Medium")); quality_combobox_->addItem(tr("Low+")); quality_combobox_->addItem(tr("Low")); // Default to "medium" quality_combobox_->setCurrentIndex(10); layout->addWidget(quality_combobox_, row, 1); } void CineformSection::AddOpts(EncodingParams *params) { params->set_video_option(QStringLiteral("quality"), QString::number(quality_combobox_->currentIndex())); } void CineformSection::SetOpts(const EncodingParams *p) { quality_combobox_->setCurrentIndex(p->video_option(QStringLiteral("quality")).toInt()); } } ================================================ FILE: app/dialog/export/codec/cineformsection.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CINEFORMSECTION_H #define CINEFORMSECTION_H #include #include "codecsection.h" namespace olive { class CineformSection : public CodecSection { Q_OBJECT public: CineformSection(QWidget *parent = nullptr); virtual void AddOpts(EncodingParams* params) override; virtual void SetOpts(const EncodingParams *p) override; private: QComboBox *quality_combobox_; }; } #endif // CINEFORMSECTION_H ================================================ FILE: app/dialog/export/codec/codecsection.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "codecsection.h" namespace olive { CodecSection::CodecSection(QWidget *parent) : QWidget(parent) { } } ================================================ FILE: app/dialog/export/codec/codecsection.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CODECSECTION_H #define CODECSECTION_H #include #include "codec/encoder.h" namespace olive { class CodecSection : public QWidget { Q_OBJECT public: CodecSection(QWidget* parent = nullptr); virtual void AddOpts(EncodingParams* params){Q_UNUSED(params)} virtual void SetOpts(const EncodingParams *p){Q_UNUSED(p)} }; } #endif // CODECSECTION_H ================================================ FILE: app/dialog/export/codec/codecstack.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "codecstack.h" namespace olive { #define super QStackedWidget CodecStack::CodecStack(QWidget *parent) : super{parent} { connect(this, &CodecStack::currentChanged, this, &CodecStack::OnChange); } void CodecStack::addWidget(QWidget *widget) { super::addWidget(widget); OnChange(currentIndex()); } void CodecStack::OnChange(int index) { for (int i=0; isetSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } else { widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); } widget(i)->adjustSize(); } adjustSize(); } } ================================================ FILE: app/dialog/export/codec/codecstack.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef CODECSTACK_H #define CODECSTACK_H #include namespace olive { class CodecStack : public QStackedWidget { Q_OBJECT public: explicit CodecStack(QWidget *parent = nullptr); void addWidget(QWidget *widget); signals: private slots: void OnChange(int index); }; } #endif // CODECSTACK_H ================================================ FILE: app/dialog/export/codec/h264section.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "h264section.h" #include #include #include #include #include "common/qtutils.h" #include "widget/slider/integerslider.h" namespace olive { H264Section::H264Section(QWidget *parent) : H264Section(H264CRFSection::kDefaultH264CRF, parent) { } H264Section::H264Section(int default_crf, QWidget *parent) : CodecSection(parent) { QGridLayout* layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0); preset_combobox_ = new QComboBox(); preset_combobox_->setToolTip(tr("This setting allows you to tweak the ratio of export speed to compression quality. \n\n" "If using Constant Rate Factor, slower speeds will result in smaller file sizes for the same quality. \n\n" "If using Target Bit Rate or Target File Size, slower speeds will result in higher quality for the same bitrate/filesize. \n\n" "This setting is equivalent to the `preset` setting in libx264.")); preset_combobox_->addItem(tr("Ultra Fast")); preset_combobox_->addItem(tr("Super Fast")); preset_combobox_->addItem(tr("Very Fast")); preset_combobox_->addItem(tr("Faster")); preset_combobox_->addItem(tr("Fast")); preset_combobox_->addItem(tr("Medium")); preset_combobox_->addItem(tr("Slow")); preset_combobox_->addItem(tr("Slower")); preset_combobox_->addItem(tr("Very Slow")); //Default to "medium" preset_combobox_->setCurrentIndex(5); layout->addWidget(preset_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Compression Method:")), row, 0); QComboBox* compression_box = new QComboBox(); // These items must correspond to the CompressionMethod enum compression_box->addItem(tr("Constant Rate Factor")); compression_box->addItem(tr("Target Bit Rate")); compression_box->addItem(tr("Target File Size")); layout->addWidget(compression_box, row, 1); row++; compression_method_stack_ = new QStackedWidget(); layout->addWidget(compression_method_stack_, row, 0, 1, 2); crf_section_ = new H264CRFSection(default_crf); compression_method_stack_->addWidget(crf_section_); bitrate_section_ = new H264BitRateSection(); compression_method_stack_->addWidget(bitrate_section_); filesize_section_ = new H264FileSizeSection(); compression_method_stack_->addWidget(filesize_section_); connect(compression_box, static_cast(&QComboBox::currentIndexChanged), compression_method_stack_, &QStackedWidget::setCurrentIndex); } void H264Section::AddOpts(EncodingParams *params) { // FIXME: Implement two-pass CompressionMethod method = static_cast(compression_method_stack_->currentIndex()); // This option is not used by the encoder (nor is anything with the ove_ prefix), it's to help us // identify which option was chosen when params are restored params->set_video_option(QStringLiteral("ove_compressionmethod"), QString::number(method)); if (method == kConstantRateFactor) { // Simply set CRF value params->set_video_option(QStringLiteral("crf"), QString::number(crf_section_->GetValue())); } else { int64_t target_rate, max_rate, min_rate; if (method == kTargetBitRate) { // Use user-supplied values for the bit rate target_rate = bitrate_section_->GetTargetBitRate(); min_rate = 0; max_rate = bitrate_section_->GetMaximumBitRate(); } else { // Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second) int64_t target_fs = filesize_section_->GetFileSize(); target_rate = qRound64(static_cast(target_fs) / params->GetExportLength().toDouble()); min_rate = target_rate; max_rate = target_rate; params->set_video_option(QStringLiteral("ove_targetfilesize"), QString::number(target_fs)); } // Disable CRF encoding params->set_video_option(QStringLiteral("crf"), QStringLiteral("-1")); params->set_video_bit_rate(target_rate); params->set_video_min_bit_rate(min_rate); params->set_video_max_bit_rate(max_rate); params->set_video_buffer_size(2000000); } params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex())); } void H264Section::SetOpts(const EncodingParams *p) { CompressionMethod method = static_cast(p->video_option(QStringLiteral("ove_compressionmethod")).toInt()); compression_method_stack_->setCurrentIndex(method); if (method == kConstantRateFactor) { crf_section_->SetValue(p->video_option(QStringLiteral("crf")).toInt()); } else { int64_t target_rate = p->video_bit_rate(); int64_t max_rate = p->video_max_bit_rate(); if (method == kTargetBitRate) { // Use user-supplied values for the bit rate bitrate_section_->SetTargetBitRate(target_rate); bitrate_section_->SetMaximumBitRate(max_rate); } else { // Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second) filesize_section_->SetFileSize(p->video_option(QStringLiteral("ove_targetfilesize")).toLongLong()); } } } H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); crf_slider_ = new QSlider(Qt::Horizontal); crf_slider_->setMinimum(kMinimumCRF); crf_slider_->setMaximum(kMaximumCRF); crf_slider_->setValue(default_crf); layout->addWidget(crf_slider_); IntegerSlider* crf_input = new IntegerSlider(); crf_input->setMaximumWidth(QtUtils::QFontMetricsWidth(crf_input->fontMetrics(), QStringLiteral("HHHH"))); crf_input->SetMinimum(kMinimumCRF); crf_input->SetMaximum(kMaximumCRF); crf_input->SetValue(default_crf); crf_input->SetDefaultValue(default_crf); layout->addWidget(crf_input); connect(crf_slider_, &QSlider::valueChanged, crf_input, &IntegerSlider::SetValue); connect(crf_input, &IntegerSlider::ValueChanged, crf_slider_, &QSlider::setValue); } int H264CRFSection::GetValue() const { return crf_slider_->value(); } void H264CRFSection::SetValue(int c) { crf_slider_->setValue(c); } H264BitRateSection::H264BitRateSection(QWidget *parent) : QWidget(parent) { QGridLayout* layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Target Bit Rate (Mbps):")), row, 0); target_rate_ = new FloatSlider(); target_rate_->SetMinimum(0); layout->addWidget(target_rate_, row, 1); row++; layout->addWidget(new QLabel(tr("Maximum Bit Rate (Mbps):")), row, 0); max_rate_ = new FloatSlider(); max_rate_->SetMinimum(0); layout->addWidget(max_rate_, row, 1); row++; layout->addWidget(new QLabel(tr("Two-Pass")), row, 0); QCheckBox* two_pass_box = new QCheckBox(); layout->addWidget(two_pass_box, row, 1); // Bit rate defaults target_rate_->SetValue(16.0); max_rate_->SetValue(32.0); } int64_t H264BitRateSection::GetTargetBitRate() const { return qRound64(target_rate_->GetValue() * 1000000.0); } void H264BitRateSection::SetTargetBitRate(int64_t b) { target_rate_->SetValue(double(b) * 0.000001); } int64_t H264BitRateSection::GetMaximumBitRate() const { return qRound64(max_rate_->GetValue() * 1000000.0); } void H264BitRateSection::SetMaximumBitRate(int64_t b) { max_rate_->SetValue(double(b) * 0.000001); } H264FileSizeSection::H264FileSizeSection(QWidget *parent) : QWidget(parent) { QGridLayout* layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Target File Size (MB):")), row, 0); file_size_ = new FloatSlider(); file_size_->SetMinimum(0); layout->addWidget(file_size_, row, 1); row++; layout->addWidget(new QLabel(tr("Two-Pass")), row, 0); QCheckBox* two_pass_box = new QCheckBox(); layout->addWidget(two_pass_box, row, 1); // File size defaults file_size_->SetValue(700.0); } int64_t H264FileSizeSection::GetFileSize() const { // Convert megabytes to BITS return qRound64(file_size_->GetValue() * 1024.0 * 1024.0 * 8.0); } void H264FileSizeSection::SetFileSize(int64_t f) { // Convert bits back to megabytes file_size_->SetValue(double(f) / 8.0 / 1024.0 / 1024.0); } H265Section::H265Section(QWidget *parent) : H264Section(H264CRFSection::kDefaultH265CRF, parent) { } } ================================================ FILE: app/dialog/export/codec/h264section.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef H264SECTION_H #define H264SECTION_H #include #include #include #include "codecsection.h" #include "widget/slider/floatslider.h" namespace olive { class H264CRFSection : public QWidget { Q_OBJECT public: H264CRFSection(int default_crf, QWidget* parent = nullptr); int GetValue() const; void SetValue(int c); static const int kDefaultH264CRF = 18; static const int kDefaultH265CRF = 23; private: static const int kMinimumCRF = 0; static const int kMaximumCRF = 51; QSlider* crf_slider_; }; class H264BitRateSection : public QWidget { Q_OBJECT public: H264BitRateSection(QWidget* parent = nullptr); /** * @brief Get user-selected target bit rate (returns in BITS) */ int64_t GetTargetBitRate() const; void SetTargetBitRate(int64_t b); /** * @brief Get user-selected maximum bit rate (returns in BITS) */ int64_t GetMaximumBitRate() const; void SetMaximumBitRate(int64_t b); private: FloatSlider* target_rate_; FloatSlider* max_rate_; }; class H264FileSizeSection : public QWidget { Q_OBJECT public: H264FileSizeSection(QWidget* parent = nullptr); /** * @brief Returns file size in BITS */ int64_t GetFileSize() const; void SetFileSize(int64_t f); private: FloatSlider* file_size_; }; class H264Section : public CodecSection { Q_OBJECT public: enum CompressionMethod { kConstantRateFactor, kTargetBitRate, kTargetFileSize }; H264Section(QWidget* parent = nullptr); H264Section(int default_crf, QWidget* parent); virtual void AddOpts(EncodingParams* params) override; virtual void SetOpts(const EncodingParams *p) override; private: QStackedWidget* compression_method_stack_; H264CRFSection* crf_section_; H264BitRateSection* bitrate_section_; H264FileSizeSection* filesize_section_; QComboBox *preset_combobox_; }; class H265Section : public H264Section { Q_OBJECT public: H265Section(QWidget* parent = nullptr); }; } #endif // H264SECTION_H ================================================ FILE: app/dialog/export/codec/imagesection.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "imagesection.h" #include #include namespace olive { ImageSection::ImageSection(QWidget* parent) : CodecSection(parent) { QGridLayout* layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); int row = 0; layout->addWidget(new QLabel(tr("Image Sequence:")), row, 0); image_sequence_checkbox_ = new QCheckBox(); connect(image_sequence_checkbox_, &QCheckBox::toggled, this, &ImageSection::ImageSequenceCheckBoxToggled); layout->addWidget(image_sequence_checkbox_, row, 1); row++; layout->addWidget(new QLabel(tr("Frame to Export:")), row, 0); frame_slider_ = new RationalSlider(); frame_slider_->SetMinimum(0); frame_slider_->SetValue(0); frame_slider_->SetDisplayType(RationalSlider::kTime); connect(frame_slider_, &RationalSlider::ValueChanged, this, &ImageSection::TimeChanged); layout->addWidget(frame_slider_, row, 1); } void ImageSection::ImageSequenceCheckBoxToggled(bool e) { frame_slider_->setEnabled(!e); } } ================================================ FILE: app/dialog/export/codec/imagesection.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef IMAGESECTION_H #define IMAGESECTION_H #include #include "codecsection.h" #include "widget/slider/rationalslider.h" namespace olive { class ImageSection : public CodecSection { Q_OBJECT public: ImageSection(QWidget* parent = nullptr); bool IsImageSequenceChecked() const { return image_sequence_checkbox_->isChecked(); } void SetImageSequenceChecked(bool e) { image_sequence_checkbox_->setChecked(e); } void SetTimebase(const rational& r) { frame_slider_->SetTimebase(r); } rational GetTime() const { return frame_slider_->GetValue(); } void SetTime(const rational &t) { frame_slider_->SetValue(t); } signals: void TimeChanged(const rational &t); private: QCheckBox* image_sequence_checkbox_; RationalSlider* frame_slider_; private slots: void ImageSequenceCheckBoxToggled(bool e); }; } #endif // IMAGESECTION_H ================================================ FILE: app/dialog/export/export.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "export.h" #include #include #include #include #include #include #include #include #include #include "common/digit.h" #include "common/qtutils.h" #include "dialog/task/task.h" #include "exportsavepresetdialog.h" #include "node/project.h" #include "node/project/sequence/sequence.h" #include "task/taskmanager.h" #include "ui/icons/icons.h" #include "widget/timeruler/timeruler.h" namespace olive { #define super QDialog ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) : super(parent), viewer_node_(viewer_node), stills_only_mode_(stills_only_mode), loading_presets_(false) { QHBoxLayout* layout = new QHBoxLayout(this); QSplitter* splitter = new QSplitter(Qt::Horizontal); splitter->setChildrenCollapsible(false); layout->addWidget(splitter); preferences_area_ = new QWidget(); QGridLayout* preferences_layout = new QGridLayout(preferences_area_); preferences_layout->setContentsMargins(0, 0, 0, 0); int row = 0; QLabel* fn_lbl = new QLabel(tr("Filename:")); fn_lbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(fn_lbl, row, 0); filename_edit_ = new QLineEdit(); preferences_layout->addWidget(filename_edit_, row, 1, 1, 2); QPushButton* file_browse_btn = new QPushButton(); file_browse_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); file_browse_btn->setIcon(icon::Folder); file_browse_btn->setToolTip(tr("Browse for exported file filename")); connect(file_browse_btn, &QPushButton::clicked, this, &ExportDialog::BrowseFilename); preferences_layout->addWidget(file_browse_btn, row, 3); row++; QLabel* preset_lbl = new QLabel(tr("Preset:")); preset_lbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(preset_lbl, row, 0); preset_combobox_ = new QComboBox(); LoadPresets(); connect(preset_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportDialog::PresetComboBoxChanged); preferences_layout->addWidget(preset_combobox_, row, 1, 1, 2); /*QPushButton* preset_load_btn = new QPushButton(); preset_load_btn->setIcon(icon::Open); preset_load_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(preset_load_btn, row, 2);*/ QPushButton* preset_save_btn = new QPushButton(); preset_save_btn->setIcon(icon::Save); preset_save_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); preferences_layout->addWidget(preset_save_btn, row, 3); connect(preset_save_btn, &QPushButton::clicked, this, &ExportDialog::SavePreset); row++; preferences_layout->addWidget(QtUtils::CreateHorizontalLine(), row, 0, 1, 4); row++; preferences_layout->addWidget(new QLabel(tr("Range:")), row, 0); range_combobox_ = new QComboBox(); range_combobox_->addItem(tr("Entire Sequence")); range_combobox_->addItem(tr("In to Out")); range_combobox_->setEnabled(viewer_node_->GetWorkArea()->enabled()); preferences_layout->addWidget(range_combobox_, row, 1, 1, 3); row++; preferences_layout->addWidget(QtUtils::CreateHorizontalLine(), row, 0, 1, 4); row++; preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0); format_combobox_ = new ExportFormatComboBox(); preferences_layout->addWidget(format_combobox_, row, 1, 1, 3); row++; QHBoxLayout* av_enabled_layout = new QHBoxLayout(); video_enabled_ = new QCheckBox(tr("Export Video")); av_enabled_layout->addWidget(video_enabled_); audio_enabled_ = new QCheckBox(tr("Export Audio")); av_enabled_layout->addWidget(audio_enabled_); subtitles_enabled_ = new QCheckBox(tr("Export Subtitles")); av_enabled_layout->addWidget(subtitles_enabled_); preferences_layout->addLayout(av_enabled_layout, row, 0, 1, 4); row++; preferences_tabs_ = new QTabWidget(); color_manager_ = viewer_node_->project()->color_manager(); video_tab_ = new ExportVideoTab(color_manager_); AddPreferencesTab(video_tab_, tr("Video")); // Set video tab time and make connections connect(viewer_node, &ViewerOutput::PlayheadChanged, video_tab_, &ExportVideoTab::SetTime); connect(video_tab_, &ExportVideoTab::TimeChanged, viewer_node, &ViewerOutput::SetPlayhead); video_tab_->SetTime(viewer_node->GetPlayhead()); audio_tab_ = new ExportAudioTab(); AddPreferencesTab(audio_tab_, tr("Audio")); subtitle_tab_ = new ExportSubtitlesTab(); AddPreferencesTab(subtitle_tab_, tr("Subtitles")); preferences_layout->addWidget(preferences_tabs_, row, 0, 1, 4); row++; { QGroupBox *options_group = new QGroupBox(); preferences_layout->addWidget(options_group, row, 0, 1, 4); QGridLayout *options_layout = new QGridLayout(options_group); int opt_row = 0; export_bkg_box_ = new QCheckBox(tr("Run In Background")); export_bkg_box_->setToolTip(tr("Exporting in the background allows you to continue using Olive while " "exporting, but may result in slower export speeds, and may" "severely impact editing and playback performance.")); options_layout->addWidget(export_bkg_box_, opt_row, 0); import_file_after_export_ = new QCheckBox(tr("Import Result After Export")); options_layout->addWidget(import_file_after_export_, opt_row, 1); connect(export_bkg_box_, &QCheckBox::toggled, import_file_after_export_, [this](bool e){ import_file_after_export_->setEnabled(!e); }); } row++; QHBoxLayout *btn_layout = new QHBoxLayout(); btn_layout->setContentsMargins(0, 0, 0, 0); preferences_layout->addLayout(btn_layout, row, 0, 1, 4); btn_layout->addStretch(); QPushButton *export_btn = new QPushButton(tr("Export")); btn_layout->addWidget(export_btn); connect(export_btn, &QPushButton::clicked, this, &ExportDialog::StartExport); QPushButton *cancel_btn = new QPushButton(tr("Cancel")); btn_layout->addWidget(cancel_btn); connect(cancel_btn, &QPushButton::clicked, this, &ExportDialog::reject); btn_layout->addStretch(); splitter->addWidget(preferences_area_); QWidget* preview_area = new QWidget(); QVBoxLayout* preview_layout = new QVBoxLayout(preview_area); preview_layout->addWidget(new QLabel(tr("Preview"))); preview_viewer_ = new ViewerWidget(); preview_viewer_->ruler()->SetMarkerEditingEnabled(false); preview_viewer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); preview_layout->addWidget(preview_viewer_); splitter->addWidget(preview_area); // Prioritize preview area splitter->setSizes({1, 99999}); // Set default filename SetDefaultFilename(); // Set defaults previously_selected_format_ = ExportFormat::kFormatMPEG4Video; connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged); VideoParams vp = viewer_node_->GetVideoParams(); video_aspect_ratio_ = static_cast(vp.width()) / static_cast(vp.height()); connect(video_tab_->width_slider(), &IntegerSlider::ValueChanged, this, &ExportDialog::ResolutionChanged); connect(video_tab_->height_slider(), &IntegerSlider::ValueChanged, this, &ExportDialog::ResolutionChanged); connect(video_tab_->scaling_method_combobox(), static_cast(&QComboBox::currentIndexChanged), this, &ExportDialog::UpdateViewerDimensions); connect(video_tab_->maintain_aspect_checkbox(), &QCheckBox::toggled, this, &ExportDialog::ResolutionChanged); connect(video_tab_, &ExportVideoTab::ColorSpaceChanged, preview_viewer_, static_cast(&ViewerWidget::SetColorTransform)); connect(video_tab_, &ExportVideoTab::ImageSequenceCheckBoxChanged, this, &ExportDialog::ImageSequenceCheckBoxChanged); // We don't check if the codec supports subtitles because we can always export to a sidecar file bool has_subtitle_tracks = SequenceHasSubtitles(); connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); subtitles_enabled_->setEnabled(has_subtitle_tracks); // If the viewer already has cached params, use them if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { // This will automatically set the param data QtUtils::SetComboBoxData(preset_combobox_, kPresetLastUsed); } else { SetDefaults(); } // Set viewer to view the node and set its colorspace preview_viewer_->ConnectViewerNode(viewer_node_); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); qApp->installEventFilter(this); connect(video_enabled_, &QCheckBox::toggled, video_tab_, &QWidget::setEnabled); video_tab_->setEnabled(video_enabled_->isChecked()); connect(audio_enabled_, &QCheckBox::toggled, audio_tab_, &QWidget::setEnabled); audio_tab_->setEnabled(audio_enabled_->isChecked()); connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); subtitle_tab_->setEnabled(subtitles_enabled_->isChecked()); } rational ExportDialog::GetSelectedTimebase() const { return video_tab_->GetSelectedFrameRate().flipped(); } void ExportDialog::SetSelectedTimebase(const rational &r) { video_tab_->SetSelectedFrameRate(r.flipped()); } void ExportDialog::StartExport() { if (!video_enabled_->isChecked() && !audio_enabled_->isChecked() && !subtitles_enabled_->isChecked()) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid parameters"), tr("Video, audio, and subtitles are disabled. There's nothing to export.")); return; } // Validate if the entered filename contains the correct extension (the extension is necessary // for both FFmpeg and OIIO to determine the output format) QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(format_combobox_->GetFormat())); QString proposed_filename = filename_edit_->text().trimmed(); // If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export. if (!proposed_filename.endsWith(necessary_ext, Qt::CaseInsensitive)) { if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Invalid filename"), tr("The filename must contain the extension \"%1\". Would you like to append it " "automatically?").arg(necessary_ext), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { filename_edit_->setText(proposed_filename.append(necessary_ext)); } else { return; } } // Validate the intended path QFileInfo file_info(proposed_filename); QFileInfo dir_info(file_info.path()); // If the directory does not exist, try to create it QDir dest_dir(file_info.path()); if (!FileFunctions::DirectoryIsValid(dest_dir)) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Failed to create output directory"), tr("The intended output directory doesn't exist and Olive couldn't create it. " "Please choose a different filename.")); return; } // Validate if this is an image sequence and if the filename contains enough digits if (video_tab_->IsImageSequenceSet()) { // Ensure filename contains digits if (!Encoder::FilenameContainsDigitPlaceholder(proposed_filename)) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"), tr("Export is set to an image sequence, but the filename does not have a section for digits " "(formatted as [#####] where the amount of # is the amount of digits).")); return; } int64_t frame_count = GetExportLengthInTimebaseUnits(); int64_t needed_digit_count = GetDigitCount(frame_count); int current_digit_count = Encoder::GetImageSequencePlaceholderDigitCount(proposed_filename); if (current_digit_count < needed_digit_count) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"), tr("Filename doesn't contain enough digits for the amount of frames " "this export will need (need %1 for %n frame(s)).", nullptr, frame_count) .arg(QString::number(needed_digit_count))); return; } } // Validate if the file exists and whether the user wishes to overwrite it if (file_info.exists()) { if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Confirm Overwrite"), tr("The file \"%1\" already exists. Do you want to overwrite it?") .arg(proposed_filename), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { return; } } // Validate video resolution if (video_enabled_->isChecked() && (video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264 || video_tab_->GetSelectedCodec() == ExportCodec::kCodecH265) && (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid Parameters"), tr("Width and height must be multiples of 2.")); return; } ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams()); if (export_bkg_box_->isChecked()) { // Send to TaskManager to export in background TaskManager::instance()->AddTask(task); this->accept(); } else { // Use modal dialog box TaskDialog* td = new TaskDialog(task, tr("Export"), this); connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished); td->open(); } } void ExportDialog::ExportFinished() { TaskDialog* td = static_cast(sender()); if (td->GetTask()->IsCancelled()) { // If this task was cancelled, we stay open so the user can potentially queue another export } else { // Accept this dialog and close if (import_file_after_export_->isEnabled() && import_file_after_export_->isChecked()) { QString filename = filename_edit_->text().trimmed(); emit RequestImportFile(filename); } this->accept(); } } void ExportDialog::ImageSequenceCheckBoxChanged(bool e) { QFileInfo current_fileinfo(filename_edit_->text()); QString basename = current_fileinfo.completeBaseName(); QString suffix = current_fileinfo.suffix(); if (e) { if (!Encoder::FilenameContainsDigitPlaceholder(basename)) { basename.append(QStringLiteral("_[#####]")); } } else { basename = Encoder::FilenameRemoveDigitPlaceholder(basename); } // Set filename if (!suffix.isEmpty()) { basename.append('.'); basename.append(suffix); } filename_edit_->setText(current_fileinfo.dir().filePath(basename)); } void ExportDialog::SavePreset() { ExportSavePresetDialog d(GenerateParams(), this); if (d.exec() == QDialog::Accepted) { LoadPresets(); preset_combobox_->setCurrentText(d.GetSelectedPresetName()); } } void ExportDialog::PresetComboBoxChanged() { if (loading_presets_) { return; } QComboBox *c = static_cast(sender()); int preset_number = c->currentData().toInt(); if (preset_number == kPresetDefault) { SetDefaults(); } else if (preset_number == kPresetLastUsed) { SetParams(viewer_node_->GetLastUsedEncodingParams()); } else { SetParams(presets_.at(preset_number)); } } void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title) { QScrollArea* scroll_area = new QScrollArea(); scroll_area->setWidgetResizable(true); scroll_area->setWidget(inner_widget); preferences_tabs_->addTab(scroll_area, title); } void ExportDialog::BrowseFilename() { ExportFormat::Format f = format_combobox_->GetFormat(); QString browsed_fn = QFileDialog::getSaveFileName(this, "", filename_edit_->text().trimmed(), QStringLiteral("%1 (*.%2)").arg(ExportFormat::GetName(f), ExportFormat::GetExtension(f)), nullptr, // We don't confirm overwrite here because we do it later QFileDialog::DontConfirmOverwrite); if (!browsed_fn.isEmpty()) { filename_edit_->setText(browsed_fn); } } void ExportDialog::FormatChanged(ExportFormat::Format current_format) { QString current_filename = filename_edit_->text().trimmed(); QString previously_selected_ext = ExportFormat::GetExtension(previously_selected_format_); QString currently_selected_ext = ExportFormat::GetExtension(current_format); // If the previous extension was added, remove it if (current_filename.endsWith(previously_selected_ext, Qt::CaseInsensitive)) { current_filename.resize(current_filename.size() - previously_selected_ext.size() - 1); } // Add the extension and set it current_filename.append('.'); current_filename.append(currently_selected_ext); filename_edit_->setText(current_filename); previously_selected_format_ = current_format; // Update video and audio comboboxes bool has_video_codecs = video_tab_->SetFormat(current_format); video_enabled_->setChecked(has_video_codecs); video_enabled_->setEnabled(has_video_codecs); bool has_audio_codecs = audio_tab_->SetFormat(current_format); audio_enabled_->setChecked(has_audio_codecs); audio_enabled_->setEnabled(has_audio_codecs); if (subtitles_enabled_->isEnabled()) { subtitle_tab_->SetFormat(current_format); } } void ExportDialog::ResolutionChanged() { if (video_tab_->maintain_aspect_checkbox()->isChecked()) { // Keep aspect ratio maintained if (sender() == video_tab_->height_slider()) { // Convert height to float double new_width = video_tab_->height_slider()->GetValue(); // Generate width from aspect ratio new_width *= video_aspect_ratio_; // Align to even number and set video_tab_->width_slider()->SetValue(new_width); } else { // Convert width to float double new_height = video_tab_->width_slider()->GetValue(); // Generate height from aspect ratio new_height /= video_aspect_ratio_; // Align to even number and set video_tab_->height_slider()->SetValue(new_height); } } UpdateViewerDimensions(); } void ExportDialog::LoadPresets() { loading_presets_ = true; preset_combobox_->clear(); presets_.clear(); preset_combobox_->addItem(tr("Default"), kPresetDefault); if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { preset_combobox_->addItem(tr("Last Used"), kPresetLastUsed); } preset_combobox_->insertSeparator(preset_combobox_->count()); QStringList l = EncodingParams::GetListOfPresets(); presets_.reserve(l.size()); for (const QString &preset : l) { EncodingParams p; QFile f(EncodingParams::GetPresetPath().filePath(preset)); if (f.open(QFile::ReadOnly)) { if (p.Load(&f)) { preset_combobox_->addItem(preset, int(presets_.size())); presets_.push_back(p); } f.close(); } } loading_presets_ = false; } void ExportDialog::SetDefaultFilename() { Project* p = viewer_node_->project(); QDir doc_location; if (p->filename().isEmpty()) { doc_location.setPath(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); } else { doc_location = QFileInfo(p->filename()).dir(); } QString file_location = doc_location.filePath(viewer_node_->GetLabel()); filename_edit_->setText(file_location); } bool ExportDialog::SequenceHasSubtitles() const { if (Sequence *s = dynamic_cast(viewer_node_)) { TrackList *tl = s->track_list(Track::kSubtitle); for (Track *t : tl->GetTracks()) { if (!t->IsMuted() && !t->Blocks().empty()) { return true; } } } return false; } void ExportDialog::SetDefaults() { if (!stills_only_mode_) { format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); } else { format_combobox_->SetFormat(ExportFormat::kFormatPNG); } FormatChanged(format_combobox_->GetFormat()); VideoParams vp = viewer_node_->GetVideoParams(); AudioParams ap = viewer_node_->GetAudioParams(); video_tab_->width_slider()->SetValue(vp.width()); video_tab_->width_slider()->SetDefaultValue(vp.width()); video_tab_->height_slider()->SetValue(vp.height()); video_tab_->height_slider()->SetDefaultValue(vp.height()); video_tab_->SetSelectedFrameRate(vp.frame_rate()); video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio()); video_tab_->pixel_format_field()->SetPixelFormat(static_cast(OLIVE_CONFIG("OnlinePixelFormat").toInt())); video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing()); audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate()); audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false); audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout()); subtitles_enabled_->setChecked(SequenceHasSubtitles()); subtitle_tab_->SetSidecarFormat(ExportFormat::kFormatSRT); } EncodingParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue()), GetSelectedTimebase(), video_tab_->pixel_format_field()->GetPixelFormat(), VideoParams::kInternalChannelCount, video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(), video_tab_->interlaced_combobox()->GetInterlaceMode(), 1); AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->GetSampleRate(), audio_tab_->channel_layout_combobox()->GetChannelLayout(), audio_tab_->sample_format_combobox()->GetSampleFormat()); EncodingParams params; params.set_format(format_combobox_->GetFormat()); params.SetFilename(filename_edit_->text().trimmed()); params.SetExportLength(viewer_node_->GetLength()); if (ExportCodec::IsCodecAStillImage(video_tab_->GetSelectedCodec()) && !video_tab_->IsImageSequenceSet()) { // Exporting as image without exporting image sequence, only export one frame rational export_time = video_tab_->GetStillImageTime(); params.set_custom_range(TimeRange(export_time, export_time + GetSelectedTimebase())); } else if (range_combobox_->currentIndex() == kRangeInToOut) { // Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor params.set_custom_range(viewer_node_->GetWorkArea()->range()); } if (video_tab_->scaling_method_combobox()->isEnabled()) { params.set_video_scaling_method(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt())); } if (video_enabled_->isChecked()) { ExportCodec::Codec video_codec = video_tab_->GetSelectedCodec(); video_render_params.set_color_range(video_tab_->color_range()); params.EnableVideo(video_render_params, video_codec); params.set_video_threads(video_tab_->threads()); if (video_tab_->isVisible()) { video_tab_->GetCodecSection()->AddOpts(¶ms); } params.set_color_transform(video_tab_->CurrentOCIOColorSpace()); params.set_video_pix_fmt(video_tab_->pix_fmt()); params.set_video_is_image_sequence(video_tab_->IsImageSequenceSet()); } if (audio_enabled_->isChecked()) { ExportCodec::Codec audio_codec = audio_tab_->GetCodec(); params.EnableAudio(audio_render_params, audio_codec); params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000); } if (subtitles_enabled_->isEnabled() && subtitles_enabled_->isChecked()) { if (!subtitle_tab_->GetSidecarEnabled()) { // Export subtitles embedded in container params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); } else { // Export subtitles to a sidecar file params.EnableSidecarSubtitles(subtitle_tab_->GetSidecarFormat(), subtitle_tab_->GetSubtitleCodec()); } } return params; } void ExportDialog::SetParams(const EncodingParams &e) { format_combobox_->SetFormat(e.format()); FormatChanged(format_combobox_->GetFormat()); if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) { range_combobox_->setCurrentIndex(kRangeInToOut); } QtUtils::SetComboBoxData(video_tab_->scaling_method_combobox(), e.video_scaling_method()); video_enabled_->setChecked(e.video_enabled()); if (e.video_enabled()) { video_tab_->width_slider()->SetValue(e.video_params().width()); video_tab_->height_slider()->SetValue(e.video_params().height()); SetSelectedTimebase(e.video_params().time_base()); video_tab_->pixel_format_field()->SetPixelFormat(e.video_params().format()); video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(e.video_params().pixel_aspect_ratio()); video_tab_->interlaced_combobox()->SetInterlaceMode(e.video_params().interlacing()); video_tab_->SetSelectedCodec(e.video_codec()); video_tab_->SetColorRange(e.video_params().color_range()); video_tab_->SetThreads(e.video_threads()); if (video_tab_->isVisible()) { video_tab_->GetCodecSection()->SetOpts(&e); } video_tab_->SetOCIOColorSpace(e.color_transform().output()); video_tab_->SetPixFmt(e.video_pix_fmt()); video_tab_->SetImageSequence(e.video_is_image_sequence()); } audio_enabled_->setChecked(e.audio_enabled()); if (e.audio_enabled()) { audio_tab_->sample_rate_combobox()->SetSampleRate(e.audio_params().sample_rate()); audio_tab_->channel_layout_combobox()->SetChannelLayout(e.audio_params().channel_layout()); audio_tab_->sample_format_combobox()->SetSampleFormat(e.audio_params().format()); audio_tab_->SetCodec(e.audio_codec()); audio_tab_->bit_rate_slider()->SetValue(e.audio_bit_rate() / 1000); } if (subtitles_enabled_->isEnabled()) { subtitles_enabled_->setChecked(e.subtitles_enabled()); subtitle_tab_->SetSidecarEnabled(e.subtitles_are_sidecar()); if (e.subtitles_enabled()) { subtitle_tab_->SetSubtitleCodec(e.subtitles_codec()); if (e.subtitles_are_sidecar()) { subtitle_tab_->SetSidecarFormat(e.subtitle_sidecar_fmt()); } } } } bool ExportDialog::eventFilter(QObject *o, QEvent *e) { // Any parameters in scrollable areas, ignore wheel events so the user doesn't unwittingly change // them while trying to scroll through the pages if (e->type() == QEvent::Wheel) { while ((o = o->parent())) { if (o == video_tab_ || o == audio_tab_ || o == subtitle_tab_) { e->ignore(); return true; } } } return super::eventFilter(o, e); } void ExportDialog::done(int r) { preview_viewer_->ConnectViewerNode(nullptr); if (!stills_only_mode_) { viewer_node_->SetLastUsedEncodingParams(GenerateParams()); } super::done(r); } rational ExportDialog::GetExportLength() const { if (range_combobox_->currentIndex() == kRangeInToOut) { return viewer_node_->GetWorkArea()->range().length(); } else { return viewer_node_->GetLength(); } } int64_t ExportDialog::GetExportLengthInTimebaseUnits() const { return Timecode::time_to_timestamp(GetExportLength(), GetSelectedTimebase()); } void ExportDialog::UpdateViewerDimensions() { preview_viewer_->SetViewerResolution(static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue())); VideoParams vp = viewer_node_->GetVideoParams(); QMatrix4x4 transform = EncodingParams::GenerateMatrix( static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), vp.width(), vp.height(), static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue()) ); preview_viewer_->SetMatrix(transform); } } ================================================ FILE: app/dialog/export/export.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTDIALOG_H #define EXPORTDIALOG_H #include #include #include #include #include #include "codec/exportcodec.h" #include "codec/exportformat.h" #include "dialog/export/exportformatcombobox.h" #include "exportaudiotab.h" #include "exportsubtitlestab.h" #include "exportvideotab.h" #include "task/export/export.h" #include "widget/nodeparamview/nodeparamviewwidgetbridge.h" #include "widget/viewer/viewer.h" namespace olive { class ExportDialog : public QDialog { Q_OBJECT public: ExportDialog(ViewerOutput* viewer_node, bool stills_only_mode, QWidget* parent = nullptr); ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr) : ExportDialog(viewer_node, false, parent) {} rational GetSelectedTimebase() const; void SetSelectedTimebase(const rational &r); EncodingParams GenerateParams() const; void SetParams(const EncodingParams &e); virtual bool eventFilter(QObject *o, QEvent *e) override; public slots: virtual void done(int r) override; signals: void RequestImportFile(const QString &s); private: void AddPreferencesTab(QWidget *inner_widget, const QString &title); void LoadPresets(); void SetDefaultFilename(); bool SequenceHasSubtitles() const; void SetDefaults(); ViewerOutput* viewer_node_; ExportFormat::Format previously_selected_format_; rational GetExportLength() const; int64_t GetExportLengthInTimebaseUnits() const; enum RangeSelection { kRangeEntireSequence, kRangeInToOut }; enum AutoPreset { kPresetDefault = -1, kPresetLastUsed = -2, }; QTabWidget* preferences_tabs_; QComboBox* preset_combobox_; QComboBox* range_combobox_; std::vector presets_; QCheckBox* video_enabled_; QCheckBox* audio_enabled_; QCheckBox* subtitles_enabled_; ViewerWidget* preview_viewer_; QLineEdit* filename_edit_; ExportFormatComboBox* format_combobox_; ExportVideoTab* video_tab_; ExportAudioTab* audio_tab_; ExportSubtitlesTab* subtitle_tab_; double video_aspect_ratio_; ColorManager* color_manager_; QWidget* preferences_area_; QCheckBox *export_bkg_box_; QCheckBox *import_file_after_export_; bool stills_only_mode_; bool loading_presets_; private slots: void BrowseFilename(); void FormatChanged(ExportFormat::Format current_format); void ResolutionChanged(); void UpdateViewerDimensions(); void StartExport(); void ExportFinished(); void ImageSequenceCheckBoxChanged(bool e); void SavePreset(); void PresetComboBoxChanged(); }; } #endif // EXPORTDIALOG_H ================================================ FILE: app/dialog/export/exportadvancedvideodialog.cpp ================================================ #include "exportadvancedvideodialog.h" #include #include #include #include namespace olive { ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList &pix_fmts, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Advanced")); QVBoxLayout* layout = new QVBoxLayout(this); { // Pixel Settings QGroupBox* pixel_group = new QGroupBox(); layout->addWidget(pixel_group); pixel_group->setTitle(tr("Pixel")); QGridLayout* pixel_layout = new QGridLayout(pixel_group); int row = 0; pixel_layout->addWidget(new QLabel(tr("Pixel Format:")), row, 0); pixel_format_combobox_ = new QComboBox(); pixel_format_combobox_->addItems(pix_fmts); pixel_layout->addWidget(pixel_format_combobox_, row, 1); row++; pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0); yuv_color_range_combobox_ = new QComboBox(); yuv_color_range_combobox_->addItems({tr("Limited (16-235)"), tr("Full (0-255)")}); pixel_layout->addWidget(yuv_color_range_combobox_, row, 1); } { // Performance Settings QGroupBox* performance_group = new QGroupBox(); layout->addWidget(performance_group); performance_group->setTitle(tr("Performance")); QGridLayout* performance_layout = new QGridLayout(performance_group); int row = 0; performance_layout->addWidget(new QLabel(tr("Threads:")), row, 0); thread_slider_ = new IntegerSlider(); thread_slider_->SetMinimum(0); thread_slider_->SetDefaultValue(0); thread_slider_->InsertLabelSubstitution(0, tr("Auto")); performance_layout->addWidget(thread_slider_, row, 1); row++; } QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &ExportAdvancedVideoDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &ExportAdvancedVideoDialog::reject); layout->addWidget(buttons); } } ================================================ FILE: app/dialog/export/exportadvancedvideodialog.h ================================================ #ifndef EXPORTADVANCEDVIDEODIALOG_H #define EXPORTADVANCEDVIDEODIALOG_H #include #include #include "codec/encoder.h" #include "widget/slider/integerslider.h" namespace olive { class ExportAdvancedVideoDialog : public QDialog { Q_OBJECT public: ExportAdvancedVideoDialog(const QList& pix_fmts, QWidget* parent = nullptr); int threads() const { return static_cast(thread_slider_->GetValue()); } void set_threads(int t) { thread_slider_->SetValue(t); } QString pix_fmt() const { return pixel_format_combobox_->currentText(); } void set_pix_fmt(const QString& s) { pixel_format_combobox_->setCurrentText(s); } VideoParams::ColorRange yuv_range() const { return static_cast(yuv_color_range_combobox_->currentIndex()); } void set_yuv_range(VideoParams::ColorRange i) { yuv_color_range_combobox_->setCurrentIndex(i); } private: IntegerSlider* thread_slider_; QComboBox* pixel_format_combobox_; QComboBox* yuv_color_range_combobox_; }; } #endif // EXPORTADVANCEDVIDEODIALOG_H ================================================ FILE: app/dialog/export/exportaudiotab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportaudiotab.h" #include #include #include "core.h" namespace olive { const int ExportAudioTab::kDefaultBitRate = 320; ExportAudioTab::ExportAudioTab(QWidget* parent) : QWidget(parent) { QVBoxLayout* outer_layout = new QVBoxLayout(this); QGridLayout* layout = new QGridLayout(); outer_layout->addLayout(layout); int row = 0; layout->addWidget(new QLabel(tr("Codec:")), row, 0); codec_combobox_ = new QComboBox(); connect(codec_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateSampleFormats); connect(codec_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportAudioTab::UpdateBitRateEnabled); layout->addWidget(codec_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0); sample_rate_combobox_ = new SampleRateComboBox(); layout->addWidget(sample_rate_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Channel Layout:")), row, 0); channel_layout_combobox_ = new ChannelLayoutComboBox(); layout->addWidget(channel_layout_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Format:")), row, 0); sample_format_combobox_ = new SampleFormatComboBox(); layout->addWidget(sample_format_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Bit Rate:")), row, 0); bit_rate_slider_ = new IntegerSlider(); bit_rate_slider_->SetMinimum(32); bit_rate_slider_->SetMaximum(320); bit_rate_slider_->SetValue(kDefaultBitRate); bit_rate_slider_->SetFormat(tr("%1 kbps")); layout->addWidget(bit_rate_slider_, row, 1); outer_layout->addStretch(); } int ExportAudioTab::SetFormat(ExportFormat::Format format) { QList acodecs = ExportFormat::GetAudioCodecs(format); setEnabled(!acodecs.isEmpty()); codec_combobox_->blockSignals(true); codec_combobox_->clear(); foreach (ExportCodec::Codec acodec, acodecs) { codec_combobox_->addItem(ExportCodec::GetCodecName(acodec), acodec); } codec_combobox_->blockSignals(false); fmt_ = format; UpdateSampleFormats(); UpdateBitRateEnabled(); return acodecs.size(); } void ExportAudioTab::UpdateSampleFormats() { auto fmts = ExportFormat::GetSampleFormatsForCodec(fmt_, GetCodec()); sample_format_combobox_->SetAvailableFormats(fmts); } void ExportAudioTab::UpdateBitRateEnabled() { bool uses_bitrate = !ExportCodec::IsCodecLossless(GetCodec()); bit_rate_slider_->setEnabled(uses_bitrate); if (!uses_bitrate) { bit_rate_slider_->SetTristate(); } else { bit_rate_slider_->SetValue(kDefaultBitRate ); } } } ================================================ FILE: app/dialog/export/exportaudiotab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTAUDIOTAB_H #define EXPORTAUDIOTAB_H #include #include #include "common/define.h" #include "codec/exportformat.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" namespace olive { class ExportAudioTab : public QWidget { Q_OBJECT public: ExportAudioTab(QWidget* parent = nullptr); ExportCodec::Codec GetCodec() const { return static_cast(codec_combobox_->currentData().toInt()); } void SetCodec(ExportCodec::Codec c) { for (int i=0; icount(); i++) { if (codec_combobox_->itemData(i) == c) { codec_combobox_->setCurrentIndex(i); break; } } } SampleRateComboBox* sample_rate_combobox() const { return sample_rate_combobox_; } SampleFormatComboBox* sample_format_combobox() const { return sample_format_combobox_; } ChannelLayoutComboBox* channel_layout_combobox() const { return channel_layout_combobox_; } IntegerSlider* bit_rate_slider() const { return bit_rate_slider_; } public slots: int SetFormat(ExportFormat::Format format); private: ExportFormat::Format fmt_; QComboBox* codec_combobox_; SampleRateComboBox* sample_rate_combobox_; ChannelLayoutComboBox* channel_layout_combobox_; SampleFormatComboBox *sample_format_combobox_; IntegerSlider* bit_rate_slider_; static const int kDefaultBitRate; private slots: void UpdateSampleFormats(); void UpdateBitRateEnabled(); }; } #endif // EXPORTAUDIOTAB_H ================================================ FILE: app/dialog/export/exportformatcombobox.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportformatcombobox.h" #include #include #include "ui/icons/icons.h" namespace olive { ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) : QComboBox(parent) { custom_menu_ = new Menu(this); // Populate combobox formats switch (mode) { case kShowAllFormats: custom_menu_->addAction(CreateHeader(icon::Video, tr("Video"))); PopulateType(Track::kVideo); custom_menu_->addSeparator(); custom_menu_->addAction(CreateHeader(icon::Audio, tr("Audio"))); PopulateType(Track::kAudio); custom_menu_->addSeparator(); custom_menu_->addAction(CreateHeader(icon::Subtitles, tr("Subtitle"))); PopulateType(Track::kSubtitle); break; case kShowAudioOnly: PopulateType(Track::kAudio); break; case kShowVideoOnly: PopulateType(Track::kVideo); break; case kShowSubtitlesOnly: PopulateType(Track::kSubtitle); break; } connect(custom_menu_, &Menu::triggered, this, &ExportFormatComboBox::HandleIndexChange); } void ExportFormatComboBox::showPopup() { custom_menu_->setMinimumWidth(this->width()); custom_menu_->exec(mapToGlobal(QPoint(0, 0))); } void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt) { current_ = fmt; clear(); addItem(ExportFormat::GetName(current_)); } void ExportFormatComboBox::HandleIndexChange(QAction *a) { ExportFormat::Format f = static_cast(a->data().toInt()); SetFormat(f); emit FormatChanged(f); } void ExportFormatComboBox::PopulateType(Track::Type type) { for (int i=0; i(i); if (type == Track::kVideo && !ExportFormat::GetVideoCodecs(f).isEmpty()) { // Do nothing } else if (type == Track::kAudio && ExportFormat::GetVideoCodecs(f).isEmpty() && !ExportFormat::GetAudioCodecs(f).isEmpty()) { // Do nothing } else if (type == Track::kSubtitle && ExportFormat::GetVideoCodecs(f).isEmpty() && ExportFormat::GetAudioCodecs(f).isEmpty() && !ExportFormat::GetSubtitleCodecs(f).isEmpty()) { // Do nothing } else { continue; } QString format_name = ExportFormat::GetName(f); QAction *a = custom_menu_->addAction(format_name); a->setData(i); a->setIconVisibleInMenu(false); } } QWidgetAction *ExportFormatComboBox::CreateHeader(const QIcon &icon, const QString &title) { QWidgetAction *a = new QWidgetAction(this); QWidget *w = new QWidget(); QHBoxLayout *layout = new QHBoxLayout(w); QLabel *icon_lbl = new QLabel(); QLabel *text_lbl = new QLabel(title); text_lbl->setAlignment(Qt::AlignCenter); QFont f = text_lbl->font(); f.setWeight(QFont::Bold); text_lbl->setFont(f); icon_lbl->setPixmap(icon.pixmap(text_lbl->sizeHint())); layout->addStretch(); layout->addWidget(icon_lbl); layout->addWidget(text_lbl); layout->addStretch(); a->setDefaultWidget(w); a->setEnabled(false); return a; } } ================================================ FILE: app/dialog/export/exportformatcombobox.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTFORMATCOMBOBOX_H #define EXPORTFORMATCOMBOBOX_H #include #include #include "codec/exportformat.h" #include "node/output/track/track.h" #include "widget/menu/menu.h" namespace olive { class ExportFormatComboBox : public QComboBox { Q_OBJECT public: enum Mode { kShowAllFormats, kShowAudioOnly, kShowVideoOnly, kShowSubtitlesOnly }; ExportFormatComboBox(Mode mode, QWidget *parent = nullptr); ExportFormatComboBox(QWidget *parent = nullptr) : ExportFormatComboBox(kShowAllFormats, parent) {} ExportFormat::Format GetFormat() const { return current_; } void showPopup(); signals: void FormatChanged(ExportFormat::Format fmt); public slots: void SetFormat(ExportFormat::Format fmt); private slots: void HandleIndexChange(QAction *a); private: void PopulateType(Track::Type type); QWidgetAction *CreateHeader(const QIcon &icon, const QString &title); Menu *custom_menu_; ExportFormat::Format current_; }; } #endif // EXPORTFORMATCOMBOBOX_H ================================================ FILE: app/dialog/export/exportsavepresetdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportsavepresetdialog.h" #include #include #include #include namespace olive { ExportSavePresetDialog::ExportSavePresetDialog(const EncodingParams &p, QWidget *parent) : QDialog(parent), params_(p) { auto layout = new QVBoxLayout(this); name_edit_ = new QLineEdit(); // Populate existing list QStringList l = EncodingParams::GetListOfPresets(); if (!l.empty()) { auto list_widget_ = new QListWidget(); for (const QString &f : l) { list_widget_->addItem(f); } connect(list_widget_, &QListWidget::currentTextChanged, name_edit_, &QLineEdit::setText); layout->addWidget(list_widget_); } auto name_layout = new QHBoxLayout(); layout->addLayout(name_layout); name_layout->addWidget(new QLabel(tr("Name:"))); name_edit_->setFocus(); name_layout->addWidget(name_edit_); auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(btns, &QDialogButtonBox::accepted, this, &ExportSavePresetDialog::accept); connect(btns, &QDialogButtonBox::rejected, this, &ExportSavePresetDialog::reject); layout->addWidget(btns); setWindowTitle(tr("Save Export Preset")); } void ExportSavePresetDialog::accept() { if (name_edit_->text().isEmpty()) { QMessageBox::critical(this, tr("Invalid Name"), tr("You must enter a name to save an export preset.")); return; } QDir d(EncodingParams::GetPresetPath()); if (!d.exists()) { d.mkpath(QStringLiteral(".")); } QFile f(d.filePath(name_edit_->text())); if (f.exists()) { if (QMessageBox::question(this, tr("Overwrite Preset"), tr("A preset with the name \"%1\" already exists. Do you wish to overwrite it?").arg(name_edit_->text()), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { return; } } if (!f.open(QFile::WriteOnly)) { QMessageBox::critical(this, tr("Write Error"), tr("Failed to open file \"%1\" for writing.").arg(f.fileName())); return; } params_.Save(&f); f.close(); QDialog::accept(); } } ================================================ FILE: app/dialog/export/exportsavepresetdialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTSAVEPRESETDIALOG_H #define EXPORTSAVEPRESETDIALOG_H #include #include #include #include "codec/encoder.h" namespace olive { class ExportSavePresetDialog : public QDialog { Q_OBJECT public: ExportSavePresetDialog(const EncodingParams &p, QWidget *parent = nullptr); QString GetSelectedPresetName() const { return name_edit_->text(); } public slots: virtual void accept() override; private: QLineEdit *name_edit_; EncodingParams params_; }; } #endif // EXPORTSAVEPRESETDIALOG_H ================================================ FILE: app/dialog/export/exportsubtitlestab.cpp ================================================ #include "exportsubtitlestab.h" #include namespace olive { ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) : QWidget(parent) { QVBoxLayout* outer_layout = new QVBoxLayout(this); QGridLayout* layout = new QGridLayout(); outer_layout->addLayout(layout); int row = 0; sidecar_checkbox_ = new QCheckBox(tr("Export to sidecar file")); layout->addWidget(sidecar_checkbox_, row, 0, 1, 2); row++; sidecar_format_label_ = new QLabel(tr("Sidecar Format:")); sidecar_format_label_->setVisible(false); layout->addWidget(sidecar_format_label_, row, 0); sidecar_format_combobox_ = new ExportFormatComboBox(ExportFormatComboBox::kShowSubtitlesOnly); sidecar_format_combobox_->setVisible(true); layout->addWidget(sidecar_format_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Codec:")), row, 0); codec_combobox_ = new QComboBox(); layout->addWidget(codec_combobox_, row, 1); outer_layout->addStretch(); connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_label_, &QWidget::setVisible); connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_combobox_, &QWidget::setVisible); } int ExportSubtitlesTab::SetFormat(ExportFormat::Format format) { auto vcodecs = ExportFormat::GetVideoCodecs(format); auto acodecs = ExportFormat::GetAudioCodecs(format); auto scodecs = ExportFormat::GetSubtitleCodecs(format); if (!scodecs.empty() && vcodecs.empty() && acodecs.empty()) { // If format supports ONLY scodecs, default this to off and disable it sidecar_checkbox_->setChecked(false); sidecar_checkbox_->setEnabled(false); } else { // If format does not support scodecs, default this to checked and disable it sidecar_checkbox_->setChecked(scodecs.empty()); sidecar_checkbox_->setEnabled(!scodecs.empty()); } scodecs = ExportFormat::GetSubtitleCodecs(sidecar_format_combobox_->GetFormat()); codec_combobox_->clear(); foreach (ExportCodec::Codec scodec, scodecs) { codec_combobox_->addItem(ExportCodec::GetCodecName(scodec), scodec); } return scodecs.size(); } } ================================================ FILE: app/dialog/export/exportsubtitlestab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTSUBTITLESTAB_H #define EXPORTSUBTITLESTAB_H #include #include #include #include "codec/exportformat.h" #include "common/qtutils.h" #include "dialog/export/exportformatcombobox.h" namespace olive { class ExportSubtitlesTab : public QWidget { Q_OBJECT public: ExportSubtitlesTab(QWidget *parent = nullptr); bool GetSidecarEnabled() const { return sidecar_checkbox_->isChecked(); } void SetSidecarEnabled(bool e) { sidecar_checkbox_->setEnabled(e); } ExportFormat::Format GetSidecarFormat() const { return sidecar_format_combobox_->GetFormat(); } void SetSidecarFormat(ExportFormat::Format f) { sidecar_format_combobox_->SetFormat(f); } int SetFormat(ExportFormat::Format format); ExportCodec::Codec GetSubtitleCodec() { return static_cast(codec_combobox_->currentData().toInt()); } void SetSubtitleCodec(ExportCodec::Codec c) { QtUtils::SetComboBoxData(codec_combobox_, c); } private: QCheckBox *sidecar_checkbox_; QLabel *sidecar_format_label_; ExportFormatComboBox *sidecar_format_combobox_; QComboBox *codec_combobox_; }; } #endif // EXPORTSUBTITLESTAB_H ================================================ FILE: app/dialog/export/exportvideotab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "exportvideotab.h" #include #include #include #include #include #include "core.h" #include "exportadvancedvideodialog.h" #include "node/color/colormanager/colormanager.h" namespace olive { ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) : QWidget(parent), color_manager_(color_manager), threads_(0), color_range_(VideoParams::kColorRangeDefault) { QVBoxLayout* outer_layout = new QVBoxLayout(this); outer_layout->addWidget(SetupResolutionSection()); outer_layout->addWidget(SetupCodecSection()); outer_layout->addWidget(SetupColorSection()); outer_layout->addStretch(); } int ExportVideoTab::SetFormat(ExportFormat::Format format) { format_ = format; QList vcodecs = ExportFormat::GetVideoCodecs(format); setEnabled(!vcodecs.isEmpty()); codec_combobox()->clear(); foreach (ExportCodec::Codec vcodec, vcodecs) { codec_combobox()->addItem(ExportCodec::GetCodecName(vcodec), vcodec); } return vcodecs.size(); } bool ExportVideoTab::IsImageSequenceSet() const { ImageSection* img_section = dynamic_cast(codec_stack_->currentWidget()); return (img_section && img_section->IsImageSequenceChecked()); } void ExportVideoTab::SetImageSequence(bool e) const { if (ImageSection* img_section = dynamic_cast(codec_stack_->currentWidget())) { img_section->SetImageSequenceChecked(e); } } QWidget* ExportVideoTab::SetupResolutionSection() { int row = 0; QGroupBox* resolution_group = new QGroupBox(); resolution_group->setTitle(tr("General")); QGridLayout* layout = new QGridLayout(resolution_group); layout->addWidget(new QLabel(tr("Width:")), row, 0); width_slider_ = new IntegerSlider(); width_slider_->SetMinimum(1); layout->addWidget(width_slider_, row, 1); row++; layout->addWidget(new QLabel(tr("Height:")), row, 0); height_slider_ = new IntegerSlider(); height_slider_->SetMinimum(1); layout->addWidget(height_slider_, row, 1); row++; layout->addWidget(new QLabel(tr("Maintain Aspect Ratio:")), row, 0); maintain_aspect_checkbox_ = new QCheckBox(); maintain_aspect_checkbox_->setChecked(true); layout->addWidget(maintain_aspect_checkbox_, row, 1); row++; layout->addWidget(new QLabel(tr("Scaling Method:")), row, 0); scaling_method_combobox_ = new QComboBox(); scaling_method_combobox_->setEnabled(false); scaling_method_combobox_->addItem(tr("Fit"), EncodingParams::kFit); scaling_method_combobox_->addItem(tr("Stretch"), EncodingParams::kStretch); scaling_method_combobox_->addItem(tr("Crop"), EncodingParams::kCrop); layout->addWidget(scaling_method_combobox_, row, 1); // Automatically enable/disable the scaling method depending on maintain aspect ratio connect(maintain_aspect_checkbox_, &QCheckBox::toggled, this, &ExportVideoTab::MaintainAspectRatioChanged); row++; layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0); frame_rate_combobox_ = new FrameRateComboBox(); connect(frame_rate_combobox_, &FrameRateComboBox::FrameRateChanged, this, &ExportVideoTab::UpdateFrameRate); layout->addWidget(frame_rate_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0); pixel_aspect_combobox_ = new PixelAspectRatioComboBox(); layout->addWidget(pixel_aspect_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); interlaced_combobox_ = new InterlacedComboBox(); layout->addWidget(interlaced_combobox_, row, 1); row++; layout->addWidget(new QLabel(tr("Quality:")), row, 0); pixel_format_field_ = new PixelFormatComboBox(false); layout->addWidget(pixel_format_field_, row, 1); return resolution_group; } QWidget* ExportVideoTab::SetupColorSection() { color_space_chooser_ = new ColorSpaceChooser(color_manager_, true, false); connect(color_space_chooser_, &ColorSpaceChooser::InputColorSpaceChanged, this, &ExportVideoTab::ColorSpaceChanged); return color_space_chooser_; } QWidget *ExportVideoTab::SetupCodecSection() { int row = 0; QGroupBox* codec_group = new QGroupBox(); codec_group->setTitle(tr("Codec")); QGridLayout* codec_layout = new QGridLayout(codec_group); codec_layout->addWidget(new QLabel(tr("Codec:")), row, 0); codec_combobox_ = new QComboBox(); codec_layout->addWidget(codec_combobox_, row, 1); connect(codec_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &ExportVideoTab::VideoCodecChanged); row++; codec_stack_ = new CodecStack(); codec_layout->addWidget(codec_stack_, row, 0, 1, 2); image_section_ = new ImageSection(); connect(image_section_, &ImageSection::TimeChanged, this, &ExportVideoTab::TimeChanged); codec_stack_->addWidget(image_section_); h264_section_ = new H264Section(); codec_stack_->addWidget(h264_section_); h265_section_ = new H265Section(); codec_stack_->addWidget(h265_section_); av1_section_ = new AV1Section(); codec_stack_->addWidget(av1_section_); cineform_section_ = new CineformSection(); codec_stack_->addWidget(cineform_section_); row++; QPushButton* advanced_btn = new QPushButton(tr("Advanced")); connect(advanced_btn, &QPushButton::clicked, this, &ExportVideoTab::OpenAdvancedDialog); codec_layout->addWidget(advanced_btn, row, 1); return codec_group; } void ExportVideoTab::MaintainAspectRatioChanged(bool val) { scaling_method_combobox_->setEnabled(!val); } void ExportVideoTab::OpenAdvancedDialog() { // Find export formats compatible with this encoder QStringList pixel_formats = ExportFormat::GetPixelFormatsForCodec(format_, GetSelectedCodec()); ExportAdvancedVideoDialog d(pixel_formats, this); d.set_threads(threads_); d.set_pix_fmt(pix_fmt_); d.set_yuv_range(color_range_); if (d.exec() == QDialog::Accepted) { threads_ = d.threads(); pix_fmt_ = d.pix_fmt(); color_range_ = d.yuv_range(); } } void ExportVideoTab::UpdateFrameRate(rational r) { // Convert frame rate to timebase r.flip(); for (int i=0; icount(); i++) { ImageSection* img = dynamic_cast(codec_stack_->widget(i)); if (img) { img->SetTimebase(r); } } } void ExportVideoTab::VideoCodecChanged() { ExportCodec::Codec codec = GetSelectedCodec(); switch (codec) { case ExportCodec::kCodecH264: case ExportCodec::kCodecH264rgb: SetCodecSection(h264_section_); break; case ExportCodec::kCodecH265: SetCodecSection(h265_section_); break; case ExportCodec::kCodecAV1: SetCodecSection(av1_section_); break; case ExportCodec::kCodecCineform: SetCodecSection(cineform_section_); break; default: SetCodecSection(ExportCodec::IsCodecAStillImage(codec) ? image_section_ : nullptr); } // Set default pixel format QStringList pix_fmts = ExportFormat::GetPixelFormatsForCodec(format_, codec); if (!pix_fmts.isEmpty()) { pix_fmt_ = pix_fmts.first(); } else { pix_fmt_.clear(); } } void ExportVideoTab::SetTime(const rational &time) { for (int i=0; icount(); i++) { ImageSection* img = dynamic_cast(codec_stack_->widget(i)); if (img) { img->SetTime(time); } } } } ================================================ FILE: app/dialog/export/exportvideotab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef EXPORTVIDEOTAB_H #define EXPORTVIDEOTAB_H #include #include #include #include "common/qtutils.h" #include "dialog/export/codec/av1section.h" #include "dialog/export/codec/cineformsection.h" #include "dialog/export/codec/codecstack.h" #include "dialog/export/codec/h264section.h" #include "dialog/export/codec/imagesection.h" #include "node/color/colormanager/colormanager.h" #include "widget/colorwheel/colorspacechooser.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" namespace olive { class ExportVideoTab : public QWidget { Q_OBJECT public: ExportVideoTab(ColorManager* color_manager, QWidget* parent = nullptr); int SetFormat(ExportFormat::Format format); bool IsImageSequenceSet() const; void SetImageSequence(bool e) const; rational GetStillImageTime() const { return image_section_->GetTime(); } ExportCodec::Codec GetSelectedCodec() const { return static_cast(codec_combobox()->currentData().toInt()); } void SetSelectedCodec(ExportCodec::Codec c) { QtUtils::SetComboBoxData(codec_combobox(), c); } QComboBox* codec_combobox() const { return codec_combobox_; } IntegerSlider* width_slider() const { return width_slider_; } IntegerSlider* height_slider() const { return height_slider_; } QCheckBox* maintain_aspect_checkbox() const { return maintain_aspect_checkbox_; } QComboBox* scaling_method_combobox() const { return scaling_method_combobox_; } rational GetSelectedFrameRate() const { return frame_rate_combobox_->GetFrameRate(); } void SetSelectedFrameRate(const rational& fr) { frame_rate_combobox_->SetFrameRate(fr); UpdateFrameRate(fr); } QString CurrentOCIOColorSpace() { return color_space_chooser_->input(); } void SetOCIOColorSpace(const QString &s) { color_space_chooser_->set_input(s); } CodecSection* GetCodecSection() const { return static_cast(codec_stack_->currentWidget()); } void SetCodecSection(CodecSection* section) { if (section) { codec_stack_->setVisible(true); codec_stack_->setCurrentWidget(section); } else { codec_stack_->setVisible(false); } } InterlacedComboBox* interlaced_combobox() const { return interlaced_combobox_; } PixelAspectRatioComboBox* pixel_aspect_combobox() const { return pixel_aspect_combobox_; } PixelFormatComboBox* pixel_format_field() const { return pixel_format_field_; } const int& threads() const { return threads_; } void SetThreads(int t) { threads_ = t; } const QString& pix_fmt() const { return pix_fmt_; } void SetPixFmt(const QString &s) { pix_fmt_ = s; } VideoParams::ColorRange color_range() const { return color_range_; } void SetColorRange(VideoParams::ColorRange c) { color_range_ = c; } public slots: void VideoCodecChanged(); void SetTime(const rational &time); signals: void ColorSpaceChanged(const QString& colorspace); void ImageSequenceCheckBoxChanged(bool e); void TimeChanged(const rational &time); private: QWidget* SetupResolutionSection(); QWidget* SetupColorSection(); QWidget* SetupCodecSection(); QComboBox* codec_combobox_; FrameRateComboBox* frame_rate_combobox_; QCheckBox* maintain_aspect_checkbox_; QComboBox* scaling_method_combobox_; CodecStack* codec_stack_; ImageSection* image_section_; H264Section* h264_section_; H264Section* h265_section_; AV1Section* av1_section_; CineformSection *cineform_section_; ColorSpaceChooser* color_space_chooser_; IntegerSlider* width_slider_; IntegerSlider* height_slider_; ColorManager* color_manager_; InterlacedComboBox* interlaced_combobox_; PixelAspectRatioComboBox* pixel_aspect_combobox_; PixelFormatComboBox* pixel_format_field_; int threads_; QString pix_fmt_; VideoParams::ColorRange color_range_; ExportFormat::Format format_; private slots: void MaintainAspectRatioChanged(bool val); void OpenAdvancedDialog(); void UpdateFrameRate(rational r); }; } #endif // EXPORTVIDEOTAB_H ================================================ FILE: app/dialog/footageproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2020 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(streamproperties) set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/footageproperties/footageproperties.cpp dialog/footageproperties/footageproperties.h PARENT_SCOPE ) ================================================ FILE: app/dialog/footageproperties/footageproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "footageproperties.h" #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "node/nodeundo.h" #include "streamproperties/audiostreamproperties.h" #include "streamproperties/videostreamproperties.h" namespace olive { FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) : QDialog(parent), footage_(footage) { QGridLayout* layout = new QGridLayout(this); setWindowTitle(tr("\"%1\" Properties").arg(footage_->GetLabelOrName())); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); int row = 0; layout->addWidget(new QLabel(tr("Name:")), row, 0); footage_name_field_ = new QLineEdit(footage_->GetLabel()); layout->addWidget(footage_name_field_, row, 1); row++; layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); row++; track_list = new QListWidget(); layout->addWidget(track_list, row, 0, 1, 2); row++; stacked_widget_ = new QStackedWidget(); layout->addWidget(stacked_widget_, row, 0, 1, 2); int first_usable_stream = -1; for (int i=0; iGetTotalStreamCount(); i++) { Track::Reference reference = footage_->GetReferenceFromRealIndex(i); QString description; bool is_enabled = false; switch (reference.type()) { case Track::kVideo: { stacked_widget_->addWidget(new VideoStreamProperties(footage_, reference.index())); VideoParams vp = footage_->GetVideoParams(reference.index()); is_enabled = vp.enabled(); description = Footage::DescribeVideoStream(vp); break; } case Track::kAudio: { stacked_widget_->addWidget(new AudioStreamProperties(footage_, reference.index())); AudioParams ap = footage_->GetAudioParams(reference.index()); is_enabled = ap.enabled(); description = Footage::DescribeAudioStream(ap); break; } case Track::kSubtitle: { SubtitleParams sp = footage_->GetSubtitleParams(reference.index()); is_enabled = sp.enabled(); // FIXME: Language? description = tr("Subtitles"); break; } default: stacked_widget_->addWidget(new StreamProperties()); description = tr("Unknown"); break; } QListWidgetItem* item = new QListWidgetItem(description, track_list); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked); track_list->addItem(item); if (first_usable_stream == -1 && (reference.type() == Track::kVideo || reference.type() == Track::kAudio || reference.type() == Track::kSubtitle)) { first_usable_stream = i; } } row++; QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->setCenterButtons(true); layout->addWidget(buttons, row, 0, 1, 2); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex); // Auto-select first item that actually has properties if (first_usable_stream >= 0) { track_list->setCurrentRow(first_usable_stream); } track_list->setFocus(); } void FootagePropertiesDialog::accept() { // Perform sanity check on all pages for (int i=0;icount();i++) { if (!static_cast(stacked_widget_->widget(i))->SanityCheck()) { // Switch to the failed panel in question stacked_widget_->setCurrentIndex(i); // Do nothing (it's up to the property panel itself to throw the error message) return; } } MultiUndoCommand* command = new MultiUndoCommand(); if (footage_->GetLabel() != footage_name_field_->text()) { NodeRenameCommand *nrc = new NodeRenameCommand(); nrc->AddNode(footage_, footage_name_field_->text()); command->add_child(nrc); } for (int i=0; iGetTotalStreamCount(); i++) { Track::Reference reference = footage_->GetReferenceFromRealIndex(i); bool new_stream_enabled = (track_list->item(i)->checkState() == Qt::Checked); bool old_stream_enabled = new_stream_enabled; switch (reference.type()) { case Track::kVideo: old_stream_enabled = footage_->GetVideoParams(reference.index()).enabled(); break; case Track::kAudio: old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled(); break; case Track::kSubtitle: old_stream_enabled = footage_->GetSubtitleParams(reference.index()).enabled(); break; case Track::kNone: case Track::kCount: break; } if (old_stream_enabled != new_stream_enabled) { command->add_child(new StreamEnableChangeCommand(footage_, reference.type(), reference.index(), new_stream_enabled)); } } for (int i=0;icount();i++) { static_cast(stacked_widget_->widget(i))->Accept(command); } Core::instance()->undo_stack()->push(command, tr("Set Footage \"%1\" Properties").arg(footage_->GetLabel())); QDialog::accept(); } FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled) : footage_(footage), type_(type), index_(index_in_type), new_enabled_(enabled) { } Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const { return footage_->project(); } void FootagePropertiesDialog::StreamEnableChangeCommand::redo() { switch (type_) { case Track::kVideo: { VideoParams vp = footage_->GetVideoParams(index_); old_enabled_ = vp.enabled(); vp.set_enabled(new_enabled_); footage_->SetVideoParams(vp, index_); break; } case Track::kAudio: { AudioParams ap = footage_->GetAudioParams(index_); old_enabled_ = ap.enabled(); ap.set_enabled(new_enabled_); footage_->SetAudioParams(ap, index_); break; } case Track::kSubtitle: { SubtitleParams sp = footage_->GetSubtitleParams(index_); old_enabled_ = sp.enabled(); sp.set_enabled(new_enabled_); footage_->SetSubtitleParams(sp, index_); break; } case Track::kNone: case Track::kCount: break; } } void FootagePropertiesDialog::StreamEnableChangeCommand::undo() { switch (type_) { case Track::kVideo: { VideoParams vp = footage_->GetVideoParams(index_); vp.set_enabled(old_enabled_); footage_->SetVideoParams(vp, index_); break; } case Track::kAudio: { AudioParams ap = footage_->GetAudioParams(index_); ap.set_enabled(old_enabled_); footage_->SetAudioParams(ap, index_); break; } case Track::kSubtitle: { SubtitleParams sp = footage_->GetSubtitleParams(index_); sp.set_enabled(old_enabled_); footage_->SetSubtitleParams(sp, index_); break; } case Track::kNone: case Track::kCount: break; } } } ================================================ FILE: app/dialog/footageproperties/footageproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef MEDIAPROPERTIESDIALOG_H #define MEDIAPROPERTIESDIALOG_H #include #include #include #include #include #include #include #include "node/project/footage/footage.h" #include "undo/undocommand.h" namespace olive { /** * @brief The MediaPropertiesDialog class * * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given * a valid Media object. */ class FootagePropertiesDialog : public QDialog { Q_OBJECT public: /** * @brief MediaPropertiesDialog Constructor * * @param parent * * QWidget parent. Usually MainWindow or Project panel. * * @param i * * Media object to set properties for. */ FootagePropertiesDialog(QWidget *parent, Footage* footage); private: class StreamEnableChangeCommand : public UndoCommand { public: StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled); virtual Project* GetRelevantProject() const override; protected: virtual void redo() override; virtual void undo() override; private: Footage *footage_; Track::Type type_; int index_; bool old_enabled_; bool new_enabled_; }; /** * @brief Stack of widgets that changes based on whether the stream is a video or audio stream */ QStackedWidget* stacked_widget_; /** * @brief Media name text field */ QLineEdit* footage_name_field_; /** * @brief Internal pointer to Media object (set in constructor) */ Footage* footage_; /** * @brief A list widget for listing the tracks in Media */ QListWidget* track_list; /** * @brief Frame rate to conform to */ QDoubleSpinBox* conform_fr; private slots: /** * @brief Overridden accept function for saving the properties back to the Media class */ void accept(); }; } #endif // MEDIAPROPERTIESDIALOG_H ================================================ FILE: app/dialog/footageproperties/streamproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2020 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/footageproperties/streamproperties/streamproperties.h dialog/footageproperties/streamproperties/streamproperties.cpp dialog/footageproperties/streamproperties/audiostreamproperties.h dialog/footageproperties/streamproperties/audiostreamproperties.cpp dialog/footageproperties/streamproperties/videostreamproperties.h dialog/footageproperties/streamproperties/videostreamproperties.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "audiostreamproperties.h" namespace olive { AudioStreamProperties::AudioStreamProperties(Footage *footage, int audio_index) : footage_(footage), audio_index_(audio_index) { } void AudioStreamProperties::Accept(MultiUndoCommand*) { Q_UNUSED(footage_) Q_UNUSED(audio_index_) } } ================================================ FILE: app/dialog/footageproperties/streamproperties/audiostreamproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef AUDIOSTREAMPROPERTIES_H #define AUDIOSTREAMPROPERTIES_H #include "node/project/footage/footage.h" #include "streamproperties.h" namespace olive { class AudioStreamProperties : public StreamProperties { public: AudioStreamProperties(Footage *footage, int audio_index); virtual void Accept(MultiUndoCommand* parent) override; private: Footage *footage_; int audio_index_; }; } #endif // AUDIOSTREAMPROPERTIES_H ================================================ FILE: app/dialog/footageproperties/streamproperties/streamproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "streamproperties.h" namespace olive { StreamProperties::StreamProperties(QWidget *parent) : QWidget(parent) { } } ================================================ FILE: app/dialog/footageproperties/streamproperties/streamproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef STREAMPROPERTIES_H #define STREAMPROPERTIES_H #include #include "common/define.h" #include "undo/undocommand.h" namespace olive { class StreamProperties : public QWidget { public: StreamProperties(QWidget* parent = nullptr); virtual void Accept(MultiUndoCommand*){} virtual bool SanityCheck(){return true;} }; } #endif // STREAMPROPERTIES_H ================================================ FILE: app/dialog/footageproperties/streamproperties/videostreamproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "videostreamproperties.h" #include #include #include #include #include #include "common/ocioutils.h" #include "core.h" #include "undo/undostack.h" namespace olive { VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) : footage_(footage), video_index_(video_index), video_premultiply_alpha_(nullptr) { QGridLayout* video_layout = new QGridLayout(this); video_layout->setContentsMargins(0, 0, 0, 0); int row = 0; video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0); VideoParams vp = footage_->GetVideoParams(video_index_); pixel_aspect_combo_ = new PixelAspectRatioComboBox(); pixel_aspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); video_layout->addWidget(pixel_aspect_combo_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); video_interlace_combo_ = new InterlacedComboBox(); video_interlace_combo_->SetInterlaceMode(vp.interlacing()); video_layout->addWidget(video_interlace_combo_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0); video_color_space_ = new QComboBox(); OCIO::ConstConfigRcPtr config = footage_->project()->color_manager()->GetConfig(); int number_of_colorspaces = config->getNumColorSpaces(); video_color_space_->addItem(tr("Default (%1)").arg(footage_->project()->color_manager()->GetDefaultInputColorSpace())); for (int i=0;igetColorSpaceNameByIndex(i); video_color_space_->addItem(colorspace); } video_color_space_->setCurrentText(vp.colorspace()); video_layout->addWidget(video_color_space_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Color Range:")), row, 0); color_range_combo_ = new QComboBox(); color_range_combo_->addItem(tr("Limited (16-235)"), VideoParams::kColorRangeLimited); color_range_combo_->addItem(tr("Full (0-255)"), VideoParams::kColorRangeFull); color_range_combo_->setCurrentIndex(vp.color_range()); video_layout->addWidget(color_range_combo_, row, 1); if (vp.channel_count() == VideoParams::kRGBAChannelCount) { row++; video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); video_premultiply_alpha_->setChecked(vp.premultiplied_alpha()); video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); } row++; if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence")); QGridLayout* imgseq_layout = new QGridLayout(imgseq_group); int imgseq_row = 0; imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0); imgseq_start_time_ = new IntegerSlider(); imgseq_start_time_->SetMinimum(0); imgseq_start_time_->SetValue(vp.start_time()); imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1); imgseq_row++; imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0); imgseq_end_time_ = new IntegerSlider(); imgseq_end_time_->SetMinimum(0); imgseq_end_time_->SetValue(vp.start_time() + vp.duration() - 1); imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1); imgseq_row++; imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0); imgseq_frame_rate_ = new FrameRateComboBox(); imgseq_frame_rate_->SetFrameRate(vp.frame_rate()); imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1); video_layout->addWidget(imgseq_group, row, 0, 1, 2); } } void VideoStreamProperties::Accept(MultiUndoCommand *parent) { QString set_colorspace; if (video_color_space_->currentIndex() > 0) { set_colorspace = video_color_space_->currentText(); } VideoParams vp = footage_->GetVideoParams(video_index_); if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) || set_colorspace != vp.colorspace() || static_cast(video_interlace_combo_->currentIndex()) != vp.interlacing() || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio() || color_range_combo_->currentData().toInt() != vp.color_range()) { parent->add_child(new VideoStreamChangeCommand(footage_, video_index_, video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(), set_colorspace, static_cast(video_interlace_combo_->currentIndex()), pixel_aspect_combo_->GetPixelAspectRatio(), static_cast(color_range_combo_->currentData().toInt()))); } if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1; if (vp.start_time() != imgseq_start_time_->GetValue() || vp.duration() != new_dur || vp.frame_rate() != imgseq_frame_rate_->GetFrameRate()) { parent->add_child(new ImageSequenceChangeCommand(footage_, video_index_, imgseq_start_time_->GetValue(), new_dur, imgseq_frame_rate_->GetFrameRate())); } } } bool VideoStreamProperties::SanityCheck() { if (footage_->GetVideoParams(video_index_).video_type() == VideoParams::kVideoTypeImageSequence) { if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) { QMessageBox::critical(this, tr("Invalid Configuration"), tr("Image sequence end index must be a value higher than the start index."), QMessageBox::Ok); return false; } } return true; } VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footage *footage, int video_index, bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, const rational &pixel_ar, VideoParams::ColorRange range) : footage_(footage), video_index_(video_index), new_premultiplied_(premultiplied), new_colorspace_(colorspace), new_interlacing_(interlacing), new_pixel_ar_(pixel_ar), new_range_(range) { } Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const { return footage_->project(); } void VideoStreamProperties::VideoStreamChangeCommand::redo() { VideoParams vp = footage_->GetVideoParams(video_index_); old_premultiplied_ = vp.premultiplied_alpha(); old_colorspace_ = vp.colorspace(); old_interlacing_ = vp.interlacing(); old_pixel_ar_ = vp.pixel_aspect_ratio(); old_range_ = vp.color_range(); vp.set_premultiplied_alpha(new_premultiplied_); vp.set_colorspace(new_colorspace_); vp.set_interlacing(new_interlacing_); vp.set_pixel_aspect_ratio(new_pixel_ar_); vp.set_color_range(new_range_); footage_->SetVideoParams(vp, video_index_); } void VideoStreamProperties::VideoStreamChangeCommand::undo() { VideoParams vp = footage_->GetVideoParams(video_index_); vp.set_premultiplied_alpha(old_premultiplied_); vp.set_colorspace(old_colorspace_); vp.set_interlacing(old_interlacing_); vp.set_pixel_aspect_ratio(old_pixel_ar_); vp.set_color_range(old_range_); footage_->SetVideoParams(vp, video_index_); } VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational &frame_rate) : footage_(footage), video_index_(video_index), new_start_index_(start_index), new_duration_(duration), new_frame_rate_(frame_rate) { } Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const { return footage_->project(); } void VideoStreamProperties::ImageSequenceChangeCommand::redo() { VideoParams vp = footage_->GetVideoParams(video_index_); old_start_index_ = vp.start_time(); vp.set_start_time(new_start_index_); old_duration_ = vp.duration(); vp.set_duration(new_duration_); old_frame_rate_ = vp.frame_rate(); vp.set_frame_rate(new_frame_rate_); vp.set_time_base(new_frame_rate_.flipped()); footage_->SetVideoParams(vp, video_index_); } void VideoStreamProperties::ImageSequenceChangeCommand::undo() { VideoParams vp = footage_->GetVideoParams(video_index_); vp.set_start_time(old_start_index_); vp.set_duration(old_duration_); vp.set_frame_rate(old_frame_rate_); vp.set_time_base(old_frame_rate_.flipped()); footage_->SetVideoParams(vp, video_index_); } } ================================================ FILE: app/dialog/footageproperties/streamproperties/videostreamproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef VIDEOSTREAMPROPERTIES_H #define VIDEOSTREAMPROPERTIES_H #include #include #include "node/project/footage/footage.h" #include "streamproperties.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" namespace olive { class VideoStreamProperties : public StreamProperties { Q_OBJECT public: VideoStreamProperties(Footage *footage, int video_index); virtual void Accept(MultiUndoCommand *parent) override; virtual bool SanityCheck() override; private: Footage *footage_; int video_index_; /** * @brief Setting for associated/premultiplied alpha */ QCheckBox* video_premultiply_alpha_; /** * @brief Setting for this media's color space */ QComboBox* video_color_space_; /** * @brief Setting for this streams's color range */ QComboBox *color_range_combo_; /** * @brief Setting for video interlacing */ InterlacedComboBox* video_interlace_combo_; /** * @brief Sets the start index for image sequences */ IntegerSlider* imgseq_start_time_; /** * @brief Sets the end index for image sequences */ IntegerSlider* imgseq_end_time_; /** * @brief Sets the frame rate for image sequences */ FrameRateComboBox* imgseq_frame_rate_; /** * @brief Sets the pixel aspect ratio of the stream */ PixelAspectRatioComboBox* pixel_aspect_combo_; class VideoStreamChangeCommand : public UndoCommand { public: VideoStreamChangeCommand(Footage *footage, int video_index, bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, const rational& pixel_ar, VideoParams::ColorRange range); virtual Project* GetRelevantProject() const override; protected: virtual void redo() override; virtual void undo() override; private: Footage *footage_; int video_index_; bool new_premultiplied_; QString new_colorspace_; VideoParams::Interlacing new_interlacing_; rational new_pixel_ar_; VideoParams::ColorRange new_range_; bool old_premultiplied_; QString old_colorspace_; VideoParams::Interlacing old_interlacing_; rational old_pixel_ar_; VideoParams::ColorRange old_range_; }; class ImageSequenceChangeCommand : public UndoCommand { public: ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational& frame_rate); virtual Project* GetRelevantProject() const override; protected: virtual void redo() override; virtual void undo() override; private: Footage *footage_; int video_index_; int64_t new_start_index_; int64_t old_start_index_; int64_t new_duration_; int64_t old_duration_; rational new_frame_rate_; rational old_frame_rate_; }; }; } #endif // VIDEOSTREAMPROPERTIES_H ================================================ FILE: app/dialog/footagerelink/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/footagerelink/footagerelinkdialog.h dialog/footagerelink/footagerelinkdialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/footagerelink/footagerelinkdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "footagerelinkdialog.h" #include #include #include #include #include #include #include #include namespace olive { FootageRelinkDialog::FootageRelinkDialog(const QVector &footage, QWidget* parent) : QDialog(parent), footage_(footage) { QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(new QLabel("The following files couldn't be found. Clips using them will be " "unplayable until they're relinked.")); table_ = new QTreeWidget(); table_->setColumnCount(3); table_->setHeaderLabels({tr("Footage"), tr("Filename"), tr("Actions")}); table_->setRootIsDecorated(false); table_->setSelectionBehavior(QAbstractItemView::SelectRows); table_->header()->setSectionsMovable(false); // Prefer stretching URL column (QHeaderView defaults to stretching the last column, which in // our case is just a browse button) table_->header()->setSectionResizeMode(1, QHeaderView::Stretch); table_->header()->setStretchLastSection(false); for (int i=0; isetProperty("index", i); connect(item_browse_btn, &QPushButton::clicked, this, &FootageRelinkDialog::BrowseForFootage); item_actions_layout->addWidget(item_browse_btn); item->setIcon(0, f->data(Node::ICON).value()); item->setText(0, f->GetLabel()); item->setText(1, f->filename()); table_->addTopLevelItem(item); table_->setItemWidget(item, 2, item_actions); } layout->addWidget(table_); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &FootageRelinkDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &FootageRelinkDialog::reject); layout->addWidget(buttons); setWindowTitle(tr("Relink Footage")); } void FootageRelinkDialog::UpdateFootageItem(int index) { Footage* f = footage_.at(index); QTreeWidgetItem* item = table_->topLevelItem(index); item->setIcon(0, f->data(Node::ICON).value()); item->setText(1, f->filename()); } void FootageRelinkDialog::BrowseForFootage() { int index = sender()->property("index").toInt(); Footage* f = footage_.at(index); QFileInfo info(f->filename()); QString new_fn = QFileDialog::getOpenFileName(this, tr("Relink \"%1\"").arg(f->GetLabel()), info.absolutePath()); // Originally, this function would attempt to filter to the exact filename of the missing file. // However, this would break on Windows if the filename had any spaces in it. The reason is // Windows separates its extensions with ';' while Qt separates them with ' '. Qt isn't // intelligent enough to determine whether it's a list of extensions or a single filename with a // space in it, it just does a global replace of ' ' to ';'. There's no way around it, outside of // bypassing Qt entirely and using Win32's GetOpenFileName() directly. As annoying as it is, I've // just disabled it for now. //QStringLiteral("%1 (\"%1\");;%2 (*)").arg(info.fileName(), tr("All Files"))); // We received a new filename if (!new_fn.isEmpty()) { // Store original dir since we might be able to use this to find other files QDir original_dir = info.dir(); QDir new_dir = QFileInfo(new_fn).dir(); // Set new filename since this was set manually by the user f->set_filename(new_fn); // Assume footage is valid here. We could do some decoder probing to ensure it's a usable file // but otherwise we assume the user knows what they're doing here. // Set footage to valid and update icon f->SetValid(); // Update item visually UpdateFootageItem(index); // Check all other footage files for matches for (int it=0; itIsValid()) { // Get footage path relative to original directory QString relative_to_original = original_dir.relativeFilePath(other_footage->filename()); QString absolute_to_new = new_dir.filePath(relative_to_original); // Second attempt. Try appending the filename to our new filepath if (!QFileInfo::exists(absolute_to_new)) { QFileInfo file_info(other_footage->filename()); absolute_to_new = new_dir.filePath(file_info.fileName()); } // Check if file exists if (QFileInfo::exists(absolute_to_new)) { other_footage->set_filename(absolute_to_new); other_footage->SetValid(); UpdateFootageItem(it); } } } } // Check where the next invalid footage is. If there is none, accept automatically. Otherwise, // jump to that footage so the user knows where it is. int next_invalid = -1; for (int i=0; iIsValid()) { next_invalid = i; break; } } if (next_invalid == -1) { // No more invalid footage, just accept this->accept(); } else { // Jump to next invalid footage QModelIndex idx = table_->model()->index(next_invalid, 0); table_->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); table_->scrollTo(idx); } } } ================================================ FILE: app/dialog/footagerelink/footagerelinkdialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef FOOTAGERELINKDIALOG_H #define FOOTAGERELINKDIALOG_H #include #include #include "node/project/footage/footage.h" namespace olive { class FootageRelinkDialog : public QDialog { Q_OBJECT public: FootageRelinkDialog(const QVector& footage, QWidget* parent = nullptr); private: void UpdateFootageItem(int index); QTreeWidget* table_; QVector footage_; private slots: void BrowseForFootage(); }; } #endif // FOOTAGERELINKDIALOG_H ================================================ FILE: app/dialog/keyframeproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/keyframeproperties/keyframeproperties.h dialog/keyframeproperties/keyframeproperties.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/keyframeproperties/keyframeproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "keyframeproperties.h" #include #include #include "core.h" #include "node/nodeundo.h" #include "widget/keyframeview/keyframeviewundo.h" namespace olive { KeyframePropertiesDialog::KeyframePropertiesDialog(const std::vector &keys, const rational &timebase, QWidget *parent) : QDialog(parent), keys_(keys), timebase_(timebase) { setWindowTitle(tr("Keyframe Properties")); QGridLayout* layout = new QGridLayout(this); int row = 0; layout->addWidget(new QLabel("Time:"), row, 0); time_slider_ = new RationalSlider(); time_slider_->SetDisplayType(RationalSlider::kTime); time_slider_->SetTimebase(timebase_); layout->addWidget(time_slider_, row, 1); row++; layout->addWidget(new QLabel("Type:"), row, 0); type_select_ = new QComboBox(); connect(type_select_, SIGNAL(currentIndexChanged(int)), this, SLOT(KeyTypeChanged(int))); layout->addWidget(type_select_, row, 1); row++; // Bezier handles bezier_group_ = new QGroupBox(); QGridLayout* bezier_group_layout = new QGridLayout(bezier_group_); bezier_group_layout->addWidget(new QLabel(tr("In:")), 0, 0); bezier_in_x_slider_ = new FloatSlider(); bezier_group_layout->addWidget(bezier_in_x_slider_, 0, 1); bezier_in_y_slider_ = new FloatSlider(); bezier_group_layout->addWidget(bezier_in_y_slider_, 0, 2); bezier_group_layout->addWidget(new QLabel(tr("Out:")), 1, 0); bezier_out_x_slider_ = new FloatSlider(); bezier_group_layout->addWidget(bezier_out_x_slider_, 1, 1); bezier_out_y_slider_ = new FloatSlider(); bezier_group_layout->addWidget(bezier_out_y_slider_, 1, 2); layout->addWidget(bezier_group_, row, 0, 1, 2); bool all_same_time = true; bool can_set_time = true; bool all_same_type = true; bool all_same_bezier_in_x = true; bool all_same_bezier_in_y = true; bool all_same_bezier_out_x = true; bool all_same_bezier_out_y = true; for (size_t i=0;i 0) { NodeKeyframe* prev_key = keys_.at(i-1); NodeKeyframe* this_key = keys_.at(i); // Determine if the keyframes are all the same time or not if (all_same_time) { all_same_time = (prev_key->time() == this_key->time()); } // Determine if the keyframes are all the same type if (all_same_type) { all_same_type = (prev_key->type() == this_key->type()); } // Check all four bezier control points if (all_same_bezier_in_x) { all_same_bezier_in_x = (prev_key->bezier_control_in().x() == this_key->bezier_control_in().x()); } if (all_same_bezier_in_y) { all_same_bezier_in_y = (prev_key->bezier_control_in().y() == this_key->bezier_control_in().y()); } if (all_same_bezier_out_x) { all_same_bezier_out_x = (prev_key->bezier_control_out().x() == this_key->bezier_control_out().x()); } if (all_same_bezier_out_y) { all_same_bezier_out_y = (prev_key->bezier_control_out().y() == this_key->bezier_control_out().y()); } } // Determine if any keyframes are on the same track (in which case we can't set the time) if (can_set_time) { for (size_t j=0;jtrack() == keys_.at(i)->track()) { can_set_time = false; break; } } } if (!all_same_time && !all_same_type && !can_set_time && !all_same_bezier_in_x && !all_same_bezier_in_y && !all_same_bezier_out_x && !all_same_bezier_out_y) { break; } } if (all_same_time) { time_slider_->SetValue(keys_.front()->time()); } else { time_slider_->SetTristate(); } time_slider_->setEnabled(can_set_time); if (!all_same_type) { // If all keyframes aren't the same type, add an empty item type_select_->addItem(QStringLiteral("--"), -1); // Ensure UI updates for the index being 0 KeyTypeChanged(0); } type_select_->addItem(tr("Linear"), NodeKeyframe::kLinear); type_select_->addItem(tr("Hold"), NodeKeyframe::kHold); type_select_->addItem(tr("Bezier"), NodeKeyframe::kBezier); if (all_same_type) { // If all keyframes are the same type, set it here for (int i=0;icount();i++) { if (type_select_->itemData(i).toInt() == keys_.front()->type()) { type_select_->setCurrentIndex(i); // Ensure UI updates for this index KeyTypeChanged(i); break; } } } SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.front()->bezier_control_in().x()); SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.front()->bezier_control_in().y()); SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.front()->bezier_control_out().x()); SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.front()->bezier_control_out().y()); row++; QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->setCenterButtons(true); layout->addWidget(buttons, row, 0, 1, 2); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); } void KeyframePropertiesDialog::accept() { MultiUndoCommand* command = new MultiUndoCommand(); rational new_time = time_slider_->GetValue(); int new_type = type_select_->currentData().toInt(); foreach (NodeKeyframe* key, keys_) { if (time_slider_->isEnabled() && !time_slider_->IsTristate()) { command->add_child(new NodeParamSetKeyframeTimeCommand(key, new_time)); } if (new_type > -1) { command->add_child(new KeyframeSetTypeCommand(key, static_cast(new_type))); } if (bezier_group_->isEnabled()) { command->add_child(new KeyframeSetBezierControlPoint(key, NodeKeyframe::kInHandle, QPointF(bezier_in_x_slider_->GetValue(), bezier_in_y_slider_->GetValue()))); command->add_child(new KeyframeSetBezierControlPoint(key, NodeKeyframe::kOutHandle, QPointF(bezier_out_x_slider_->GetValue(), bezier_out_y_slider_->GetValue()))); } } Core::instance()->undo_stack()->push(command, tr("Set Keyframe Properties")); QDialog::accept(); } void KeyframePropertiesDialog::SetUpBezierSlider(FloatSlider *slider, bool all_same, double value) { if (all_same) { slider->SetValue(value); } else { slider->SetTristate(); } } void KeyframePropertiesDialog::KeyTypeChanged(int index) { bezier_group_->setEnabled(type_select_->itemData(index) == NodeKeyframe::kBezier); } } ================================================ FILE: app/dialog/keyframeproperties/keyframeproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef KEYFRAMEPROPERTIESDIALOG_H #define KEYFRAMEPROPERTIESDIALOG_H #include #include #include #include "node/keyframe.h" #include "widget/slider/floatslider.h" #include "widget/slider/rationalslider.h" namespace olive { class KeyframePropertiesDialog : public QDialog { Q_OBJECT public: KeyframePropertiesDialog(const std::vector& keys, const rational& timebase, QWidget* parent = nullptr); public slots: virtual void accept() override; private: void SetUpBezierSlider(FloatSlider *slider, bool all_same, double value); const std::vector& keys_; rational timebase_; RationalSlider* time_slider_; QComboBox* type_select_; QGroupBox* bezier_group_; FloatSlider* bezier_in_x_slider_; FloatSlider* bezier_in_y_slider_; FloatSlider* bezier_out_x_slider_; FloatSlider* bezier_out_y_slider_; private slots: void KeyTypeChanged(int index); }; } #endif // KEYFRAMEPROPERTIESDIALOG_H ================================================ FILE: app/dialog/markerproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/markerproperties/markerpropertiesdialog.h dialog/markerproperties/markerpropertiesdialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/markerproperties/markerpropertiesdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "markerpropertiesdialog.h" #include #include #include #include #include #include "core.h" namespace olive { #define super QDialog MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vector &markers, const rational &timebase, QWidget *parent) : super(parent), markers_(markers) { QGridLayout *layout = new QGridLayout(this); int row = 0; QGroupBox *time_group = new QGroupBox(tr("Time")); QGridLayout *time_layout = new QGridLayout(time_group); { int time_row = 0; time_layout->addWidget(new QLabel(tr("In:")), time_row, 0); in_slider_ = new RationalSlider(); time_layout->addWidget(in_slider_, time_row, 1); time_row++; time_layout->addWidget(new QLabel(tr("Out:")), time_row, 0); out_slider_ = new RationalSlider(); time_layout->addWidget(out_slider_, time_row, 1); } if (markers.size() == 1) { in_slider_->SetValue(markers.front()->time().in()); in_slider_->SetDisplayType(RationalSlider::kTime); in_slider_->SetTimebase(timebase); out_slider_->SetValue(markers.front()->time().out()); out_slider_->SetDisplayType(RationalSlider::kTime); out_slider_->SetTimebase(timebase); } else { // Markers cannot be on the same time, so we disable setting time if multiple markers are selected in_slider_->setEnabled(false); in_slider_->SetTristate(); out_slider_->setEnabled(false); out_slider_->SetTristate(); } layout->addWidget(time_group, row, 0, 1, 2); row++; layout->addWidget(new QLabel(tr("Color:")), row, 0); color_menu_ = new ColorCodingComboBox(); layout->addWidget(color_menu_, row, 1); color_menu_->SetColor(markers.front()->color()); for (size_t i=1; icolor() != color_menu_->GetSelectedColor()) { color_menu_->SetColor(-1); break; } } row++; layout->addWidget(new QLabel(tr("Name:")), row, 0); label_edit_ = new LineEditWithFocusSignal(); connect(label_edit_, &LineEditWithFocusSignal::Focused, this, [this]{ label_edit_->setPlaceholderText(QString()); }); layout->addWidget(label_edit_, row, 1); // Determine what the startup label text should be label_edit_->setText(markers.front()->name()); for (size_t i=1; iname() != label_edit_->text()) { label_edit_->clear(); label_edit_->setPlaceholderText(tr("(multiple)")); break; } } row++; QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &MarkerPropertiesDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &MarkerPropertiesDialog::reject); layout->addWidget(buttons, row, 0, 1, 2); setWindowTitle(tr("Edit Markers")); label_edit_->setFocus(); } void MarkerPropertiesDialog::accept() { if (in_slider_->isEnabled() && in_slider_->GetValue() > out_slider_->GetValue()) { QMessageBox::critical(this, tr("Invalid Values"), tr("In point must be less than or equal to out point.")); return; } MultiUndoCommand *command = new MultiUndoCommand(); int color = color_menu_->GetSelectedColor(); foreach (TimelineMarker *m, markers_) { if (color != -1) { command->add_child(new MarkerChangeColorCommand(m, color)); } if (label_edit_->placeholderText().isEmpty()) { command->add_child(new MarkerChangeNameCommand(m, label_edit_->text())); } } if (markers_.size() == 1) { command->add_child(new MarkerChangeTimeCommand(markers_.front(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue()))); } Core::instance()->undo_stack()->push(command, tr("Set Marker Properties")); super::accept(); } } ================================================ FILE: app/dialog/markerproperties/markerpropertiesdialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef MARKERPROPERTIESDIALOG_H #define MARKERPROPERTIESDIALOG_H #include #include #include "timeline/timelinemarker.h" #include "widget/colorlabelmenu/colorcodingcombobox.h" #include "widget/slider/rationalslider.h" namespace olive { class LineEditWithFocusSignal : public QLineEdit { Q_OBJECT public: LineEditWithFocusSignal(QWidget *parent = nullptr) : QLineEdit(parent) { } protected: virtual void focusInEvent(QFocusEvent *e) override { QLineEdit::focusInEvent(e); emit Focused(); } signals: void Focused(); }; class MarkerPropertiesDialog : public QDialog { Q_OBJECT public: MarkerPropertiesDialog(const std::vector &markers, const rational &timebase, QWidget *parent = nullptr); public slots: virtual void accept() override; private: std::vector markers_; LineEditWithFocusSignal *label_edit_; ColorCodingComboBox *color_menu_; RationalSlider *in_slider_; RationalSlider *out_slider_; }; } #endif // MARKERPROPERTIESDIALOG_H ================================================ FILE: app/dialog/otioproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2019 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/otioproperties/otiopropertiesdialog.h dialog/otioproperties/otiopropertiesdialog.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/otioproperties/otiopropertiesdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "otiopropertiesdialog.h" #include #include #include #include #include #include #include #include "core.h" #include "dialog/sequence/sequence.h" namespace olive { OTIOPropertiesDialog::OTIOPropertiesDialog(const QList& sequences, Project* active_project, QWidget* parent) : QDialog(parent), sequences_(sequences) { QVBoxLayout* layout = new QVBoxLayout(this); QLabel *msg = new QLabel(tr("OpenTimelineIO files do not store sequence parameters (resolution, frame rate, etc.)\n\n" "Please set the correct parameters on the sequences below (they have been set to your default sequence parameters as a starting point).")); msg->setWordWrap(true); layout->addWidget(msg); table_ = new QTreeWidget(); table_->setColumnCount(2); table_->setHeaderLabels({tr("Sequence"), tr("Actions")}); table_->setRootIsDecorated(false); for (int i = 0; i < sequences.size(); i++) { QTreeWidgetItem* item = new QTreeWidgetItem(); Sequence* s = sequences.at(i); QWidget* item_actions = new QWidget(); QHBoxLayout* item_actions_layout = new QHBoxLayout(item_actions); QPushButton* item_settings_btn = new QPushButton(tr("Settings")); item_settings_btn->setProperty("index", i); connect(item_settings_btn, &QPushButton::clicked, this, &OTIOPropertiesDialog::SetupSequence); item_actions_layout->addWidget(item_settings_btn); item->setText(0, s->GetLabel()); table_->addTopLevelItem(item); table_->setItemWidget(item, 1, item_actions); } // Stretch first column to take up as much space as possible, and second column to take as little table_->header()->setSectionResizeMode(0, QHeaderView::Stretch); table_->header()->setSectionResizeMode(1, QHeaderView::Fixed); table_->header()->setStretchLastSection(false); layout->addWidget(table_); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &OTIOPropertiesDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &OTIOPropertiesDialog::reject); layout->addWidget(buttons); setWindowTitle(tr("Load OpenTimelineIO Project")); } void OTIOPropertiesDialog::SetupSequence() { int index = sender()->property("index").toInt(); Sequence* s = sequences_.at(index); SequenceDialog sd(s, SequenceDialog::kNew); sd.SetUndoable(false); sd.exec(); } } // namespace olive ================================================ FILE: app/dialog/otioproperties/otiopropertiesdialog.h ================================================ #ifndef OTIOPROPERTIESDIALOG_H #define OTIOPROPERTIESDIALOG_H #include #include #include "common/define.h" #include "opentimelineio/timeline.h" #include "node/project/sequence/sequence.h" #include "node/project.h" namespace olive { /** * @brief Dialog to load setting for OTIO sequences. * * Takes a list of Sequences and allows the setting of options for each. */ class OTIOPropertiesDialog : public QDialog { Q_OBJECT public: OTIOPropertiesDialog(const QList& sequences, Project* active_project, QWidget* parent = nullptr); private: QTreeWidget* table_; const QList sequences_; private slots: /** * @brief Brings up the Sequence settings dialog. */ void SetupSequence(); }; } //namespace olive #endif // OTIOPROPERTIESDIALOG_H ================================================ FILE: app/dialog/preferences/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . add_subdirectory(tabs) set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/preferences/keysequenceeditor.h dialog/preferences/keysequenceeditor.cpp dialog/preferences/preferences.h dialog/preferences/preferences.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/preferences/keysequenceeditor.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "keysequenceeditor.h" #include #include namespace olive { #define super QKeySequenceEdit KeySequenceEditor::KeySequenceEditor(QWidget* parent, QAction* a) : super(parent), action(a) { setKeySequence(action->shortcut()); } void KeySequenceEditor::set_action_shortcut() { action->setShortcut(keySequence()); } void KeySequenceEditor::reset_to_default() { setKeySequence(action->property("keydefault").toString()); } QString KeySequenceEditor::action_name() { return action->property("id").toString(); } QString KeySequenceEditor::export_shortcut() { QKeySequence ks = keySequence(); if (ks != action->property("keydefault").value()) { return action->property("id").toString() + "\t" + ks.toString(); } return nullptr; } void KeySequenceEditor::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Backspace) { clear(); } else if (e->key() == Qt::Key_Escape) { e->ignore(); } else{ super::keyPressEvent(e); } } void KeySequenceEditor::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Backspace) { // Do nothing } else if (e->key() == Qt::Key_Escape) { e->ignore(); } else { super::keyReleaseEvent(e); } } } ================================================ FILE: app/dialog/preferences/keysequenceeditor.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef KEYSEQUENCEEDITOR_H #define KEYSEQUENCEEDITOR_H #include #include "common/debug.h" namespace olive { /** * @brief The KeySequenceEditor class * * Simple derived class of QKeySequenceEdit that attaches to a QAction and provides functions for transferring * keyboard shortcuts to and from it. */ class KeySequenceEditor : public QKeySequenceEdit { Q_OBJECT public: /** * @brief KeySequenceEditor Constructor * * @param parent * * QWidget parent. * * @param a * * The QAction to link to. This cannot be changed throughout the lifetime of a KeySequenceEditor. */ KeySequenceEditor(QWidget *parent, QAction* a); /** * @brief Sets the attached QAction's shortcut to the shortcut entered in this field. * * This is not done automatically in case the user cancels out of the Preferences dialog, in which case the * expectation is that the changes made will not be saved. Therefore, this needs to be triggered manually when * PreferencesDialog saves. */ void set_action_shortcut(); /** * @brief Set this shortcut back to the QAction's default shortcut * * Each QAction contains the default shortcut in its `property("default")` and can be used to restore the default * "hard-coded" shortcut with this function. * * This function does not save the default shortcut back into the QAction, it simply loads the default shortcut from * the QAction into this edit field. To save it into the QAction, it's necessary to call set_action_shortcut() after * calling this function. */ void reset_to_default(); /** * @brief Return attached QAction's unique ID * * Each of Olive's menu actions has a unique string ID (that, unlike the text, is not translated) for matching with * an external shortcut configuration file. The ID is stored in the QAction's `property("id")`. This function returns * that ID. * * @return * * The QAction's unique ID. */ QString action_name(); /** * @brief Serialize this shortcut entry into a string that can be saved to a file * * @return * * A string serialization of this shortcut. The format is "[ID]\t[SEQUENCE]" where [ID] is the attached QAction's * unique identifier and [SEQUENCE] is the current keyboard shortcut in the field (NOT necessarily the shortcut in * the QAction). If the entered shortcut is the same as the QAction's default shortcut, the return value is empty * because a default shortcut does not need to be saved to a file. */ QString export_shortcut(); protected: virtual void keyPressEvent(QKeyEvent *e) override; virtual void keyReleaseEvent(QKeyEvent *e) override; private: /** * @brief Internal reference to the linked QAction */ QAction* action; }; } #endif // KEYSEQUENCEEDITOR_H ================================================ FILE: app/dialog/preferences/preferences.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferences.h" #include #include #include #include #include "config/config.h" #include "tabs/preferencesgeneraltab.h" #include "tabs/preferencesbehaviortab.h" #include "tabs/preferencesappearancetab.h" #include "tabs/preferencesdisktab.h" #include "tabs/preferencesaudiotab.h" #include "tabs/preferenceskeyboardtab.h" #include "window/mainwindow/mainwindow.h" namespace olive { PreferencesDialog::PreferencesDialog(MainWindow *main_window) : ConfigDialogBase(main_window) { setWindowTitle(tr("Preferences")); AddTab(new PreferencesGeneralTab(), tr("General")); AddTab(new PreferencesAppearanceTab(), tr("Appearance")); AddTab(new PreferencesBehaviorTab(), tr("Behavior")); AddTab(new PreferencesDiskTab(), tr("Disk")); AddTab(new PreferencesAudioTab(), tr("Audio")); AddTab(new PreferencesKeyboardTab(main_window), tr("Keyboard")); } void PreferencesDialog::AcceptEvent() { Config::Save(); } } ================================================ FILE: app/dialog/preferences/preferences.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESDIALOG_H #define PREFERENCESDIALOG_H #include #include #include #include #include #include #include "dialog/configbase/configdialogbase.h" namespace olive { class MainWindow; /** * @brief The PreferencesDialog class * * A dialog for the global application settings. Mostly an interface for Config. */ class PreferencesDialog : public ConfigDialogBase { Q_OBJECT public: PreferencesDialog(MainWindow *main_window); protected: virtual void AcceptEvent() override; }; } #endif // PREFERENCESDIALOG_H ================================================ FILE: app/dialog/preferences/tabs/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/preferences/tabs/preferencesgeneraltab.h dialog/preferences/tabs/preferencesgeneraltab.cpp dialog/preferences/tabs/preferencesbehaviortab.h dialog/preferences/tabs/preferencesbehaviortab.cpp dialog/preferences/tabs/preferencesdisktab.h dialog/preferences/tabs/preferencesdisktab.cpp dialog/preferences/tabs/preferencesappearancetab.h dialog/preferences/tabs/preferencesappearancetab.cpp dialog/preferences/tabs/preferencesaudiotab.h dialog/preferences/tabs/preferencesaudiotab.cpp dialog/preferences/tabs/preferenceskeyboardtab.h dialog/preferences/tabs/preferenceskeyboardtab.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/preferences/tabs/preferencesappearancetab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferencesappearancetab.h" #include #include #include #include #include #include "node/node.h" #include "widget/colorbutton/colorbutton.h" #include "widget/menu/menushared.h" #include "ui/colorcoding.h" namespace olive { PreferencesAppearanceTab::PreferencesAppearanceTab() { QVBoxLayout* layout = new QVBoxLayout(this); QGridLayout* appearance_layout = new QGridLayout(); layout->addLayout(appearance_layout); int row = 0; // Appearance -> Theme appearance_layout->addWidget(new QLabel(tr("Theme")), row, 0); style_combobox_ = new QComboBox(); { const QMap& themes = StyleManager::available_themes(); QMap::const_iterator i; for (i=themes.cbegin(); i!=themes.cend(); i++) { style_combobox_->addItem(i.value(), i.key()); if (StyleManager::GetStyle() == i.key()) { style_combobox_->setCurrentIndex(style_combobox_->count()-1); } } } appearance_layout->addWidget(style_combobox_, row, 1); row++; { QGroupBox* color_group = new QGroupBox(); color_group->setTitle(tr("Default Node Colors")); QGridLayout* color_layout = new QGridLayout(color_group); for (int i=0; i(i)); color_layout->addWidget(new QLabel(cat_name), i, 0); ColorCodingComboBox* ccc = new ColorCodingComboBox(); ccc->SetColor(OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(i)).toInt()); color_layout->addWidget(ccc, i, 1); color_btns_.append(ccc); } appearance_layout->addWidget(color_group, row, 0, 1, 2); } row++; { QGroupBox* marker_group = new QGroupBox(); marker_group->setTitle(tr("Miscellaneous")); QGridLayout* marker_layout = new QGridLayout(marker_group); marker_layout->addWidget(new QLabel("Default Marker Color"), 0, 0); marker_btn_ = new ColorCodingComboBox(); marker_btn_->SetColor(OLIVE_CONFIG("MarkerColor").toInt()); marker_layout->addWidget(marker_btn_, 0, 1); appearance_layout->addWidget(marker_group, row, 0, 1, 2); } layout->addStretch(); } void PreferencesAppearanceTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) QString style_path = style_combobox_->currentData().toString(); if (style_path != StyleManager::GetStyle()) { StyleManager::SetStyle(style_path); OLIVE_CONFIG("Style") = style_path; } for (int i=0; iGetSelectedColor(); } OLIVE_CONFIG("MarkerColor") = marker_btn_->GetSelectedColor(); } } ================================================ FILE: app/dialog/preferences/tabs/preferencesappearancetab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESAPPEARANCETAB_H #define PREFERENCESAPPEARANCETAB_H #include #include #include #include "dialog/configbase/configdialogbase.h" #include "ui/style/style.h" #include "widget/colorlabelmenu/colorcodingcombobox.h" namespace olive { class PreferencesAppearanceTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesAppearanceTab(); virtual void Accept(MultiUndoCommand* command) override; private: /** * @brief UI widget for selecting the current UI style */ QComboBox* style_combobox_; QVector color_btns_; ColorCodingComboBox* marker_btn_; }; } #endif // PREFERENCESAPPEARANCETAB_H ================================================ FILE: app/dialog/preferences/tabs/preferencesaudiotab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferencesaudiotab.h" #include #include #include #include "audio/audiomanager.h" #include "config/config.h" namespace olive { PreferencesAudioTab::PreferencesAudioTab() { QVBoxLayout* audio_tab_layout = new QVBoxLayout(this); { // Backend Layout QGridLayout* main_layout = new QGridLayout(); main_layout->setContentsMargins(0, 0, 0, 0); int row = 0; main_layout->addWidget(new QLabel(tr("Backend:")), row, 0); audio_backend_combobox_ = new QComboBox(); connect(audio_backend_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &PreferencesAudioTab::RefreshDevices); main_layout->addWidget(audio_backend_combobox_, row, 1); audio_tab_layout->addLayout(main_layout); } { QGroupBox* groupbox = new QGroupBox(); audio_tab_layout->addWidget(groupbox); QVBoxLayout* layout = new QVBoxLayout(groupbox); int row = 0; { // Output Group QGroupBox* output_group = new QGroupBox(); output_group->setTitle(tr("Output")); layout->addWidget(output_group); QGridLayout* output_layout = new QGridLayout(output_group); output_layout->addWidget(new QLabel(tr("Device:")), row, 0); audio_output_devices_ = new QComboBox(); output_layout->addWidget(audio_output_devices_, row, 1); row++; { int output_row = 0; QGroupBox *output_param_group = new QGroupBox(tr("Advanced")); output_layout->addWidget(output_param_group, row, 0, 1, 2); QGridLayout *output_param_layout = new QGridLayout(output_param_group); output_param_layout->addWidget(new QLabel(tr("Sample Rate:")), output_row, 0); output_rate_combo_ = new SampleRateComboBox(); output_rate_combo_->SetSampleRate(OLIVE_CONFIG("AudioOutputSampleRate").toInt()); output_param_layout->addWidget(output_rate_combo_, output_row, 1); output_row++; output_param_layout->addWidget(new QLabel(tr("Channel Layout:")), output_row, 0); output_ch_layout_combo_ = new ChannelLayoutComboBox(); output_ch_layout_combo_->SetChannelLayout(OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong()); output_param_layout->addWidget(output_ch_layout_combo_, output_row, 1); output_row++; output_param_layout->addWidget(new QLabel(tr("Sample Format:")), output_row, 0); output_fmt_combo_ = new SampleFormatComboBox(); output_fmt_combo_->SetPackedFormats(); output_fmt_combo_->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioOutputSampleFormat").toString().toStdString())); output_param_layout->addWidget(output_fmt_combo_, output_row, 1); } } row = 0; { QGroupBox* input_group = new QGroupBox(); input_group->setTitle(tr("Input")); layout->addWidget(input_group); QGridLayout* input_layout = new QGridLayout(input_group); input_layout->addWidget(new QLabel(tr("Device:")), row, 0); audio_input_devices_ = new QComboBox(); input_layout->addWidget(audio_input_devices_, row, 1); row++; QGroupBox *recording_group = new QGroupBox(tr("Recording")); input_layout->addWidget(recording_group, row, 0, 1, 2); QVBoxLayout *recording_layout = new QVBoxLayout(recording_group); QHBoxLayout *fmt_layout = new QHBoxLayout(); recording_layout->addLayout(fmt_layout); fmt_layout->addWidget(new QLabel(tr("Format:"))); record_format_combo_ = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly); record_format_combo_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); record_format_combo_->SetFormat(static_cast(OLIVE_CONFIG("AudioRecordingFormat").toInt())); fmt_layout->addWidget(record_format_combo_); record_options_ = new ExportAudioTab(); record_options_->SetFormat(record_format_combo_->GetFormat()); record_options_->SetCodec(static_cast(OLIVE_CONFIG("AudioRecordingCodec").toInt())); record_options_->sample_rate_combobox()->SetSampleRate(OLIVE_CONFIG("AudioRecordingSampleRate").toInt()); record_options_->channel_layout_combobox()->SetChannelLayout(OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong()); record_options_->bit_rate_slider()->SetValue(OLIVE_CONFIG("AudioRecordingBitRate").toInt()); record_options_->sample_format_combobox()->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioRecordingSampleFormat").toString().toStdString())); recording_layout->addWidget(record_options_); connect(record_format_combo_, &ExportFormatComboBox::FormatChanged, record_options_, &ExportAudioTab::SetFormat); } QHBoxLayout* refresh_layout = new QHBoxLayout(); layout->addLayout(refresh_layout); refresh_layout->addStretch(); refresh_devices_btn_ = new QPushButton(tr("Refresh Devices")); refresh_layout->addWidget(refresh_devices_btn_); connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::HardRefreshBackends); } audio_tab_layout->addStretch(); // Populate lists RefreshBackends(); } void PreferencesAudioTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) // Get device indexes PaDeviceIndex output_device = audio_output_devices_->currentData().value(); PaDeviceIndex input_device = audio_input_devices_->currentData().value(); // Get device names, which seem to be the closest thing we have to a "unique identifier" for them OLIVE_CONFIG("AudioOutput") = audio_output_devices_->currentText(); OLIVE_CONFIG("AudioInput") = audio_input_devices_->currentText(); // Set devices to be used from now on AudioManager::instance()->SetOutputDevice(output_device); AudioManager::instance()->SetInputDevice(input_device); OLIVE_CONFIG("AudioOutputSampleRate") = output_rate_combo_->GetSampleRate(); OLIVE_CONFIG("AudioOutputChannelLayout") = QVariant::fromValue(output_ch_layout_combo_->GetChannelLayout()); OLIVE_CONFIG("AudioOutputSampleFormat") = QString::fromStdString(output_fmt_combo_->GetSampleFormat().to_string()); OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat(); OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec(); OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate(); OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout()); OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue()); OLIVE_CONFIG("AudioRecordingSampleFormat") = QString::fromStdString(record_options_->sample_format_combobox()->GetSampleFormat().to_string()); emit AudioManager::instance()->OutputParamsChanged(); } void PreferencesAudioTab::RefreshBackends() { audio_backend_combobox_->clear(); for (PaHostApiIndex i=0, end=Pa_GetHostApiCount(); iaddItem(info->name); } RefreshDevices(); AttemptToSetDevicesFromConfig(); } void PreferencesAudioTab::RefreshDevices() { if (audio_backend_combobox_->count() == 0) { return; } PaHostApiIndex host_index = audio_backend_combobox_->currentIndex(); const PaHostApiInfo *host = Pa_GetHostApiInfo(host_index); audio_output_devices_->clear(); audio_input_devices_->clear(); for (int i=0; ideviceCount; i++) { PaDeviceIndex device_index = Pa_HostApiDeviceIndexToDeviceIndex(host_index, i); const PaDeviceInfo *device = Pa_GetDeviceInfo(device_index); if (device->maxOutputChannels) { audio_output_devices_->addItem(device->name, device_index); } if (device->maxInputChannels) { audio_input_devices_->addItem(device->name, device_index); } } } void PreferencesAudioTab::HardRefreshBackends() { AudioManager::instance()->HardReset(); RefreshBackends(); } void PreferencesAudioTab::AttemptToSetDevicesFromConfig() { // Load with currently active devices PaDeviceIndex current_output_index = AudioManager::instance()->GetOutputDevice(); PaDeviceIndex current_input_index = AudioManager::instance()->GetInputDevice(); const PaDeviceInfo *current_output = nullptr, *current_input = nullptr; if (current_output_index != paNoDevice) { current_output = Pa_GetDeviceInfo(current_output_index); } if (current_input_index != paNoDevice) { current_input = Pa_GetDeviceInfo(current_input_index); } if (current_output || current_input) { PaHostApiIndex host = current_output ? current_output->hostApi : current_input->hostApi; // Set backend accordingly audio_backend_combobox_->setCurrentIndex(host); // Device comboboxes should be populated correctly now if (current_output) { audio_output_devices_->setCurrentText(current_output->name); } if (current_input) { audio_input_devices_->setCurrentText(current_input->name); } } } } ================================================ FILE: app/dialog/preferences/tabs/preferencesaudiotab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESAUDIOTAB_H #define PREFERENCESAUDIOTAB_H #include #include #include "dialog/configbase/configdialogbase.h" #include "dialog/export/exportaudiotab.h" #include "dialog/export/exportformatcombobox.h" namespace olive { class PreferencesAudioTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesAudioTab(); virtual void Accept(MultiUndoCommand* command) override; private: QComboBox* audio_backend_combobox_; /** * @brief UI widget for selecting the output audio device */ QComboBox* audio_output_devices_; /** * @brief UI widget for selecting the input audio device */ QComboBox* audio_input_devices_; /** * @brief UI widget for editing the recording channels */ QComboBox* recording_combobox_; /** * @brief Button that triggers a refresh of the available audio devices */ QPushButton* refresh_devices_btn_; SampleRateComboBox *output_rate_combo_; ChannelLayoutComboBox *output_ch_layout_combo_; SampleFormatComboBox *output_fmt_combo_; ExportFormatComboBox *record_format_combo_; ExportAudioTab *record_options_; private slots: void RefreshBackends(); void RefreshDevices(); void HardRefreshBackends(); void AttemptToSetDevicesFromConfig(); }; } #endif // PREFERENCESAUDIOTAB_H ================================================ FILE: app/dialog/preferences/tabs/preferencesbehaviortab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferencesbehaviortab.h" #include #include #include "config/config.h" namespace olive { PreferencesBehaviorTab::PreferencesBehaviorTab() { QVBoxLayout* layout = new QVBoxLayout(this); behavior_tree_ = new QTreeWidget(); layout->addWidget(behavior_tree_); behavior_tree_->setHeaderLabel(tr("Behavior")); QTreeWidgetItem* general_group = AddParent(tr("General")); AddItem(tr("Enable hover focus"), QStringLiteral("HoverFocus"), tr("Panels will be considered focused when the mouse cursor is over them without having to click them."), general_group); AddItem(tr("Enable slider ladder"), QStringLiteral("UseSliderLadders"), general_group); AddItem(tr("Scrolling zooms by default"), QStringLiteral("ScrollZooms"), tr("By default, scrolling will move the view around, and holding Ctrl/Cmd will make it zoom instead. " "Enabling this will switch those, scrolling will zoom by default, and holding Ctrl/Cmd will move the view instead."), general_group); QTreeWidgetItem* audio_group = AddParent(tr("Audio")); AddItem(tr("Enable audio scrubbing"), QStringLiteral("AudioScrubbing"), audio_group); QTreeWidgetItem* timeline_group = AddParent(tr("Timeline")); AddItem(tr("Auto-Seek to Imported Clips"), QStringLiteral("EnableSeekToImport"), timeline_group); AddItem(tr("Edit Tool Also Seeks"), QStringLiteral("EditToolAlsoSeeks"), timeline_group); AddItem(tr("Edit Tool Selects Links"), QStringLiteral("EditToolSelectsLinks"), timeline_group); AddItem(tr("Enable Drag Files to Timeline"), QStringLiteral("EnableDragFilesToTimeline"), timeline_group); AddItem(tr("Invert Timeline Scroll Axes"), QStringLiteral("InvertTimelineScrollAxes"), tr("Hold ALT on any UI element to switch scrolling axes"), timeline_group); AddItem(tr("Seek Also Selects"), QStringLiteral("SeekAlsoSelects"), timeline_group); AddItem(tr("Seek to the End of Pastes"), QStringLiteral("PasteSeeks"), timeline_group); AddItem(tr("Selecting Also Seeks"), QStringLiteral("SelectAlsoSeeks"), timeline_group); QTreeWidgetItem* playback_group = AddParent(tr("Playback")); AddItem(tr("Ask For Name When Setting Marker"), QStringLiteral("SetNameWithMarker"), playback_group); AddItem(tr("Automatically rewind at the end of a sequence"), QStringLiteral("AutoSeekToBeginning"), playback_group); QTreeWidgetItem* project_group = AddParent(tr("Project")); AddItem(tr("Drop Files on Media to Replace"), QStringLiteral("DropFileOnMediaToReplace"), project_group); QTreeWidgetItem* node_group = AddParent(tr("Nodes")); AddItem(tr("Add Default Effects to New Clips"), QStringLiteral("AddDefaultEffectsToClips"), node_group); AddItem(tr("Auto-Scale By Default"), QStringLiteral("AutoscaleByDefault"), node_group); AddItem(tr("Splitting Clips Copies Dependencies"), QStringLiteral("SplitClipsCopyNodes"), tr("Multiple clips can share the same nodes. Disable this to automatically share node " "dependencies among clips when copying or splitting them."), node_group); QTreeWidgetItem* opengl_group = AddParent(tr("OpenGL")); AddItem(tr("Use glFinish"), QStringLiteral("UseGLFinish"), opengl_group); } void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) for (auto iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { OLIVE_CONFIG_STR(iterator.value()) = (iterator.key()->checkState(0) == Qt::Checked); } } QTreeWidgetItem* PreferencesBehaviorTab::AddItem(const QString &text, const QString &config_key, const QString& tooltip, QTreeWidgetItem* parent) { QTreeWidgetItem* item = new QTreeWidgetItem({text}); item->setToolTip(0, tooltip); item->setCheckState(0, OLIVE_CONFIG_STR(config_key).toBool() ? Qt::Checked : Qt::Unchecked); config_map_.insert(item, config_key); if (parent) { parent->addChild(item); } else { behavior_tree_->addTopLevelItem(item); } return item; } QTreeWidgetItem *PreferencesBehaviorTab::AddItem(const QString &text, const QString &config_key, QTreeWidgetItem *parent) { return AddItem(text, config_key, QString(), parent); } QTreeWidgetItem *PreferencesBehaviorTab::AddParent(const QString &text, const QString &tooltip, QTreeWidgetItem *parent) { QTreeWidgetItem* item = new QTreeWidgetItem({text}); item->setToolTip(0, tooltip); if (parent) { parent->addChild(item); } else { behavior_tree_->addTopLevelItem(item); } return item; } QTreeWidgetItem *PreferencesBehaviorTab::AddParent(const QString &text, QTreeWidgetItem *parent) { return AddParent(text, QString(), parent); } } ================================================ FILE: app/dialog/preferences/tabs/preferencesbehaviortab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESBEHAVIORTAB_H #define PREFERENCESBEHAVIORTAB_H #include #include "dialog/configbase/configdialogbase.h" namespace olive { class PreferencesBehaviorTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesBehaviorTab(); virtual void Accept(MultiUndoCommand* command) override; private: QTreeWidgetItem *AddParent(const QString& text, const QString &tooltip, QTreeWidgetItem *parent = nullptr); QTreeWidgetItem *AddParent(const QString& text, QTreeWidgetItem *parent = nullptr); QTreeWidgetItem *AddItem(const QString& text, const QString& config_key, const QString &tooltip, QTreeWidgetItem *parent ); QTreeWidgetItem *AddItem(const QString& text, const QString& config_key, QTreeWidgetItem *parent); QMap config_map_; QTreeWidget* behavior_tree_; }; } #endif // PREFERENCESBEHAVIORTAB_H ================================================ FILE: app/dialog/preferences/tabs/preferencesdisktab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferencesdisktab.h" #include #include #include #include #include #include #include "common/filefunctions.h" namespace olive { PreferencesDiskTab::PreferencesDiskTab() { // Get default disk cache folder default_disk_cache_folder_ = DiskManager::instance()->GetDefaultCacheFolder(); QVBoxLayout* outer_layout = new QVBoxLayout(this); QGroupBox* disk_management_group = new QGroupBox(tr("Disk Management")); outer_layout->addWidget(disk_management_group); QGridLayout* disk_management_layout = new QGridLayout(disk_management_group); int row = 0; disk_management_layout->addWidget(new QLabel(tr("Disk Cache Location:")), row, 0); disk_cache_location_ = new PathWidget(default_disk_cache_folder_->GetPath()); disk_management_layout->addWidget(disk_cache_location_, row, 1); row++; QPushButton* disk_cache_settings_btn = new QPushButton(tr("Disk Cache Settings")); connect(disk_cache_settings_btn, &QPushButton::clicked, this, [this](){ DiskManager::instance()->ShowDiskCacheSettingsDialog(disk_cache_location_->text(), this); }); disk_management_layout->addWidget(disk_cache_settings_btn, row, 1); row++; QGroupBox* cache_behavior = new QGroupBox(tr("Cache Behavior")); outer_layout->addWidget(cache_behavior); QGridLayout* cache_behavior_layout = new QGridLayout(cache_behavior); row = 0; cache_behavior_layout->addWidget(new QLabel(tr("Cache Ahead:")), row, 0); cache_ahead_slider_ = new FloatSlider(); cache_ahead_slider_->SetFormat(tr("%1 seconds")); cache_ahead_slider_->SetMinimum(0); cache_ahead_slider_->SetValue(OLIVE_CONFIG("DiskCacheAhead").value().toDouble()); cache_behavior_layout->addWidget(cache_ahead_slider_, row, 1); cache_behavior_layout->addWidget(new QLabel(tr("Cache Behind:")), row, 2); cache_behind_slider_ = new FloatSlider(); cache_behind_slider_->SetMinimum(0); cache_behind_slider_->SetFormat(tr("%1 seconds")); cache_behind_slider_->SetValue(OLIVE_CONFIG("DiskCacheBehind").value().toDouble()); cache_behavior_layout->addWidget(cache_behind_slider_, row, 3); outer_layout->addStretch(); } bool PreferencesDiskTab::Validate() { if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) { // Disk cache location is changing // Check if the user is okay with invalidating the current cache if (!DiskManager::ShowDiskCacheChangeConfirmationDialog(this)) { return false; } // Check validity of the new path if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text())) { QMessageBox::critical(this, tr("Disk Cache"), tr("Failed to set disk cache location. Access was denied.")); return false; } } return true; } void PreferencesDiskTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) { default_disk_cache_folder_->SetPath(disk_cache_location_->text()); } OLIVE_CONFIG("DiskCacheBehind") = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue())); OLIVE_CONFIG("DiskCacheAhead") = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue())); } } ================================================ FILE: app/dialog/preferences/tabs/preferencesdisktab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESDISKTAB_H #define PREFERENCESDISKTAB_H #include #include #include #include "dialog/configbase/configdialogbase.h" #include "render/diskmanager.h" #include "widget/slider/floatslider.h" #include "widget/path/pathwidget.h" namespace olive { class PreferencesDiskTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesDiskTab(); virtual bool Validate() override; virtual void Accept(MultiUndoCommand* command) override; private: PathWidget* disk_cache_location_; FloatSlider* cache_ahead_slider_; FloatSlider* cache_behind_slider_; DiskCacheFolder* default_disk_cache_folder_; }; } #endif // PREFERENCESDISKTAB_H ================================================ FILE: app/dialog/preferences/tabs/preferencesgeneraltab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferencesgeneraltab.h" #include #include #include #include #include "common/autoscroll.h" #include "core.h" #include "dialog/sequence/sequence.h" #include "node/project/sequence/sequence.h" namespace olive { PreferencesGeneralTab::PreferencesGeneralTab() { QVBoxLayout* layout = new QVBoxLayout(this); { QGroupBox* global_groupbox = new QGroupBox(tr("Locale")); QGridLayout* global_layout = new QGridLayout(global_groupbox); layout->addWidget(global_groupbox); int row = 0; // General -> Language global_layout->addWidget(new QLabel(tr("Language:")), row, 0); language_combobox_ = new QComboBox(); // Add default language (en-US) QDir language_dir(QStringLiteral(":/ts")); QStringList languages = language_dir.entryList(); foreach (const QString& l, languages) { AddLanguage(l); } QString current_language = OLIVE_CONFIG("Language").toString(); if (current_language.isEmpty()) { // No configured language, use system language current_language = QLocale::system().name(); // If we don't have a language for this, default to en_US if (!languages.contains(current_language)) { current_language = QStringLiteral("en_US"); } } language_combobox_->setCurrentIndex(languages.indexOf(current_language)); global_layout->addWidget(language_combobox_, row, 1); } { QGroupBox* timeline_groupbox = new QGroupBox(tr("Timeline")); QGridLayout* timeline_layout = new QGridLayout(timeline_groupbox); layout->addWidget(timeline_groupbox); int row = 0; QLabel* autoscroll_lbl = new QLabel(tr("Auto-Scroll Method:")); autoscroll_lbl->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); timeline_layout->addWidget(autoscroll_lbl, row, 0); // ComboBox indices match enum indices autoscroll_method_ = new QComboBox(); autoscroll_method_->addItem(tr("None"), AutoScroll::kNone); autoscroll_method_->addItem(tr("Page Scrolling"), AutoScroll::kPage); autoscroll_method_->addItem(tr("Smooth Scrolling"), AutoScroll::kSmooth); autoscroll_method_->setCurrentIndex(OLIVE_CONFIG("Autoscroll").toInt()); timeline_layout->addWidget(autoscroll_method_, row, 1); row++; timeline_layout->addWidget(new QLabel(tr("Rectified Waveforms:")), row, 0); rectified_waveforms_ = new QCheckBox(); rectified_waveforms_->setChecked(OLIVE_CONFIG("RectifiedWaveforms").toBool()); timeline_layout->addWidget(rectified_waveforms_, row, 1); row++; timeline_layout->addWidget(new QLabel(tr("Default Still Image Length:")), row, 0); default_still_length_ = new RationalSlider(); default_still_length_->SetMinimum(rational(100, 1000)); default_still_length_->SetTimebase(rational(100, 1000)); default_still_length_->SetFormat(tr("%1 seconds")); default_still_length_->SetValue(OLIVE_CONFIG("DefaultStillLength").value()); timeline_layout->addWidget(default_still_length_); } { QGroupBox* autorecovery_groupbox = new QGroupBox(tr("Auto-Recovery")); QGridLayout* autorecovery_layout = new QGridLayout(autorecovery_groupbox); layout->addWidget(autorecovery_groupbox); int row = 0; autorecovery_layout->addWidget(new QLabel(tr("Enable Auto-Recovery:")), row, 0); autorecovery_enabled_ = new QCheckBox(); autorecovery_enabled_->setChecked(OLIVE_CONFIG("AutorecoveryEnabled").toBool()); autorecovery_layout->addWidget(autorecovery_enabled_, row, 1); row++; autorecovery_layout->addWidget(new QLabel(tr("Auto-Recovery Interval:")), row, 0); autorecovery_interval_ = new IntegerSlider(); autorecovery_interval_->SetMinimum(1); autorecovery_interval_->SetMaximum(60); autorecovery_interval_->SetFormat(QT_TRANSLATE_N_NOOP("olive::SliderBase", "%n minute(s)"), true); autorecovery_interval_->SetValue(OLIVE_CONFIG("AutorecoveryInterval").toLongLong()); autorecovery_layout->addWidget(autorecovery_interval_, row, 1); row++; autorecovery_layout->addWidget(new QLabel(tr("Maximum Versions Per Project:")), row, 0); autorecovery_maximum_ = new IntegerSlider(); autorecovery_maximum_->SetMinimum(1); autorecovery_maximum_->SetMaximum(1000); autorecovery_maximum_->SetValue(OLIVE_CONFIG("AutorecoveryMaximum").toLongLong()); autorecovery_layout->addWidget(autorecovery_maximum_, row, 1); row++; QPushButton* browse_autorecoveries = new QPushButton(tr("Browse Auto-Recoveries")); connect(browse_autorecoveries, &QPushButton::clicked, Core::instance(), &Core::BrowseAutoRecoveries); autorecovery_layout->addWidget(browse_autorecoveries, row, 1); } layout->addStretch(); } void PreferencesGeneralTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) OLIVE_CONFIG("RectifiedWaveforms") = rectified_waveforms_->isChecked(); OLIVE_CONFIG("Autoscroll") = autoscroll_method_->currentData(); OLIVE_CONFIG("DefaultStillLength") = QVariant::fromValue(default_still_length_->GetValue()); QString set_language = language_combobox_->currentData().toString(); if (QLocale::system().name() == set_language) { // Language is set to the system, assume this is effectively "auto" set_language = QString(); } // If the language has changed, set it now if (OLIVE_CONFIG("Language").toString() != set_language) { OLIVE_CONFIG("Language") = set_language; Core::instance()->SetLanguage(set_language.isEmpty() ? QLocale::system().name() : set_language); } OLIVE_CONFIG("AutorecoveryEnabled") = autorecovery_enabled_->isChecked(); OLIVE_CONFIG("AutorecoveryInterval") = QVariant::fromValue(autorecovery_interval_->GetValue()); OLIVE_CONFIG("AutorecoveryMaximum") = QVariant::fromValue(autorecovery_maximum_->GetValue()); Core::instance()->SetAutorecoveryInterval(autorecovery_interval_->GetValue()); } void PreferencesGeneralTab::AddLanguage(const QString &locale_name) { language_combobox_->addItem(tr("%1 (%2)").arg(QLocale(locale_name).nativeLanguageName(), locale_name));; language_combobox_->setItemData(language_combobox_->count() - 1, locale_name); } } ================================================ FILE: app/dialog/preferences/tabs/preferencesgeneraltab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESGENERALTAB_H #define PREFERENCESGENERALTAB_H #include #include #include #include "dialog/configbase/configdialogbase.h" #include "node/project/sequence/sequence.h" #include "widget/slider/rationalslider.h" #include "widget/slider/integerslider.h" namespace olive { class PreferencesGeneralTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesGeneralTab(); virtual void Accept(MultiUndoCommand* command) override; private: void AddLanguage(const QString& locale_name); QComboBox* language_combobox_; QComboBox* autoscroll_method_; QCheckBox* rectified_waveforms_; RationalSlider* default_still_length_; QCheckBox* autorecovery_enabled_; IntegerSlider* autorecovery_interval_; IntegerSlider* autorecovery_maximum_; }; } #endif // PREFERENCESGENERALTAB_H ================================================ FILE: app/dialog/preferences/tabs/preferenceskeyboardtab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "preferenceskeyboardtab.h" #include #include #include #include #include #include #include "window/mainwindow/mainwindow.h" namespace olive { PreferencesKeyboardTab::PreferencesKeyboardTab(MainWindow *main_window) : main_window_(main_window) { QVBoxLayout* shortcut_layout = new QVBoxLayout(this); QLineEdit* key_search_line = new QLineEdit(); key_search_line->setPlaceholderText(tr("Search for action or shortcut")); connect(key_search_line, SIGNAL(textChanged(const QString &)), this, SLOT(refine_shortcut_list(const QString &))); shortcut_layout->addWidget(key_search_line); keyboard_tree_ = new QTreeWidget(); QTreeWidgetItem* tree_header = keyboard_tree_->headerItem(); tree_header->setText(0, tr("Action")); tree_header->setText(1, tr("Shortcut")); shortcut_layout->addWidget(keyboard_tree_); QHBoxLayout* reset_shortcut_layout = new QHBoxLayout(); QPushButton* import_shortcut_button = new QPushButton(tr("Import")); reset_shortcut_layout->addWidget(import_shortcut_button); connect(import_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(load_shortcut_file())); QPushButton* export_shortcut_button = new QPushButton(tr("Export")); reset_shortcut_layout->addWidget(export_shortcut_button); connect(export_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(save_shortcut_file())); reset_shortcut_layout->addStretch(); QPushButton* reset_selected_shortcut_button = new QPushButton(tr("Reset Selected")); reset_shortcut_layout->addWidget(reset_selected_shortcut_button); connect(reset_selected_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_default_shortcut())); QPushButton* reset_all_shortcut_button = new QPushButton(tr("Reset All")); reset_shortcut_layout->addWidget(reset_all_shortcut_button); connect(reset_all_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_all_shortcuts())); shortcut_layout->addLayout(reset_shortcut_layout); setup_kbd_shortcuts(main_window_->menuBar()); } void PreferencesKeyboardTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) // Save keyboard shortcuts for (int i=0;iset_action_shortcut(); } main_window_->SaveLayout(); } void PreferencesKeyboardTab::setup_kbd_shortcuts(QMenuBar* menubar) { QList menus = menubar->actions(); for (int i=0;imenu(); QTreeWidgetItem* item = new QTreeWidgetItem(keyboard_tree_); item->setText(0, menu->title().replace("&", "")); keyboard_tree_->addTopLevelItem(item); setup_kbd_shortcut_worker(menu, item); } for (int i=0;iproperty("id").isNull()) { KeySequenceEditor* editor = new KeySequenceEditor(keyboard_tree_, key_shortcut_actions_.at(i)); keyboard_tree_->setItemWidget(key_shortcut_items_.at(i), 1, editor); key_shortcut_fields_.append(editor); } } } void PreferencesKeyboardTab::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent) { QList actions = menu->actions(); for (int i=0;iisSeparator() && a->property("keyignore").isNull()) { QTreeWidgetItem* item = new QTreeWidgetItem(parent); item->setText(0, a->text().replace("&", "")); parent->addChild(item); if (a->menu() != nullptr) { item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); setup_kbd_shortcut_worker(a->menu(), item); } else { key_shortcut_items_.append(item); key_shortcut_actions_.append(a); } } } } void PreferencesKeyboardTab::reset_default_shortcut() { QList items = keyboard_tree_->selectedItems(); for (int i=0;iselectedItems().at(i); static_cast(keyboard_tree_->itemWidget(item, 1))->reset_to_default(); } } void PreferencesKeyboardTab::reset_all_shortcuts() { if (QMessageBox::question( this, tr("Confirm Reset All Shortcuts"), tr("Are you sure you wish to reset all keyboard shortcuts to their defaults?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { for (int i=0;ireset_to_default(); } } } bool PreferencesKeyboardTab::refine_shortcut_list(const QString &s, QTreeWidgetItem* parent) { if (parent == nullptr) { for (int i=0;itopLevelItemCount();i++) { refine_shortcut_list(s, keyboard_tree_->topLevelItem(i)); } // Return value is `all_children_are_hidden` which doesn't matter at the top level return false; } else { parent->setExpanded(!s.isEmpty()); bool all_children_are_hidden = !s.isEmpty(); for (int i=0;ichildCount();i++) { QTreeWidgetItem* item = parent->child(i); if (item->childCount() > 0) { if (!refine_shortcut_list(s, item)) { all_children_are_hidden = false; } } else { item->setHidden(false); if (s.isEmpty()) { all_children_are_hidden = false; } else { QString shortcut; if (keyboard_tree_->itemWidget(item, 1) != nullptr) { shortcut = static_cast(keyboard_tree_->itemWidget(item, 1))->keySequence().toString(); } if (item->text(0).contains(s, Qt::CaseInsensitive) || shortcut.contains(s, Qt::CaseInsensitive)) { all_children_are_hidden = false; } else { item->setHidden(true); } } } } if (parent->text(0).contains(s, Qt::CaseInsensitive)) all_children_are_hidden = false; parent->setHidden(all_children_are_hidden); return all_children_are_hidden; } } void PreferencesKeyboardTab::load_shortcut_file() { QString fn = QFileDialog::getOpenFileName(this, tr("Import Keyboard Shortcuts")); if (!fn.isEmpty()) { QFile f(fn); if (f.exists() && f.open(QFile::ReadOnly)) { QString ba = f.readAll(); f.close(); for (int i=0;iaction_name()); if (index == 0 || (index > 0 && ba.at(index-1) == '\n')) { while (index < ba.size() && ba.at(index) != '\t') index++; QString ks; index++; while (index < ba.size() && ba.at(index) != '\n') { ks.append(ba.at(index)); index++; } key_shortcut_fields_.at(i)->setKeySequence(ks); } else { key_shortcut_fields_.at(i)->reset_to_default(); } } } else { QMessageBox::critical( this, tr("Error saving shortcuts"), tr("Failed to open file for reading") ); } } } void PreferencesKeyboardTab::save_shortcut_file() { QString fn = QFileDialog::getSaveFileName(this, tr("Export Keyboard Shortcuts")); if (!fn.isEmpty()) { QFile f(fn); if (f.open(QFile::WriteOnly)) { bool start = true; for (int i=0;iexport_shortcut(); if (!s.isEmpty()) { if (!start) f.write("\n"); f.write(s.toUtf8()); start = false; } } f.close(); QMessageBox::information(this, tr("Export Shortcuts"), tr("Shortcuts exported successfully")); } else { QMessageBox::critical(this, tr("Error saving shortcuts"), tr("Failed to open file for writing")); } } } } ================================================ FILE: app/dialog/preferences/tabs/preferenceskeyboardtab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PREFERENCESKEYBOARDTAB_H #define PREFERENCESKEYBOARDTAB_H #include #include #include "dialog/configbase/configdialogbase.h" #include "../keysequenceeditor.h" namespace olive { class MainWindow; class PreferencesKeyboardTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesKeyboardTab(MainWindow* main_window); virtual void Accept(MultiUndoCommand* command) override; private slots: /** * @brief Show a file dialog to load an external shortcut preset from file */ void load_shortcut_file(); /** * @brief Show a file dialog to save an external shortcut preset from file */ void save_shortcut_file(); /** * @brief Reset all selected shortcuts in keyboard_tree to their defaults */ void reset_default_shortcut(); /** * @brief Reset all shortcuts indiscriminately to their defaults * * This is safe to call directly as it'll ask the user if they wish to do so before it resets. */ void reset_all_shortcuts(); /** * @brief Shows/hides shortcut entries according to a shortcut query. * * This function can be directly connected to QLineEdit::textChanged() for simplicity. * * @param s * * The search query to compare shortcut names to. * * @param parent * * This is used as the function calls itself recursively to traverse the menu item hierarchy. This should be left as * nullptr when called externally. * * @return * * Value used as function calls itself recursively to determine if a menu parent has any children that are not hidden. * If so, TRUE is returned so the parent is shown too (even if it doesn't match the search query). If not, FALSE is * returned so the parent is hidden. */ bool refine_shortcut_list(const QString &s, QTreeWidgetItem* parent = nullptr); private: /** * @brief Populate keyboard shortcut panel with keyboard shortcuts from the menu bar * * @param menu * * A reference to the main application's menu bar. Usually MainWindow::menuBar(). */ void setup_kbd_shortcuts(QMenuBar* menu); /** * @brief Internal function called by setup_kbd_shortcuts() to traverse down the menu bar's hierarchy and populate the * shortcut panel. * * This function will call itself recursively as it finds submenus belong to the menu provided. It will also create * QTreeWidgetItems as children of the parent item provided, either using them as parents themselves for submenus * or attaching a KeySequenceEditor to them for shortcut editing. * * @param menu * * The current menu to traverse down. * * @param parent * * The parent item to add QTreeWidgetItems to. */ void setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent); /** * @brief UI widget for editing keyboard shortcuts */ QTreeWidget* keyboard_tree_; /** * @brief List of keyboard shortcut actions that can be triggered (links with key_shortcut_items and * key_shortcut_fields) */ QVector key_shortcut_actions_; /** * @brief List of keyboard shortcut items in keyboard_tree corresponding to existing actions (links with * key_shortcut_actions and key_shortcut_fields) */ QVector key_shortcut_items_; /** * @brief List of keyboard shortcut editing fields in keyboard_tree corresponding to existing actions (links with * key_shortcut_actions and key_shortcut_fields) */ QVector key_shortcut_fields_; MainWindow *main_window_; }; } #endif // PREFERENCESKEYBOARDTAB_H ================================================ FILE: app/dialog/progress/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/progress/progress.h dialog/progress/progress.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/progress/progress.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "progress.h" #include #include #include #include #include "window/mainwindow/mainwindow.h" namespace olive { #define super QDialog ProgressDialog::ProgressDialog(const QString& message, const QString& title, QWidget *parent) : super(parent), show_progress_(true), first_show_(true) { if (!title.isEmpty()) { setWindowTitle(title); } QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(new QLabel(message)); bar_ = new QProgressBar(); bar_->setMinimum(0); bar_->setValue(0); bar_->setMaximum(100); layout->addWidget(bar_); elapsed_timer_lbl_ = new ElapsedCounterWidget(); layout->addWidget(elapsed_timer_lbl_); QHBoxLayout* cancel_layout = new QHBoxLayout(); layout->addLayout(cancel_layout); cancel_layout->setContentsMargins(0, 0, 0, 0); cancel_layout->setSpacing(0); cancel_layout->addStretch(); QPushButton* cancel_btn = new QPushButton(tr("Cancel")); // Signal that derivatives can connect to connect(cancel_btn, &QPushButton::clicked, this, &ProgressDialog::Cancelled, Qt::DirectConnection); // Stop updating the elapsed/remaining timers connect(cancel_btn, &QPushButton::clicked, elapsed_timer_lbl_, &ElapsedCounterWidget::Stop); // Disable the button so that users know they don't need to keep clicking it connect(cancel_btn, &QPushButton::clicked, this, &ProgressDialog::DisableSenderWidget); // Prevent the progress bar from continuing to move connect(cancel_btn, &QPushButton::clicked, this, &ProgressDialog::DisableProgressWidgets); cancel_layout->addWidget(cancel_btn); cancel_layout->addStretch(); } void ProgressDialog::showEvent(QShowEvent *e) { super::showEvent(e); if (first_show_) { elapsed_timer_lbl_->Start(); Core::instance()->main_window()->SetApplicationProgressStatus(MainWindow::kProgressShow); first_show_ = false; } } void ProgressDialog::closeEvent(QCloseEvent *e) { super::closeEvent(e); Core::instance()->main_window()->SetApplicationProgressStatus(MainWindow::kProgressNone); elapsed_timer_lbl_->Stop(); first_show_ = true; } void ProgressDialog::SetProgress(double value) { if (!show_progress_) { return; } int percent = qRound(100.0 * value); bar_->setValue(percent); elapsed_timer_lbl_->SetProgress(value); Core::instance()->main_window()->SetApplicationProgressValue(percent); } void ProgressDialog::ShowErrorMessage(const QString &title, const QString &message) { Core::instance()->main_window()->SetApplicationProgressStatus(MainWindow::kProgressError); QMessageBox b(this); b.setIcon(QMessageBox::Critical); b.setWindowModality(Qt::WindowModal); b.setWindowTitle(title); b.setText(message); b.addButton(QMessageBox::Ok); b.exec(); } void ProgressDialog::DisableSenderWidget() { static_cast(sender())->setEnabled(false); } void ProgressDialog::DisableProgressWidgets() { show_progress_ = false; } } ================================================ FILE: app/dialog/progress/progress.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PROGRESSDIALOG_H #define PROGRESSDIALOG_H #include #include #include "common/debug.h" #include "widget/taskview/elapsedcounterwidget.h" namespace olive { class ProgressDialog : public QDialog { Q_OBJECT public: ProgressDialog(const QString &message, const QString &title, QWidget* parent = nullptr); protected: virtual void showEvent(QShowEvent* e) override; virtual void closeEvent(QCloseEvent *) override; public slots: void SetProgress(double value); signals: void Cancelled(); protected: void ShowErrorMessage(const QString& title, const QString& message); private: QProgressBar* bar_; ElapsedCounterWidget* elapsed_timer_lbl_; bool show_progress_; bool first_show_; private slots: void DisableSenderWidget(); void DisableProgressWidgets(); }; } #endif // PROGRESSDIALOG_H ================================================ FILE: app/dialog/projectproperties/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2020 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/projectproperties/projectproperties.h dialog/projectproperties/projectproperties.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/projectproperties/projectproperties.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "projectproperties.h" #include #include #include #include #include #include #include #include "common/filefunctions.h" #include "node/color/colormanager/colormanager.h" #include "render/diskmanager.h" namespace olive { #define super QDialog ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) : super(parent), working_project_(p), ocio_config_is_valid_(true) { QVBoxLayout* layout = new QVBoxLayout(this); setWindowTitle(tr("Project Properties for '%1'").arg(working_project_->name())); QTabWidget* tabs = new QTabWidget; layout->addWidget(tabs); { // Color management group QWidget* color_group = new QWidget(); QVBoxLayout* color_outer_layout = new QVBoxLayout(color_group); QGridLayout* color_layout = new QGridLayout(); color_outer_layout->addLayout(color_layout); int row = 0; color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0); ocio_filename_ = new QLineEdit(); ocio_filename_->setPlaceholderText(tr("(default)")); color_layout->addWidget(ocio_filename_, row, 1); row++; color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0); default_input_colorspace_ = new QComboBox(); color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2); row++; color_layout->addWidget(new QLabel(tr("Reference Space:")), row, 0); reference_space_ = new QComboBox(this); reference_space_->addItem(tr("Scene Linear"), OCIO::ROLE_SCENE_LINEAR); reference_space_->addItem(tr("Compositing Log"), OCIO::ROLE_COMPOSITING_LOG); QtUtils::SetComboBoxData(reference_space_, p->GetColorReferenceSpace()); color_layout->addWidget(reference_space_, row, 1, 1, 2); row++; QPushButton* browse_btn = new QPushButton(tr("Browse")); color_layout->addWidget(browse_btn, 0, 2); connect(browse_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::BrowseForOCIOConfig); ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename()); connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::OCIOFilenameUpdated); OCIOFilenameUpdated(); tabs->addTab(color_group, tr("Color Management")); color_outer_layout->addStretch(); } { // Cache group QWidget* cache_group = new QWidget(); QVBoxLayout* cache_layout = new QVBoxLayout(cache_group); QButtonGroup* disk_cache_btn_group = new QButtonGroup(); // Create radio buttons and add to widget and button group disk_cache_radios_[Project::kCacheUseDefaultLocation] = new QRadioButton(tr("Use Default Location")); disk_cache_radios_[Project::kCacheStoreAlongsideProject] = new QRadioButton(tr("Store Alongside Project")); disk_cache_radios_[Project::kCacheCustomPath] = new QRadioButton(tr("Use Custom Location:")); for (int i=0; iaddButton(disk_cache_radios_[i]); cache_layout->addWidget(disk_cache_radios_[i]); } // Create custom cache path widget custom_cache_path_ = new PathWidget(working_project_->GetCustomCachePath(), this); custom_cache_path_->setEnabled(false); cache_layout->addWidget(custom_cache_path_); // Ensure custom cache path "enabled" is tied to the radio button being checked connect(disk_cache_radios_[Project::kCacheCustomPath], &QRadioButton::toggled, custom_cache_path_, &PathWidget::setEnabled); // Check the radio button that should currently be active disk_cache_radios_[working_project_->GetCacheLocationSetting()]->setChecked(true); // Add disk cache settings button QPushButton* disk_cache_settings_btn = new QPushButton(tr("Disk Cache Settings")); connect(disk_cache_settings_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::OpenDiskCacheSettings); cache_layout->addWidget(disk_cache_settings_btn); tabs->addTab(cache_group, tr("Disk Cache")); } QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); layout->addWidget(dialog_btns); connect(dialog_btns, &QDialogButtonBox::accepted, this, &ProjectPropertiesDialog::accept); connect(dialog_btns, &QDialogButtonBox::rejected, this, &ProjectPropertiesDialog::reject); } void ProjectPropertiesDialog::accept() { if (!ocio_config_is_valid_) { QMessageBox mb(this); mb.setWindowModality(Qt::WindowModal); mb.setIcon(QMessageBox::Critical); mb.setWindowTitle(tr("OpenColorIO Config Error")); mb.setText(tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_)); mb.addButton(QMessageBox::Ok); mb.exec(); return; } if (disk_cache_radios_[Project::kCacheUseDefaultLocation]->isChecked()) { // Keep new cache path empty, which means default } else if (disk_cache_radios_[Project::kCacheStoreAlongsideProject]->isChecked()) { // Ensure alongside project path is valid if (!VerifyPathAndWarnIfBad(working_project_->get_cache_alongside_project_path())) { return; } } else { // Ensure custom path is valid if (!VerifyPathAndWarnIfBad(custom_cache_path_->text())) { return; } } if (custom_cache_path_->text() != working_project_->GetCustomCachePath()) { // Check if the user is okay with invalidating the current cache if (!DiskManager::ShowDiskCacheChangeConfirmationDialog(this)) { return; } working_project_->SetCustomCachePath(custom_cache_path_->text()); emit DiskManager::instance()->InvalidateProject(working_project_); } // This should ripple changes throughout the graph/cache that the color config has changed, and // therefore should be done after the cache path is changed if (working_project_->color_manager()->GetConfigFilename() != ocio_filename_->text()) { working_project_->color_manager()->SetConfigFilename(ocio_filename_->text()); } if (working_project_->color_manager()->GetDefaultInputColorSpace() != default_input_colorspace_->currentText()) { working_project_->color_manager()->SetDefaultInputColorSpace(default_input_colorspace_->currentText()); } if (working_project_->GetColorReferenceSpace() != reference_space_->currentData().toString()) { working_project_->SetColorReferenceSpace(reference_space_->currentData().toString()); } super::accept(); } bool ProjectPropertiesDialog::VerifyPathAndWarnIfBad(const QString &path) { if (!FileFunctions::DirectoryIsValid(path)) { QMessageBox mb(this); mb.setWindowModality(Qt::WindowModal); mb.setIcon(QMessageBox::Critical); mb.setWindowTitle(tr("Invalid path")); mb.setText(tr("The custom cache path is invalid. Please check it and try again.")); mb.addButton(QMessageBox::Ok); mb.exec(); return false; } return true; } void ProjectPropertiesDialog::BrowseForOCIOConfig() { QString fn = QFileDialog::getOpenFileName(this, tr("Browse for OpenColorIO configuration")); if (!fn.isEmpty()) { ocio_filename_->setText(fn); } } void ProjectPropertiesDialog::OCIOFilenameUpdated() { default_input_colorspace_->clear(); try { OCIO::ConstConfigRcPtr c; if (ocio_filename_->text().isEmpty()) { c = ColorManager::GetDefaultConfig(); } else { c = ColorManager::CreateConfigFromFile(ocio_filename_->text()); } ocio_filename_->setStyleSheet(QString()); ocio_config_is_valid_ = true; // List input color spaces QStringList input_cs = ColorManager::ListAvailableColorspaces(c); foreach (QString cs, input_cs) { default_input_colorspace_->addItem(cs); if (cs == working_project_->color_manager()->GetDefaultInputColorSpace()) { default_input_colorspace_->setCurrentIndex(default_input_colorspace_->count()-1); } } } catch (OCIO::Exception& e) { ocio_config_is_valid_ = false; ocio_filename_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}")); ocio_config_error_ = e.what(); } } void ProjectPropertiesDialog::OpenDiskCacheSettings() { if (disk_cache_radios_[Project::kCacheUseDefaultLocation]->isChecked()) { DiskManager::instance()->ShowDiskCacheSettingsDialog(DiskManager::instance()->GetDefaultCacheFolder(), this); } else if (disk_cache_radios_[Project::kCacheStoreAlongsideProject]->isChecked()) { DiskManager::instance()->ShowDiskCacheSettingsDialog(working_project_->get_cache_alongside_project_path(), this); } else { DiskManager::instance()->ShowDiskCacheSettingsDialog(custom_cache_path_->text(), this); } } } ================================================ FILE: app/dialog/projectproperties/projectproperties.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PROJECTPROPERTIESDIALOG_H #define PROJECTPROPERTIESDIALOG_H #include #include #include #include #include #include #include "node/project.h" #include "widget/path/pathwidget.h" namespace olive { class ProjectPropertiesDialog : public QDialog { Q_OBJECT public: ProjectPropertiesDialog(Project *p, QWidget* parent); public slots: virtual void accept() override; private: bool VerifyPathAndWarnIfBad(const QString &path); Project* working_project_; QLineEdit* ocio_filename_; QComboBox* default_input_colorspace_; QComboBox *reference_space_; bool ocio_config_is_valid_; QString ocio_config_error_; PathWidget* custom_cache_path_; static const int kDiskCacheRadioCount = 3; QRadioButton *disk_cache_radios_[kDiskCacheRadioCount]; private slots: void BrowseForOCIOConfig(); void OCIOFilenameUpdated(); void OpenDiskCacheSettings(); }; } #endif // PROJECTPROPERTIESDIALOG_H ================================================ FILE: app/dialog/rendercancel/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/rendercancel/rendercancel.h dialog/rendercancel/rendercancel.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/rendercancel/rendercancel.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "rendercancel.h" namespace olive { RenderCancelDialog::RenderCancelDialog(QWidget *parent) : ProgressDialog(tr("Waiting for workers to finish..."), tr("Renderer"), parent), busy_workers_(0), total_workers_(0) { } void RenderCancelDialog::RunIfWorkersAreBusy() { if (busy_workers_ > 0) { waiting_workers_ = busy_workers_; exec(); } } void RenderCancelDialog::SetWorkerCount(int count) { total_workers_ = count; UpdateProgress(); } void RenderCancelDialog::WorkerStarted() { busy_workers_++; UpdateProgress(); } void RenderCancelDialog::WorkerDone() { busy_workers_--; UpdateProgress(); } void RenderCancelDialog::showEvent(QShowEvent *event) { QDialog::showEvent(event); UpdateProgress(); } void RenderCancelDialog::UpdateProgress() { if (!total_workers_ || !isVisible()) { return; } SetProgress(qRound(100.0 * static_cast(waiting_workers_ - busy_workers_) / static_cast(waiting_workers_))); if (busy_workers_ == 0) { accept(); } } } ================================================ FILE: app/dialog/rendercancel/rendercancel.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef RENDERCANCELDIALOG_H #define RENDERCANCELDIALOG_H #include "dialog/progress/progress.h" namespace olive { class RenderCancelDialog : public ProgressDialog { Q_OBJECT public: RenderCancelDialog(QWidget* parent = nullptr); void RunIfWorkersAreBusy(); void SetWorkerCount(int count); void WorkerStarted(); public slots: void WorkerDone(); protected: virtual void showEvent(QShowEvent* event) override; private: void UpdateProgress(); int busy_workers_; int total_workers_; int waiting_workers_; }; } #endif // RENDERCANCELDIALOG_H ================================================ FILE: app/dialog/sequence/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/sequence/presetmanager.h dialog/sequence/sequence.h dialog/sequence/sequence.cpp dialog/sequence/sequencedialogparametertab.h dialog/sequence/sequencedialogparametertab.cpp dialog/sequence/sequencedialogpresettab.h dialog/sequence/sequencedialogpresettab.cpp dialog/sequence/sequencepreset.h PARENT_SCOPE ) ================================================ FILE: app/dialog/sequence/presetmanager.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef PRESETMANAGER_H #define PRESETMANAGER_H #include #include #include #include #include #include #include #include #include #include "common/define.h" #include "common/filefunctions.h" #include "common/xmlutils.h" namespace olive { class Preset { public: Preset() = default; virtual ~Preset(){} const QString& GetName() const { return name_; } void SetName(const QString& s) { name_ = s; } virtual void Load(QXmlStreamReader* reader) = 0; virtual void Save(QXmlStreamWriter* writer) const = 0; private: QString name_; }; using PresetPtr = std::shared_ptr; template class PresetManager { public: PresetManager(QWidget* parent, const QString& preset_name) : preset_name_(preset_name), parent_(parent) { // Load custom preset data from file QFile preset_file(GetCustomPresetFilename()); if (preset_file.open(QFile::ReadOnly)) { QXmlStreamReader reader(&preset_file); while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("presets")) { while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("preset")) { PresetPtr p = std::make_unique(); p->Load(&reader); custom_preset_data_.append(p); } else { reader.skipCurrentElement(); } } } else { reader.skipCurrentElement(); } } preset_file.close(); } } ~PresetManager() { // Save custom presets to disk QFile preset_file(GetCustomPresetFilename()); if (preset_file.open(QFile::WriteOnly)) { QXmlStreamWriter writer(&preset_file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement(QStringLiteral("presets")); foreach (PresetPtr p, custom_preset_data_) { writer.writeStartElement(QStringLiteral("preset")); p->Save(&writer); writer.writeEndElement(); // preset } writer.writeEndElement(); // presets writer.writeEndDocument(); preset_file.close(); } } QString GetPresetName(QString start) const { bool ok; forever { start = QInputDialog::getText(parent_, QCoreApplication::translate("PresetManager", "Save Preset"), QCoreApplication::translate("PresetManager", "Set preset name:"), QLineEdit::Normal, start, &ok); if (!ok) { // Dialog cancelled - leave function entirely return QString(); } if (start.isEmpty()) { // No preset name entered, start loop over QMessageBox::critical(parent_, QCoreApplication::translate("PresetManager", "Invalid preset name"), QCoreApplication::translate("PresetManager", "You must enter a preset name"), QMessageBox::Ok); } else { break; } } return start; } enum SaveStatus { kAppended, kReplaced, kNotSaved }; SaveStatus SavePreset(PresetPtr preset) { QString preset_name; int existing_preset; forever { preset_name = GetPresetName(preset_name); if (preset_name.isEmpty()) { // Dialog cancelled - leave function entirely return kNotSaved; } existing_preset = -1; for (int i=0; iGetName() == preset_name) { existing_preset = i; break; } } if (existing_preset == -1 || QMessageBox::question(parent_, QCoreApplication::translate("PresetManager", "Preset exists"), QCoreApplication::translate("PresetManager", "A preset with this name already exists. " "Would you like to replace it?")) == QMessageBox::Yes) { break; } } preset->SetName(preset_name); if (existing_preset >= 0) { custom_preset_data_.replace(existing_preset, preset); return kReplaced; } else { custom_preset_data_.append(preset); return kAppended; } } QString GetCustomPresetFilename() const { return QDir(FileFunctions::GetConfigurationLocation()).filePath(preset_name_); } PresetPtr GetPreset(int index) { return custom_preset_data_.at(index); } void DeletePreset(int index) { custom_preset_data_.removeAt(index); } int GetNumberOfPresets() const { return custom_preset_data_.size(); } const QVector& GetPresetData() const { return custom_preset_data_; } private: QVector custom_preset_data_; QString preset_name_; QWidget* parent_; }; } #endif // PRESETMANAGER_H ================================================ FILE: app/dialog/sequence/sequence.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "sequence.h" #include #include #include #include #include #include #include #include #include "config/config.h" #include "core.h" #include "common/channellayout.h" #include "common/qtutils.h" #include "undo/undostack.h" namespace olive { SequenceDialog::SequenceDialog(Sequence* s, Type t, QWidget* parent) : QDialog(parent), sequence_(s), make_undoable_(true) { QVBoxLayout* layout = new QVBoxLayout(this); QSplitter* splitter = new QSplitter(); layout->addWidget(splitter); preset_tab_ = new SequenceDialogPresetTab(); splitter->addWidget(preset_tab_); parameter_tab_ = new SequenceDialogParameterTab(sequence_); splitter->addWidget(parameter_tab_); connect(preset_tab_, &SequenceDialogPresetTab::PresetChanged, parameter_tab_, &SequenceDialogParameterTab::PresetChanged); connect(preset_tab_, &SequenceDialogPresetTab::PresetAccepted, this, &SequenceDialog::accept); connect(parameter_tab_, &SequenceDialogParameterTab::SaveParametersAsPreset, preset_tab_, &SequenceDialogPresetTab::SaveParametersAsPreset); // Set up name section QHBoxLayout* name_layout = new QHBoxLayout(); name_layout->addWidget(new QLabel(tr("Name:"))); name_field_ = new QLineEdit(); name_layout->addWidget(name_field_); layout->addLayout(name_layout); // Set up dialog buttons QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *default_btn = buttons->addButton(tr("Set As Default"), QDialogButtonBox::ActionRole); connect(buttons, &QDialogButtonBox::accepted, this, &SequenceDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &SequenceDialog::reject); connect(default_btn, &QPushButton::clicked, this, &SequenceDialog::SetAsDefaultClicked); layout->addWidget(buttons); // Set window title based on type switch (t) { case kNew: setWindowTitle(tr("New Sequence")); break; case kExisting: setWindowTitle(tr("Editing \"%1\"").arg(sequence_->GetLabel())); break; } name_field_->setText(sequence_->GetLabel()); } void SequenceDialog::SetUndoable(bool u) { make_undoable_ = u; } void SequenceDialog::SetNameIsEditable(bool e) { name_field_->setEnabled(e); } void SequenceDialog::accept() { if (name_field_->isEnabled() && name_field_->text().isEmpty()) { QtUtils::MsgBox(this, QMessageBox::Critical, tr("Error editing Sequence"), tr("Please enter a name for this Sequence.")); return; } if (!VideoParams::FormatIsFloat(parameter_tab_->GetSelectedPreviewFormat()) && !OLIVE_CONFIG("PreviewNonFloatDontAskAgain").toBool()) { QMessageBox b(this); QCheckBox *dont_show_again_ = new QCheckBox(tr("Don't ask me again")); b.setIcon(QMessageBox::Warning); b.setWindowTitle(tr("Low Quality Preview")); b.setText(tr("The preview resolution has been set to a non-float format. This may cause banding and clipping artifacts in the preview.\n\n" "Do you wish to continue?")); b.setCheckBox(dont_show_again_); b.addButton(QMessageBox::Yes); b.addButton(QMessageBox::No); if (b.exec() == QMessageBox::No) { return; } if (dont_show_again_->isChecked()) { OLIVE_CONFIG("PreviewNonFloatDontAskAgain") = true; } } // Generate video and audio parameter structs from data VideoParams video_params = VideoParams(parameter_tab_->GetSelectedVideoWidth(), parameter_tab_->GetSelectedVideoHeight(), parameter_tab_->GetSelectedVideoFrameRate().flipped(), parameter_tab_->GetSelectedPreviewFormat(), VideoParams::kInternalChannelCount, parameter_tab_->GetSelectedVideoPixelAspect(), parameter_tab_->GetSelectedVideoInterlacingMode(), parameter_tab_->GetSelectedPreviewResolution()); AudioParams audio_params = AudioParams(parameter_tab_->GetSelectedAudioSampleRate(), parameter_tab_->GetSelectedAudioChannelLayout(), Sequence::kDefaultSampleFormat); if (make_undoable_) { // Make undoable command to change the parameters SequenceParamCommand* param_command = new SequenceParamCommand(sequence_, video_params, audio_params, name_field_->text(), parameter_tab_->GetSelectedPreviewAutoCache()); Core::instance()->undo_stack()->push(param_command, tr("Set Sequence Parameters For \"%1\"").arg(sequence_->GetLabel())); } else { // Set sequence values directly with no undo command sequence_->SetVideoParams(video_params); sequence_->SetAudioParams(audio_params); sequence_->SetLabel(name_field_->text()); sequence_->SetVideoAutoCacheEnabled(parameter_tab_->GetSelectedPreviewAutoCache()); } QDialog::accept(); } void SequenceDialog::SetAsDefaultClicked() { if (QtUtils::MsgBox(this, QMessageBox::Question, tr("Confirm Set As Default"), tr("Are you sure you want to set the current parameters as defaults?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // Maybe replace with Preset system OLIVE_CONFIG("DefaultSequenceWidth") = parameter_tab_->GetSelectedVideoWidth(); OLIVE_CONFIG("DefaultSequenceHeight") = parameter_tab_->GetSelectedVideoHeight(); OLIVE_CONFIG("DefaultSequencePixelAspect") = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect()); OLIVE_CONFIG("DefaultSequenceFrameRate") = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped()); OLIVE_CONFIG("DefaultSequenceInterlacing") = parameter_tab_->GetSelectedVideoInterlacingMode(); OLIVE_CONFIG("DefaultSequenceAudioFrequency") = parameter_tab_->GetSelectedAudioSampleRate(); OLIVE_CONFIG("DefaultSequenceAudioLayout") = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout()); } } SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, const VideoParams& video_params, const AudioParams &audio_params, const QString& name, bool autocache) : sequence_(s), new_video_params_(video_params), new_audio_params_(audio_params), new_name_(name), new_autocache_(autocache), old_video_params_(s->GetVideoParams()), old_audio_params_(s->GetAudioParams()), old_name_(s->GetLabel()), old_autocache_(s->IsVideoAutoCacheEnabled()) { } Project *SequenceDialog::SequenceParamCommand::GetRelevantProject() const { return sequence_->project(); } void SequenceDialog::SequenceParamCommand::redo() { if (sequence_->GetVideoParams() != new_video_params_) { sequence_->SetVideoParams(new_video_params_); } if (sequence_->GetAudioParams() != new_audio_params_) { sequence_->SetAudioParams(new_audio_params_); } sequence_->SetLabel(new_name_); sequence_->SetVideoAutoCacheEnabled(new_autocache_); } void SequenceDialog::SequenceParamCommand::undo() { if (sequence_->GetVideoParams() != old_video_params_) { sequence_->SetVideoParams(old_video_params_); } if (sequence_->GetAudioParams() != old_audio_params_) { sequence_->SetAudioParams(old_audio_params_); } sequence_->SetLabel(old_name_); sequence_->SetVideoAutoCacheEnabled(old_autocache_); } } ================================================ FILE: app/dialog/sequence/sequence.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SEQUENCEDIALOG_H #define SEQUENCEDIALOG_H #include #include #include #include "node/project/sequence/sequence.h" #include "sequencedialogparametertab.h" #include "sequencedialogpresettab.h" #include "undo/undocommand.h" namespace olive { /** * @brief A dialog for editing Sequence parameters * * This dialog exposes all the parameters of a Sequence to users allowing them to set up a Sequence however they wish. * A Sequence can be sent to this dialog through the constructor. All fields will be filled using that Sequence's * parameters, allowing the user to view and edit them. Accepting the dialog will apply them back to that Sequence, * either directly or using an UndoCommand (see SetUndoable()). * * If creating a new Sequence, the Sequence must still be constructed first before sending it to SequenceDialog. * SequenceDialog does not create any new objects. In most cases when creating a new Sequence, editing its parameters * with SequenceDialog will be paired with the action of adding the Sequence to a project. In this situation, since the * latter will be the main undoable action, the parameter editing doesn't have to be undoable since to the user they'll * be viewed as one single action (see SetUndoable()). */ class SequenceDialog : public QDialog { Q_OBJECT public: /** * @brief Used to set the dialog mode of operation (see SequenceDialog()) */ enum Type { kNew, kExisting }; /** * @brief SequenceDialog Constructor * * @param s * Sequence to edit * * @param t * Mode of operation (changes some UI like the window title to best represent the action being performed) * * @param parent * QWidget parent */ SequenceDialog(Sequence* s, Type t = kExisting, QWidget* parent = nullptr); /** * @brief Set whether the parameter changes should be made into an undo command or not * * Defaults to true. */ void SetUndoable(bool u); /** * @brief Set whether the name of this Sequence can be edited with this dialog * * Defaults to true. */ void SetNameIsEditable(bool e); public slots: /** * @brief Function called when the user presses OK */ virtual void accept() override; private: Sequence* sequence_; SequenceDialogPresetTab* preset_tab_; SequenceDialogParameterTab* parameter_tab_; bool make_undoable_; QLineEdit* name_field_; /** * @brief An UndoCommand for setting the parameters on a sequence */ class SequenceParamCommand : public UndoCommand { public: SequenceParamCommand(Sequence* s, const VideoParams& video_params, const AudioParams& audio_params, const QString& name, bool autocache); virtual Project* GetRelevantProject() const override; protected: virtual void redo() override; virtual void undo() override; private: Sequence* sequence_; VideoParams new_video_params_; AudioParams new_audio_params_; QString new_name_; bool new_autocache_; VideoParams old_video_params_; AudioParams old_audio_params_; QString old_name_; bool old_autocache_; }; private slots: void SetAsDefaultClicked(); }; } #endif // SEQUENCEDIALOG_H ================================================ FILE: app/dialog/sequence/sequencedialogparametertab.cpp ================================================ #include "sequencedialogparametertab.h" #include #include #include #include #include "core.h" namespace olive { SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(this); int row = 0; // Set up video section QGroupBox* video_group = new QGroupBox(); video_group->setTitle(tr("Video")); QGridLayout *video_layout = new QGridLayout(video_group); video_layout->addWidget(new QLabel(tr("Width:")), row, 0); width_slider_ = new IntegerSlider(); width_slider_->SetMinimum(0); video_layout->addWidget(width_slider_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Height:")), row, 0); height_slider_ = new IntegerSlider(); height_slider_->SetMinimum(0); video_layout->addWidget(height_slider_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0); framerate_combo_ = new FrameRateComboBox(); video_layout->addWidget(framerate_combo_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0); pixelaspect_combo_ = new PixelAspectRatioComboBox(); video_layout->addWidget(pixelaspect_combo_, row, 1); row++; video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); interlacing_combo_ = new InterlacedComboBox(); video_layout->addWidget(interlacing_combo_, row, 1); layout->addWidget(video_group); row = 0; // Set up audio section QGroupBox* audio_group = new QGroupBox(); audio_group->setTitle(tr("Audio")); QGridLayout* audio_layout = new QGridLayout(audio_group); audio_layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0); audio_sample_rate_field_ = new SampleRateComboBox(); audio_layout->addWidget(audio_sample_rate_field_, row, 1); row++; audio_layout->addWidget(new QLabel(tr("Channels:")), row, 0); audio_channels_field_ = new ChannelLayoutComboBox(); audio_layout->addWidget(audio_channels_field_, row, 1); layout->addWidget(audio_group); row = 0; // Set up preview section QGroupBox* preview_group = new QGroupBox(); preview_group->setTitle(tr("Preview")); QGridLayout* preview_layout = new QGridLayout(preview_group); preview_layout->addWidget(new QLabel(tr("Resolution:")), row, 0); preview_resolution_field_ = new VideoDividerComboBox(); preview_layout->addWidget(preview_resolution_field_, row, 1); preview_resolution_label_ = new QLabel(); preview_layout->addWidget(preview_resolution_label_, row, 2); row++; preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0); preview_format_field_ = new PixelFormatComboBox(false); preview_layout->addWidget(preview_format_field_, row, 1, 1, 2); /* TEMP: Disable sequence auto-cache, wanna see if clip cache supersedes it. row++; preview_layout->addWidget(new QLabel(tr("Auto-Cache:")), row, 0); preview_layout->addWidget(preview_autocache_field_, row, 1);*/ preview_autocache_field_ = new QCheckBox(); layout->addWidget(preview_group); // Set values based on input sequence VideoParams vp = sequence->GetVideoParams(); AudioParams ap = sequence->GetAudioParams(); width_slider_->SetValue(vp.width()); height_slider_->SetValue(vp.height()); framerate_combo_->SetFrameRate(vp.time_base().flipped()); pixelaspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); preview_autocache_field_->setChecked(sequence->IsVideoAutoCacheEnabled()); audio_sample_rate_field_->SetSampleRate(ap.sample_rate()); audio_channels_field_->SetChannelLayout(ap.channel_layout()); connect(preview_resolution_field_, static_cast(&QComboBox::currentIndexChanged), this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel); layout->addStretch(); QPushButton* save_preset_btn = new QPushButton(tr("Save Preset")); connect(save_preset_btn, &QPushButton::clicked, this, &SequenceDialogParameterTab::SavePresetClicked); layout->addWidget(save_preset_btn); UpdatePreviewResolutionLabel(); } void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset) { width_slider_->SetValue(preset.width()); height_slider_->SetValue(preset.height()); framerate_combo_->SetFrameRate(preset.frame_rate()); pixelaspect_combo_->SetPixelAspectRatio(preset.pixel_aspect()); interlacing_combo_->SetInterlaceMode(preset.interlacing()); audio_sample_rate_field_->SetSampleRate(preset.sample_rate()); audio_channels_field_->SetChannelLayout(preset.channel_layout()); preview_resolution_field_->SetDivider(preset.preview_divider()); preview_format_field_->SetPixelFormat(preset.preview_format()); preview_autocache_field_->setChecked(preset.preview_autocache()); } void SequenceDialogParameterTab::SavePresetClicked() { emit SaveParametersAsPreset({QString(), GetSelectedVideoWidth(), GetSelectedVideoHeight(), GetSelectedVideoFrameRate(), GetSelectedVideoPixelAspect(), GetSelectedVideoInterlacingMode(), GetSelectedAudioSampleRate(), GetSelectedAudioChannelLayout(), GetSelectedPreviewResolution(), GetSelectedPreviewFormat(), GetSelectedPreviewAutoCache()}); } void SequenceDialogParameterTab::UpdatePreviewResolutionLabel() { VideoParams test_param(GetSelectedVideoWidth(), GetSelectedVideoHeight(), PixelFormat::INVALID, VideoParams::kInternalChannelCount, rational(1), VideoParams::kInterlaceNone, preview_resolution_field_->currentData().toInt()); preview_resolution_label_->setText(tr("(%1x%2)").arg(QString::number(test_param.effective_width()), QString::number(test_param.effective_height()))); } } ================================================ FILE: app/dialog/sequence/sequencedialogparametertab.h ================================================ #ifndef SEQUENCEDIALOGPARAMETERTAB_H #define SEQUENCEDIALOGPARAMETERTAB_H #include #include #include #include #include "node/project/sequence/sequence.h" #include "sequencepreset.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" namespace olive { class SequenceDialogParameterTab : public QWidget { Q_OBJECT public: SequenceDialogParameterTab(Sequence* sequence, QWidget* parent = nullptr); int GetSelectedVideoWidth() const { return width_slider_->GetValue(); } int GetSelectedVideoHeight() const { return height_slider_->GetValue(); } rational GetSelectedVideoFrameRate() const { return framerate_combo_->GetFrameRate(); } rational GetSelectedVideoPixelAspect() const { return pixelaspect_combo_->GetPixelAspectRatio(); } VideoParams::Interlacing GetSelectedVideoInterlacingMode() const { return interlacing_combo_->GetInterlaceMode(); } int GetSelectedAudioSampleRate() const { return audio_sample_rate_field_->GetSampleRate(); } uint64_t GetSelectedAudioChannelLayout() const { return audio_channels_field_->GetChannelLayout(); } int GetSelectedPreviewResolution() const { return preview_resolution_field_->GetDivider(); } PixelFormat GetSelectedPreviewFormat() const { return preview_format_field_->GetPixelFormat(); } bool GetSelectedPreviewAutoCache() const { //return preview_autocache_field_->isChecked(); // TEMP: Disable sequence auto-cache, wanna see if clip cache supersedes it. return false; } public slots: void PresetChanged(const SequencePreset& preset); signals: void SaveParametersAsPreset(const SequencePreset& preset); private: IntegerSlider *width_slider_; IntegerSlider *height_slider_; FrameRateComboBox *framerate_combo_; PixelAspectRatioComboBox *pixelaspect_combo_; InterlacedComboBox *interlacing_combo_; SampleRateComboBox* audio_sample_rate_field_; ChannelLayoutComboBox* audio_channels_field_; VideoDividerComboBox* preview_resolution_field_; QLabel* preview_resolution_label_; PixelFormatComboBox* preview_format_field_; QCheckBox *preview_autocache_field_; private slots: void SavePresetClicked(); void UpdatePreviewResolutionLabel(); }; } #endif // SEQUENCEDIALOGPARAMETERTAB_H ================================================ FILE: app/dialog/sequence/sequencedialogpresettab.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "sequencedialogpresettab.h" #include #include #include #include #include #include #include #include #include "common/filefunctions.h" #include "config/config.h" #include "render/videoparams.h" #include "ui/icons/icons.h" #include "widget/menu/menu.h" namespace olive { const int kDataIsPreset = Qt::UserRole; const int kDataPresetIsCustomRole = Qt::UserRole + 1; const int kDataPresetDataRole = Qt::UserRole + 2; SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) : QWidget(parent), PresetManager(this, QStringLiteral("sequencepresets")) { QVBoxLayout* outer_layout = new QVBoxLayout(this); outer_layout->setContentsMargins(0, 0, 0, 0); preset_tree_ = new QTreeWidget(); preset_tree_->setColumnCount(1); preset_tree_->setHeaderLabel(tr("Preset")); preset_tree_->setContextMenuPolicy(Qt::CustomContextMenu); connect(preset_tree_, &QTreeWidget::customContextMenuRequested, this, &SequenceDialogPresetTab::ShowContextMenu); outer_layout->addWidget(preset_tree_); connect(preset_tree_, &QTreeWidget::currentItemChanged, this, &SequenceDialogPresetTab::SelectedItemChanged); connect(preset_tree_, &QTreeWidget::itemDoubleClicked, this, &SequenceDialogPresetTab::ItemDoubleClicked); // Add "my presets" folder my_presets_folder_ = CreateFolder(tr("My Presets")); preset_tree_->addTopLevelItem(my_presets_folder_); // Add presets preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("4K UHD"), 3840, 2160, 2)); preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("1080p"), 1920, 1080, 1)); preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("720p"), 1280, 720, 1)); preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("NTSC"), 720, 480, rational(30000, 1001), VideoParams::kPixelAspectNTSCStandard, VideoParams::kPixelAspectNTSCWidescreen, 1)); preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("PAL"), 720, 576, rational(25, 1), VideoParams::kPixelAspectPALStandard, VideoParams::kPixelAspectPALWidescreen, 1)); // Load custom presets for (int i=0;i(preset); // If replaced, no need to make another item. If not saved, shared ptr will delete itself if (SavePreset(preset_ptr) == kAppended) { AddCustomItem(my_presets_folder_, preset_ptr, GetNumberOfPresets() - 1); } } QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name) { QTreeWidgetItem* folder = new QTreeWidgetItem(); folder->setText(0, name); folder->setIcon(0, icon::Folder); return folder; } QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider) { const PixelFormat default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); const bool default_autocache = false; QTreeWidgetItem* parent = CreateFolder(name); AddStandardItem(parent, std::make_shared(tr("%1 23.976 FPS").arg(name), width, height, rational(24000, 1001), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); AddStandardItem(parent, std::make_shared(tr("%1 25 FPS").arg(name), width, height, rational(25, 1), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); AddStandardItem(parent, std::make_shared(tr("%1 29.97 FPS").arg(name), width, height, rational(30000, 1001), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); AddStandardItem(parent, std::make_shared(tr("%1 50 FPS").arg(name), width, height, rational(50, 1), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); AddStandardItem(parent, std::make_shared(tr("%1 59.94 FPS").arg(name), width, height, rational(60000, 1001), VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); return parent; } QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider) { const PixelFormat default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); const bool default_autocache = false; QTreeWidgetItem* parent = CreateFolder(name); preset_tree_->addTopLevelItem(parent); AddStandardItem(parent, std::make_shared(tr("%1 Standard").arg(name), width, height, frame_rate, standard_par, VideoParams::kInterlacedBottomFirst, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); AddStandardItem(parent, std::make_shared(tr("%1 Widescreen").arg(name), width, height, frame_rate, wide_par, VideoParams::kInterlacedBottomFirst, 48000, AV_CH_LAYOUT_STEREO, divider, default_format, default_autocache)); return parent; } QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedItem() { QList selected_items = preset_tree_->selectedItems(); if (selected_items.isEmpty()) { return nullptr; } else { return selected_items.first(); } } QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedCustomPreset() { QTreeWidgetItem* sel = GetSelectedItem(); if (sel && sel->data(0, kDataIsPreset).toBool() && sel->data(0, kDataPresetIsCustomRole).toBool()) { return sel; } return nullptr; } void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, PresetPtr preset, const QString& description) { int index = default_preset_data_.size(); default_preset_data_.append(preset); AddItemInternal(folder, preset, false, index, description); } void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, PresetPtr preset, int index, const QString &description) { AddItemInternal(folder, preset, true, index, description); } void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, PresetPtr preset, bool is_custom, int index, const QString &description) { QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, preset->GetName()); item->setIcon(0, icon::Video); item->setToolTip(0, description); item->setData(0, kDataIsPreset, true); item->setData(0, kDataPresetIsCustomRole, is_custom); item->setData(0, kDataPresetDataRole, index); folder->addChild(item); } void SequenceDialogPresetTab::SelectedItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { Q_UNUSED(previous) if (current->data(0, kDataIsPreset).toBool()) { int preset_index = current->data(0, kDataPresetDataRole).toInt(); PresetPtr preset_data = (current->data(0, kDataPresetIsCustomRole).toBool()) ? GetPreset(preset_index) : default_preset_data_.at(preset_index); emit PresetChanged(*static_cast(preset_data.get())); } } void SequenceDialogPresetTab::ItemDoubleClicked(QTreeWidgetItem *item, int column) { Q_UNUSED(column) if (item->data(0, kDataIsPreset).toBool()) { emit PresetAccepted(); } } void SequenceDialogPresetTab::ShowContextMenu() { QTreeWidgetItem* sel = GetSelectedCustomPreset(); if (sel) { Menu m(this); QAction* delete_action = m.addAction(tr("Delete Preset")); connect(delete_action, &QAction::triggered, this, &SequenceDialogPresetTab::DeleteSelectedPreset); m.exec(QCursor::pos()); } } void SequenceDialogPresetTab::DeleteSelectedPreset() { QTreeWidgetItem* sel = GetSelectedCustomPreset(); if (sel) { int preset_index = sel->data(0, kDataPresetDataRole).toInt(); // Shift all items whose index was after this preset forward for (int i=0; ichildCount(); i++) { QTreeWidgetItem* custom_item = my_presets_folder_->child(i); int this_item_index = custom_item->data(0, kDataPresetDataRole).toInt(); if (this_item_index > preset_index) { custom_item->setData(0, kDataPresetDataRole, this_item_index - 1); } } // Remove the preset DeletePreset(preset_index); // Delete the item delete sel; } } } ================================================ FILE: app/dialog/sequence/sequencedialogpresettab.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SEQUENCEDIALOGPRESETTAB_H #define SEQUENCEDIALOGPRESETTAB_H #include #include #include #include "presetmanager.h" #include "sequencepreset.h" namespace olive { class SequenceDialogPresetTab : public QWidget, public PresetManager { Q_OBJECT public: SequenceDialogPresetTab(QWidget* parent = nullptr); public slots: void SaveParametersAsPreset(SequencePreset preset); signals: void PresetChanged(const SequencePreset& preset); void PresetAccepted(); private: QTreeWidgetItem *CreateFolder(const QString& name); QTreeWidgetItem *CreateHDPresetFolder(const QString& name, int width, int height, int divider); QTreeWidgetItem *CreateSDPresetFolder(const QString& name, int width, int height, const rational &frame_rate, const rational& standard_par, const rational& wide_par, int divider); QTreeWidgetItem* GetSelectedItem(); QTreeWidgetItem* GetSelectedCustomPreset(); void AddStandardItem(QTreeWidgetItem* folder, PresetPtr preset, const QString &description = QString()); void AddCustomItem(QTreeWidgetItem* folder, PresetPtr preset, int index, const QString& description = QString()); void AddItemInternal(QTreeWidgetItem* folder, PresetPtr preset, bool is_custom, int index, const QString& description = QString()); QTreeWidget* preset_tree_; QTreeWidgetItem* my_presets_folder_; QVector default_preset_data_; private slots: void SelectedItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); void ItemDoubleClicked(QTreeWidgetItem *item, int column); void ShowContextMenu(); void DeleteSelectedPreset(); }; } #endif // SEQUENCEDIALOGPRESETTAB_H ================================================ FILE: app/dialog/sequence/sequencepreset.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SEQUENCEPARAM_H #define SEQUENCEPARAM_H #include #include #include "common/xmlutils.h" #include "dialog/sequence/presetmanager.h" #include "render/videoparams.h" namespace olive { class SequencePreset : public Preset { public: SequencePreset() = default; SequencePreset(const QString& name, int width, int height, const rational& frame_rate, const rational& pixel_aspect, VideoParams::Interlacing interlacing, int sample_rate, uint64_t channel_layout, int preview_divider, PixelFormat preview_format, bool preview_autocache) : width_(width), height_(height), frame_rate_(frame_rate), pixel_aspect_(pixel_aspect), interlacing_(interlacing), sample_rate_(sample_rate), channel_layout_(channel_layout), preview_divider_(preview_divider), preview_format_(preview_format), preview_autocache_(preview_autocache) { SetName(name); } virtual void Load(QXmlStreamReader* reader) override { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("name")) { SetName(reader->readElementText()); } else if (reader->name() == QStringLiteral("width")) { width_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("height")) { height_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("framerate")) { frame_rate_ = rational::fromString(reader->readElementText().toStdString()); } else if (reader->name() == QStringLiteral("pixelaspect")) { pixel_aspect_ = rational::fromString(reader->readElementText().toStdString()); } else if (reader->name() == QStringLiteral("interlacing")) { interlacing_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("samplerate")) { sample_rate_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("chlayout")) { channel_layout_ = reader->readElementText().toULongLong(); } else if (reader->name() == QStringLiteral("divider")) { preview_divider_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("format")) { preview_format_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("autocache")) { preview_autocache_ = reader->readElementText().toInt(); } else { reader->skipCurrentElement(); } } } virtual void Save(QXmlStreamWriter* writer) const override { writer->writeTextElement(QStringLiteral("name"), GetName()); writer->writeTextElement(QStringLiteral("width"), QString::number(width_)); writer->writeTextElement(QStringLiteral("height"), QString::number(height_)); writer->writeTextElement(QStringLiteral("framerate"), QString::fromStdString(frame_rate_.toString())); writer->writeTextElement(QStringLiteral("pixelaspect"), QString::fromStdString(pixel_aspect_.toString())); writer->writeTextElement(QStringLiteral("interlacing_"), QString::number(interlacing_)); writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_)); writer->writeTextElement(QStringLiteral("chlayout"), QString::number(channel_layout_)); writer->writeTextElement(QStringLiteral("divider"), QString::number(preview_divider_)); writer->writeTextElement(QStringLiteral("format"), QString::number(preview_format_)); writer->writeTextElement(QStringLiteral("autocache"), QString::number(preview_autocache_)); } int width() const { return width_; } int height() const { return height_; } const rational& frame_rate() const { return frame_rate_; } const rational& pixel_aspect() const { return pixel_aspect_; } VideoParams::Interlacing interlacing() const { return interlacing_; } int sample_rate() const { return sample_rate_; } uint64_t channel_layout() const { return channel_layout_; } int preview_divider() const { return preview_divider_; } PixelFormat preview_format() const { return preview_format_; } bool preview_autocache() const { return preview_autocache_; } private: int width_; int height_; rational frame_rate_; rational pixel_aspect_; VideoParams::Interlacing interlacing_; int sample_rate_; uint64_t channel_layout_; int preview_divider_; PixelFormat preview_format_; bool preview_autocache_; }; } #endif // SEQUENCEPARAM_H ================================================ FILE: app/dialog/speedduration/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/speedduration/speeddurationdialog.cpp dialog/speedduration/speeddurationdialog.h PARENT_SCOPE ) ================================================ FILE: app/dialog/speedduration/speeddurationdialog.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2023 Olive Studios LLC This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "speeddurationdialog.h" #include #include #include #include #include "core.h" #include "node/nodeundo.h" #include "timeline/timelineundopointer.h" namespace olive { #define super QDialog SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, const rational &timebase, QWidget *parent) : super(parent), clips_(clips), timebase_(timebase) { setWindowTitle(tr("Clip Properties")); QVBoxLayout *layout = new QVBoxLayout(this); { QGroupBox *speed_group = new QGroupBox(tr("Speed/Duration")); layout->addWidget(speed_group); QGridLayout *speed_layout = new QGridLayout(speed_group); int row = 0; speed_layout->addWidget(new QLabel(tr("Speed:")), row, 0); speed_slider_ = new FloatSlider(); speed_slider_->SetDisplayType(FloatSlider::kPercentage); connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged); speed_layout->addWidget(speed_slider_, row, 1); row++; speed_layout->addWidget(new QLabel(tr("Duration:")), row, 0); dur_slider_ = new RationalSlider(); dur_slider_->SetTimebase(timebase); dur_slider_->SetDisplayType(RationalSlider::kTime); connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged); speed_layout->addWidget(dur_slider_, row, 1); row++; link_box_ = new QCheckBox(tr("Link Speed and Duration")); link_box_->setChecked(true); speed_layout->addWidget(link_box_, row, 0, 1, 2); row++; reverse_box_ = new QCheckBox(tr("Reverse")); speed_layout->addWidget(reverse_box_, row, 0, 1, 2); row++; maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch")); speed_layout->addWidget(maintain_audio_pitch_box_, row, 0, 1, 2); row++; ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); speed_layout->addWidget(ripple_box_, row, 0, 1, 2); } { auto loop_box = new QGroupBox(tr("Loop")); layout->addWidget(loop_box); auto loop_layout = new QGridLayout(loop_box); int row = 0; loop_layout->addWidget(new QLabel(tr("Loop:")), row, 0); loop_combo_ = new QComboBox(); loop_combo_->addItem(tr("None"), int(LoopMode::kLoopModeOff)); loop_combo_->addItem(tr("Loop"), int(LoopMode::kLoopModeLoop)); loop_combo_->addItem(tr("Clamp"), int(LoopMode::kLoopModeClamp)); loop_layout->addWidget(loop_combo_, row, 1); } QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); btns->setCenterButtons(true); connect(btns, &QDialogButtonBox::accepted, this, &SpeedDurationDialog::accept); connect(btns, &QDialogButtonBox::rejected, this, &SpeedDurationDialog::reject); layout->addWidget(btns); // Determine which speed value to use start_speed_ = clips.first()->speed(); start_duration_ = clips.first()->length(); start_reverse_ = clips.first()->reverse(); start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch(); start_loop_ = int(clips.first()->loop_mode()); for (int i=1; ispeed())) { // Speed differs per clip start_speed_ = qSNaN(); } if (start_duration_ != -1 && c->length() != start_duration_) { start_duration_ = -1; } // Yes, in theory a bool should only ever be 0 or 1 anyway, but MSVC complained and it is // *possible* that a bool could be something else, so this code is safer int clip_reverse = c->reverse() ? 1 : 0; int clip_maintain_pitch = c->maintain_audio_pitch() ? 1 : 0; if (start_reverse_ != -1 && clip_reverse != start_reverse_) { start_reverse_ = -1; } if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) { start_maintain_audio_pitch_ = -1; } if (start_loop_ != -1 && int(c->loop_mode()) != start_loop_) { start_loop_ = -1; } } if (qIsNaN(start_speed_)) { speed_slider_->SetTristate(); } else { speed_slider_->SetValue(start_speed_); } if (start_duration_ == -1) { dur_slider_->SetTristate(); } else { dur_slider_->SetValue(start_duration_); } if (start_reverse_ == -1) { reverse_box_->setTristate(); } else { reverse_box_->setChecked(start_reverse_); } if (start_maintain_audio_pitch_ == -1) { maintain_audio_pitch_box_->setTristate(); } else { maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_); } if (start_loop_ == -1) { loop_combo_->setCurrentIndex(-1); } else { loop_combo_->setCurrentIndex(start_loop_); } } void SpeedDurationDialog::accept() { MultiUndoCommand *command = new MultiUndoCommand(); // Set duration values TimelineRippleDeleteGapsAtRegionsCommand::RangeList ripple_ranges; foreach (ClipBlock *c, clips_) { rational proposed_length = c->length(); if (dur_slider_->IsTristate()) { if (link_box_->isChecked() && !speed_slider_->IsTristate()) { proposed_length = GetLengthAdjustment(c->length(), c->speed(), speed_slider_->GetValue(), timebase_); } } else { proposed_length = dur_slider_->GetValue(); } if (proposed_length != c->length()) { // Clip length should ideally change, but check if there's "room" to do so if (proposed_length > c->length() && c->next()) { if (GapBlock *gap = dynamic_cast(c->next())) { proposed_length = qMin(proposed_length, gap->out() - c->in()); } else { proposed_length = c->length(); } } if (proposed_length != c->length()) { command->add_child(new BlockTrimCommand(c->track(), c, proposed_length, Timeline::kTrimOut)); ripple_ranges.append({c->track(), TimeRange(c->in() + proposed_length, c->out())}); } } } if (ripple_box_->isChecked()) { command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(clips_.first()->track()->sequence(), ripple_ranges)); } // Set speed values if (speed_slider_->IsTristate()) { if (link_box_->isChecked() && !dur_slider_->IsTristate()) { // Automatically determine speed from duration foreach (ClipBlock *c, clips_) { command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), GetSpeedAdjustment(c->speed(), c->length(), dur_slider_->GetValue()))); } } } else { // Set speeds to value of slider foreach (ClipBlock *c, clips_) { command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), speed_slider_->GetValue())); } } // Set reverse values if (!reverse_box_->isTristate()) { foreach (ClipBlock *c, clips_) { command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kReverseInput)), reverse_box_->isChecked())); } } // Set reverse values if (!maintain_audio_pitch_box_->isTristate()) { foreach (ClipBlock *c, clips_) { command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kMaintainAudioPitchInput)), maintain_audio_pitch_box_->isChecked())); } } if (loop_combo_->currentIndex() != -1) { foreach (ClipBlock *c, clips_) { command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kLoopModeInput)), loop_combo_->currentData())); } } QString name = (clips_.size() > 1) ? tr("Set %1 Clip Properties").arg(clips_.size()) : tr("Set Clip \"%1\" Properties").arg(clips_.first()->GetLabelOrName()); Core::instance()->undo_stack()->push(command, name); super::accept(); } rational SpeedDurationDialog::GetLengthAdjustment(const rational &original_length, double original_speed, double new_speed, const rational &timebase) { return Timecode::snap_time_to_timebase(rational::fromDouble(original_length.toDouble() / new_speed * original_speed), timebase); } double SpeedDurationDialog::GetSpeedAdjustment(double original_speed, const rational &original_length, const rational &new_length) { return original_speed / new_length.toDouble() * original_length.toDouble(); } void SpeedDurationDialog::SpeedChanged(double s) { if (!link_box_->isChecked()) { return; } if (start_duration_ == -1) { dur_slider_->SetTristate(); } else { dur_slider_->SetValue(GetLengthAdjustment(start_duration_, start_speed_, s, timebase_)); } } void SpeedDurationDialog::DurationChanged(const rational &r) { if (!link_box_->isChecked()) { return; } if (qIsNaN(start_speed_)) { speed_slider_->SetTristate(); } else { speed_slider_->SetValue(GetSpeedAdjustment(start_speed_, start_duration_, r)); } } } ================================================ FILE: app/dialog/speedduration/speeddurationdialog.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef SPEEDDURATIONDIALOG_H #define SPEEDDURATIONDIALOG_H #include #include #include #include "node/block/clip/clip.h" #include "node/block/gap/gap.h" #include "undo/undocommand.h" #include "widget/slider/floatslider.h" #include "widget/slider/rationalslider.h" namespace olive { class SpeedDurationDialog : public QDialog { Q_OBJECT public: explicit SpeedDurationDialog(const QVector &clips, const rational &timebase, QWidget *parent = nullptr); public slots: virtual void accept() override; signals: private: static rational GetLengthAdjustment(const rational &original_length, double original_speed, double new_speed, const rational &timebase); static double GetSpeedAdjustment(double original_speed, const rational &original_length, const rational &new_length); QVector clips_; FloatSlider *speed_slider_; RationalSlider *dur_slider_; QCheckBox *link_box_; QCheckBox *reverse_box_; QCheckBox *maintain_audio_pitch_box_; QCheckBox *ripple_box_; QComboBox *loop_combo_; int start_reverse_; int start_maintain_audio_pitch_; double start_speed_; rational start_duration_; int start_loop_; rational timebase_; private slots: void SpeedChanged(double s); void DurationChanged(const rational &r); }; } #endif // SPEEDDURATIONDIALOG_H ================================================ FILE: app/dialog/task/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/task/task.h dialog/task/task.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/task/task.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "task.h" #include namespace olive { #define super ProgressDialog TaskDialog::TaskDialog(Task* task, const QString& title, QWidget *parent) : super(task->GetTitle(), title, parent), task_(task), destroy_on_close_(true), already_shown_(false) { // Clear task when this dialog is destroyed task_->setParent(this); // Connect the save manager progress signal to the progress bar update on the dialog connect(task_, &Task::ProgressChanged, this, &TaskDialog::SetProgress, Qt::QueuedConnection); // Connect cancel signal (must be a direct connection or it'll be queued after the task has // already finished) connect(this, &TaskDialog::Cancelled, task_, &Task::Cancel, Qt::DirectConnection); } void TaskDialog::showEvent(QShowEvent *e) { super::showEvent(e); if (!already_shown_) { // Create watcher for when the task finishes QFutureWatcher* task_watcher = new QFutureWatcher(); // Listen for when the task finishes connect(task_watcher, &QFutureWatcher::finished, this, &TaskDialog::TaskFinished, Qt::QueuedConnection); // Run task in another thread with QtConcurrent task_watcher->setFuture( #if QT_VERSION_MAJOR >= 6 QtConcurrent::run(&Task::Start, task_) #else QtConcurrent::run(task_, &Task::Start) #endif ); already_shown_ = true; } } void TaskDialog::closeEvent(QCloseEvent *e) { // Cancel task if it is running task_->Cancel(); // Standard close function super::closeEvent(e); // Reset shown already_shown_ = false; // Clean up this task and dialog if (destroy_on_close_) { deleteLater(); } } void TaskDialog::TaskFinished() { QFutureWatcher* task_watcher = static_cast*>(sender()); if (task_watcher->result()) { emit TaskSucceeded(task_); } else { ShowErrorMessage(tr("Task Failed"), task_->GetError()); emit TaskFailed(task_); } task_watcher->deleteLater(); close(); } } ================================================ FILE: app/dialog/task/task.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef TASKDIALOG_H #define TASKDIALOG_H #include "dialog/progress/progress.h" #include "task/task.h" namespace olive { class TaskDialog : public ProgressDialog { Q_OBJECT public: /** * @brief TaskDialog Constructor * * Creates a TaskDialog. The TaskDialog takes ownership of the Task and will destroy it on close. * Connect to the Task::Succeeded() if you want to retrieve information from the task before it * gets destroyed. */ TaskDialog(Task *task, const QString &title, QWidget* parent = nullptr); /** * @brief Set whether TaskDialog should destroy itself (and the task) when it's closed * * This is TRUE by default. */ void SetDestroyOnClose(bool e) { destroy_on_close_ = e; } /** * @brief Returns this dialog's task */ Task* GetTask() const { return task_; } protected: virtual void showEvent(QShowEvent* e) override; virtual void closeEvent(QCloseEvent* e) override; signals: void TaskSucceeded(Task* task); void TaskFailed(Task* task); private: Task* task_; bool destroy_on_close_; bool already_shown_; private slots: void TaskFinished(); }; } #endif // TASKDIALOG_H ================================================ FILE: app/dialog/text/CMakeLists.txt ================================================ # Olive - Non-Linear Video Editor # Copyright (C) 2022 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/text/text.h dialog/text/text.cpp PARENT_SCOPE ) ================================================ FILE: app/dialog/text/text.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "text.h" #include #include #include #include #include #include "ui/icons/icons.h" namespace olive { TextDialog::TextDialog(const QString &start, QWidget* parent) : QDialog(parent) { QVBoxLayout* layout = new QVBoxLayout(this); // Create text edit widget text_edit_ = new QPlainTextEdit(); text_edit_->document()->setPlainText(start); layout->addWidget(text_edit_); // Create buttons QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, this, &TextDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &TextDialog::reject); } } ================================================ FILE: app/dialog/text/text.h ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef RICHTEXTDIALOG_H #define RICHTEXTDIALOG_H #include #include #include #include "common/define.h" #include "widget/slider/floatslider.h" namespace olive { class TextDialog : public QDialog { Q_OBJECT public: TextDialog(const QString &start, QWidget* parent = nullptr); QString text() const { return text_edit_->toPlainText(); } private: QPlainTextEdit* text_edit_; }; } #endif // RICHTEXTDIALOG_H ================================================ FILE: app/main.cpp ================================================ /*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ /** \mainpage Olive Video Editor - Code Documentation * * This documentation is a primarily a developer resource. For information on using Olive, visit the website * https://www.olivevideoeditor.org/ * * Use the navigation above to find documentation on classes or source files. */ extern "C" { #include #include } #include #include #include #include #include #include "core.h" #include "common/commandlineparser.h" #include "common/debug.h" #include "node/project/serializer/serializer.h" #include "version.h" #ifdef _WIN32 #include #include #include #include #endif #ifdef USE_CRASHPAD #include "common/crashpadinterface.h" #endif // USE_CRASHPAD int decompress_project(const QString &project) { if (project.isEmpty()) { printf("%s\n", QCoreApplication::translate("main", "No project filename set to decompress").toUtf8().constData()); return 1; } QFile project_file(project); if (!project_file.open(QFile::ReadOnly)) { printf("%s\n", QCoreApplication::translate("main", "Failed to open file \"%1\"").arg(project).toUtf8().constData()); return 1; } printf("%s\n", QCoreApplication::translate("main", "Decompressing project...").toUtf8().constData()); if (!olive::ProjectSerializer::CheckCompressedID(&project_file)) { printf("%s\n", QCoreApplication::translate("main", "Failed to decompress, project may be corrupt").toUtf8().constData()); return 1; } QByteArray b = project_file.readAll(); project_file.close(); QByteArray decompressed = qUncompress(b); if (decompressed.isEmpty()) { printf("%s\n", QCoreApplication::translate("main", "Failed to decompress, project may be corrupt").toUtf8().constData()); return 1; } QFileInfo info(project); QString filename; QString append; int append_num = 0; do { filename = info.dir().filePath(info.completeBaseName().append(append).append(QStringLiteral(".ovexml"))); append_num++; append = QStringLiteral("-%1").arg(append_num); } while(QFileInfo::exists(filename)); printf("%s\n", QCoreApplication::translate("main", "Outputting to file \"%1\"").arg(filename).toUtf8().constData()); QFile out(filename); if (!out.open(QFile::WriteOnly)) { printf("%s\n", QCoreApplication::translate("main", "Failed to open output file \"%1\"").arg(filename).toUtf8().constData()); return 1; } out.write(decompressed); out.close(); printf("%s\n", QCoreApplication::translate("main", "Decompressed successfully").toUtf8().constData()); return 0; } int main(int argc, char *argv[]) { // Set up debug handler qInstallMessageHandler(olive::DebugHandler); // Set application metadata QCoreApplication::setOrganizationName("olivevideoeditor.org"); QCoreApplication::setOrganizationDomain("olivevideoeditor.org"); QCoreApplication::setApplicationName("Olive"); QGuiApplication::setDesktopFileName("org.olivevideoeditor.Olive"); QCoreApplication::setApplicationVersion(olive::kAppVersionLong); // // Parse command line arguments // QVector args; #if defined(_WIN32) && defined(UNICODE) int wargc; LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); args.resize(wargc); for (int i=0; i