diff -Nru shotcut-18.03.06/debian/changelog shotcut-18.06.02/debian/changelog --- shotcut-18.03.06/debian/changelog 2018-03-06 21:41:12.000000000 +0000 +++ shotcut-18.06.02/debian/changelog 2018-06-02 18:14:36.000000000 +0000 @@ -1,3 +1,9 @@ +shotcut (18.06.02-xenial1) xenial; urgency=medium + + * New release: 18.06.02 + + -- Harald Hvaal Sat, 02 Jun 2018 20:14:36 +0200 + shotcut (18.03.06-xenial1) xenial; urgency=medium * New release: 18.03.06 Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/bin/ffmpeg and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/bin/ffmpeg differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/bin/ffplay and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/bin/ffplay differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/bin/ffprobe and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/bin/ffprobe differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/bin/melt and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/bin/melt differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/bin/shotcut and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/bin/shotcut differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/frei0r-1/emboss.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/frei0r-1/emboss.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavcodec.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavcodec.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavcodec.so.57 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavcodec.so.57 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavcodec.so.57.107.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavcodec.so.57.107.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavcodec.so.58 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavcodec.so.58 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavcodec.so.58.18.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavcodec.so.58.18.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavdevice.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavdevice.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavdevice.so.57 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavdevice.so.57 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavdevice.so.57.10.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavdevice.so.57.10.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavdevice.so.58 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavdevice.so.58 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavdevice.so.58.3.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavdevice.so.58.3.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavfilter.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavfilter.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavfilter.so.6 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavfilter.so.6 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavfilter.so.6.107.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavfilter.so.6.107.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavfilter.so.7 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavfilter.so.7 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavfilter.so.7.16.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavfilter.so.7.16.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavformat.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavformat.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavformat.so.57 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavformat.so.57 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavformat.so.57.83.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavformat.so.57.83.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavformat.so.58 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavformat.so.58 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavformat.so.58.12.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavformat.so.58.12.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavutil.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavutil.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavutil.so.55 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavutil.so.55 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavutil.so.55.78.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavutil.so.55.78.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavutil.so.56 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavutil.so.56 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libavutil.so.56.14.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libavutil.so.56.14.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt++.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt++.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt++.so.3 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt++.so.3 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt.so.6 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt.so.6 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt.so.6.7.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt.so.6.7.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt++.so.6.7.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt++.so.6.7.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt.so.6.9.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt.so.6.9.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libmlt++.so.6.9.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libmlt++.so.6.9.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libpostproc.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libpostproc.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libpostproc.so.54 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libpostproc.so.54 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libpostproc.so.54.7.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libpostproc.so.54.7.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libpostproc.so.55 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libpostproc.so.55 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libpostproc.so.55.1.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libpostproc.so.55.1.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswresample.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswresample.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswresample.so.2 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswresample.so.2 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswresample.so.2.9.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswresample.so.2.9.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswresample.so.3 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswresample.so.3 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswresample.so.3.1.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswresample.so.3.1.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswscale.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswscale.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswscale.so.4 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswscale.so.4 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswscale.so.4.8.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswscale.so.4.8.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswscale.so.5 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswscale.so.5 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libswscale.so.5.1.100 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libswscale.so.5.1.100 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvidstab.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvidstab.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvidstab.so.1.1 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvidstab.so.1.1 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvpx.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvpx.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvpx.so.5 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvpx.so.5 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvpx.so.5.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvpx.so.5.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libvpx.so.5.0.0 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libvpx.so.5.0.0 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libx265.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libx265.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libx265.so.151 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libx265.so.151 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/libx265.so.160 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/libx265.so.160 differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltavformat.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltavformat.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltcore.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltcore.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltdecklink.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltdecklink.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltfrei0r.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltfrei0r.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltgtk2.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltgtk2.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltjackrack.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltjackrack.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltkdenlive.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltkdenlive.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltlinsys.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltlinsys.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltmotion_est.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltmotion_est.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltnormalize.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltnormalize.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltoldfilm.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltoldfilm.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltopengl.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltopengl.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltplusgpl.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltplusgpl.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltplus.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltplus.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltqt.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltqt.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltresample.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltresample.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltrtaudio.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltrtaudio.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltsdl2.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltsdl2.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltsox.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltsox.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltvideostab.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltvideostab.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltvidstab.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltvidstab.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltvmfx.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltvmfx.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltxine.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltxine.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/mlt/libmltxml.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/mlt/libmltxml.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/x86_64-linux-gnu/libvidstab.so and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/x86_64-linux-gnu/libvidstab.so differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/lib/x86_64-linux-gnu/libvidstab.so.1.1 and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/lib/x86_64-linux-gnu/libvidstab.so.1.1 differ diff -Nru shotcut-18.03.06/Shotcut.app/lib/x86_64-linux-gnu/pkgconfig/vidstab.pc shotcut-18.06.02/Shotcut.app/lib/x86_64-linux-gnu/pkgconfig/vidstab.pc --- shotcut-18.03.06/Shotcut.app/lib/x86_64-linux-gnu/pkgconfig/vidstab.pc 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/lib/x86_64-linux-gnu/pkgconfig/vidstab.pc 2018-06-02 08:01:01.000000000 +0000 @@ -0,0 +1,11 @@ +# file generated by vid.stab cmake build +prefix=/root/shotcut/shotcut/Shotcut/Shotcut.app +libdir=/root/shotcut/shotcut/Shotcut/Shotcut.app/lib/x86_64-linux-gnu +includedir=/root/shotcut/shotcut/Shotcut/Shotcut.app/include + +Name: vidstab +Description: Vid.Stab, a library for stabilizing video clips +Version: 1.10 +Libs: -L${libdir} -lvidstab -lm -lgomp -lpthread +Cflags: -I${includedir} + diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/avio_dir_cmd.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/avio_dir_cmd.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/avio_dir_cmd.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/avio_dir_cmd.c 2018-06-02 08:53:02.000000000 +0000 @@ -143,8 +143,6 @@ return 1; } - /* register codecs and formats and other lavf/lavc components*/ - av_register_all(); avformat_network_init(); op = argv[1]; diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/avio_reading.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/avio_reading.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/avio_reading.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/avio_reading.c 2018-06-02 08:53:02.000000000 +0000 @@ -44,6 +44,8 @@ struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); + if (!buf_size) + return AVERROR_EOF; printf("ptr:%p size:%zu\n", bd->ptr, bd->size); /* copy internal buffer data to buf */ @@ -72,9 +74,6 @@ } input_filename = argv[1]; - /* register codecs and formats and other lavf/lavc components*/ - av_register_all(); - /* slurp file content into buffer */ ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL); if (ret < 0) diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/decode_audio.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/decode_audio.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/decode_audio.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/decode_audio.c 2018-06-02 08:53:02.000000000 +0000 @@ -94,9 +94,6 @@ filename = argv[1]; outfilename = argv[2]; - /* register all the codecs */ - avcodec_register_all(); - pkt = av_packet_alloc(); /* find the MPEG audio decoder */ diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/decode_video.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/decode_video.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/decode_video.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/decode_video.c 2018-06-02 08:53:02.000000000 +0000 @@ -101,8 +101,6 @@ filename = argv[1]; outfilename = argv[2]; - avcodec_register_all(); - pkt = av_packet_alloc(); if (!pkt) exit(1); diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/demuxing_decoding.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/demuxing_decoding.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/demuxing_decoding.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/demuxing_decoding.c 2018-06-02 08:53:02.000000000 +0000 @@ -252,9 +252,6 @@ video_dst_filename = argv[2]; audio_dst_filename = argv[3]; - /* register all formats and codecs */ - av_register_all(); - /* open input file, and allocate format context */ if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) { fprintf(stderr, "Could not open source file %s\n", src_filename); diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/encode_audio.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/encode_audio.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/encode_audio.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/encode_audio.c 2018-06-02 08:53:02.000000000 +0000 @@ -138,9 +138,6 @@ } filename = argv[1]; - /* register all the codecs */ - avcodec_register_all(); - /* find the MP2 encoder */ codec = avcodec_find_encoder(AV_CODEC_ID_MP2); if (!codec) { diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/encode_video.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/encode_video.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/encode_video.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/encode_video.c 2018-06-02 08:53:02.000000000 +0000 @@ -84,8 +84,6 @@ filename = argv[1]; codec_name = argv[2]; - avcodec_register_all(); - /* find the mpeg1video encoder */ codec = avcodec_find_encoder_by_name(codec_name); if (!codec) { diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/extract_mvs.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/extract_mvs.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/extract_mvs.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/extract_mvs.c 2018-06-02 08:53:02.000000000 +0000 @@ -129,8 +129,6 @@ } src_filename = argv[1]; - av_register_all(); - if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) { fprintf(stderr, "Could not open source file %s\n", src_filename); exit(1); diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filter_audio.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filter_audio.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filter_audio.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filter_audio.c 2018-06-02 08:53:02.000000000 +0000 @@ -64,13 +64,13 @@ { AVFilterGraph *filter_graph; AVFilterContext *abuffer_ctx; - AVFilter *abuffer; + const AVFilter *abuffer; AVFilterContext *volume_ctx; - AVFilter *volume; + const AVFilter *volume; AVFilterContext *aformat_ctx; - AVFilter *aformat; + const AVFilter *aformat; AVFilterContext *abuffersink_ctx; - AVFilter *abuffersink; + const AVFilter *abuffersink; AVDictionary *options_dict = NULL; uint8_t options_str[1024]; @@ -289,8 +289,6 @@ return 1; } - avfilter_register_all(); - /* Allocate the frame we will be using to store the data. */ frame = av_frame_alloc(); if (!frame) { diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filtering_audio.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filtering_audio.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filtering_audio.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filtering_audio.c 2018-06-02 08:53:02.000000000 +0000 @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -90,8 +89,8 @@ { char args[512]; int ret = 0; - AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); - AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); + const AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); + const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, -1 }; @@ -229,9 +228,6 @@ exit(1); } - av_register_all(); - avfilter_register_all(); - if ((ret = open_input_file(argv[1])) < 0) goto end; if ((ret = init_filters(filter_descr)) < 0) diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filtering_video.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filtering_video.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/filtering_video.c 2018-03-06 09:44:40.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/filtering_video.c 2018-06-02 08:53:02.000000000 +0000 @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -93,8 +92,8 @@ { char args[512]; int ret = 0; - AVFilter *buffersrc = avfilter_get_by_name("buffer"); - AVFilter *buffersink = avfilter_get_by_name("buffersink"); + const AVFilter *buffersrc = avfilter_get_by_name("buffer"); + const AVFilter *buffersink = avfilter_get_by_name("buffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base; @@ -223,9 +222,6 @@ exit(1); } - av_register_all(); - avfilter_register_all(); - if ((ret = open_input_file(argv[1])) < 0) goto end; if ((ret = init_filters(filter_descr)) < 0) diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/http_multiclient.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/http_multiclient.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/http_multiclient.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/http_multiclient.c 2018-06-02 08:53:02.000000000 +0000 @@ -114,7 +114,6 @@ in_uri = argv[1]; out_uri = argv[2]; - av_register_all(); avformat_network_init(); if ((ret = av_dict_set(&options, "listen", "2", 0)) < 0) { diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/hw_decode.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/hw_decode.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/hw_decode.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/hw_decode.c 2018-06-02 08:53:02.000000000 +0000 @@ -44,34 +44,6 @@ static enum AVPixelFormat hw_pix_fmt; static FILE *output_file = NULL; -static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) -{ - enum AVPixelFormat fmt; - - switch (type) { - case AV_HWDEVICE_TYPE_VAAPI: - fmt = AV_PIX_FMT_VAAPI; - break; - case AV_HWDEVICE_TYPE_DXVA2: - fmt = AV_PIX_FMT_DXVA2_VLD; - break; - case AV_HWDEVICE_TYPE_D3D11VA: - fmt = AV_PIX_FMT_D3D11; - break; - case AV_HWDEVICE_TYPE_VDPAU: - fmt = AV_PIX_FMT_VDPAU; - break; - case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: - fmt = AV_PIX_FMT_VIDEOTOOLBOX; - break; - default: - fmt = AV_PIX_FMT_NONE; - break; - } - - return fmt; -} - static int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type) { int err = 0; @@ -114,7 +86,7 @@ return ret; } - while (ret >= 0) { + while (1) { if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) { fprintf(stderr, "Can not alloc frame\n"); ret = AVERROR(ENOMEM); @@ -166,13 +138,10 @@ fail: av_frame_free(&frame); av_frame_free(&sw_frame); - if (buffer) - av_freep(&buffer); + av_freep(&buffer); if (ret < 0) return ret; } - - return 0; } int main(int argc, char *argv[]) @@ -184,18 +153,20 @@ AVCodec *decoder = NULL; AVPacket packet; enum AVHWDeviceType type; + int i; if (argc < 4) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %s \n", argv[0]); return -1; } - av_register_all(); - type = av_hwdevice_find_type_by_name(argv[1]); - hw_pix_fmt = find_fmt_by_hw_type(type); - if (hw_pix_fmt == -1) { - fprintf(stderr, "Cannot support '%s' in this example.\n", argv[1]); + if (type == AV_HWDEVICE_TYPE_NONE) { + fprintf(stderr, "Device type %s is not supported.\n", argv[1]); + fprintf(stderr, "Available device types:"); + while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) + fprintf(stderr, " %s", av_hwdevice_get_type_name(type)); + fprintf(stderr, "\n"); return -1; } @@ -218,6 +189,20 @@ } video_stream = ret; + for (i = 0;; i++) { + const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); + if (!config) { + fprintf(stderr, "Decoder %s does not support device type %s.\n", + decoder->name, av_hwdevice_get_type_name(type)); + return -1; + } + if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && + config->device_type == type) { + hw_pix_fmt = config->pix_fmt; + break; + } + } + if (!(decoder_ctx = avcodec_alloc_context3(decoder))) return AVERROR(ENOMEM); diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/metadata.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/metadata.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/metadata.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/metadata.c 2018-06-02 08:53:02.000000000 +0000 @@ -44,7 +44,6 @@ return 1; } - av_register_all(); if ((ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL))) return ret; diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/muxing.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/muxing.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/muxing.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/muxing.c 2018-06-02 08:53:02.000000000 +0000 @@ -488,9 +488,9 @@ } } fill_yuv_image(ost->tmp_frame, ost->next_pts, c->width, c->height); - sws_scale(ost->sws_ctx, - (const uint8_t * const *)ost->tmp_frame->data, ost->tmp_frame->linesize, - 0, c->height, ost->frame->data, ost->frame->linesize); + sws_scale(ost->sws_ctx, (const uint8_t * const *) ost->tmp_frame->data, + ost->tmp_frame->linesize, 0, c->height, ost->frame->data, + ost->frame->linesize); } else { fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height); } @@ -564,9 +564,6 @@ AVDictionary *opt = NULL; int i; - /* Initialize libavcodec, and register all codecs and formats. */ - av_register_all(); - if (argc < 2) { printf("usage: %s output_file\n" "API example program to output a media file with libavformat.\n" diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/qsvdec.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/qsvdec.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/qsvdec.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/qsvdec.c 2018-06-02 08:53:02.000000000 +0000 @@ -150,8 +150,6 @@ int ret, i; - av_register_all(); - if (argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; @@ -210,7 +208,6 @@ video_st->codecpar->extradata_size); decoder_ctx->extradata_size = video_st->codecpar->extradata_size; } - decoder_ctx->refcounted_frames = 1; decoder_ctx->opaque = &decode; decoder_ctx->get_format = get_format; diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/remuxing.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/remuxing.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/remuxing.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/remuxing.c 2018-06-02 08:53:02.000000000 +0000 @@ -65,8 +65,6 @@ in_filename = argv[1]; out_filename = argv[2]; - av_register_all(); - if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) { fprintf(stderr, "Could not open input file '%s'", in_filename); goto end; diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/transcode_aac.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/transcode_aac.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/transcode_aac.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/transcode_aac.c 2018-06-02 08:53:02.000000000 +0000 @@ -1,4 +1,6 @@ /* + * Copyright (c) 2013-2018 Andreas Unterweger + * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or @@ -8,7 +10,7 @@ * * FFmpeg 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public @@ -18,10 +20,11 @@ /** * @file - * simple audio converter + * Simple audio converter * * @example transcode_aac.c * Convert an input audio file to AAC in an MP4 container using FFmpeg. + * Formats other than MP4 are supported based on the output file extension. * @author Andreas Unterweger (dustsigns@gmail.com) */ @@ -40,12 +43,18 @@ #include "libswresample/swresample.h" -/** The output bit rate in kbit/s */ +/* The output bit rate in bit/s */ #define OUTPUT_BIT_RATE 96000 -/** The number of output channels */ +/* The number of output channels */ #define OUTPUT_CHANNELS 2 -/** Open an input file and the required decoder. */ +/** + * Open an input file and the required decoder. + * @param filename File to be opened + * @param[out] input_format_context Format context of opened file + * @param[out] input_codec_context Codec context of opened file + * @return Error code (0 if successful) + */ static int open_input_file(const char *filename, AVFormatContext **input_format_context, AVCodecContext **input_codec_context) @@ -54,7 +63,7 @@ AVCodec *input_codec; int error; - /** Open the input file to read from it. */ + /* Open the input file to read from it. */ if ((error = avformat_open_input(input_format_context, filename, NULL, NULL)) < 0) { fprintf(stderr, "Could not open input file '%s' (error '%s')\n", @@ -63,7 +72,7 @@ return error; } - /** Get information on the input file (number of streams etc.). */ + /* Get information on the input file (number of streams etc.). */ if ((error = avformat_find_stream_info(*input_format_context, NULL)) < 0) { fprintf(stderr, "Could not open find stream info (error '%s')\n", av_err2str(error)); @@ -71,7 +80,7 @@ return error; } - /** Make sure that there is only one stream in the input file. */ + /* Make sure that there is only one stream in the input file. */ if ((*input_format_context)->nb_streams != 1) { fprintf(stderr, "Expected one audio input stream, but found %d\n", (*input_format_context)->nb_streams); @@ -79,14 +88,14 @@ return AVERROR_EXIT; } - /** Find a decoder for the audio stream. */ + /* Find a decoder for the audio stream. */ if (!(input_codec = avcodec_find_decoder((*input_format_context)->streams[0]->codecpar->codec_id))) { fprintf(stderr, "Could not find input codec\n"); avformat_close_input(input_format_context); return AVERROR_EXIT; } - /** allocate a new decoding context */ + /* Allocate a new decoding context. */ avctx = avcodec_alloc_context3(input_codec); if (!avctx) { fprintf(stderr, "Could not allocate a decoding context\n"); @@ -94,7 +103,7 @@ return AVERROR(ENOMEM); } - /** initialize the stream parameters with demuxer information */ + /* Initialize the stream parameters with demuxer information. */ error = avcodec_parameters_to_context(avctx, (*input_format_context)->streams[0]->codecpar); if (error < 0) { avformat_close_input(input_format_context); @@ -102,7 +111,7 @@ return error; } - /** Open the decoder for the audio stream to use it later. */ + /* Open the decoder for the audio stream to use it later. */ if ((error = avcodec_open2(avctx, input_codec, NULL)) < 0) { fprintf(stderr, "Could not open input codec (error '%s')\n", av_err2str(error)); @@ -111,7 +120,7 @@ return error; } - /** Save the decoder context for easier access later. */ + /* Save the decoder context for easier access later. */ *input_codec_context = avctx; return 0; @@ -121,6 +130,11 @@ * Open an output file and the required encoder. * Also set some basic encoder parameters. * Some of these parameters are based on the input file's parameters. + * @param filename File to be opened + * @param input_codec_context Codec context of input file + * @param[out] output_format_context Format context of output file + * @param[out] output_codec_context Codec context of output file + * @return Error code (0 if successful) */ static int open_output_file(const char *filename, AVCodecContext *input_codec_context, @@ -133,7 +147,7 @@ AVCodec *output_codec = NULL; int error; - /** Open the output file to write to it. */ + /* Open the output file to write to it. */ if ((error = avio_open(&output_io_context, filename, AVIO_FLAG_WRITE)) < 0) { fprintf(stderr, "Could not open output file '%s' (error '%s')\n", @@ -141,32 +155,35 @@ return error; } - /** Create a new format context for the output container format. */ + /* Create a new format context for the output container format. */ if (!(*output_format_context = avformat_alloc_context())) { fprintf(stderr, "Could not allocate output format context\n"); return AVERROR(ENOMEM); } - /** Associate the output file (pointer) with the container format context. */ + /* Associate the output file (pointer) with the container format context. */ (*output_format_context)->pb = output_io_context; - /** Guess the desired container format based on the file extension. */ + /* Guess the desired container format based on the file extension. */ if (!((*output_format_context)->oformat = av_guess_format(NULL, filename, NULL))) { fprintf(stderr, "Could not find output file format\n"); goto cleanup; } - av_strlcpy((*output_format_context)->filename, filename, - sizeof((*output_format_context)->filename)); + if (!((*output_format_context)->url = av_strdup(filename))) { + fprintf(stderr, "Could not allocate url.\n"); + error = AVERROR(ENOMEM); + goto cleanup; + } - /** Find the encoder to be used by its name. */ + /* Find the encoder to be used by its name. */ if (!(output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC))) { fprintf(stderr, "Could not find an AAC encoder.\n"); goto cleanup; } - /** Create a new audio stream in the output file container. */ + /* Create a new audio stream in the output file container. */ if (!(stream = avformat_new_stream(*output_format_context, NULL))) { fprintf(stderr, "Could not create new stream\n"); error = AVERROR(ENOMEM); @@ -180,31 +197,27 @@ goto cleanup; } - /** - * Set the basic encoder parameters. - * The input file's sample rate is used to avoid a sample rate conversion. - */ + /* Set the basic encoder parameters. + * The input file's sample rate is used to avoid a sample rate conversion. */ avctx->channels = OUTPUT_CHANNELS; avctx->channel_layout = av_get_default_channel_layout(OUTPUT_CHANNELS); avctx->sample_rate = input_codec_context->sample_rate; avctx->sample_fmt = output_codec->sample_fmts[0]; avctx->bit_rate = OUTPUT_BIT_RATE; - /** Allow the use of the experimental AAC encoder */ + /* Allow the use of the experimental AAC encoder. */ avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; - /** Set the sample rate for the container. */ + /* Set the sample rate for the container. */ stream->time_base.den = input_codec_context->sample_rate; stream->time_base.num = 1; - /** - * Some container formats (like MP4) require global headers to be present - * Mark the encoder so that it behaves accordingly. - */ + /* Some container formats (like MP4) require global headers to be present. + * Mark the encoder so that it behaves accordingly. */ if ((*output_format_context)->oformat->flags & AVFMT_GLOBALHEADER) avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - /** Open the encoder for the audio stream to use it later. */ + /* Open the encoder for the audio stream to use it later. */ if ((error = avcodec_open2(avctx, output_codec, NULL)) < 0) { fprintf(stderr, "Could not open output codec (error '%s')\n", av_err2str(error)); @@ -217,7 +230,7 @@ goto cleanup; } - /** Save the encoder context for easier access later. */ + /* Save the encoder context for easier access later. */ *output_codec_context = avctx; return 0; @@ -230,16 +243,23 @@ return error < 0 ? error : AVERROR_EXIT; } -/** Initialize one data packet for reading or writing. */ +/** + * Initialize one data packet for reading or writing. + * @param packet Packet to be initialized + */ static void init_packet(AVPacket *packet) { av_init_packet(packet); - /** Set the packet data and size so that it is recognized as being empty. */ + /* Set the packet data and size so that it is recognized as being empty. */ packet->data = NULL; packet->size = 0; } -/** Initialize one audio frame for reading from the input file */ +/** + * Initialize one audio frame for reading from the input file. + * @param[out] frame Frame to be initialized + * @return Error code (0 if successful) + */ static int init_input_frame(AVFrame **frame) { if (!(*frame = av_frame_alloc())) { @@ -253,6 +273,10 @@ * Initialize the audio resampler based on the input and output codec settings. * If the input and output sample formats differ, a conversion is required * libswresample takes care of this, but requires initialization. + * @param input_codec_context Codec context of the input file + * @param output_codec_context Codec context of the output file + * @param[out] resample_context Resample context for the required conversion + * @return Error code (0 if successful) */ static int init_resampler(AVCodecContext *input_codec_context, AVCodecContext *output_codec_context, @@ -260,7 +284,7 @@ { int error; - /** + /* * Create a resampler context for the conversion. * Set the conversion parameters. * Default channel layouts based on the number of channels @@ -279,14 +303,14 @@ fprintf(stderr, "Could not allocate resample context\n"); return AVERROR(ENOMEM); } - /** + /* * Perform a sanity check so that the number of converted samples is * not greater than the number of samples to be converted. * If the sample rates differ, this case has to be handled differently */ av_assert0(output_codec_context->sample_rate == input_codec_context->sample_rate); - /** Open the resampler with the specified parameters. */ + /* Open the resampler with the specified parameters. */ if ((error = swr_init(*resample_context)) < 0) { fprintf(stderr, "Could not open resample context\n"); swr_free(resample_context); @@ -295,10 +319,15 @@ return 0; } -/** Initialize a FIFO buffer for the audio samples to be encoded. */ +/** + * Initialize a FIFO buffer for the audio samples to be encoded. + * @param[out] fifo Sample buffer + * @param output_codec_context Codec context of the output file + * @return Error code (0 if successful) + */ static int init_fifo(AVAudioFifo **fifo, AVCodecContext *output_codec_context) { - /** Create the FIFO buffer based on the specified output sample format. */ + /* Create the FIFO buffer based on the specified output sample format. */ if (!(*fifo = av_audio_fifo_alloc(output_codec_context->sample_fmt, output_codec_context->channels, 1))) { fprintf(stderr, "Could not allocate FIFO\n"); @@ -307,7 +336,11 @@ return 0; } -/** Write the header of the output file container. */ +/** + * Write the header of the output file container. + * @param output_format_context Format context of the output file + * @return Error code (0 if successful) + */ static int write_output_file_header(AVFormatContext *output_format_context) { int error; @@ -319,20 +352,32 @@ return 0; } -/** Decode one audio frame from the input file. */ +/** + * Decode one audio frame from the input file. + * @param frame Audio frame to be decoded + * @param input_format_context Format context of the input file + * @param input_codec_context Codec context of the input file + * @param[out] data_present Indicates whether data has been decoded + * @param[out] finished Indicates whether the end of file has + * been reached and all data has been + * decoded. If this flag is false, there + * is more data to be decoded, i.e., this + * function has to be called again. + * @return Error code (0 if successful) + */ static int decode_audio_frame(AVFrame *frame, AVFormatContext *input_format_context, AVCodecContext *input_codec_context, int *data_present, int *finished) { - /** Packet used for temporary storage. */ + /* Packet used for temporary storage. */ AVPacket input_packet; int error; init_packet(&input_packet); - /** Read one audio frame from the input file into a temporary packet. */ + /* Read one audio frame from the input file into a temporary packet. */ if ((error = av_read_frame(input_format_context, &input_packet)) < 0) { - /** If we are at the end of the file, flush the decoder below. */ + /* If we are at the end of the file, flush the decoder below. */ if (error == AVERROR_EOF) *finished = 1; else { @@ -342,34 +387,52 @@ } } - /** - * Decode the audio frame stored in the temporary packet. - * The input audio stream decoder is used to do this. - * If we are at the end of the file, pass an empty packet to the decoder - * to flush it. - */ - if ((error = avcodec_decode_audio4(input_codec_context, frame, - data_present, &input_packet)) < 0) { - fprintf(stderr, "Could not decode frame (error '%s')\n", + /* Send the audio frame stored in the temporary packet to the decoder. + * The input audio stream decoder is used to do this. */ + if ((error = avcodec_send_packet(input_codec_context, &input_packet)) < 0) { + fprintf(stderr, "Could not send packet for decoding (error '%s')\n", av_err2str(error)); - av_packet_unref(&input_packet); return error; } - /** - * If the decoder has not been flushed completely, we are not finished, - * so that this function has to be called again. - */ - if (*finished && *data_present) - *finished = 0; + /* Receive one frame from the decoder. */ + error = avcodec_receive_frame(input_codec_context, frame); + /* If the decoder asks for more data to be able to decode a frame, + * return indicating that no data is present. */ + if (error == AVERROR(EAGAIN)) { + error = 0; + goto cleanup; + /* If the end of the input file is reached, stop decoding. */ + } else if (error == AVERROR_EOF) { + *finished = 1; + error = 0; + goto cleanup; + } else if (error < 0) { + fprintf(stderr, "Could not decode frame (error '%s')\n", + av_err2str(error)); + goto cleanup; + /* Default case: Return decoded data. */ + } else { + *data_present = 1; + goto cleanup; + } + +cleanup: av_packet_unref(&input_packet); - return 0; + return error; } /** * Initialize a temporary storage for the specified number of audio samples. * The conversion requires temporary storage due to the different format. * The number of audio samples to be allocated is specified in frame_size. + * @param[out] converted_input_samples Array of converted samples. The + * dimensions are reference, channel + * (for multi-channel audio), sample. + * @param output_codec_context Codec context of the output file + * @param frame_size Number of samples to be converted in + * each round + * @return Error code (0 if successful) */ static int init_converted_samples(uint8_t ***converted_input_samples, AVCodecContext *output_codec_context, @@ -377,8 +440,7 @@ { int error; - /** - * Allocate as many pointers as there are audio channels. + /* Allocate as many pointers as there are audio channels. * Each pointer will later point to the audio samples of the corresponding * channels (although it may be NULL for interleaved formats). */ @@ -388,10 +450,8 @@ return AVERROR(ENOMEM); } - /** - * Allocate memory for the samples of all channels in one consecutive - * block for convenience. - */ + /* Allocate memory for the samples of all channels in one consecutive + * block for convenience. */ if ((error = av_samples_alloc(*converted_input_samples, NULL, output_codec_context->channels, frame_size, @@ -408,8 +468,15 @@ /** * Convert the input audio samples into the output sample format. - * The conversion happens on a per-frame basis, the size of which is specified - * by frame_size. + * The conversion happens on a per-frame basis, the size of which is + * specified by frame_size. + * @param input_data Samples to be decoded. The dimensions are + * channel (for multi-channel audio), sample. + * @param[out] converted_data Converted samples. The dimensions are channel + * (for multi-channel audio), sample. + * @param frame_size Number of samples to be converted + * @param resample_context Resample context for the conversion + * @return Error code (0 if successful) */ static int convert_samples(const uint8_t **input_data, uint8_t **converted_data, const int frame_size, @@ -417,7 +484,7 @@ { int error; - /** Convert the samples using the resampler. */ + /* Convert the samples using the resampler. */ if ((error = swr_convert(resample_context, converted_data, frame_size, input_data , frame_size)) < 0) { @@ -429,23 +496,28 @@ return 0; } -/** Add converted input audio samples to the FIFO buffer for later processing. */ +/** + * Add converted input audio samples to the FIFO buffer for later processing. + * @param fifo Buffer to add the samples to + * @param converted_input_samples Samples to be added. The dimensions are channel + * (for multi-channel audio), sample. + * @param frame_size Number of samples to be converted + * @return Error code (0 if successful) + */ static int add_samples_to_fifo(AVAudioFifo *fifo, uint8_t **converted_input_samples, const int frame_size) { int error; - /** - * Make the FIFO as large as it needs to be to hold both, - * the old and the new samples. - */ + /* Make the FIFO as large as it needs to be to hold both, + * the old and the new samples. */ if ((error = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) { fprintf(stderr, "Could not reallocate FIFO\n"); return error; } - /** Store the new samples in the FIFO buffer. */ + /* Store the new samples in the FIFO buffer. */ if (av_audio_fifo_write(fifo, (void **)converted_input_samples, frame_size) < frame_size) { fprintf(stderr, "Could not write data to FIFO\n"); @@ -455,8 +527,20 @@ } /** - * Read one audio frame from the input file, decodes, converts and stores + * Read one audio frame from the input file, decode, convert and store * it in the FIFO buffer. + * @param fifo Buffer used for temporary storage + * @param input_format_context Format context of the input file + * @param input_codec_context Codec context of the input file + * @param output_codec_context Codec context of the output file + * @param resampler_context Resample context for the conversion + * @param[out] finished Indicates whether the end of file has + * been reached and all data has been + * decoded. If this flag is false, + * there is more data to be decoded, + * i.e., this function has to be called + * again. + * @return Error code (0 if successful) */ static int read_decode_convert_and_store(AVAudioFifo *fifo, AVFormatContext *input_format_context, @@ -465,45 +549,41 @@ SwrContext *resampler_context, int *finished) { - /** Temporary storage of the input samples of the frame read from the file. */ + /* Temporary storage of the input samples of the frame read from the file. */ AVFrame *input_frame = NULL; - /** Temporary storage for the converted input samples. */ + /* Temporary storage for the converted input samples. */ uint8_t **converted_input_samples = NULL; - int data_present; + int data_present = 0; int ret = AVERROR_EXIT; - /** Initialize temporary storage for one input frame. */ + /* Initialize temporary storage for one input frame. */ if (init_input_frame(&input_frame)) goto cleanup; - /** Decode one frame worth of audio samples. */ + /* Decode one frame worth of audio samples. */ if (decode_audio_frame(input_frame, input_format_context, input_codec_context, &data_present, finished)) goto cleanup; - /** - * If we are at the end of the file and there are no more samples + /* If we are at the end of the file and there are no more samples * in the decoder which are delayed, we are actually finished. - * This must not be treated as an error. - */ - if (*finished && !data_present) { + * This must not be treated as an error. */ + if (*finished) { ret = 0; goto cleanup; } - /** If there is decoded data, convert and store it */ + /* If there is decoded data, convert and store it. */ if (data_present) { - /** Initialize the temporary storage for the converted input samples. */ + /* Initialize the temporary storage for the converted input samples. */ if (init_converted_samples(&converted_input_samples, output_codec_context, input_frame->nb_samples)) goto cleanup; - /** - * Convert the input samples to the desired output sample format. - * This requires a temporary storage provided by converted_input_samples. - */ + /* Convert the input samples to the desired output sample format. + * This requires a temporary storage provided by converted_input_samples. */ if (convert_samples((const uint8_t**)input_frame->extended_data, converted_input_samples, input_frame->nb_samples, resampler_context)) goto cleanup; - /** Add the converted input samples to the FIFO buffer for later processing. */ + /* Add the converted input samples to the FIFO buffer for later processing. */ if (add_samples_to_fifo(fifo, converted_input_samples, input_frame->nb_samples)) goto cleanup; @@ -524,6 +604,10 @@ /** * Initialize one input frame for writing to the output file. * The frame will be exactly frame_size samples large. + * @param[out] frame Frame to be initialized + * @param output_codec_context Codec context of the output file + * @param frame_size Size of the frame + * @return Error code (0 if successful) */ static int init_output_frame(AVFrame **frame, AVCodecContext *output_codec_context, @@ -531,28 +615,24 @@ { int error; - /** Create a new frame to store the audio samples. */ + /* Create a new frame to store the audio samples. */ if (!(*frame = av_frame_alloc())) { fprintf(stderr, "Could not allocate output frame\n"); return AVERROR_EXIT; } - /** - * Set the frame's parameters, especially its size and format. + /* Set the frame's parameters, especially its size and format. * av_frame_get_buffer needs this to allocate memory for the * audio samples of the frame. * Default channel layouts based on the number of channels - * are assumed for simplicity. - */ + * are assumed for simplicity. */ (*frame)->nb_samples = frame_size; (*frame)->channel_layout = output_codec_context->channel_layout; (*frame)->format = output_codec_context->sample_fmt; (*frame)->sample_rate = output_codec_context->sample_rate; - /** - * Allocate the samples of the created frame. This call will make - * sure that the audio frame can hold as many samples as specified. - */ + /* Allocate the samples of the created frame. This call will make + * sure that the audio frame can hold as many samples as specified. */ if ((error = av_frame_get_buffer(*frame, 0)) < 0) { fprintf(stderr, "Could not allocate output frame samples (error '%s')\n", av_err2str(error)); @@ -563,87 +643,114 @@ return 0; } -/** Global timestamp for the audio frames */ +/* Global timestamp for the audio frames. */ static int64_t pts = 0; -/** Encode one frame worth of audio to the output file. */ +/** + * Encode one frame worth of audio to the output file. + * @param frame Samples to be encoded + * @param output_format_context Format context of the output file + * @param output_codec_context Codec context of the output file + * @param[out] data_present Indicates whether data has been + * encoded + * @return Error code (0 if successful) + */ static int encode_audio_frame(AVFrame *frame, AVFormatContext *output_format_context, AVCodecContext *output_codec_context, int *data_present) { - /** Packet used for temporary storage. */ + /* Packet used for temporary storage. */ AVPacket output_packet; int error; init_packet(&output_packet); - /** Set a timestamp based on the sample rate for the container. */ + /* Set a timestamp based on the sample rate for the container. */ if (frame) { frame->pts = pts; pts += frame->nb_samples; } - /** - * Encode the audio frame and store it in the temporary packet. - * The output audio stream encoder is used to do this. - */ - if ((error = avcodec_encode_audio2(output_codec_context, &output_packet, - frame, data_present)) < 0) { - fprintf(stderr, "Could not encode frame (error '%s')\n", + /* Send the audio frame stored in the temporary packet to the encoder. + * The output audio stream encoder is used to do this. */ + error = avcodec_send_frame(output_codec_context, frame); + /* The encoder signals that it has nothing more to encode. */ + if (error == AVERROR_EOF) { + error = 0; + goto cleanup; + } else if (error < 0) { + fprintf(stderr, "Could not send packet for encoding (error '%s')\n", av_err2str(error)); - av_packet_unref(&output_packet); return error; } - /** Write one audio frame from the temporary packet to the output file. */ - if (*data_present) { - if ((error = av_write_frame(output_format_context, &output_packet)) < 0) { - fprintf(stderr, "Could not write frame (error '%s')\n", - av_err2str(error)); - av_packet_unref(&output_packet); - return error; - } + /* Receive one encoded frame from the encoder. */ + error = avcodec_receive_packet(output_codec_context, &output_packet); + /* If the encoder asks for more data to be able to provide an + * encoded frame, return indicating that no data is present. */ + if (error == AVERROR(EAGAIN)) { + error = 0; + goto cleanup; + /* If the last frame has been encoded, stop encoding. */ + } else if (error == AVERROR_EOF) { + error = 0; + goto cleanup; + } else if (error < 0) { + fprintf(stderr, "Could not encode frame (error '%s')\n", + av_err2str(error)); + goto cleanup; + /* Default case: Return encoded data. */ + } else { + *data_present = 1; + } - av_packet_unref(&output_packet); + /* Write one audio frame from the temporary packet to the output file. */ + if (*data_present && + (error = av_write_frame(output_format_context, &output_packet)) < 0) { + fprintf(stderr, "Could not write frame (error '%s')\n", + av_err2str(error)); + goto cleanup; } - return 0; +cleanup: + av_packet_unref(&output_packet); + return error; } /** * Load one audio frame from the FIFO buffer, encode and write it to the * output file. + * @param fifo Buffer used for temporary storage + * @param output_format_context Format context of the output file + * @param output_codec_context Codec context of the output file + * @return Error code (0 if successful) */ static int load_encode_and_write(AVAudioFifo *fifo, AVFormatContext *output_format_context, AVCodecContext *output_codec_context) { - /** Temporary storage of the output samples of the frame written to the file. */ + /* Temporary storage of the output samples of the frame written to the file. */ AVFrame *output_frame; - /** - * Use the maximum number of possible samples per frame. + /* Use the maximum number of possible samples per frame. * If there is less than the maximum possible frame size in the FIFO - * buffer use this number. Otherwise, use the maximum possible frame size - */ + * buffer use this number. Otherwise, use the maximum possible frame size. */ const int frame_size = FFMIN(av_audio_fifo_size(fifo), output_codec_context->frame_size); int data_written; - /** Initialize temporary storage for one output frame. */ + /* Initialize temporary storage for one output frame. */ if (init_output_frame(&output_frame, output_codec_context, frame_size)) return AVERROR_EXIT; - /** - * Read as many samples from the FIFO buffer as required to fill the frame. - * The samples are stored in the frame temporarily. - */ + /* Read as many samples from the FIFO buffer as required to fill the frame. + * The samples are stored in the frame temporarily. */ if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) { fprintf(stderr, "Could not read data from FIFO\n"); av_frame_free(&output_frame); return AVERROR_EXIT; } - /** Encode one frame worth of audio samples. */ + /* Encode one frame worth of audio samples. */ if (encode_audio_frame(output_frame, output_format_context, output_codec_context, &data_written)) { av_frame_free(&output_frame); @@ -653,7 +760,11 @@ return 0; } -/** Write the trailer of the output file container. */ +/** + * Write the trailer of the output file container. + * @param output_format_context Format context of the output file + * @return Error code (0 if successful) + */ static int write_output_file_trailer(AVFormatContext *output_format_context) { int error; @@ -665,7 +776,6 @@ return 0; } -/** Convert an audio file to an AAC file in an MP4 container. */ int main(int argc, char **argv) { AVFormatContext *input_format_context = NULL, *output_format_context = NULL; @@ -674,90 +784,75 @@ AVAudioFifo *fifo = NULL; int ret = AVERROR_EXIT; - if (argc < 3) { + if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } - /** Register all codecs and formats so that they can be used. */ - av_register_all(); - /** Open the input file for reading. */ + /* Open the input file for reading. */ if (open_input_file(argv[1], &input_format_context, &input_codec_context)) goto cleanup; - /** Open the output file for writing. */ + /* Open the output file for writing. */ if (open_output_file(argv[2], input_codec_context, &output_format_context, &output_codec_context)) goto cleanup; - /** Initialize the resampler to be able to convert audio sample formats. */ + /* Initialize the resampler to be able to convert audio sample formats. */ if (init_resampler(input_codec_context, output_codec_context, &resample_context)) goto cleanup; - /** Initialize the FIFO buffer to store audio samples to be encoded. */ + /* Initialize the FIFO buffer to store audio samples to be encoded. */ if (init_fifo(&fifo, output_codec_context)) goto cleanup; - /** Write the header of the output file container. */ + /* Write the header of the output file container. */ if (write_output_file_header(output_format_context)) goto cleanup; - /** - * Loop as long as we have input samples to read or output samples - * to write; abort as soon as we have neither. - */ + /* Loop as long as we have input samples to read or output samples + * to write; abort as soon as we have neither. */ while (1) { - /** Use the encoder's desired frame size for processing. */ + /* Use the encoder's desired frame size for processing. */ const int output_frame_size = output_codec_context->frame_size; int finished = 0; - /** - * Make sure that there is one frame worth of samples in the FIFO + /* Make sure that there is one frame worth of samples in the FIFO * buffer so that the encoder can do its work. * Since the decoder's and the encoder's frame size may differ, we * need to FIFO buffer to store as many frames worth of input samples - * that they make up at least one frame worth of output samples. - */ + * that they make up at least one frame worth of output samples. */ while (av_audio_fifo_size(fifo) < output_frame_size) { - /** - * Decode one frame worth of audio samples, convert it to the - * output sample format and put it into the FIFO buffer. - */ + /* Decode one frame worth of audio samples, convert it to the + * output sample format and put it into the FIFO buffer. */ if (read_decode_convert_and_store(fifo, input_format_context, input_codec_context, output_codec_context, resample_context, &finished)) goto cleanup; - /** - * If we are at the end of the input file, we continue - * encoding the remaining audio samples to the output file. - */ + /* If we are at the end of the input file, we continue + * encoding the remaining audio samples to the output file. */ if (finished) break; } - /** - * If we have enough samples for the encoder, we encode them. + /* If we have enough samples for the encoder, we encode them. * At the end of the file, we pass the remaining samples to - * the encoder. - */ + * the encoder. */ while (av_audio_fifo_size(fifo) >= output_frame_size || (finished && av_audio_fifo_size(fifo) > 0)) - /** - * Take one frame worth of audio samples from the FIFO buffer, - * encode it and write it to the output file. - */ + /* Take one frame worth of audio samples from the FIFO buffer, + * encode it and write it to the output file. */ if (load_encode_and_write(fifo, output_format_context, output_codec_context)) goto cleanup; - /** - * If we are at the end of the input file and have encoded - * all remaining samples, we can exit this loop and finish. - */ + /* If we are at the end of the input file and have encoded + * all remaining samples, we can exit this loop and finish. */ if (finished) { int data_written; - /** Flush the encoder as it may have delayed frames. */ + /* Flush the encoder as it may have delayed frames. */ do { + data_written = 0; if (encode_audio_frame(NULL, output_format_context, output_codec_context, &data_written)) goto cleanup; @@ -766,7 +861,7 @@ } } - /** Write the trailer of the output file container. */ + /* Write the trailer of the output file container. */ if (write_output_file_trailer(output_format_context)) goto cleanup; ret = 0; diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/transcoding.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/transcoding.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/transcoding.c 2018-03-06 09:44:41.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/transcoding.c 2018-06-02 08:53:02.000000000 +0000 @@ -30,7 +30,6 @@ #include #include -#include #include #include #include @@ -228,8 +227,8 @@ { char args[512]; int ret = 0; - AVFilter *buffersrc = NULL; - AVFilter *buffersink = NULL; + const AVFilter *buffersrc = NULL; + const AVFilter *buffersink = NULL; AVFilterContext *buffersrc_ctx = NULL; AVFilterContext *buffersink_ctx = NULL; AVFilterInOut *outputs = avfilter_inout_alloc(); @@ -518,9 +517,6 @@ return 1; } - av_register_all(); - avfilter_register_all(); - if ((ret = open_input_file(argv[1])) < 0) goto end; if ((ret = open_output_file(argv[2])) < 0) diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/vaapi_encode.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/vaapi_encode.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/vaapi_encode.c 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/vaapi_encode.c 2018-06-02 08:53:02.000000000 +0000 @@ -0,0 +1,222 @@ +/* + * Video Acceleration API (video encoding) encode sample + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Intel VAAPI-accelerated encoding example. + * + * @example vaapi_encode.c + * This example shows how to do VAAPI-accelerated encoding. now only support NV12 + * raw file, usage like: vaapi_encode 1920 1080 input.yuv output.h264 + * + */ + +#include +#include +#include + +#include +#include +#include + +static int width, height; +static AVBufferRef *hw_device_ctx = NULL; + +static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx) +{ + AVBufferRef *hw_frames_ref; + AVHWFramesContext *frames_ctx = NULL; + int err = 0; + + if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + fprintf(stderr, "Failed to create VAAPI frame context.\n"); + return -1; + } + frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); + frames_ctx->format = AV_PIX_FMT_VAAPI; + frames_ctx->sw_format = AV_PIX_FMT_NV12; + frames_ctx->width = width; + frames_ctx->height = height; + frames_ctx->initial_pool_size = 20; + if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { + fprintf(stderr, "Failed to initialize VAAPI frame context." + "Error code: %s\n",av_err2str(err)); + av_buffer_unref(&hw_frames_ref); + return err; + } + ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); + if (!ctx->hw_frames_ctx) + err = AVERROR(ENOMEM); + + av_buffer_unref(&hw_frames_ref); + return err; +} + +static int encode_write(AVCodecContext *avctx, AVFrame *frame, FILE *fout) +{ + int ret = 0; + AVPacket enc_pkt; + + av_init_packet(&enc_pkt); + enc_pkt.data = NULL; + enc_pkt.size = 0; + + if ((ret = avcodec_send_frame(avctx, frame)) < 0) { + fprintf(stderr, "Error code: %s\n", av_err2str(ret)); + goto end; + } + while (1) { + ret = avcodec_receive_packet(avctx, &enc_pkt); + if (ret) + break; + + enc_pkt.stream_index = 0; + ret = fwrite(enc_pkt.data, enc_pkt.size, 1, fout); + av_packet_unref(&enc_pkt); + } + +end: + ret = ((ret == AVERROR(EAGAIN)) ? 0 : -1); + return ret; +} + +int main(int argc, char *argv[]) +{ + int size, err; + FILE *fin = NULL, *fout = NULL; + AVFrame *sw_frame = NULL, *hw_frame = NULL; + AVCodecContext *avctx = NULL; + AVCodec *codec = NULL; + const char *enc_name = "h264_vaapi"; + + if (argc < 5) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return -1; + } + + width = atoi(argv[1]); + height = atoi(argv[2]); + size = width * height; + + if (!(fin = fopen(argv[3], "r"))) { + fprintf(stderr, "Fail to open input file : %s\n", strerror(errno)); + return -1; + } + if (!(fout = fopen(argv[4], "w+b"))) { + fprintf(stderr, "Fail to open output file : %s\n", strerror(errno)); + err = -1; + goto close; + } + + err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, + NULL, NULL, 0); + if (err < 0) { + fprintf(stderr, "Failed to create a VAAPI device. Error code: %s\n", av_err2str(err)); + goto close; + } + + if (!(codec = avcodec_find_encoder_by_name(enc_name))) { + fprintf(stderr, "Could not find encoder.\n"); + err = -1; + goto close; + } + + if (!(avctx = avcodec_alloc_context3(codec))) { + err = AVERROR(ENOMEM); + goto close; + } + + avctx->width = width; + avctx->height = height; + avctx->time_base = (AVRational){1, 25}; + avctx->framerate = (AVRational){25, 1}; + avctx->sample_aspect_ratio = (AVRational){1, 1}; + avctx->pix_fmt = AV_PIX_FMT_VAAPI; + + /* set hw_frames_ctx for encoder's AVCodecContext */ + if ((err = set_hwframe_ctx(avctx, hw_device_ctx)) < 0) { + fprintf(stderr, "Failed to set hwframe context.\n"); + goto close; + } + + if ((err = avcodec_open2(avctx, codec, NULL)) < 0) { + fprintf(stderr, "Cannot open video encoder codec. Error code: %s\n", av_err2str(err)); + goto close; + } + + while (1) { + if (!(sw_frame = av_frame_alloc())) { + err = AVERROR(ENOMEM); + goto close; + } + /* read data into software frame, and transfer them into hw frame */ + sw_frame->width = width; + sw_frame->height = height; + sw_frame->format = AV_PIX_FMT_NV12; + if ((err = av_frame_get_buffer(sw_frame, 32)) < 0) + goto close; + if ((err = fread((uint8_t*)(sw_frame->data[0]), size, 1, fin)) <= 0) + break; + if ((err = fread((uint8_t*)(sw_frame->data[1]), size/2, 1, fin)) <= 0) + break; + + if (!(hw_frame = av_frame_alloc())) { + err = AVERROR(ENOMEM); + goto close; + } + if ((err = av_hwframe_get_buffer(avctx->hw_frames_ctx, hw_frame, 0)) < 0) { + fprintf(stderr, "Error code: %s.\n", av_err2str(err)); + goto close; + } + if (!hw_frame->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto close; + } + if ((err = av_hwframe_transfer_data(hw_frame, sw_frame, 0)) < 0) { + fprintf(stderr, "Error while transferring frame data to surface." + "Error code: %s.\n", av_err2str(err)); + goto close; + } + + if ((err = (encode_write(avctx, hw_frame, fout))) < 0) { + fprintf(stderr, "Failed to encode.\n"); + goto close; + } + av_frame_free(&hw_frame); + av_frame_free(&sw_frame); + } + + /* flush encoder */ + err = encode_write(avctx, NULL, fout); + if (err == AVERROR_EOF) + err = 0; + +close: + if (fin) + fclose(fin); + if (fout) + fclose(fout); + av_frame_free(&sw_frame); + av_frame_free(&hw_frame); + avcodec_free_context(&avctx); + av_buffer_unref(&hw_device_ctx); + + return err; +} diff -Nru shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/vaapi_transcode.c shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/vaapi_transcode.c --- shotcut-18.03.06/Shotcut.app/share/ffmpeg/examples/vaapi_transcode.c 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/ffmpeg/examples/vaapi_transcode.c 2018-06-02 08:53:02.000000000 +0000 @@ -0,0 +1,304 @@ +/* + * Video Acceleration API (video transcoding) transcode sample + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Intel VAAPI-accelerated transcoding example. + * + * @example vaapi_transcode.c + * This example shows how to do VAAPI-accelerated transcoding. + * Usage: vaapi_transcode input_stream codec output_stream + * e.g: - vaapi_transcode input.mp4 h264_vaapi output_h264.mp4 + * - vaapi_transcode input.mp4 vp9_vaapi output_vp9.ivf + */ + +#include +#include + +#include +#include +#include + +static AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL; +static AVBufferRef *hw_device_ctx = NULL; +static AVCodecContext *decoder_ctx = NULL, *encoder_ctx = NULL; +static int video_stream = -1; +static AVStream *ost; +static int initialized = 0; + +static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, + const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + if (*p == AV_PIX_FMT_VAAPI) + return *p; + } + + fprintf(stderr, "Unable to decode this file using VA-API.\n"); + return AV_PIX_FMT_NONE; +} + +static int open_input_file(const char *filename) +{ + int ret; + AVCodec *decoder = NULL; + AVStream *video = NULL; + + if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) { + fprintf(stderr, "Cannot open input file '%s', Error code: %s\n", + filename, av_err2str(ret)); + return ret; + } + + if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { + fprintf(stderr, "Cannot find input stream information. Error code: %s\n", + av_err2str(ret)); + return ret; + } + + ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); + if (ret < 0) { + fprintf(stderr, "Cannot find a video stream in the input file. " + "Error code: %s\n", av_err2str(ret)); + return ret; + } + video_stream = ret; + + if (!(decoder_ctx = avcodec_alloc_context3(decoder))) + return AVERROR(ENOMEM); + + video = ifmt_ctx->streams[video_stream]; + if ((ret = avcodec_parameters_to_context(decoder_ctx, video->codecpar)) < 0) { + fprintf(stderr, "avcodec_parameters_to_context error. Error code: %s\n", + av_err2str(ret)); + return ret; + } + + decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); + if (!decoder_ctx->hw_device_ctx) { + fprintf(stderr, "A hardware device reference create failed.\n"); + return AVERROR(ENOMEM); + } + decoder_ctx->get_format = get_vaapi_format; + + if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) + fprintf(stderr, "Failed to open codec for decoding. Error code: %s\n", + av_err2str(ret)); + + return ret; +} + +static int encode_write(AVFrame *frame) +{ + int ret = 0; + AVPacket enc_pkt; + + av_init_packet(&enc_pkt); + enc_pkt.data = NULL; + enc_pkt.size = 0; + + if ((ret = avcodec_send_frame(encoder_ctx, frame)) < 0) { + fprintf(stderr, "Error during encoding. Error code: %s\n", av_err2str(ret)); + goto end; + } + while (1) { + ret = avcodec_receive_packet(encoder_ctx, &enc_pkt); + if (ret) + break; + + enc_pkt.stream_index = 0; + av_packet_rescale_ts(&enc_pkt, ifmt_ctx->streams[video_stream]->time_base, + ofmt_ctx->streams[0]->time_base); + ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt); + if (ret < 0) { + fprintf(stderr, "Error during writing data to output file. " + "Error code: %s\n", av_err2str(ret)); + return -1; + } + } + +end: + if (ret == AVERROR_EOF) + return 0; + ret = ((ret == AVERROR(EAGAIN)) ? 0:-1); + return ret; +} + +static int dec_enc(AVPacket *pkt, AVCodec *enc_codec) +{ + AVFrame *frame; + int ret = 0; + + ret = avcodec_send_packet(decoder_ctx, pkt); + if (ret < 0) { + fprintf(stderr, "Error during decoding. Error code: %s\n", av_err2str(ret)); + return ret; + } + + while (ret >= 0) { + if (!(frame = av_frame_alloc())) + return AVERROR(ENOMEM); + + ret = avcodec_receive_frame(decoder_ctx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + av_frame_free(&frame); + return 0; + } else if (ret < 0) { + fprintf(stderr, "Error while decoding. Error code: %s\n", av_err2str(ret)); + goto fail; + } + + if (!initialized) { + /* we need to ref hw_frames_ctx of decoder to initialize encoder's codec. + Only after we get a decoded frame, can we obtain its hw_frames_ctx */ + encoder_ctx->hw_frames_ctx = av_buffer_ref(decoder_ctx->hw_frames_ctx); + if (!encoder_ctx->hw_frames_ctx) { + ret = AVERROR(ENOMEM); + goto fail; + } + /* set AVCodecContext Parameters for encoder, here we keep them stay + * the same as decoder. + * xxx: now the the sample can't handle resolution change case. + */ + encoder_ctx->time_base = av_inv_q(decoder_ctx->framerate); + encoder_ctx->pix_fmt = AV_PIX_FMT_VAAPI; + encoder_ctx->width = decoder_ctx->width; + encoder_ctx->height = decoder_ctx->height; + + if ((ret = avcodec_open2(encoder_ctx, enc_codec, NULL)) < 0) { + fprintf(stderr, "Failed to open encode codec. Error code: %s\n", + av_err2str(ret)); + goto fail; + } + + if (!(ost = avformat_new_stream(ofmt_ctx, enc_codec))) { + fprintf(stderr, "Failed to allocate stream for output format.\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + + ost->time_base = encoder_ctx->time_base; + ret = avcodec_parameters_from_context(ost->codecpar, encoder_ctx); + if (ret < 0) { + fprintf(stderr, "Failed to copy the stream parameters. " + "Error code: %s\n", av_err2str(ret)); + goto fail; + } + + /* write the stream header */ + if ((ret = avformat_write_header(ofmt_ctx, NULL)) < 0) { + fprintf(stderr, "Error while writing stream header. " + "Error code: %s\n", av_err2str(ret)); + goto fail; + } + + initialized = 1; + } + + if ((ret = encode_write(frame)) < 0) + fprintf(stderr, "Error during encoding and writing.\n"); + +fail: + av_frame_free(&frame); + if (ret < 0) + return ret; + } + return 0; +} + +int main(int argc, char **argv) +{ + int ret = 0; + AVPacket dec_pkt; + AVCodec *enc_codec; + + if (argc != 4) { + fprintf(stderr, "Usage: %s \n" + "The output format is guessed according to the file extension.\n" + "\n", argv[0]); + return -1; + } + + ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, NULL, NULL, 0); + if (ret < 0) { + fprintf(stderr, "Failed to create a VAAPI device. Error code: %s\n", av_err2str(ret)); + return -1; + } + + if ((ret = open_input_file(argv[1])) < 0) + goto end; + + if (!(enc_codec = avcodec_find_encoder_by_name(argv[2]))) { + fprintf(stderr, "Could not find encoder '%s'\n", argv[2]); + ret = -1; + goto end; + } + + if ((ret = (avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, argv[3]))) < 0) { + fprintf(stderr, "Failed to deduce output format from file extension. Error code: " + "%s\n", av_err2str(ret)); + goto end; + } + + if (!(encoder_ctx = avcodec_alloc_context3(enc_codec))) { + ret = AVERROR(ENOMEM); + goto end; + } + + ret = avio_open(&ofmt_ctx->pb, argv[3], AVIO_FLAG_WRITE); + if (ret < 0) { + fprintf(stderr, "Cannot open output file. " + "Error code: %s\n", av_err2str(ret)); + goto end; + } + + /* read all packets and only transcoding video */ + while (ret >= 0) { + if ((ret = av_read_frame(ifmt_ctx, &dec_pkt)) < 0) + break; + + if (video_stream == dec_pkt.stream_index) + ret = dec_enc(&dec_pkt, enc_codec); + + av_packet_unref(&dec_pkt); + } + + /* flush decoder */ + dec_pkt.data = NULL; + dec_pkt.size = 0; + ret = dec_enc(&dec_pkt, enc_codec); + av_packet_unref(&dec_pkt); + + /* flush encoder */ + ret = encode_write(NULL); + + /* write the trailer for output stream */ + av_write_trailer(ofmt_ctx); + +end: + avformat_close_input(&ifmt_ctx); + avformat_close_input(&ofmt_ctx); + avcodec_free_context(&decoder_ctx); + avcodec_free_context(&encoder_ctx); + av_buffer_unref(&hw_device_ctx); + return ret; +} diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/core/loader.ini shotcut-18.06.02/Shotcut.app/share/mlt/core/loader.ini --- shotcut-18.03.06/Shotcut.app/share/mlt/core/loader.ini 2018-03-06 09:48:05.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/core/loader.ini 2018-06-02 08:56:09.000000000 +0000 @@ -14,7 +14,7 @@ resizer=movit.resize,resize # audio filters -channels=audiochannels +channels=swresample,audiochannels resampler=resample # metadata filters diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/kdenlive/filter_boxblur.yml shotcut-18.06.02/Shotcut.app/share/mlt/kdenlive/filter_boxblur.yml --- shotcut-18.03.06/Shotcut.app/share/mlt/kdenlive/filter_boxblur.yml 2018-03-06 09:48:05.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/kdenlive/filter_boxblur.yml 2018-06-02 08:56:09.000000000 +0000 @@ -2,7 +2,7 @@ type: filter identifier: boxblur title: Box Blur -version: 2 +version: 3 copyright: Leny Grisel, Jean-Baptiste Mardelle creator: Leny Grisel, Jean-Baptiste Mardelle license: LGPLv2.1 diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/plus/filter_affine.yml shotcut-18.06.02/Shotcut.app/share/mlt/plus/filter_affine.yml --- shotcut-18.03.06/Shotcut.app/share/mlt/plus/filter_affine.yml 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/plus/filter_affine.yml 2018-06-02 08:56:10.000000000 +0000 @@ -2,7 +2,7 @@ type: filter identifier: affine title: Transform -version: 1 +version: 2 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/plus/transition_affine.yml shotcut-18.06.02/Shotcut.app/share/mlt/plus/transition_affine.yml --- shotcut-18.03.06/Shotcut.app/share/mlt/plus/transition_affine.yml 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/plus/transition_affine.yml 2018-06-02 08:56:10.000000000 +0000 @@ -2,7 +2,7 @@ type: transition identifier: affine title: Transform -version: 1 +version: 2 copyright: Meltytech, LLC creator: Charles Yates contributor: @@ -15,6 +15,7 @@ - identifier: geometry title: Rectangle type: geometry + description: This property is deprecated. Use rect instead. - identifier: distort title: Ignore aspect ratio @@ -288,3 +289,17 @@ CPUs. Otherwise, set the number of threads to use up to the slice count. minimum: 0 default: 0 + + - identifier: rect + title: Rectangle + description: > + This replaces the geometry property and specifies a specifies a rectangle + for the size and position of the image. The format of this is + "X/Y:WxH[:opacity]" and can be animated with key frames. + Unlike the geometry property, if you use percentages you must use them for + every field in the above format. For example, you cannot mix and match + usage of absolute coordinates and percentages for size and opacity. + type: rect + default: "0%/0%:100%x100%:100%" + readonly: no + mutable: yes diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/presets/consumer/avformat/GIF shotcut-18.06.02/Shotcut.app/share/mlt/presets/consumer/avformat/GIF --- shotcut-18.03.06/Shotcut.app/share/mlt/presets/consumer/avformat/GIF 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/presets/consumer/avformat/GIF 2018-06-02 08:56:10.000000000 +0000 @@ -7,4 +7,3 @@ meta.preset.extension=gif meta.preset.name=GIF Animation -meta.preset.hidden=1 diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/presets/consumer/avformat/YouTube shotcut-18.06.02/Shotcut.app/share/mlt/presets/consumer/avformat/YouTube --- shotcut-18.03.06/Shotcut.app/share/mlt/presets/consumer/avformat/YouTube 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/presets/consumer/avformat/YouTube 2018-06-02 08:56:10.000000000 +0000 @@ -1,17 +1,17 @@ -f=mp4 -movflags=+faststart - -acodec=aac -ar=48000 -ab=384k - -vcodec=libx264 -progressive=1 -preset=fast -threads=0 -crf=21 -g=15 -bf=2 - -meta.preset.extension=mp4 -meta.preset.note=H.264/AAC MP4 for YouTube +f=mp4 +movflags=+faststart + +acodec=aac +ar=48000 +ab=384k + +vcodec=libx264 +progressive=1 +preset=fast +threads=0 +crf=21 +g=15 +bf=2 + +meta.preset.extension=mp4 +meta.preset.note=H.264/AAC MP4 for YouTube diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/vid.stab/filter_deshake.yml shotcut-18.06.02/Shotcut.app/share/mlt/vid.stab/filter_deshake.yml --- shotcut-18.03.06/Shotcut.app/share/mlt/vid.stab/filter_deshake.yml 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/vid.stab/filter_deshake.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -schema_version: 0.1 -type: filter -identifier: vid.stab.deshake -title: Vid.Stab Deshake -copyright: Jakub Ksiezniak -creator: Georg Martius -version: 1 -license: GPLv2 -language: en -url: http://public.hronopik.de/vid.stab/ -tags: - - Video -description: Stabilize Video (for wiggly/rolling video) -notes: > - Deshakes a video clip by extracting relative transformations - of subsequent frames and transforms the high-frequency away. - This is a single pass version of the vidstab filter. - -parameters: - - identifier: shakiness - title: Shakiness - type: integer - description: How shaky the video is. - readonly: no - required: no - minimum: 1 - maximum: 10 - default: 4 - mutable: yes - widget: spinner - - - identifier: accuracy - title: Accuracy - type: integer - description: The accuracy of shakiness detection. - readonly: no - required: no - minimum: 1 - maximum: 15 - default: 4 - mutable: yes - widget: spinner - - - identifier: stepsize - title: Stepsize - type: integer - description: The step size of the search process. - readonly: no - required: no - minimum: 0 - maximum: 100 - default: 6 - mutable: yes - widget: spinner - - - identifier: mincontrast - title: Minimum Contrast - type: float - description: Below this contrast, a field is discarded. - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0.3 - mutable: yes - widget: spinner - - - identifier: smoothing - title: Smoothing - type: integer - description: Number of frames for lowpass filtering (2N + 1 frames) - readonly: no - required: no - minimum: 0 - maximum: 100 - default: 15 - mutable: yes - widget: spinner - - - identifier: maxshift - title: Maxshift - type: integer - description: Maximum number of pixels to transform the image. -1 = no limit - unit: pixels - readonly: no - required: no - minimum: -1 - maximum: 1000 - default: -1 - mutable: yes - widget: spinner - - - identifier: maxangle - title: Maxangle - type: float - description: Maximum angle to rotate, -1 = no limit - unit: radians - readonly: no - required: no - minimum: -1 - maximum: 3.142 - default: -1 - mutable: yes - widget: spinner - - - identifier: crop - title: Crop - type: integer - description: 0 = keep border, 1 = black background - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0 - mutable: yes - widget: spinner - - - identifier: zoom - title: Zoom - type: integer - description: Additional zoom amount - unit: percent - readonly: no - required: no - minimum: -500 - maximum: 500 - default: 0 - mutable: yes - widget: spinner - - - identifier: optzoom - title: Optimal Zoom - type: integer - description: Automatically determine optimal zoom. 1 - static zoom, 2 - adaptive zoom - readonly: no - required: no - minimum: 0 - maximum: 2 - default: 1 - mutable: yes - widget: spinner - - - identifier: zoomspeed - title: Optimal Zoom Speed - type: float - description: Zoom per frame (used when optzoom = 2) - unit: percent - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0.25 - mutable: yes - widget: spinner diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/vid.stab/filter_vidstab.yml shotcut-18.06.02/Shotcut.app/share/mlt/vid.stab/filter_vidstab.yml --- shotcut-18.03.06/Shotcut.app/share/mlt/vid.stab/filter_vidstab.yml 2018-03-06 09:48:06.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/vid.stab/filter_vidstab.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,271 +0,0 @@ -schema_version: 0.1 -type: filter -identifier: vidstab -title: Vid.Stab Detect and Transform -copyright: Jakub Ksiezniak -creator: Marco Gittler -version: 1 -license: GPL -language: en -url: http://public.hronopik.de/vid.stab/ -tags: - - Video -description: Stabilize Video (for wiggly/rolling video) -notes: > - This filter requires two passes. The first pass performs analysis and stores - the result in a file. Upon successful completion of the analysis, the - "results" property is updated with the name of the file storing the results. - The second pass applies the results to the image. - - To use with melt, use 'melt ... -consumer xml:output.mlt all=1 real_time=-1' for the - first pass. Parallel processing (real_time < -1 or > 1) is not supported for - the first pass. For the second pass, use output.mlt as the input. - -parameters: - - identifier: results - title: Analysis Results - type: string - description: > - Set after analysis. Used during application. - A set of image motion information that describes the analyzed clip. - When results are not supplied, the filter computes the results and stores - them in a file. This property is updated with the name of that file when - the last frame has been processed. - mutable: no - - - identifier: filename - title: Target File Name - type: string - description: > - Used during analysis. - The name of the file to store the analysis results in. - required: no - readonly: no - default: vidstab.trf - widget: fileopen - - - identifier: shakiness - title: Shakiness - type: integer - description: > - Used during analysis. - How shaky the video is. - readonly: no - required: no - minimum: 1 - maximum: 10 - default: 4 - mutable: no - widget: spinner - - - identifier: accuracy - title: Accuracy - type: integer - description: > - Used during analysis. - The accuracy of shakiness detection. - readonly: no - required: no - minimum: 1 - maximum: 15 - default: 4 - mutable: no - widget: spinner - - - identifier: stepsize - title: Stepsize - type: integer - description: > - Used during analysis. - The step size of the search process. - readonly: no - required: no - minimum: 0 - maximum: 100 - default: 6 - mutable: no - widget: spinner - - - identifier: mincontrast - title: Minimum Contrast - type: float - description: > - Used during analysis. - Below this contrast, a field is discarded. - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0.3 - mutable: no - widget: spinner - - - identifier: show - title: Show - type: integer - description: > - Used during analysis. - 0 = draw nothing - 1 or 2 = show fields and transforms - readonly: no - required: no - minimum: 0 - maximum: 2 - default: 0 - mutable: no - widget: spinner - - - identifier: tripod - title: Tripod - type: integer - description: > - Used during analysis and application. - if 0, tripod mode is disabled. - if > 0, specifies the frame to be used as a reference frame for tripod mode - During application, relative and smoothing properties are both ignored if tripod mode is in use. - readonly: no - required: no - minimum: 0 - maximum: 100000 - default: 0 - mutable: no - widget: spinner - - - identifier: smoothing - title: Smoothing - type: integer - description: > - Used during application. - Number of frames for lowpass filtering (2N + 1 frames) - readonly: no - required: no - minimum: 0 - maximum: 100 - default: 15 - mutable: yes - widget: spinner - - - identifier: maxshift - title: Maxshift - type: integer - description: > - Used during application. - Maximum number of pixels to transform the image. -1 = no limit - unit: pixels - readonly: no - required: no - minimum: -1 - maximum: 1000 - default: -1 - mutable: yes - widget: spinner - - - identifier: maxangle - title: Maxangle - type: float - description: > - Used during application. - Maximum angle to rotate, -1 = no limit - unit: radians - readonly: no - required: no - minimum: -1 - maximum: 3.142 - default: -1 - mutable: yes - widget: spinner - - - identifier: crop - title: Crop - type: integer - description: > - Used during application. - 0 = keep border, 1 = black background - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0 - mutable: yes - widget: spinner - - - identifier: invert - title: Invert - type: integer - description: > - Used during application. - Invert transforms - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0 - mutable: yes - widget: spinner - - - identifier: relative - title: Relative - type: integer - description: > - Used during application. - Consider transforms as absolute (0) or relative (1) - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 1 - mutable: yes - widget: spinner - - - identifier: zoom - title: Zoom - type: integer - description: > - Used during application. - Additional zoom amount - unit: percent - readonly: no - required: no - minimum: -500 - maximum: 500 - default: 0 - mutable: yes - widget: spinner - - - identifier: optzoom - title: Optimal Zoom - type: integer - description: > - Used during application. - Automatically determine optimal zoom. 1 - static zoom, 2 - adaptive zoom - readonly: no - required: no - minimum: 0 - maximum: 2 - default: 1 - mutable: yes - widget: spinner - - - identifier: zoomspeed - title: Optimal Zoom Speed - type: float - description: > - Used during application. - Zoom per frame (used when optzoom = 2) - unit: percent - readonly: no - required: no - minimum: 0 - maximum: 1 - default: 0.25 - mutable: yes - widget: spinner - - - identifier: reload - title: Reload Results - description: > - The application should set this to 1 when it updates the results property to indicate that the results should be reloaded. - type: integer - minimum: 0 - maximum: 1 - mutable: yes diff -Nru shotcut-18.03.06/Shotcut.app/share/mlt/xml/mlt-xml.dtd shotcut-18.06.02/Shotcut.app/share/mlt/xml/mlt-xml.dtd --- shotcut-18.03.06/Shotcut.app/share/mlt/xml/mlt-xml.dtd 2018-03-06 09:48:05.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/mlt/xml/mlt-xml.dtd 2018-06-02 08:56:10.000000000 +0000 @@ -9,10 +9,12 @@ root CDATA #IMPLIED profile CDATA #IMPLIED title CDATA #IMPLIED + producer IDREF #IMPLIED > @@ -53,11 +56,12 @@ a_track CDATA #IMPLIED b_track CDATA #IMPLIED > - + - + + * Copyright (c) 2013-2018 Meltytech, 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 @@ -26,12 +25,25 @@ property string fromParameter: 'from' property string toParameter: 'to' Component.onCompleted: { + if (application.audioChannels() === 1) { + fromCombo.enabled = false + toCombo.enabled = false + } else if (application.audioChannels() === 6) { + fromCombo.model = [qsTr('Front left'), + qsTr('Front right'), + qsTr('Center'), + qsTr('Low frequency'), + qsTr('Left surround'), + qsTr('Right surround')] + } if (filter.isNew) { // Set default parameter values - combo.currentIndex = 0 + fromCombo.currentIndex = 0 + toCombo.currentIndex = (application.audioChannels() === 1) ? 0 : 1 } else { // Initialize parameter values - combo.currentIndex = filter.get(fromParameter) * 1 + fromCombo.currentIndex = filter.get(fromParameter) + toCombo.currentIndex = filter.get(toParameter) } } @@ -42,12 +54,15 @@ RowLayout { Label { text: qsTr('Copy from') } ComboBox { - id: combo - model: [qsTr('Left to right'), qsTr('Right to left')] - onCurrentIndexChanged: { - filter.set(fromParameter, currentIndex) - filter.set(toParameter, 1 - currentIndex) - } + id: fromCombo + model: [qsTr('Left'), qsTr('Right')] + onCurrentIndexChanged: filter.set(fromParameter, currentIndex) + } + Label { text: qsTr('to') } + ComboBox { + id: toCombo + model: fromCombo.model + onCurrentIndexChanged: filter.set(toParameter, currentIndex) } } Item { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadein/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadein/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadein/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadein/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ qml: "ui.qml" isFavorite: true allowMultiple: false + keyframes { + allowTrim: false + allowAnimateIn: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadein/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadein/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadein/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadein/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -30,13 +30,21 @@ Component.onCompleted: { if (filter.isNew) { duration = Math.ceil(settings.audioInDuration * profile.fps) - filter.set('gain', 0) - filter.set('end', 1) - filter.set('in', filter.producerIn) - filter.set('out', filter.getDouble('in') + duration - 1) + } else if (filter.animateIn === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateIn } } + Connections { + target: filter + onAnimateInChanged: duration = filter.animateIn + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -47,8 +55,10 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 - onValueChanged: filter.set('out', filter.getDouble('in') + value - 1) + onValueChanged: { + filter.animateIn = duration + filter.set('level', '0=-60; %1=0'.arg(duration - 1)) + } onSetDefaultClicked: { duration = Math.ceil(settings.audioInDuration * profile.fps) } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ qml: "ui.qml" isFavorite: true allowMultiple: false + keyframes { + allowTrim: false + allowAnimateOut: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_fadeout/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -30,13 +30,26 @@ Component.onCompleted: { if (filter.isNew) { duration = Math.ceil(settings.audioOutDuration * profile.fps) - filter.set('gain', 1) - filter.set('end', 0) - filter.set('out', filter.producerOut) - filter.set('in', filter.getDouble('out') - duration + 1) + } else if (filter.animateOut === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateOut } } + Connections { + target: filter + onAnimateOutChanged: duration = filter.animateOut + } + + function updateFilter() { + var filterDuration = producer.duration + filter.set('level', '%1=0; %2=-60'.arg(filterDuration - duration).arg(filterDuration - 1)) + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -47,9 +60,9 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 onValueChanged: { - filter.set('in', filter.getDouble('out') - duration + 1) + filter.animateOut = duration + updateFilter() } onSetDefaultClicked: { duration = Math.ceil(settings.audioOutDuration * profile.fps) diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_gain/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_gain/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_gain/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_gain/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,19 @@ mlt_service: "volume" qml: "ui.qml" isFavorite: true + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['level'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'level' + isSimple: true + isCurve: true + minimum: -50 + maximum: 24 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_gain/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_gain/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_gain/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_gain/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -17,19 +16,57 @@ */ import QtQuick 2.1 -import QtQuick.Controls 1.0 +import QtQuick.Controls 1.1 import QtQuick.Layouts 1.0 import Shotcut.Controls 1.0 Item { - width: 300 + width: 200 height: 50 - property string gainParameter: 'gain' + property bool blockUpdate: true + property double startValue: 0.0 + property double middleValue: 0.0 + property double endValue: 0.0 + Component.onCompleted: { if (filter.isNew) { // Set default parameter values - filter.set(gainParameter, 1.0) - slider.value = toDb(filter.getDouble(gainParameter)) + filter.set('level', 0) + } else { + // Convert old version of filter. + if (filter.getDouble('gain') !== 0.0) { + filter.set('level', toDb(filter.getDouble('gain'))) + filter.resetProperty('gain') + } + + middleValue = filter.getDouble('level', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('level', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('level', filter.duration - 1) + } + setControls() + } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + gainSlider.value = filter.getDouble('level', getPosition()) + blockUpdate = false + gainSlider.enabled = true + } } } @@ -37,31 +74,87 @@ return 20 * Math.log(value) / Math.LN10 } - function fromDb(value) { - return Math.pow(10, value / 20); + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + gainSlider.value = filter.getDouble('level', position) + blockUpdate = false + gainSlider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = gainSlider.value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = gainSlider.value + else + middleValue = gainSlider.value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('level') + gainKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('level', startValue, 0) + filter.set('level', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('level', middleValue, filter.duration - filter.animateOut) + filter.set('level', endValue, filter.duration - 1) + } + } else if (!gainKeyframesButton.checked) { + filter.resetProperty('level') + filter.set('level', middleValue) + } else if (position !== null) { + filter.set('level', gainSlider.value, position) + } } - ColumnLayout { + GridLayout { + columns: 4 anchors.fill: parent anchors.margins: 8 - RowLayout { - Label { text: qsTr('Gain') } - SliderSpinner { - id: slider - minimumValue: -50 - maximumValue: 24 - suffix: ' dB' - decimals: 1 - value: toDb(filter.getDouble(gainParameter)) - onValueChanged: filter.set(gainParameter, fromDb(value)) - } - UndoButton { - onClicked: slider.value = 0 + Label { + text: qsTr('Level') + Layout.alignment: Qt.AlignRight + } + SliderSpinner { + id: gainSlider + minimumValue: -50 + maximumValue: 24 + suffix: ' dB' + decimals: 1 + onValueChanged: updateFilter(getPosition()) + } + UndoButton { + onClicked: gainSlider.value = 0.0 + } + KeyframesButton { + id: gainKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('level') > 0 + onToggled: { + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('level') + blockUpdate = false + filter.set('level', gainSlider.value, getPosition()) + } else { + filter.resetProperty('level') + filter.set('level', gainSlider.value) + } } } + Item { - Layout.fillHeight: true; + Layout.fillHeight: true } } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_swapchannels/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_swapchannels/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/audio_swapchannels/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/audio_swapchannels/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -21,9 +20,54 @@ import QtQuick.Layouts 1.0 Item { + width: 100 + height: 50 + property string fromParameter: 'from' + property string toParameter: 'to' Component.onCompleted: { + if (application.audioChannels() === 1) { + fromCombo.enabled = false + toCombo.enabled = false + } else if (application.audioChannels() === 6) { + fromCombo.model = [qsTr('Front left'), + qsTr('Front right'), + qsTr('Center'), + qsTr('Low frequency'), + qsTr('Left surround'), + qsTr('Right surround')] + } filter.set('swap', 1) - filter.set('from', 0) - filter.set('top', 1) + if (filter.isNew) { + // Set default parameter values + fromCombo.currentIndex = 0 + toCombo.currentIndex = (application.audioChannels() === 1) ? 0 : 1 + } else { + // Initialize parameter values + fromCombo.currentIndex = filter.get(fromParameter) + toCombo.currentIndex = filter.get(toParameter) + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 + + RowLayout { + Label { text: qsTr('Swap') } + ComboBox { + id: fromCombo + model: [qsTr('Left'), qsTr('Right')] + onCurrentIndexChanged: filter.set(fromParameter, currentIndex) + } + Label { text: qsTr('with') } + ComboBox { + id: toCombo + model: fromCombo.model + onCurrentIndexChanged: filter.set(toParameter, currentIndex) + } + } + Item { + Layout.fillHeight: true; + } } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/meta_boxblur.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/meta_boxblur.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/meta_boxblur.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/meta_boxblur.qml 2018-06-02 07:58:01.000000000 +0000 @@ -7,4 +7,28 @@ mlt_service: "boxblur" qml: "ui_boxblur.qml" gpuAlt: "movit.blur" + keyframes { + minimumVersion: '3' + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['hori', 'vert'] + parameters: [ + Parameter { + name: qsTr('Width') + property: 'hori' + isSimple: true + isCurve: true + minimum: 1 + maximum: 99 + }, + Parameter { + name: qsTr('Height') + property: 'vert' + isSimple: true + isCurve: true + minimum: 1 + maximum: 99 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -7,4 +7,19 @@ mlt_service: "movit.blur" needsGPU: true qml: "ui_movit.qml" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['radius'] + parameters: [ + Parameter { + name: qsTr('Radius') + property: 'radius' + isSimple: true + isCurve: true + minimum: 0 + maximum: 99.99 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/ui_boxblur.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/ui_boxblur.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/ui_boxblur.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/ui_boxblur.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Brian Matherly + * Copyright (c) 2013-2018 Meltytech, 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 @@ -24,19 +23,114 @@ Item { width: 200 height: 50 + property bool blockUpdate: true + property int startWidthValue: 1 + property int middleWidthValue: 2 + property int endWidthValue: 1 + property int startHeightValue: 1 + property int middleHeightValue: 2 + property int endHeightValue: 1 + Component.onCompleted: { filter.set('start', 1) if (filter.isNew) { // Set default parameter values filter.set('hori', 2) - wslider.value = 2; filter.set('vert', 2) - hslider.value = 2 + } else { + middleWidthValue = filter.getDouble('hori', filter.animateIn) + middleHeightValue = filter.getDouble('vert', filter.animateIn) + if (filter.animateIn > 0) { + startWidthValue = filter.getDouble('hori', 0) + startHeightValue = filter.getDouble('vert', 0) + } + if (filter.animateOut > 0) { + endWidthValue = filter.getDouble('hori', filter.duration - 1) + endHeightValue = filter.getDouble('vert', filter.duration - 1) + } + } + setControls() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + wslider.value = filter.getDouble('hori', position) + hslider.value = filter.getDouble('vert', position) + blockUpdate = false + wslider.enabled = hslider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilterWidth(position) { + if (blockUpdate) return + var value = wslider.value + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startWidthValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endWidthValue = value + else + middleWidthValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('hori') + widthKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('hori', startWidthValue, 0) + filter.set('hori', middleWidthValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('hori', middleWidthValue, filter.duration - filter.animateOut) + filter.set('hori', endWidthValue, filter.duration - 1) + } + } else if (!widthKeyframesButton.checked) { + filter.resetProperty('hori') + filter.set('hori', middleWidthValue) + } else if (position !== null) { + filter.set('hori', value, position) + } + } + + function updateFilterHeight(position) { + if (blockUpdate) return + var value = hslider.value + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startHeightValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endHeightValue = value + else + middleHeightValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('vert') + heightKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('vert', startHeightValue, 0) + filter.set('vert', middleHeightValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('vert', middleHeightValue, filter.duration - filter.animateOut) + filter.set('vert', endHeightValue, filter.duration - 1) + } + } else if (!heightKeyframesButton.checked) { + filter.resetProperty('vert') + filter.set('vert', middleHeightValue) + } else if (position !== null) { + filter.set('vert', value, position) } } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 @@ -49,12 +143,31 @@ minimumValue: 1 maximumValue: 99 suffix: ' px' - value: filter.getDouble('hori') - onValueChanged: filter.set('hori', value) + onValueChanged: updateFilterWidth(getPosition()) } UndoButton { onClicked: wslider.value = 2 } + KeyframesButton { + id: widthKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('hori') > 0 + onToggled: { + if (checked) { + blockUpdate = true + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('vert') + filter.set('vert', middleHeightValue) + hslider.enabled = true + } + filter.clearSimpleAnimation('hori') + blockUpdate = false + filter.set('hori', wslider.value, getPosition()) + } else { + filter.resetProperty('hori') + filter.set('hori', wslider.value) + } + } + } Label { text: qsTr('Height') @@ -65,15 +178,58 @@ minimumValue: 1 maximumValue: 99 suffix: ' px' - value: filter.getDouble('vert') - onValueChanged: filter.set('vert', value) + onValueChanged: updateFilterHeight(getPosition()) } UndoButton { onClicked: hslider.value = 2 } - + KeyframesButton { + id: heightKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('vert') > 0 + onToggled: { + if (checked) { + blockUpdate = true + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('hori') + filter.set('hori', middleWidthValue) + wslider.enabled = true + } + filter.clearSimpleAnimation('vert') + blockUpdate = false + filter.set('vert', hslider.value, getPosition()) + } else { + filter.resetProperty('vert') + filter.set('vert', hslider.value) + } + } + } + Item { Layout.fillHeight: true } } + + Connections { + target: filter + onInChanged: { updateFilterWidth(null); updateFilterHeight(null) } + onOutChanged: { updateFilterWidth(null); updateFilterHeight(null) } + onAnimateInChanged: { updateFilterWidth(null); updateFilterHeight(null) } + onAnimateOutChanged: { updateFilterWidth(null); updateFilterHeight(null) } + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + wslider.value = filter.getDouble('hori', getPosition()) + hslider.value = filter.getDouble('vert', getPosition()) + blockUpdate = false + wslider.enabled = true + hslider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/ui_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/ui_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/blur/ui_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/blur/ui_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Brian Matherly + * Copyright (c) 2013-2018 Meltytech, 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 @@ -25,6 +24,69 @@ width: 200 height: 50 + property bool blockUpdate: true + property double startValue: 0.0 + property double middleValue: 3.0 + property double endValue: 0.0 + + Component.onCompleted: { + if (filter.isNew) { + // Set default parameter values + filter.set('radius', 3.0) + } else { + middleValue = filter.getDouble('radius', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('radius', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('radius', filter.duration - 1) + } + setControls() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + slider.value = filter.getDouble('radius', position) + blockUpdate = false + slider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = slider.value + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('radius') + keyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('radius', startValue, 0) + filter.set('radius', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('radius', middleValue, filter.duration - filter.animateOut) + filter.set('radius', endValue, filter.duration - 1) + } + } else if (!keyframesButton.checked) { + filter.resetProperty('radius') + filter.set('radius', middleValue) + } else if (position !== null) { + filter.set('radius', value, position) + } + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -35,17 +97,53 @@ id: slider minimumValue: 0 maximumValue: 99.99 - value: filter.getDouble('radius') decimals: 2 - onValueChanged: filter.set('radius', value) + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: slider.value = 3.0 } + KeyframesButton { + id: keyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('radius') > 0 + onToggled: { + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('radius') + blockUpdate = false + filter.set('radius', slider.value, getPosition()) + } else { + filter.resetProperty('radius') + filter.set('radius', slider.value) + } + } + } } - + Item { - Layout.fillHeight: true; + Layout.fillHeight: true + } + } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + slider.value = filter.getDouble('radius', getPosition()) + blockUpdate = false + slider.enabled = true + } } } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,13 +1,27 @@ -import QtQuick 2.0 -import org.shotcut.qml 1.0 - -Metadata { - type: Metadata.Filter - name: qsTr("Brightness") - objectName: "movitBrightness" - mlt_service: "movit.opacity" - needsGPU: true - qml: "ui_movit.qml" - isFavorite: true - allowMultiple: false -} +import QtQuick 2.0 +import org.shotcut.qml 1.0 + +Metadata { + type: Metadata.Filter + name: qsTr("Brightness") + objectName: "movitBrightness" + mlt_service: "movit.opacity" + needsGPU: true + qml: "ui_movit.qml" + isFavorite: true + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['opacity'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'opacity' + isSimple: true + isCurve: true + minimum: 0 + maximum: 2 + } + ] + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,5 +8,19 @@ qml: "ui.qml" isFavorite: true gpuAlt: "movit.opacity" - allowMultiple: false + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['level'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'level' + isSimple: true + isCurve: true + minimum: 0 + maximum: 2 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/ui_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/ui_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/ui_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/ui_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Meltytech, LLC + * Copyright (c) 2016-2018 Meltytech, 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 @@ -23,23 +23,100 @@ Item { width: 200 height: 50 + property bool blockUpdate: true + property double startValue: 1.0 + property double middleValue: 1.0 + property double endValue: 1.0 + Component.onCompleted: { if (filter.isNew) { // Set default parameter values filter.set('alpha', 1.0) filter.set('opacity', 1.0); + } else { + middleValue = filter.getDouble('opacity', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('opacity', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('opacity', filter.duration - 1) + } + setControls() + } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + brightnessSlider.value = filter.getDouble('opacity', getPosition()) * 100.0 + blockUpdate = false + brightnessSlider.enabled = true + } + } + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + brightnessSlider.value = filter.getDouble('opacity', position) * 100.0 + blockUpdate = false + brightnessSlider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = brightnessSlider.value / 100.0 + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('opacity') + brightnessKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('opacity', startValue, 0) + filter.set('opacity', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('opacity', middleValue, filter.duration - filter.animateOut) + filter.set('opacity', endValue, filter.duration - 1) + } + } else if (!brightnessKeyframesButton.checked) { + filter.resetProperty('opacity') + filter.set('opacity', middleValue) + } else if (position !== null) { + filter.set('opacity', value, position) } - brightnessSlider.value = filter.getDouble('opacity') * 100.0 } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 Label { - text: qsTr('Brightness') + text: qsTr('Level') Layout.alignment: Qt.AlignRight } SliderSpinner { @@ -48,11 +125,28 @@ maximumValue: 200.0 decimals: 1 suffix: ' %' - onValueChanged: filter.set("opacity", value / 100.0) + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: brightnessSlider.value = 100 } + KeyframesButton { + id: brightnessKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('opacity') > 0 + onToggled: { + var value = brightnessSlider.value / 100.0 + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('opacity') + blockUpdate = false + filter.set('opacity', value, getPosition()) + } else { + filter.resetProperty('opacity') + filter.set('opacity', value) + } + } + } + Item { Layout.fillHeight: true diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/brightness/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/brightness/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Meltytech, LLC + * Copyright (c) 2016-2018 Meltytech, 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 @@ -23,22 +23,99 @@ Item { width: 200 height: 50 - + property bool blockUpdate: true + property double startValue: 1.0 + property double middleValue: 1.0 + property double endValue: 1.0 + Component.onCompleted: { if (filter.isNew) { // Set default parameter values - filter.set("level", 1.0); + filter.set('level', 1.0) + } else { + middleValue = filter.getDouble('level', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('level', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('level', filter.duration - 1) } - brightnessSlider.value = filter.getDouble("level") * 100.0 + setControls() + } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + brightnessSlider.value = filter.getDouble('level', getPosition()) * 100.0 + blockUpdate = false + brightnessSlider.enabled = true + } + } + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + brightnessSlider.value = filter.getDouble('level', position) * 100.0 + blockUpdate = false + brightnessSlider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = brightnessSlider.value / 100.0 + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('level') + brightnessKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('level', startValue, 0) + filter.set('level', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('level', middleValue, filter.duration - filter.animateOut) + filter.set('level', endValue, filter.duration - 1) + } + } else if (!brightnessKeyframesButton.checked) { + filter.resetProperty('level') + filter.set('level', middleValue) + } else if (position !== null) { + filter.set('level', value, position) + } +// console.log('level: ' + filter.get('level')) } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 Label { - text: qsTr('Brightness') + text: qsTr('Level') Layout.alignment: Qt.AlignRight } SliderSpinner { @@ -47,11 +124,27 @@ maximumValue: 200.0 decimals: 1 suffix: ' %' - onValueChanged: filter.set("level", value / 100.0) + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: brightnessSlider.value = 100 } + KeyframesButton { + id: brightnessKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('level') > 0 + onToggled: { + var value = brightnessSlider.value / 100.0 + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('level') + blockUpdate = false + filter.set('level', value, getPosition()) + } else { + filter.resetProperty('level') + filter.set('level', value) + } + } + } Item { Layout.fillHeight: true diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,20 @@ needsGPU: true qml: "ui.qml" isFavorite: true + keyframes { + parameters: [ + Parameter { + name: qsTr('Shadows (Lift)') + property: 'lift_r' + }, + Parameter { + name: qsTr('Midtones (Gamma)') + property: 'gamma_r' + }, + Parameter { + name: qsTr('Highlights (Gain)') + property: 'gain_r' + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,20 @@ qml: "ui.qml" isFavorite: true gpuAlt: "movit.lift_gamma_gain" + keyframes { + parameters: [ + Parameter { + name: qsTr('Shadows (Lift)') + property: 'lift_r' + }, + Parameter { + name: qsTr('Midtones (Gamma)') + property: 'gamma_r' + }, + Parameter { + name: qsTr('Highlights (Gain)') + property: 'gain_r' + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/color/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/color/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -23,23 +23,12 @@ Item { property var defaultParameters: ['lift_r', 'lift_g', 'lift_b', 'gamma_r', 'gamma_g', 'gamma_b', 'gain_r', 'gain_g', 'gain_b'] - property var gammaFactor: 2.0 - property var gainFactor: 4.0 + property double gammaFactor: 2.0 + property double gainFactor: 4.0 + property bool blockUpdate: true width: 455 height: 280 - function loadValues() { - liftRedSpinner.value = filter.getDouble("lift_r") * 100.0 - liftGreenSpinner.value = filter.getDouble("lift_g") * 100.0 - liftBlueSpinner.value = filter.getDouble("lift_b") * 100.0 - gammaRedSpinner.value = filter.getDouble("gamma_r") * 100.0 / gammaFactor - gammaGreenSpinner.value = filter.getDouble("gamma_g") * 100.0 / gammaFactor - gammaBlueSpinner.value = filter.getDouble("gamma_b") * 100.0 / gammaFactor - gainRedSpinner.value = filter.getDouble("gain_r") * 100.0 / gainFactor - gainGreenSpinner.value = filter.getDouble("gain_g") * 100.0 / gainFactor - gainBlueSpinner.value = filter.getDouble("gain_b") * 100.0 / gainFactor - } - Component.onCompleted: { if (filter.isNew) { // Set default parameter values @@ -57,8 +46,27 @@ loadValues() } + function loadValues() { + var position = getPosition() + blockUpdate = true + liftRedSpinner.value = filter.getDouble("lift_r", position) * 100.0 + liftGreenSpinner.value = filter.getDouble("lift_g", position) * 100.0 + liftBlueSpinner.value = filter.getDouble("lift_b", position) * 100.0 + gammaRedSpinner.value = filter.getDouble("gamma_r", position) * 100.0 / gammaFactor + gammaGreenSpinner.value = filter.getDouble("gamma_g", position) * 100.0 / gammaFactor + gammaBlueSpinner.value = filter.getDouble("gamma_b", position) * 100.0 / gammaFactor + gainRedSpinner.value = filter.getDouble("gain_r", position) * 100.0 / gainFactor + gainGreenSpinner.value = filter.getDouble("gain_g", position) * 100.0 / gainFactor + gainBlueSpinner.value = filter.getDouble("gain_b", position) * 100.0 / gainFactor + blockUpdate = false + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + GridLayout { - columns: 6 + columns: 9 anchors.fill: parent anchors.margins: 8 @@ -68,8 +76,19 @@ Layout.alignment: Qt.AlignRight } Preset { - Layout.columnSpan: 5 + Layout.columnSpan: 8 parameters: defaultParameters + onBeforePresetLoaded: { + filter.resetProperty('lift_r') + filter.resetProperty('lift_g') + filter.resetProperty('lift_b') + filter.resetProperty('gamma_r') + filter.resetProperty('gamma_g') + filter.resetProperty('gamma_b') + filter.resetProperty('gain_r') + filter.resetProperty('gain_g') + filter.resetProperty('gain_b') + } onPresetSelected: { loadValues() } @@ -87,6 +106,25 @@ liftBlueSpinner.value = 0.0 } } + KeyframesButton { + id: liftKeyframesButton + checked: filter.keyframeCount('lift_r') > 0 + Layout.alignment: Qt.AlignLeft + onToggled: { + if (checked) { + filter.set('lift_r', liftwheel.redF, getPosition()) + filter.set('lift_g', liftwheel.greenF, getPosition()) + filter.set('lift_b', liftwheel.blueF, getPosition()) + } else { + filter.resetProperty('lift_r') + filter.resetProperty('lift_g') + filter.resetProperty('lift_b') + filter.set('lift_r', liftwheel.redF) + filter.set('lift_g', liftwheel.greenF) + filter.set('lift_b', liftwheel.blueF) + } + } + } Label { text: qsTr('Midtones (Gamma)') } UndoButton { Layout.alignment: Qt.AlignRight @@ -98,6 +136,25 @@ gammaBlueSpinner.value = 100.0 / gammaFactor } } + KeyframesButton { + id: gammaKeyframesButton + checked: filter.keyframeCount('gamma_r') > 0 + Layout.alignment: Qt.AlignLeft + onToggled: { + if (checked) { + filter.set('gamma_r', gammawheel.redF * gammaFactor, getPosition()) + filter.set('gamma_g', gammawheel.greenF * gammaFactor, getPosition()) + filter.set('gamma_b', gammawheel.blueF * gammaFactor, getPosition()) + } else { + filter.resetProperty('gamma_r') + filter.resetProperty('gamma_g') + filter.resetProperty('gamma_b') + filter.set('gamma_r', gammawheel.redF * gammaFactor) + filter.set('gamma_g', gammawheel.greenF * gammaFactor) + filter.set('gamma_b', gammawheel.blueF * gammaFactor) + } + } + } Label { text: qsTr('Highlights (Gain)') } UndoButton { Layout.alignment: Qt.AlignRight @@ -109,11 +166,30 @@ gainBlueSpinner.value = 100.0 / gainFactor } } - + KeyframesButton { + id: gainKeyframesButton + checked: filter.keyframeCount('gain_r') > 0 + Layout.alignment: Qt.AlignLeft + onToggled: { + if (checked) { + filter.set('gain_r', gainwheel.redF * gainFactor, getPosition()) + filter.set('gain_g', gainwheel.greenF * gainFactor, getPosition()) + filter.set('gain_b', gainwheel.blueF * gainFactor, getPosition()) + } else { + filter.resetProperty('gain_r') + filter.resetProperty('gain_g') + filter.resetProperty('gain_b') + filter.set('gain_r', gainwheel.redF * gainFactor) + filter.set('gain_g', gainwheel.greenF * gainFactor) + filter.set('gain_b', gainwheel.blueF * gainFactor) + } + } + } + // Row 3 ColorWheelItem { id: liftwheel - Layout.columnSpan: 2 + Layout.columnSpan: 3 implicitWidth: parent.width / 3 - parent.columnSpacing implicitHeight: implicitWidth / 1.1 Layout.alignment : Qt.AlignCenter | Qt.AlignTop @@ -129,11 +205,26 @@ if( liftBlueSpinner.value != liftwheel.blueF * 100.0 ) { liftBlueSpinner.value = liftwheel.blueF * 100.0 } + if (!blockUpdate) { + if (!liftKeyframesButton.checked) { + filter.resetProperty('lift_r') + filter.resetProperty('lift_g') + filter.resetProperty('lift_b') + filter.set('lift_r', liftwheel.redF) + filter.set('lift_g', liftwheel.greenF) + filter.set('lift_b', liftwheel.blueF) + } else { + var position = getPosition() + filter.set('lift_r', liftwheel.redF, position) + filter.set('lift_g', liftwheel.greenF, position) + filter.set('lift_b', liftwheel.blueF, position) + } + } } } ColorWheelItem { id: gammawheel - Layout.columnSpan: 2 + Layout.columnSpan: 3 implicitWidth: parent.width / 3 - parent.columnSpacing implicitHeight: implicitWidth / 1.1 Layout.alignment : Qt.AlignCenter | Qt.AlignTop @@ -149,11 +240,26 @@ if( gammaBlueSpinner.value != gammawheel.blueF * 100.0 ) { gammaBlueSpinner.value = gammawheel.blueF * 100.0 } + if (!blockUpdate) { + if (!gammaKeyframesButton.checked) { + filter.resetProperty('gamma_r') + filter.resetProperty('gamma_g') + filter.resetProperty('gamma_b') + filter.set('gamma_r', gammawheel.redF * gammaFactor) + filter.set('gamma_g', gammawheel.greenF * gammaFactor) + filter.set('gamma_b', gammawheel.blueF * gammaFactor) + } else { + var position = getPosition() + filter.set('gamma_r', gammawheel.redF * gammaFactor, position) + filter.set('gamma_g', gammawheel.greenF * gammaFactor, position) + filter.set('gamma_b', gammawheel.blueF * gammaFactor, position) + } + } } } ColorWheelItem { id: gainwheel - Layout.columnSpan: 2 + Layout.columnSpan: 3 implicitWidth: parent.width / 3 - parent.columnSpacing implicitHeight: implicitWidth / 1.1 Layout.alignment : Qt.AlignCenter | Qt.AlignTop @@ -169,12 +275,27 @@ if( gainBlueSpinner.value != gainwheel.blueF * 100.0 ) { gainBlueSpinner.value = gainwheel.blueF * 100.0 } + if (!blockUpdate) { + if (!gainKeyframesButton.checked) { + filter.resetProperty('gain_r') + filter.resetProperty('gain_g') + filter.resetProperty('gain_b') + filter.set('gain_r', gainwheel.redF * gainFactor) + filter.set('gain_g', gainwheel.greenF * gainFactor) + filter.set('gain_b', gainwheel.blueF * gainFactor) + } else { + var position = getPosition() + filter.set('gain_r', gainwheel.redF * gainFactor, position) + filter.set('gain_g', gainwheel.greenF * gainFactor, position) + filter.set('gain_b', gainwheel.blueF * gainFactor, position) + } + } } } // Row 4 RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'R' } SpinBox { @@ -189,12 +310,11 @@ if( liftwheel.redF != value / 100.0 ) { liftwheel.redF = value / 100.0 } - filter.set("lift_r", value / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'R' } SpinBox { @@ -209,12 +329,11 @@ if( gammawheel.redF != value / 100.0 ) { gammawheel.redF = value / 100.0 } - filter.set("gamma_r", value * gammaFactor / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'R' } SpinBox { @@ -229,14 +348,13 @@ if( gainwheel.redF != value / 100.0 ) { gainwheel.redF = value / 100.0 } - filter.set("gain_r", value * gainFactor / 100.0); } } } // Row 5 RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'G' } SpinBox { @@ -251,12 +369,11 @@ if( liftwheel.greenF != value / 100.0 ) { liftwheel.greenF = value / 100.0 } - filter.set("lift_g", value / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'G' } SpinBox { @@ -271,12 +388,11 @@ if( gammawheel.greenF != value / 100.0 ) { gammawheel.greenF = value / 100.0 } - filter.set("gamma_g", value * gammaFactor / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'G' } SpinBox { @@ -291,14 +407,13 @@ if( gainwheel.greenF != value / 100.0 ) { gainwheel.greenF = value / 100.0 } - filter.set("gain_g", value * gainFactor / 100.0); } } } // Row 6 RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'B' } SpinBox { @@ -313,12 +428,11 @@ if( liftwheel.blueF != value / 100.0 ) { liftwheel.blueF = value / 100.0 } - filter.set("lift_b", value / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'B' } SpinBox { @@ -333,12 +447,11 @@ if( gammawheel.blueF != value / 100.0 ) { gammawheel.blueF = value / 100.0 } - filter.set("gamma_b", value * gammaFactor / 100.0); } } } RowLayout { - Layout.columnSpan: 2 + Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Label { text: 'B' } SpinBox { @@ -353,11 +466,15 @@ if( gainwheel.blueF != value / 100.0 ) { gainwheel.blueF = value / 100.0 } - filter.set("gain_b", value * gainFactor / 100.0); } } } Item { Layout.fillHeight: true } } + + Connections { + target: producer + onPositionChanged: loadValues() + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/dynamictext/vui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/dynamictext/vui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/dynamictext/vui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/dynamictext/vui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -38,7 +38,7 @@ contentY: video.offset.y function getAspectRatio() { - return (filter.get(fillProperty) === '1')? filter.producerAspect : 0.0 + return (filter.get(fillProperty) === '1')? producer.sampleAspectRatio : 0.0 } function setSizeFromRect() { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ isFavorite: true gpuAlt: "movit.opacity" allowMultiple: false + keyframes { + allowTrim: false + allowAnimateIn: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_brightness/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -29,15 +29,29 @@ Component.onCompleted: { if (filter.isNew) { - duration = Math.ceil(settings.videoInDuration * profile.fps) - filter.set('level', '0=0; %1=1'.arg(duration - 1)) filter.set('alpha', 1) - filter.set('in', filter.producerIn) - filter.set('out', filter.getDouble('in') + duration - 1) + duration = Math.ceil(settings.videoInDuration * profile.fps) + } else if (filter.animateIn === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateIn } alphaCheckbox.checked = filter.get('alpha') != 1 } + Connections { + target: filter + onAnimateInChanged: duration = filter.animateIn + } + + function updateFilter() { + var name = (filter.get('alpha') != 1)? 'alpha' : 'level' + filter.set(name, '0=0; %1=1'.arg(duration - 1)) + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -48,14 +62,9 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 onValueChanged: { - var out = filter.getDouble('in') + value - 1 - filter.set('out', filter.getDouble('in') + value - 1) - if (filter.get('alpha') != 1) - filter.set('alpha', '0=0; %1=1'.arg(duration - 1)) - else - filter.set('level', '0=0; %1=1'.arg(duration - 1)) + filter.animateIn = duration + updateFilter() } onSetDefaultClicked: { duration = Math.ceil(settings.videoInDuration * profile.fps) @@ -70,12 +79,12 @@ text: qsTr('Adjust opacity instead of fade with black') onClicked: { if (checked) { + filter.set('alpha', 0) filter.set('level', 1) - filter.set('alpha', '0=0; %1=1'.arg(duration - 1)) } else { - filter.set('level', '0=0; %1=1'.arg(duration - 1)) filter.set('alpha', 1) } + updateFilter() } } Item { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_movit/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_movit/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_movit/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_movit/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ qml: "ui.qml" isFavorite: true allowMultiple: false + keyframes { + allowTrim: false + allowAnimateIn: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_movit/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_movit/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadein_movit/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadein_movit/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -29,15 +29,24 @@ Component.onCompleted: { if (filter.isNew) { - duration = Math.ceil(settings.videoInDuration * profile.fps) - filter.set('opacity', '0~=0; %1=1'.arg(duration - 1)) filter.set('alpha', 1) - filter.set('in', filter.producerIn) - filter.set('out', filter.getDouble('in') + duration - 1) + duration = Math.ceil(settings.videoInDuration * profile.fps) + } else if (filter.animateIn === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateIn } alphaCheckbox.checked = filter.get('alpha') != 1 } + Connections { + target: filter + onAnimateInChanged: duration = filter.animateIn + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -48,8 +57,10 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 - onValueChanged: filter.set('out', filter.getDouble('in') + value - 1) + onValueChanged: { + filter.animateIn = duration + filter.set('opacity', '0~=0; %1=1'.arg(duration - 1)) + } onSetDefaultClicked: { duration = Math.ceil(settings.videoInDuration * profile.fps) } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ isFavorite: true gpuAlt: "movit.opacity" allowMultiple: false + keyframes { + allowTrim: false + allowAnimateOut: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_brightness/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -29,17 +29,30 @@ Component.onCompleted: { if (filter.isNew) { - duration = Math.ceil(settings.videoOutDuration * profile.fps) - var out = filter.producerOut - var inFrame = out - duration + 1 - filter.set('level', '0=1; %1=0'.arg(duration - 1)) filter.set('alpha', 1) - filter.set('in', inFrame) - filter.set('out', out) + duration = Math.ceil(settings.videoOutDuration * profile.fps) + } else if (filter.animateOut === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateOut } alphaCheckbox.checked = filter.get('alpha') != 1 } + Connections { + target: filter + onAnimateOutChanged: duration = filter.animateOut + } + + function updateFilter() { + var name = (filter.get('alpha') != 1)? 'alpha' : 'level' + var filterDuration = producer.duration + filter.set(name, '%1=1; %2=0'.arg(filterDuration - duration).arg(filterDuration - 1)) + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -50,14 +63,9 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 onValueChanged: { - var inFrame = filter.getDouble('out') - duration + 1 - filter.set('in', inFrame) - if (filter.get('alpha') != 1) - filter.set('alpha', '0=1; %1=0'.arg(duration - 1)) - else - filter.set('level', '0=1; %1=0'.arg(duration - 1)) + filter.animateOut = duration + updateFilter() } onSetDefaultClicked: { duration = Math.ceil(settings.videoOutDuration * profile.fps) @@ -72,12 +80,12 @@ text: qsTr('Adjust opacity instead of fade with black') onClicked: { if (checked) { + filter.set('alpha', 0) filter.set('level', 1) - filter.set('alpha', '0=1; %1=0'.arg(duration - 1)) } else { - filter.set('level', '0=1; %1=0'.arg(duration - 1)) filter.set('alpha', 1) } + updateFilter() } } Item { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,8 @@ qml: "ui.qml" isFavorite: true allowMultiple: false + keyframes { + allowTrim: false + allowAnimateOut: true + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/fadeout_movit/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -29,17 +29,29 @@ Component.onCompleted: { if (filter.isNew) { - duration = Math.ceil(settings.videoOutDuration * profile.fps) - var out = filter.producerOut - var inFrame = out - duration + 1 - filter.set('opacity', '0~=1; %1=0'.arg(duration - 1)) filter.set('alpha', 1) - filter.set('in', inFrame) - filter.set('out', out) + duration = Math.ceil(settings.videoOutDuration * profile.fps) + } else if (filter.animateOut === 0) { + // Convert legacy filter. + duration = filter.duration + filter.in = producer.in + filter.out = producer.out + } else { + duration = filter.animateOut } alphaCheckbox.checked = filter.get('alpha') != 1 } + Connections { + target: filter + onAnimateOutChanged: duration = filter.animateOut + } + + function updateFilter() { + var filterDuration = producer.duration + filter.set('opacity', '%1~=1; %2=0'.arg(filterDuration - duration).arg(filterDuration - 1)) + } + ColumnLayout { anchors.fill: parent anchors.margins: 8 @@ -50,8 +62,10 @@ id: timeSpinner minimumValue: 2 maximumValue: 5000 - value: filter.getDouble('out') - filter.getDouble('in') + 1 - onValueChanged: filter.set('in', filter.getDouble('out') - duration + 1) + onValueChanged: { + filter.animateOut = duration + updateFilter() + } onSetDefaultClicked: { duration = Math.ceil(settings.videoOutDuration * profile.fps) } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/meta.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Meltytech, 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 . + */ + +import QtQuick 2.0 +import org.shotcut.qml 1.0 + +Metadata { + type: Metadata.Filter + name: qsTr("Hue/Lightness/Saturation") + mlt_service: 'avfilter.hue' + qml: 'ui.qml' +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/ui.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/hue_lightness_saturation/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Meltytech, 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import Shotcut.Controls 1.0 + +Item { + property double hueDegreeDefault: 100 + property double lightnessDefault: 100 + property double saturationDefault: 100 + + property var defaultParameters: ["av.h", "av.b", "av.s"] + + width: 200 + height: 125 + Component.onCompleted: { + if (filter.isNew) { + filter.set("av.h", hueDegreeDefault) + filter.set("av.b", lightnessDefault) + filter.set("av.s", saturationDefault) + filter.savePreset(defaultParameters) + } + setControls() + } + + function setControls() { + hueDegreeSlider.value = filter.getDouble("av.h") + lightnessSlider.value = filter.getDouble("av.b") + saturationSlider.value = filter.getDouble("av.s") + } + + GridLayout { + columns: 3 + anchors.fill: parent + anchors.margins: 8 + + Label { + text: qsTr('Preset') + Layout.alignment: Qt.AlignRight + } + Preset { + id: presetItem + Layout.columnSpan: 2 + onPresetSelected: setControls() + } + + Label { + text: qsTr('Hue') + Layout.alignment: Qt.AlignRight + } + SliderSpinner { + id: hueDegreeSlider + minimumValue: 0 + maximumValue: 200 + suffix: ' %' + onValueChanged: filter.set("av.h", (value - 100) * 360 / 100) + } + UndoButton { + onClicked: hueDegreeSlider.value = hueDegreeDefault + } + + Label { + text: qsTr('Lightness') + Layout.alignment: Qt.AlignRight + } + SliderSpinner { + id: lightnessSlider + minimumValue: 0 + maximumValue: 200 + suffix: ' %' + onValueChanged: filter.set("av.b", (value - 100) * 10 / 100) + } + UndoButton { + onClicked: lightnessSlider.value = lightnessDefault + } + + Label { + text: qsTr('Saturation') + Layout.alignment: Qt.AlignRight + } + SliderSpinner { + id: saturationSlider + minimumValue: 0 + maximumValue: 500 + suffix: ' %' + onValueChanged: filter.set("av.s", value / 100.0) + } + UndoButton { + onClicked: saturationSlider.value = saturationDefault + } + + Item { Layout.fillHeight: true } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/lut3d/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/lut3d/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/lut3d/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/lut3d/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Meltytech, LLC + * Copyright (c) 2016-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ id: lut3dRoot width: 350 height: 100 - property string settingsOpenPath: settings.openPath + property url settingsOpenPath: 'file:///' + settings.openPath SystemPalette { id: activePalette; colorGroup: SystemPalette.Active } Shotcut.File { id: lutFile } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/mask/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/mask/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/mask/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/mask/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -6,4 +6,43 @@ name: qsTr("Mask") mlt_service: "frei0r.alphaspot" qml: "ui.qml" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['1', '2', '3', '4'] + parameters: [ + Parameter { + name: qsTr('Horizontal') + property: '1' + isSimple: true + isCurve: true + minimum: -1 + maximum: 1 + }, + Parameter { + name: qsTr('Vertical') + property: '2' + isSimple: true + isCurve: true + minimum: -1 + maximum: 1 + }, + Parameter { + name: qsTr('Width') + property: '3' + isSimple: true + isCurve: true + minimum: 0 + maximum: 1 + }, + Parameter { + name: qsTr('Height') + property: '4' + isSimple: true + isCurve: true + minimum: 0 + maximum: 1 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/mask/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/mask/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/mask/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/mask/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* * Copyright (c) 2017-2018 Meltytech, LLC - * Author: Dan Dennedy * * 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 @@ -30,7 +29,12 @@ property string paramRotation: '5' property string paramSoftness: '6' property string paramOperation: '9' - property var defaultParameters: [paramHorizontal, paramShape, paramWidth, paramVertical, paramRotation, paramSoftness, paramOperation] + property var defaultParameters: [paramHorizontal, paramShape, paramWidth, paramHeight, paramVertical, paramRotation, paramSoftness, paramOperation] + property bool blockUpdate: true + property var startValues: [0.5, 0.5, 0, 0] + property var middleValues: [0.5, 0.5, 0.1, 0.1] + property var endValues: [0.5, 0.5, 0, 0] + width: 350 height: 250 @@ -43,26 +47,102 @@ filter.set(paramVertical, 0.5) filter.set(paramWidth, 0.1) filter.set(paramHeight, 0.1) - filter.set(paramRotation, 0) + filter.set(paramRotation, 0.5) filter.set(paramSoftness, 0.2) filter.savePreset(defaultParameters) + } else { + initSimpleAnimation() } setControls() } + function initSimpleAnimation() { + middleValues = [filter.getDouble(paramHorizontal, filter.animateIn), + filter.getDouble(paramVertical, filter.animateIn), + filter.getDouble(paramWidth, filter.animateIn), + filter.getDouble(paramHeight, filter.animateIn)] + if (filter.animateIn > 0) { + startValues = [filter.getDouble(paramHorizontal, 0), + filter.getDouble(paramVertical, 0), + filter.getDouble(paramWidth, 0), + filter.getDouble(paramHeight, 0)] + } + if (filter.animateOut > 0) { + endValues = [filter.getDouble(paramHorizontal, filter.duration - 1), + filter.getDouble(paramVertical, filter.duration - 1), + filter.getDouble(paramWidth, filter.duration - 1), + filter.getDouble(paramHeight, filter.duration - 1)] + } + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + function setControls() { + var position = getPosition() + blockUpdate = true + horizontalSlider.value = filter.getDouble(paramHorizontal, position) * 100 + verticalSlider.value = filter.getDouble(paramVertical, position) * 100 + widthSlider.value = filter.getDouble(paramWidth, position) * 100 + heightSlider.value = filter.getDouble(paramHeight, position) * 100 + blockUpdate = false + horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled + = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) operationCombo.currentIndex = Math.round(filter.getDouble(paramOperation) * 4) shapeCombo.currentIndex = Math.round(filter.getDouble(paramShape) * 3) - horizontalSlider.value = filter.getDouble(paramHorizontal) * 100 - verticalSlider.value = filter.getDouble(paramVertical) * 100 - widthSlider.value = filter.getDouble(paramWidth) * 100 - heightSlider.value = filter.getDouble(paramHeight) * 100 rotationSlider.value = filter.getDouble(paramRotation) * 100 softnessSlider.value = filter.getDouble(paramSoftness) * 100 } + function updateFilter(parameter, value, position, button) { + if (blockUpdate) return + var index = parseInt(parameter - 1) + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValues[index] = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValues[index] = value + else + middleValues[index] = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty(parameter) + button.checked = false + if (filter.animateIn > 0) { + filter.set(parameter, startValues[index], 0) + filter.set(parameter, middleValues[index], filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set(parameter, middleValues[index], filter.duration - filter.animateOut) + filter.set(parameter, endValues[index], filter.duration - 1) + } + } else if (!button.checked) { + filter.resetProperty(parameter) + filter.set(parameter, middleValues[index]) + } else if (position !== null) { + filter.set(parameter, value, position) + } + } + + function onKeyframesButtonClicked(checked, parameter, value) { + if (checked) { + blockUpdate = true + horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled = true + horizontalKeyframesButton.checked = verticalKeyframesButton.checked = widthKeyframesButton.checked = heightKeyframesButton.checked = true + filter.clearSimpleAnimation(parameter) + blockUpdate = false + filter.set(parameter, value, getPosition()) + } else { + filter.resetProperty(parameter) + filter.set(parameter, value) + } + } + GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 @@ -71,9 +151,18 @@ Layout.alignment: Qt.AlignRight } Preset { - Layout.columnSpan: 2 + Layout.columnSpan: 3 parameters: defaultParameters - onPresetSelected: setControls() + onBeforePresetLoaded: { + filter.resetProperty(paramHorizontal) + filter.resetProperty(paramVertical) + filter.resetProperty(paramWidth) + filter.resetProperty(paramHeight) + } + onPresetSelected: { + setControls() + initSimpleAnimation() + } } Label { @@ -87,6 +176,7 @@ onCurrentIndexChanged: filter.set(paramOperation, currentIndex / 4) } UndoButton { + Layout.columnSpan: 2 onClicked: operationCombo.currentIndex = 0 } @@ -101,6 +191,7 @@ onCurrentIndexChanged: filter.set(paramShape, currentIndex / 3) } UndoButton { + Layout.columnSpan: 2 onClicked: shapeCombo.currentIndex = 0 } @@ -114,11 +205,16 @@ maximumValue: 100 decimals: 2 suffix: ' %' - onValueChanged: filter.set(paramHorizontal, value / 100) + onValueChanged: updateFilter(paramHorizontal, value/100, getPosition(), horizontalKeyframesButton) } UndoButton { onClicked: horizontalSlider.value = 50 } + KeyframesButton { + id: horizontalKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramHorizontal) > 0 + onToggled: onKeyframesButtonClicked(checked, paramHorizontal, horizontalSlider.value / 100) + } Label { text: qsTr('Vertical') @@ -130,11 +226,16 @@ maximumValue: 100 decimals: 2 suffix: ' %' - onValueChanged: filter.set(paramVertical, value / 100) + onValueChanged: updateFilter(paramVertical, value/100, getPosition(), verticalKeyframesButton) } UndoButton { onClicked: verticalSlider.value = 50 } + KeyframesButton { + id: verticalKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramVertical) > 0 + onToggled: onKeyframesButtonClicked(checked, paramVertical, verticalSlider.value / 100) + } Label { text: qsTr('Width') @@ -146,11 +247,16 @@ maximumValue: 100 decimals: 2 suffix: ' %' - onValueChanged: filter.set(paramWidth, value / 100) + onValueChanged: updateFilter(paramWidth, value/100, getPosition(), widthKeyframesButton) } UndoButton { onClicked: widthSlider.value = 10 } + KeyframesButton { + id: widthKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramWidth) > 0 + onToggled: onKeyframesButtonClicked(checked, paramWidth, widthSlider.value / 100) + } Label { text: qsTr('Height') @@ -162,11 +268,16 @@ maximumValue: 100 decimals: 2 suffix: ' %' - onValueChanged: filter.set(paramHeight, value / 100) + onValueChanged: updateFilter(paramHeight, value/100, getPosition(), heightKeyframesButton) } UndoButton { onClicked: heightSlider.value = 10 } + KeyframesButton { + id: heightKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramHeight) > 0 + onToggled: onKeyframesButtonClicked(checked, paramHeight, heightSlider.value / 100) + } Label { text: qsTr('Rotation') @@ -181,6 +292,7 @@ onValueChanged: filter.set(paramRotation, value / 100) } UndoButton { + Layout.columnSpan: 2 onClicked: rotationSlider.value = 0 } @@ -197,11 +309,45 @@ onValueChanged: filter.set(paramSoftness, value / 100) } UndoButton { + Layout.columnSpan: 2 onClicked: softnessSlider.value = 20 } Item { + Layout.columnSpan: 2 Layout.fillHeight: true; } } + + function updatedSimpleAnimation() { + updateFilter(paramHorizontal, horizontalSlider.value/100, getPosition(), horizontalKeyframesButton) + updateFilter(paramVertical, verticalSlider.value/100, getPosition(), verticalKeyframesButton) + updateFilter(paramWidth, widthSlider.value/100, getPosition(), widthKeyframesButton) + updateFilter(paramHeight, heightSlider.value/100, getPosition(), heightKeyframesButton) + } + + Connections { + target: filter + onInChanged: updatedSimpleAnimation() + onOutChanged: updatedSimpleAnimation() + onAnimateInChanged: updatedSimpleAnimation() + onAnimateOutChanged: updatedSimpleAnimation() + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + horizontalSlider.value = filter.getDouble(paramHorizontal, getPosition()) * 100 + verticalSlider.value = filter.getDouble(paramVertical, getPosition()) * 100 + widthSlider.value = filter.getDouble(paramWidth, getPosition()) * 100 + heightSlider.value = filter.getDouble(paramHeight, getPosition()) * 100 + blockUpdate = false + horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,19 @@ mlt_service: "movit.opacity" needsGPU: true qml: "ui.qml" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['opacity'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'opacity' + isSimple: true + isCurve: true + minimum: 0 + maximum: 1 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,19 @@ mlt_service: "brightness" qml: "ui.qml" gpuAlt: "movit.opacity" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['alpha'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'alpha' + isSimple: true + isCurve: true + minimum: 0 + maximum: 1 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/opacity/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/opacity/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -24,24 +24,87 @@ Item { width: 350 height: 50 + property bool blockUpdate: true + property double startValue: 1.0 + property double middleValue: 1.0 + property double endValue: 1.0 + Component.onCompleted: { filter.set('start', 1) if (filter.isNew) { // Set default parameter values filter.set('level', 1) - filter.set('alpha', 100.0 / 100.0) + filter.set('alpha', 1.0) filter.set('opacity', filter.getDouble('alpha')) - slider.value = filter.getDouble('alpha') * 100.0 + } else { + middleValue = filter.getDouble('opacity', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('opacity', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('opacity', filter.duration - 1) + } + setControls() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + slider.value = filter.getDouble('opacity', position) * 100.0 + blockUpdate = false + slider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = slider.value / 100.0 + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('alpha') + filter.resetProperty('opacity') + keyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('alpha', startValue, 0) + filter.set('alpha', middleValue, filter.animateIn - 1) + filter.set('opacity', startValue, 0) + filter.set('opacity', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('alpha', middleValue, filter.duration - filter.animateOut) + filter.set('alpha', endValue, filter.duration - 1) + filter.set('opacity', middleValue, filter.duration - filter.animateOut) + filter.set('opacity', endValue, filter.duration - 1) + } + } else if (!keyframesButton.checked) { + filter.resetProperty('alpha') + filter.resetProperty('opacity') + filter.set('alpha', middleValue) + filter.set('opacity', middleValue) + } else if (position !== null) { + filter.set('alpha', value, position) + filter.set('opacity', value, position) } } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 Label { - text: qsTr('Opacity') + text: qsTr('Level') Layout.alignment: Qt.AlignRight } SliderSpinner { @@ -49,18 +112,56 @@ minimumValue: 0 maximumValue: 100 suffix: ' %' - value: filter.getDouble('alpha') * 100.0 - onValueChanged: { - filter.set('alpha', value / 100.0) - filter.set('opacity', filter.getDouble('alpha')) - } + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: slider.value = 100 } + KeyframesButton { + id: keyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('opacity') > 0 + onToggled: { + var value = slider.value / 100.0 + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('alpha') + filter.clearSimpleAnimation('opacity') + blockUpdate = false + filter.set('alpha', value, getPosition()) + filter.set('opacity', value, getPosition()) + } else { + filter.resetProperty('alpha') + filter.resetProperty('opacity') + filter.set('alpha', value) + filter.set('opacity', value) + } + } + } Item { Layout.fillHeight: true } } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + slider.value = filter.getDouble('opacity', getPosition()) * 100.0 + blockUpdate = false + slider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/meta_frei0r.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/meta_frei0r.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/meta_frei0r.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/meta_frei0r.qml 2018-06-02 07:58:01.000000000 +0000 @@ -7,4 +7,19 @@ mlt_service: "frei0r.saturat0r" qml: "ui_frei0r.qml" gpuAlt: "movit.saturation" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['0'] + parameters: [ + Parameter { + name: qsTr('Level') + property: '0' + isSimple: true + isCurve: true + minimum: 0 + maximum: 0.375 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -7,4 +7,19 @@ mlt_service: "movit.saturation" needsGPU: true qml: "ui_movit.qml" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['saturation'] + parameters: [ + Parameter { + name: qsTr('Level') + property: 'saturation' + isSimple: true + isCurve: true + minimum: 0 + maximum: 3 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/ui_frei0r.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/ui_frei0r.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/ui_frei0r.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/ui_frei0r.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -26,17 +25,74 @@ height: 50 property string saturationParameter: '0' property real frei0rMaximum: 800 + property bool blockUpdate: true + property double startValue: 0.125 + property double middleValue: 0.125 + property double endValue: 0.125 + Component.onCompleted: { if (filter.isNew) { // Set default parameter values + filter.set(saturationParameter, 0) + filter.savePreset(preset.parameters, qsTr('Grayscale')) filter.set(saturationParameter, 100 / frei0rMaximum) - slider.value = filter.getDouble(saturationParameter) * frei0rMaximum filter.savePreset(preset.parameters) + } else { + middleValue = filter.getDouble(saturationParameter, filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble(saturationParameter, 0) + if (filter.animateOut > 0) + endValue = filter.getDouble(saturationParameter, filter.duration - 1) + } + setControls() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + slider.value = filter.getDouble(saturationParameter, position) * frei0rMaximum + blockUpdate = false + slider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = slider.value / frei0rMaximum + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty(saturationParameter) + brightnessKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set(saturationParameter, startValue, 0) + filter.set(saturationParameter, middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set(saturationParameter, middleValue, filter.duration - filter.animateOut) + filter.set(saturationParameter, endValue, filter.duration - 1) + } + } else if (!brightnessKeyframesButton.checked) { + filter.resetProperty(saturationParameter) + filter.set(saturationParameter, middleValue) + } else if (position !== null) { + filter.set(saturationParameter, value, position) } } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 @@ -46,15 +102,23 @@ } Preset { id: preset - Layout.columnSpan: 2 + Layout.columnSpan: 3 parameters: [saturationParameter] + onBeforePresetLoaded: { + filter.resetProperty(saturationParameter) + } onPresetSelected: { - slider.value = filter.getDouble(saturationParameter) * frei0rMaximum + setControls() + middleValue = filter.getDouble(saturationParameter, filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble(saturationParameter, 0) + if (filter.animateOut > 0) + endValue = filter.getDouble(saturationParameter, filter.duration - 1) } } Label { - text: qsTr('Saturation') + text: qsTr('Level') Layout.alignment: Qt.AlignRight } SliderSpinner { @@ -62,15 +126,52 @@ minimumValue: 0 maximumValue: 300 suffix: ' %' - value: filter.getDouble(saturationParameter) * frei0rMaximum - onValueChanged: filter.set(saturationParameter, value / frei0rMaximum) + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: slider.value = 100 } + KeyframesButton { + id: brightnessKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(saturationParameter) > 0 + onToggled: { + var value = slider.value / frei0rMaximum + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation(saturationParameter) + blockUpdate = false + filter.set(saturationParameter, value, getPosition()) + } else { + filter.resetProperty(saturationParameter) + filter.set(saturationParameter, value) + } + } + } Item { Layout.fillHeight: true } } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + slider.value = filter.getDouble(saturationParameter, getPosition()) * frei0rMaximum + blockUpdate = false + slider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/ui_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/ui_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/saturation/ui_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/saturation/ui_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -25,36 +24,149 @@ width: 350 height: 50 property string saturationParameter: 'saturation' + property bool blockUpdate: true + property double startValue: 1.0 + property double middleValue: 1.0 + property double endValue: 1.0 + Component.onCompleted: { if (filter.isNew) { // Set default parameter values + filter.set(saturationParameter, 0) + filter.savePreset(preset.parameters, qsTr('Grayscale')) filter.set(saturationParameter, 1.0) - slider.value = 100 + } else { + middleValue = filter.getDouble(saturationParameter, filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble(saturationParameter, 0) + if (filter.animateOut > 0) + endValue = filter.getDouble(saturationParameter, filter.duration - 1) + } + setControls() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + slider.value = filter.getDouble(saturationParameter, position) * 100 + blockUpdate = false + slider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = slider.value / 100 + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty(saturationParameter) + brightnessKeyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set(saturationParameter, startValue, 0) + filter.set(saturationParameter, middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set(saturationParameter, middleValue, filter.duration - filter.animateOut) + filter.set(saturationParameter, endValue, filter.duration - 1) + } + } else if (!brightnessKeyframesButton.checked) { + filter.resetProperty(saturationParameter) + filter.set(saturationParameter, middleValue) + } else if (position !== null) { + filter.set(saturationParameter, value, position) } } - ColumnLayout { + GridLayout { + columns: 4 anchors.fill: parent anchors.margins: 8 - RowLayout { - anchors.fill: parent - - Label { text: qsTr('Saturation') } - SliderSpinner { - id: slider - minimumValue: 0 - maximumValue: 300 - suffix: ' %' - value: filter.getDouble(saturationParameter) * 100 - onValueChanged: filter.set(saturationParameter, value / 100) + Label { + text: qsTr('Preset') + Layout.alignment: Qt.AlignRight + } + Preset { + id: preset + Layout.columnSpan: 3 + parameters: [saturationParameter] + onBeforePresetLoaded: { + filter.resetProperty(saturationParameter) } - UndoButton { - onClicked: slider.value = 100 + onPresetSelected: { + setControls() + middleValue = filter.getDouble(saturationParameter, filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble(saturationParameter, 0) + if (filter.animateOut > 0) + endValue = filter.getDouble(saturationParameter, filter.duration - 1) } } + + Label { text: qsTr('Level') } + SliderSpinner { + id: slider + minimumValue: 0 + maximumValue: 300 + suffix: ' %' + onValueChanged: updateFilter(getPosition()) + } + UndoButton { + onClicked: slider.value = 100 + } + KeyframesButton { + id: brightnessKeyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(saturationParameter) > 0 + onToggled: { + var value = slider.value / 100 + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation(saturationParameter) + blockUpdate = false + filter.set(saturationParameter, value, getPosition()) + } else { + filter.resetProperty(saturationParameter) + filter.set(saturationParameter, value) + } + } + } + Item { Layout.fillHeight: true; } } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + slider.value = filter.getDouble(saturationParameter, getPosition()) * 100 + blockUpdate = false + slider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/meta_affine.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/meta_affine.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/meta_affine.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/meta_affine.qml 2018-06-02 07:58:01.000000000 +0000 @@ -9,5 +9,9 @@ qml: 'ui_affine.qml' vui: 'vui_affine.qml' gpuAlt: 'movit.rect' - allowMultiple: false + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['transition.rect'] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/meta_movit.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/meta_movit.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/meta_movit.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/meta_movit.qml 2018-06-02 07:58:01.000000000 +0000 @@ -10,4 +10,10 @@ qml: 'ui_movit.qml' vui: 'vui_movit.qml' allowMultiple: false + keyframes { + allowTrim: false + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['rect'] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionUI.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionUI.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionUI.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionUI.qml 2018-06-02 07:58:01.000000000 +0000 @@ -24,19 +24,28 @@ Item { property string fillProperty property string distortProperty + property string legacyRectProperty: null property string rectProperty property string valignProperty property string halignProperty - property rect filterRect: filter.getRect(rectProperty) + property rect filterRect + property string startValue: '_shotcut:startValue' + property string middleValue: '_shotcut:middleValue' + property string endValue: '_shotcut:endValue' width: 350 height: 180 Component.onCompleted: { + filter.blockSignals = true + filter.set(middleValue, Qt.rect(0, 0, profile.width, profile.height)) + filter.set(startValue, Qt.rect(0, 0, profile.width, profile.height)) + filter.set(endValue, Qt.rect(0, 0, profile.width, profile.height)) if (filter.isNew) { filter.set(fillProperty, 0) filter.set(distortProperty, 0) - filter.set(rectProperty, '0/50%:50%x50%') + + filter.set(rectProperty, '0%/50%:50%x50%') filter.set(valignProperty, 'bottom') filter.set(halignProperty, 'left') filter.savePreset(preset.parameters, qsTr('Bottom Left')) @@ -46,42 +55,132 @@ filter.set(halignProperty, 'right') filter.savePreset(preset.parameters, qsTr('Bottom Right')) - filter.set(rectProperty, '0/0:50%x50%') + filter.set(rectProperty, '0%/0%:50%x50%') filter.set(valignProperty, 'top') filter.set(halignProperty, 'left') filter.savePreset(preset.parameters, qsTr('Top Left')) - filter.set(rectProperty, '50%/0:50%x50%') + filter.set(rectProperty, '50%/0%:50%x50%') filter.set(valignProperty, 'top') filter.set(halignProperty, 'right') filter.savePreset(preset.parameters, qsTr('Top Right')) - filter.set(rectProperty, '0/0:100%x100%') + // Add some animated presets. + filter.set(valignProperty, 'middle') + filter.set(halignProperty, 'center') + filter.animateIn = profile.fps + filter.set(rectProperty, '0=-100%/0%:100%x100%; :1.0=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slide In From Left')) + filter.set(rectProperty, '0=100%/0%:100%x100%; :1.0=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slide In From Right')) + filter.set(rectProperty, '0=0%/-100%:100%x100%; :1.0=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slide In From Top')) + filter.set(rectProperty, '0=0%/100%:100%x100%; :1.0=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slide In From Bottom')) + filter.animateIn = 0 + filter.animateOut = profile.fps + filter.set(rectProperty, ':-1.0=0%/0%:100%x100%; -1=-100%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animOut'), qsTr('Slide Out Left')) + filter.set(rectProperty, ':-1.0=0%/0%:100%x100%; -1=100%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animOut'), qsTr('Slide Out Right')) + filter.set(rectProperty, ':-1.0=0%/0%:100%x100%; -1=0%/-100%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animOut'), qsTr('Slide Out Top')) + filter.set(rectProperty, ':-1.0=0%/0%:100%x100%; -1=0%/100%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animOut'), qsTr('Slide Out Bottom')) + filter.set(fillProperty, 1) + filter.animateOut = 0 + filter.animateIn = filter.duration + filter.set(rectProperty, '0=0%/0%:100%x100%; -1=-5%/-5%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom In')) + filter.set(rectProperty, '0=-5%/-5%:110%x110%; -1=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom Out')) + filter.set(rectProperty, '0=-5%/-5%:110%x110%; -1=-10%/-5%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Pan Left')) + filter.set(rectProperty, '0=-5%/-5%:110%x110%; -1=0%/-5%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Pan Right')) + filter.set(rectProperty, '0=-5%/-5%:110%x110%; -1=-5%/-10%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Pan Up')) + filter.set(rectProperty, '0=-5%/-5%:110%x110%; -1=-5%/0%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Pan Down')) + filter.set(rectProperty, '0=0%/0%:100%x100%; -1=-10%/-10%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom In, Pan Up Left')) + filter.set(rectProperty, '0=0%/0%:100%x100%; -1=0%/0%:110%x110%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom In, Pan Down Right')) + filter.set(rectProperty, '0=-10%/0%:110%x110%; -1=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom Out, Pan Up Right')) + filter.set(rectProperty, '0=0%/-10%:110%x110%; -1=0%/0%:100%x100%') + filter.savePreset(preset.parameters.concat('shotcut:animIn'), qsTr('Slow Zoom Out, Pan Down Left')) + filter.animateIn = 0 + filter.resetProperty(rectProperty) + + // Add default preset. + filter.set(rectProperty, '0%/0%:100%x100%') filter.set(valignProperty, 'top') filter.set(halignProperty, 'left') filter.savePreset(preset.parameters) + } else { + if (legacyRectProperty !== null) { + var old = filter.get(legacyRectProperty) + if (old && old.length > 0) { + filter.resetProperty(legacyRectProperty) + filter.set(rectProperty, old) + } + } + filterRect = filter.getRect(rectProperty) + filter.set(middleValue, filter.getRect(rectProperty, filter.animateIn)) + if (filter.animateIn > 0) + filter.set(startValue, filter.getRect(rectProperty, 0)) + if (filter.animateOut > 0) + filter.set(endValue, filter.getRect(rectProperty, filter.duration - 1)) } + filter.blockSignals = false setControls() + setKeyframedControls() + filter.changed() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) } - function setFilter() { + function setFilter(position, force) { var x = parseFloat(rectX.text) var y = parseFloat(rectY.text) var w = parseFloat(rectW.text) var h = parseFloat(rectH.text) - if (x !== filterRect.x || + if (force || + x !== filterRect.x || y !== filterRect.y || w !== filterRect.width || - h !== filterRect.height) { + h !== filterRect.height) + { filterRect.x = x filterRect.y = y filterRect.width = w filterRect.height = h - filter.set(rectProperty, '%L1%/%L2%:%L3%x%L4%' - .arg(x / profile.width * 100) - .arg(y / profile.height * 100) - .arg(w / profile.width * 100) - .arg(h / profile.height * 100)) + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + filter.set(startValue, filterRect) + else if (position >= filter.duration - 1 && filter.animateOut > 0) + filter.set(endValue, filterRect) + else + filter.set(middleValue, filterRect) + } + + filter.resetProperty(rectProperty) + if (filter.animateIn > 0 || filter.animateOut > 0) { + if (filter.animateIn > 0) { + filter.set(rectProperty, filter.getRect(startValue), 1.0, 0) + filter.set(rectProperty, filter.getRect(middleValue), 1.0, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set(rectProperty, filter.getRect(middleValue), 1.0, filter.duration - filter.animateOut) + filter.set(rectProperty, filter.getRect(endValue), 1.0, filter.duration - 1) + } + } else { + filter.set(rectProperty, filter.getRect(middleValue)) + } } } @@ -108,12 +207,24 @@ bottomRadioButton.checked = true } + function setKeyframedControls() { + var position = getPosition() + var newValue = filter.getRect(rectProperty, position) + if (filterRect !== newValue) + filterRect = newValue + var enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + rectX.enabled = enabled + rectY.enabled = enabled + rectW.enabled = enabled + rectH.enabled = enabled + } + ExclusiveGroup { id: sizeGroup } ExclusiveGroup { id: halignGroup } ExclusiveGroup { id: valignGroup } GridLayout { - columns: 5 + columns: 6 anchors.fill: parent anchors.margins: 8 @@ -124,8 +235,19 @@ Preset { id: preset parameters: [fillProperty, distortProperty, rectProperty, halignProperty, valignProperty] - Layout.columnSpan: 4 - onPresetSelected: setControls() + Layout.columnSpan: 5 + onBeforePresetLoaded: { + filter.resetProperty(rectProperty) + } + onPresetSelected: { + setControls() + filterRect = filter.getRect(rectProperty, getPosition()) + filter.set(middleValue, filter.getRect(rectProperty, filter.animateIn)) + if (filter.animateIn > 0) + filter.set(startValue, filter.getRect(rectProperty, 0)) + if (filter.animateOut > 0) + filter.set(endValue, filter.getRect(rectProperty, filter.duration - 1)) + } } Label { @@ -136,18 +258,25 @@ Layout.columnSpan: 4 TextField { id: rectX - text: filterRect.x + text: filterRect.x.toFixed() horizontalAlignment: Qt.AlignRight - onEditingFinished: setFilter() + onEditingFinished: setFilter(getPosition()) } Label { text: ',' } TextField { id: rectY - text: filterRect.y + text: filterRect.y.toFixed() horizontalAlignment: Qt.AlignRight - onEditingFinished: setFilter() + onEditingFinished: setFilter(getPosition()) + } + } + UndoButton { + onClicked: { + rectX.text = rectY.text = 0 + setFilter(getPosition()) } } + Label { text: qsTr('Size') Layout.alignment: Qt.AlignRight @@ -156,16 +285,23 @@ Layout.columnSpan: 4 TextField { id: rectW - text: filterRect.width + text: filterRect.width.toFixed() horizontalAlignment: Qt.AlignRight - onEditingFinished: setFilter() + onEditingFinished: setFilter(getPosition()) } Label { text: 'x' } TextField { id: rectH - text: filterRect.height + text: filterRect.height.toFixed() horizontalAlignment: Qt.AlignRight - onEditingFinished: setFilter() + onEditingFinished: setFilter(getPosition()) + } + } + UndoButton { + onClicked: { + rectW.text = profile.width + rectH.text = profile.height + setFilter(getPosition()) } } @@ -173,36 +309,41 @@ text: qsTr('Size mode') Layout.alignment: Qt.AlignRight } - RowLayout { - Layout.columnSpan: 4 - RadioButton { - id: fitRadioButton - text: qsTr('Fit') - exclusiveGroup: sizeGroup - onClicked: { - filter.set(fillProperty, 0) - filter.set(distortProperty, 0) - } + RadioButton { + id: fitRadioButton + text: qsTr('Fit') + exclusiveGroup: sizeGroup + onClicked: { + filter.set(fillProperty, 0) + filter.set(distortProperty, 0) } - RadioButton { - id: fillRadioButton - text: qsTr('Fill') - exclusiveGroup: sizeGroup - onClicked: { - filter.set(fillProperty, 1) - filter.set(distortProperty, 0) - } + } + RadioButton { + id: fillRadioButton + text: qsTr('Fill') + exclusiveGroup: sizeGroup + onClicked: { + filter.set(fillProperty, 1) + filter.set(distortProperty, 0) } - RadioButton { - id: distortRadioButton - text: qsTr('Distort') - exclusiveGroup: sizeGroup - onClicked: { - filter.set(fillProperty, 1) - filter.set(distortProperty, 1) - } + } + RadioButton { + id: distortRadioButton + text: qsTr('Distort') + exclusiveGroup: sizeGroup + onClicked: { + filter.set(fillProperty, 1) + filter.set(distortProperty, 1) } } + UndoButton { + onClicked: { + fillRadioButton.checked = true + filter.set(fillProperty, 1) + filter.set(distortProperty, 0) + } + } + Item { Layout.fillWidth: true } Label { text: qsTr('Horizontal fit') @@ -229,6 +370,12 @@ enabled: fitRadioButton.checked onClicked: filter.set(halignProperty, 'right') } + UndoButton { + onClicked: { + leftRadioButton.checked = true + filter.set(halignProperty, 'left') + } + } Item { Layout.fillWidth: true } Label { @@ -256,6 +403,12 @@ enabled: fitRadioButton.checked onClicked: filter.set(valignProperty, 'bottom') } + UndoButton { + onClicked: { + topRadioButton.checked = true + filter.set(valignProperty, 'top') + } + } Item { Layout.fillWidth: true } Item { Layout.fillHeight: true } @@ -263,10 +416,18 @@ Connections { target: filter - onChanged: { - var newValue = filter.getRect(rectProperty) - if (filterRect !== newValue) - filterRect = newValue + onChanged: setKeyframedControls() + onInChanged: setFilter(null) + onOutChanged: setFilter(null) + onAnimateInChanged: setFilter(null, true) + onAnimateOutChanged: setFilter(null, true) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) + setKeyframedControls() } } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionVUI.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionVUI.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionVUI.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/SizePositionVUI.qml 2018-06-02 07:58:01.000000000 +0000 @@ -31,18 +31,69 @@ interactive: false clip: true property real zoom: (video.zoom > 0)? video.zoom : 1.0 - property rect filterRect: filter.getRect(rectProperty) + property rect filterRect + property string startValue: '_shotcut:startValue' + property string middleValue: '_shotcut:middleValue' + property string endValue: '_shotcut:endValue' + contentWidth: video.rect.width * zoom contentHeight: video.rect.height * zoom contentX: video.offset.x contentY: video.offset.y function getAspectRatio() { - return (filter.get(fillProperty) === '1' && filter.get(distortProperty) === '0')? filter.producerAspect : 0.0 + return (filter.get(fillProperty) === '1' && filter.get(distortProperty) === '0')? producer.sampleAspectRatio : 0.0 } Component.onCompleted: { - rectangle.setHandles(filter.getRect(rectProperty)) + filterRect = filter.getRect(rectProperty, getPosition()) + rectangle.setHandles(filterRect) + setRectangleControl() + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setRectangleControl() { + var position = getPosition() + var newValue = filter.getRect(rectProperty, position) + if (filterRect !== newValue) { + filterRect = newValue + rectangle.setHandles(filterRect) + } + rectangle.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function setFilter(position) { + var rect = rectangle.rectangle + filterRect.x = Math.round(rect.x / rectangle.widthScale) + filterRect.y = Math.round(rect.y / rectangle.heightScale) + filterRect.width = Math.round(rect.width / rectangle.widthScale) + filterRect.height = Math.round(rect.height / rectangle.heightScale) + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + filter.set(startValue, filterRect) + else if (position >= filter.duration - 1 && filter.animateOut > 0) + filter.set(endValue, filterRect) + else + filter.set(middleValue, filterRect) + } + + filter.resetProperty(rectProperty) + if (filter.animateIn > 0 || filter.animateOut > 0) { + if (filter.animateIn > 0) { + filter.set(rectProperty, filter.getRect(startValue), 1.0, 0) + filter.set(rectProperty, filter.getRect(middleValue), 1.0, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set(rectProperty, filter.getRect(middleValue), 1.0, filter.duration - filter.animateOut) + filter.set(rectProperty, filter.getRect(endValue), 1.0, filter.duration - 1) + } + } else { + filter.set(rectProperty, filter.getRect(middleValue)) + } } DropArea { anchors.fill: parent } @@ -62,40 +113,28 @@ aspectRatio: getAspectRatio() handleSize: Math.max(Math.round(8 / zoom), 4) borderSize: Math.max(Math.round(1.33 / zoom), 1) - onWidthScaleChanged: setHandles(filter.getRect(rectProperty)) - onHeightScaleChanged: setHandles(filter.getRect(rectProperty)) - onRectChanged: { - filterRect.x = Math.round(rect.x / rectangle.widthScale) - filterRect.y = Math.round(rect.y / rectangle.heightScale) - filterRect.width = Math.round(rect.width / rectangle.widthScale) - filterRect.height = Math.round(rect.height / rectangle.heightScale) - filter.set(rectProperty, '%L1%/%L2%:%L3%x%L4%' - .arg(filterRect.x / profile.width * 100) - .arg(filterRect.y / profile.height * 100) - .arg(filterRect.width / profile.width * 100) - .arg(filterRect.height / profile.height * 100)) - } + onWidthScaleChanged: setHandles(filterRect) + onHeightScaleChanged: setHandles(filterRect) + onRectChanged: setFilter(getPosition()) } } Connections { target: filter onChanged: { - var newRect = filter.getRect(rectProperty) - if (filterRect !== newRect) { - filterRect = newRect - rectangle.setHandles(filterRect) - } + setRectangleControl() if (rectangle.aspectRatio !== getAspectRatio()) { rectangle.aspectRatio = getAspectRatio() rectangle.setHandles(filterRect) - var rect = rectangle.rectangle - filter.set(rectProperty, '%L1%/%L2%:%L3%x%L4%' - .arg(Math.round(rect.x / rectangle.widthScale) / profile.width * 100) - .arg(Math.round(rect.y / rectangle.heightScale) / profile.height * 100) - .arg(Math.round(rect.width / rectangle.widthScale) / profile.width * 100) - .arg(Math.round(rect.height / rectangle.heightScale) / profile.height * 100)) } } } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) + setRectangleControl() + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/ui_affine.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/ui_affine.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/ui_affine.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/ui_affine.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -20,7 +20,8 @@ SizePositionUI { fillProperty: 'transition.fill' distortProperty: 'transition.distort' - rectProperty: 'transition.geometry' + legacyRectProperty: 'transition.geometry' + rectProperty: 'transition.rect' valignProperty: 'transition.valign' halignProperty: 'transition.halign' Component.onCompleted: { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/vui_affine.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/vui_affine.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/size_position/vui_affine.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/size_position/vui_affine.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ SizePositionVUI { fillProperty: 'transition.fill' distortProperty: 'transition.distort' - rectProperty: 'transition.geometry' + rectProperty: 'transition.rect' valignProperty: 'transition.valign' halignProperty: 'transition.halign' } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/stabilize/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/stabilize/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/stabilize/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/stabilize/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -8,4 +8,7 @@ qml: "ui.qml" isClipOnly: true isGpuCompatible: false + keyframes { + allowTrim: false + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/stabilize/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/stabilize/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/stabilize/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/stabilize/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC + * Copyright (c) 2013-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ id: stabilizeRoot width: 350 height: 150 - property string settingsSavePath: settings.savePath + property url settingsSavePath: 'file:///' + settings.savePath property string _analysisRequiredMessage: qsTr('Click Analyze to use this filter.') Component.onCompleted: { @@ -167,7 +167,6 @@ Layout.alignment: Qt.AlignRight onClicked: { button.enabled = false - fileDialog.folder = settings.savePath fileDialog.open() } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Brian Matherly * * This program is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ id: webvfxRoot width: 350 height: 100 - property string settingsSavePath: settings.savePath + property url settingsSavePath: 'file:///' + settings.savePath SystemPalette { id: activePalette; colorGroup: SystemPalette.Active } Shotcut.File { id: htmlFile } @@ -82,8 +82,8 @@ fileLabelTip.text = qsTr('No HTML file loaded. Click "Open" or "New" to load a file.') filter.set("disable", 1) } - filter.set('in', filter.producerIn) - filter.set('out', filter.producerOut) + filter.set('in', producer.in) + filter.set('out', producer.out) } FileDialog { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/meta.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/meta.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/meta.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/meta.qml 2018-06-02 07:58:01.000000000 +0000 @@ -7,4 +7,19 @@ name: qsTr("Circular Frame (HTML)") mlt_service: "webvfx" qml: "ui.qml" + keyframes { + allowAnimateIn: true + allowAnimateOut: true + simpleProperties: ['radius'] + parameters: [ + Parameter { + name: qsTr('Radius') + property: 'radius' + isSimple: true + isCurve: true + minimum: 0 + maximum: 1 + } + ] + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_circular_frame/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015 Meltytech, LLC + * Copyright (c) 2013-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -23,22 +23,77 @@ import QtQuick.Dialogs 1.0 import Shotcut.Controls 1.0 -Rectangle { +Item { width: 400 height: 100 - color: 'transparent' + property bool blockUpdate: true + property double startValue: 0.5 + property double middleValue: 0.5 + property double endValue: 0.5 + Component.onCompleted: { if (filter.isNew) { filter.set('resource', filter.path + 'filter-demo.html') // Set default parameter values colorSwatch.value = 'black' filter.set('radius', 0.5) - slider.value = filter.getDouble('radius') * slider.maximumValue + } else { + middleValue = filter.getDouble('radius', filter.animateIn) + if (filter.animateIn > 0) + startValue = filter.getDouble('radius', 0) + if (filter.animateOut > 0) + endValue = filter.getDouble('radius', filter.duration - 1) + } + setControls() + slider.value = filter.getDouble('radius') * slider.maximumValue + } + + function getPosition() { + return Math.max(producer.position - (filter.in - producer.in), 0) + } + + function setControls() { + var position = getPosition() + blockUpdate = true + slider.value = filter.getDouble('radius', position) * slider.maximumValue + blockUpdate = false + slider.enabled = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1) + } + + function updateFilter(position) { + if (blockUpdate) return + var value = slider.value / 100.0 + + if (position !== null) { + if (position <= 0 && filter.animateIn > 0) + startValue = value + else if (position >= filter.duration - 1 && filter.animateOut > 0) + endValue = value + else + middleValue = value + } + + if (filter.animateIn > 0 || filter.animateOut > 0) { + filter.resetProperty('radius') + keyframesButton.checked = false + if (filter.animateIn > 0) { + filter.set('radius', startValue, 0) + filter.set('radius', middleValue, filter.animateIn - 1) + } + if (filter.animateOut > 0) { + filter.set('radius', middleValue, filter.duration - filter.animateOut) + filter.set('radius', endValue, filter.duration - 1) + } + } else if (!keyframesButton.checked) { + filter.resetProperty('radius') + filter.set('radius', middleValue) + } else if (position !== null) { + filter.set('radius', value, position) } } GridLayout { - columns: 3 + columns: 4 anchors.fill: parent anchors.margins: 8 @@ -51,12 +106,28 @@ minimumValue: 0 maximumValue: 100 suffix: ' %' - value: filter.getDouble('radius') * slider.maximumValue - onValueChanged: filter.set('radius', value / maximumValue) + onValueChanged: updateFilter(getPosition()) } UndoButton { onClicked: slider.value = 50 } + KeyframesButton { + id: keyframesButton + checked: filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('radius') > 0 + onToggled: { + var value = slider.value / 100.0 + if (checked) { + blockUpdate = true + filter.clearSimpleAnimation('radius') + blockUpdate = false + filter.set('radius', value, getPosition()) + } else { + filter.resetProperty('radius') + filter.set('radius', value) + } + } + } + Label { text: qsTr('Color') @@ -78,9 +149,32 @@ filter.set("disable", 1); } } + Item {} Item { Layout.fillHeight: true } } + + Connections { + target: filter + onInChanged: updateFilter(null) + onOutChanged: updateFilter(null) + onAnimateInChanged: updateFilter(null) + onAnimateOutChanged: updateFilter(null) + } + + Connections { + target: producer + onPositionChanged: { + if (filter.animateIn > 0 || filter.animateOut > 0) { + setControls() + } else { + blockUpdate = true + slider.value = filter.getDouble('radius', getPosition()) * slider.maximumValue + blockUpdate = false + slider.enabled = true + } + } + } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_ruttetraizer/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_ruttetraizer/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_ruttetraizer/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_ruttetraizer/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Meltytech, LLC + * Copyright (c) 2015-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -39,8 +39,8 @@ setControls(); } - filter.set('in', filter.producerIn) - filter.set('out', filter.producerOut) + filter.set('in', producer.in) + filter.set('out', producer.out) } function setControls() { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_threejs_text/ui.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_threejs_text/ui.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/filters/webvfx_threejs_text/ui.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/filters/webvfx_threejs_text/ui.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -45,8 +45,8 @@ setControls(); } - filter.set('in', filter.producerIn) - filter.set('out', filter.producerOut) + filter.set('in', producer.in) + filter.set('out', producer.out) } function setControls() { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/KeyframesButton.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/KeyframesButton.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/KeyframesButton.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/KeyframesButton.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018 Meltytech, 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 . + */ + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import Shotcut.Controls 1.0 as Shotcut +import QtQuick.Dialogs 1.2 + +CheckBox { + id: checkbox + enabled: metadata !== null && metadata.keyframes.enabled + opacity: enabled? 1.0 : 0.0 + + signal toggled() + + style: CheckBoxStyle { + background: Rectangle { + implicitWidth: 20 + implicitHeight: 20 + radius: 3 + SystemPalette { id: activePalette } + color: control.checked? activePalette.highlight : activePalette.button + border.color: activePalette.shadow + border.width: 1 + } + indicator: ToolButton { + x: 3 + implicitWidth: 16 + implicitHeight: 16 + iconName: 'chronometer' + iconSource: 'qrc:///icons/oxygen/32x32/actions/chronometer.png' + } + } + Shotcut.ToolTip { id: tooltip; text: qsTr('Use Keyframes for this parameter') } + + onClicked: { + tooltip.isVisible = false + if (!checked) { + checked = true + confirmDialog.visible = true + } else { + keyframes.show() + keyframes.raise() + toggled() + } + tooltip.isVisible = true + } + + MessageDialog { + id: confirmDialog + visible: false + modality: Qt.WindowModal + icon: StandardIcon.Question + title: qsTr("Confirm Removing Keyframes") + text: qsTr('This will remove all keyframes for this parameter.

Do you still want to do this?') + standardButtons: StandardButton.Yes | StandardButton.No + onYes: { + checkbox.checked = false + checkbox.toggled() + } + onNo: checkbox.checked = true + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/Preset.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/Preset.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/Preset.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/Preset.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2016 Meltytech, LLC + * Copyright (c) 2013-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ property var parameters: [] // Tell the parent QML page to update its controls. + signal beforePresetLoaded() signal presetSelected() Component.onCompleted: { @@ -38,8 +39,18 @@ Layout.maximumWidth: 300 model: filter.presets onCurrentTextChanged: { - filter.preset(currentText) - presetSelected() + if (currentText.length > 0) { + filter.blockSignals = true + filter.animateIn = 0 + filter.animateOut = 0 + beforePresetLoaded() + filter.preset(currentText) + presetSelected() + filter.blockSignals = false + filter.changed() + filter.animateInChanged() + filter.animateOutChanged() + } } } Button { @@ -72,7 +83,10 @@ height: 90 function acceptName() { - presetCombo.currentIndex = filter.savePreset(parameters, nameField.text) + var params = parameters + params.push('shotcut:animIn') + params.push('shotcut:animOut') + presetCombo.currentIndex = filter.savePreset(params, nameField.text) nameDialog.close() } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/qmldir shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/qmldir --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/qmldir 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/qmldir 2018-06-02 07:58:01.000000000 +0000 @@ -3,6 +3,7 @@ Preset 1.0 Preset.qml ToolTip 1.0 ToolTip.qml UndoButton 1.0 UndoButton.qml +KeyframesButton 1.0 KeyframesButton.qml TimeSpinner 1.0 TimeSpinner.qml SaveDefaultButton 1.0 SaveDefaultButton.qml SliderSpinner 1.0 SliderSpinner.qml diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/RectangleControl.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/RectangleControl.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/RectangleControl.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/RectangleControl.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ property int handleSize: 10 property int borderSize: 2 property alias rectangle: rectangle - property color handleColor: Qt.rgba(1, 1, 1, 0.9) + property color handleColor: Qt.rgba(1, 1, 1, enabled? 0.9 : 0.2) signal rectChanged(Rectangle rect) @@ -65,14 +65,14 @@ // Provides contrasting thick line to above rectangle. color: 'transparent' border.width: handleSize - borderSize - border.color: Qt.rgba(0, 0, 0, 0.4) + border.color: Qt.rgba(0, 0, 0, item.enabled? 0.4 : 0.2) anchors.fill: rectangle anchors.margins: borderSize } Rectangle { id: positionHandle - color: Qt.rgba(0, 0, 0, 0.5) + color: Qt.rgba(0, 0, 0, item.enabled? 0.5 : 0.2) border.width: borderSize border.color: handleColor width: handleSize * 2 diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/ToolTip.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/ToolTip.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/ToolTip.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/modules/Shotcut/Controls/ToolTip.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Brian Matherly * * This program is free software: you can redistribute it and/or modify @@ -24,10 +24,14 @@ Item { property alias text: toolTipText.text - + property alias isVisible: toolTipMouseArea.enabled + property alias cursorShape: toolTipMouseArea.cursorShape + property alias containsMouse: toolTipMouseArea.containsMouse + anchors.fill: parent MouseArea { + id: toolTipMouseArea anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: true @@ -37,6 +41,9 @@ onExited: { toolTipWindow.endDisplay() } + onEnabledChanged: { + if (!enabled) toolTipWindow.close() + } } Window { @@ -76,7 +83,7 @@ if (toolTipWindow.visible) { toolTipWindow.close() } - else { + else if (toolTipMouseArea.enabled) { toolTipWindow.show() } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Clip.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Clip.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Clip.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Clip.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2016 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -44,6 +43,7 @@ property bool selected: false property string hash: '' property double speed: 1.0 + property string audioIndex: '' signal clicked(var clip) signal moved(var clip) @@ -143,7 +143,7 @@ Row { id: waveform - visible: !isBlank && settings.timelineShowWaveforms && !trackHeaderRepeater.itemAt(trackIndex).isMute + visible: !isBlank && settings.timelineShowWaveforms && !trackHeaderRepeater.itemAt(trackIndex).isMute && (parseInt(audioIndex) > -1 || audioIndex === 'all') height: isAudio? parent.height : parent.height / 2 anchors.left: parent.left anchors.bottom: parent.bottom @@ -365,14 +365,15 @@ onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var delta = Math.round((parent.x - startX) / timeScale) - var duration = startFadeIn + delta + var duration = Math.max(0, startFadeIn + delta) timeline.fadeIn(trackIndex, index, duration) // Show fade duration as time in a "bubble" help. - var s = timeline.timecode(Math.max(duration, 0)) + var s = application.timecode(duration) bubbleHelp.show(clipRoot.x, trackRoot.y + clipRoot.height, s.substring(6)) } } + onDoubleClicked: timeline.fadeIn(trackIndex, index, (fadeIn > 0) ? 0 : Math.round(profile.fps)) } SequentialAnimation on scale { loops: Animation.Infinite @@ -457,14 +458,15 @@ onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var delta = Math.round((startX - parent.x) / timeScale) - var duration = startFadeOut + delta + var duration = Math.max(0, startFadeOut + delta) timeline.fadeOut(trackIndex, index, duration) // Show fade duration as time in a "bubble" help. - var s = timeline.timecode(Math.max(duration, 0)) + var s = application.timecode(duration) bubbleHelp.show(clipRoot.x + clipRoot.width, trackRoot.y + clipRoot.height, s.substring(6)) } } + onDoubleClicked: timeline.fadeOut(trackIndex, index, (fadeOut > 0) ? 0 : Math.round(profile.fps)) } SequentialAnimation on scale { loops: Animation.Infinite @@ -630,6 +632,11 @@ onTriggered: timeline.mergeClipWithNext(trackIndex, index, false) } MenuItem { + visible: !isBlank && !isTransition && !isAudio + text: qsTr('Detach Audio') + onTriggered: timeline.detachAudio(trackIndex, index) + } + MenuItem { visible: !isBlank && !isTransition && settings.timelineShowWaveforms text: qsTr('Rebuild Audio Waveform') onTriggered: timeline.remakeAudioLevels(trackIndex, index) diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Ruler.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Ruler.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Ruler.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Ruler.qml 2018-06-02 07:58:01.000000000 +0000 @@ -46,7 +46,7 @@ anchors.bottomMargin: 2 color: activePalette.windowText x: index * stepSize + 2 - text: timeline.timecode(index * stepSize / timeScale) + text: application.timecode(index * stepSize / timeScale) font.pointSize: 7.5 } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/timeline.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/timeline.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/timeline.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/timeline.qml 2018-06-02 07:58:01.000000000 +0000 @@ -91,18 +91,18 @@ DropArea { anchors.fill: parent onEntered: { - if (drag.formats.indexOf('application/mlt+xml') >= 0) + if (drag.formats.indexOf('application/vnd.mlt+xml') >= 0) drag.acceptProposedAction() } onExited: Logic.dropped() onPositionChanged: { - if (drag.formats.indexOf('application/mlt+xml') >= 0) + if (drag.formats.indexOf('application/vnd.mlt+xml') >= 0) Logic.dragging(drag, drag.text) } onDropped: { - if (drop.formats.indexOf('application/mlt+xml') >= 0) { + if (drop.formats.indexOf('application/vnd.mlt+xml') >= 0) { if (currentTrack >= 0) { - Logic.acceptDrop(drop.getDataAsString('application/mlt+xml')) + Logic.acceptDrop(drop.getDataAsString('application/vnd.mlt+xml')) drop.acceptProposedAction() } } @@ -183,6 +183,7 @@ isLocked: model.locked isVideo: !model.audio isFiltered: model.filtered + isBottomVideo: model.isBottomVideo width: headerWidth height: Logic.trackHeight(model.audio) selected: false @@ -507,7 +508,7 @@ // Show distance moved as time in a "bubble" help. var track = tracksRepeater.itemAt(clip.trackIndex) var delta = Math.round((clip.x - clip.originalX) / multitrack.scaleFactor) - var s = timeline.timecode(Math.abs(delta)) + var s = application.timecode(Math.abs(delta)) // remove leading zeroes if (s.substring(0, 3) === '00:') s = s.substring(3) diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/TrackHead.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/TrackHead.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/TrackHead.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/TrackHead.qml 2018-06-02 07:58:01.000000000 +0000 @@ -30,6 +30,7 @@ property bool isLocked property bool isVideo property bool isFiltered + property bool isBottomVideo property bool selected: false property bool current: false signal clicked() @@ -157,12 +158,14 @@ ToolButton { id: compositeButton visible: isVideo + enabled: !isBottomVideo + opacity: enabled? 1.0 : 0.5 implicitWidth: 20 implicitHeight: 20 iconName: isComposite ? 'merge' : 'split' iconSource: isComposite ? 'qrc:///icons/oxygen/32x32/actions/merge.png' : 'qrc:///icons/oxygen/32x32/actions/split.png' onClicked: timeline.setTrackComposite(index, !isComposite) - tooltip: isComposite? qsTr('Disable compositing') : qsTr('Composite') + tooltip: isComposite? qsTr('Disable compositing') : qsTr('Enable compositing') } ToolButton { diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Track.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Track.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/timeline/Track.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/timeline/Track.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013-2016 Meltytech, LLC - * Author: Dan Dennedy + * Copyright (c) 2013-2018 Meltytech, 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 @@ -82,6 +81,7 @@ fadeOut: model.fadeOut hash: model.hash speed: model.speed + audioIndex: model.audioIndex selected: trackRoot.isCurrentTrack && trackRoot.selection.indexOf(index) !== -1 onClicked: trackRoot.clipClicked(clip, trackRoot); @@ -127,10 +127,10 @@ if (timeline.trimClipIn(trackRoot.DelegateModel.itemsIndex, clip.DelegateModel.itemsIndex, delta, toolbar.ripple)) { // Show amount trimmed as a time in a "bubble" help. - var s = timeline.timecode(Math.abs(clip.originalX)) + var s = application.timecode(Math.abs(clip.originalX)) s = '%1%2 = %3'.arg((clip.originalX < 0)? '-' : (clip.originalX > 0)? '+' : '') .arg(s.substring(3)) - .arg(timeline.timecode(clipDuration)) + .arg(application.timecode(clipDuration)) bubbleHelp.show(clip.x, trackRoot.y + trackRoot.height, s) } else { clip.originalX -= originalDelta @@ -153,10 +153,10 @@ if (timeline.trimClipOut(trackRoot.DelegateModel.itemsIndex, clip.DelegateModel.itemsIndex, delta, toolbar.ripple)) { // Show amount trimmed as a time in a "bubble" help. - var s = timeline.timecode(Math.abs(clip.originalX)) + var s = application.timecode(Math.abs(clip.originalX)) s = '%1%2 = %3'.arg((clip.originalX < 0)? '+' : (clip.originalX > 0)? '-' : '') .arg(s.substring(3)) - .arg(timeline.timecode(clipDuration)) + .arg(application.timecode(clipDuration)) bubbleHelp.show(clip.x + clip.width, trackRoot.y + trackRoot.height, s) } else { clip.originalX -= originalDelta diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/filter/AttachedFilters.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/filter/AttachedFilters.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/filter/AttachedFilters.qml 2018-03-06 08:57:37.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/filter/AttachedFilters.qml 2018-06-02 07:58:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Meltytech, LLC + * Copyright (c) 2014-2018 Meltytech, LLC * Author: Brian Matherly * * This program is free software: you can redistribute it and/or modify @@ -96,7 +96,6 @@ anchors.fill: parent onDoubleClicked: { model.checkState = !model.checkState - filterDelegateCheck.checkedState = model.checkState } } } diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Clip.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Clip.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Clip.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Clip.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2016-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.2 +import QtQuick.Controls 1.0 +import Shotcut.Controls 1.0 +import QtGraphicalEffects 1.0 +import QtQml.Models 2.2 +import QtQuick.Window 2.2 + +Rectangle { + id: clipRoot + property string clipName: '' + property string clipResource: '' + property string mltService: '' + property int inPoint: 0 + property int outPoint: 0 + property int clipDuration: outPoint - inPoint + 1 + property bool isBlank: false + property bool isAudio: false + property var audioLevels + property int animateIn: 0 + property int animateOut: 0 + property int trackIndex: 0 + property int index: 0 + property int originalTrackIndex: trackIndex + property int originalClipIndex: index + property int originalX: x + property bool selected: false + property string hash: '' + property double speed: 1.0 + property bool inThumbnailVisible: true + property bool outThumbnailVisible: true + + signal trimmingIn(var clip, real delta, var mouse) + signal trimmedIn(var clip) + signal trimmingOut(var clip, real delta, var mouse) + signal trimmedOut(var clip) + + SystemPalette { id: activePalette } + gradient: Gradient { + GradientStop { + id: gradientStop + position: 0.00 + color: Qt.lighter(getColor()) + } + GradientStop { + id: gradientStop2 + position: 1.0 + color: getColor() + } + } + + width: clipDuration * timeScale + border.color: selected? 'red' : 'black' + border.width: 1 + clip: true + opacity: isBlank? 0.5 : 1.0 + + function getColor() { + return isAudio? 'darkseagreen' : root.shotcutBlue + } + + function generateWaveform() { + // This is needed to make the model have the correct count. + // Model as a property expression is not working in all cases. + waveformRepeater.model = Math.ceil(waveform.innerWidth / waveform.maxWidth) + for (var i = 0; i < waveformRepeater.count; i++) + waveformRepeater.itemAt(0).update() + } + + function imagePath(time) { + return 'image://thumbnail/' + hash + '/' + mltService + '/' + clipResource + '#' + time + } + + onAudioLevelsChanged: generateWaveform() + + Image { + id: outThumbnail + visible: settings.timelineShowThumbnails && outThumbnailVisible + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: parent.border.width + anchors.rightMargin: parent.border.width + 1 + anchors.bottom: parent.bottom + anchors.bottomMargin: parent.height / 2 + width: height * 16.0/9.0 + fillMode: Image.PreserveAspectFit + source: visible? imagePath(outPoint) : '' + } + + Image { + id: inThumbnail + visible: settings.timelineShowThumbnails && inThumbnailVisible && metadata !== null + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: parent.border.width + anchors.bottom: parent.bottom + anchors.bottomMargin: parent.height / 2 + width: height * 16.0/9.0 + fillMode: Image.PreserveAspectFit + source: visible? imagePath(inPoint) : '' + } + + Row { + id: waveform + visible: settings.timelineShowWaveforms + height: isAudio? parent.height : parent.height / 2 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: parent.border.width + opacity: 0.7 + property int maxWidth: 10000 + property int innerWidth: clipRoot.width - clipRoot.border.width * 2 + + Repeater { + id: waveformRepeater + TimelineWaveform { + width: Math.min(waveform.innerWidth, waveform.maxWidth) + height: waveform.height + fillColor: getColor() + property int channels: 2 + inPoint: Math.round((clipRoot.inPoint + index * waveform.maxWidth / timeScale) * speed) * channels + outPoint: inPoint + Math.round(width / timeScale * speed) * channels + levels: audioLevels + } + } + } + + Rectangle { + // audio peak line + width: parent.width - parent.border.width * 2 + height: 1 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.leftMargin: parent.border.width + anchors.bottomMargin: waveform.height * 0.9 + color: Qt.darker(parent.color) + opacity: 0.7 + } + + Rectangle { + // text background + color: 'lightgray' + visible: !isBlank + opacity: 0.7 + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: parent.border.width + anchors.leftMargin: parent.border.width + + ((isAudio || !settings.timelineShowThumbnails) ? 0 : inThumbnail.width) + width: label.width + 2 + height: label.height + } + + Text { + id: label + text: clipName + visible: !isBlank + font.pointSize: 8 + anchors { + top: parent.top + left: parent.left + topMargin: parent.border.width + 1 + leftMargin: parent.border.width + + ((isAudio || !settings.timelineShowThumbnails) ? 0 : inThumbnail.width) + 1 + } + color: 'black' + } + + states: [ + State { + name: 'normal' + when: !clipRoot.selected + PropertyChanges { + target: clipRoot + z: 0 + } + }, + State { + name: 'selectedBlank' + when: clipRoot.selected && clipRoot.isBlank + PropertyChanges { + target: gradientStop2 + color: Qt.lighter(selectedTrackColor) + } + PropertyChanges { + target: gradientStop + color: Qt.darker(selectedTrackColor) + } + }, + State { + name: 'selected' + when: clipRoot.selected + PropertyChanges { + target: clipRoot + z: 1 + } + PropertyChanges { + target: gradientStop + color: Qt.darker(getColor()) + } + } + ] + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + propagateComposedEvents: true + cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : + (animateInMouseArea.drag.active || animateOutMouseArea.drag.active)? Qt.PointingHandCursor : + Qt.ArrowCursor + onClicked: menu.popup() + onWheel: zoomByWheel(wheel) + } + + TimelineTriangle { + id: animateInTriangle + visible: !isBlank + width: parent.animateIn * timeScale + height: parent.height - parent.border.width * 2 + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: parent.border.width + opacity: 0.5 + onWidthChanged: { + if (width === 0) { + animateInControl.anchors.horizontalCenter = undefined + animateInControl.anchors.left = animateInTriangle.left + } else if (animateInControl.anchors.left && !animateInMouseArea.pressed) { + animateInControl.anchors.left = undefined + animateInControl.anchors.horizontalCenter = animateInTriangle.right + } + } + } + Rectangle { + id: animateInControl + visible: metadata !== null && metadata.keyframes.allowAnimateIn + enabled: !isBlank + anchors.left: animateInTriangle.width > radius? undefined : animateInTriangle.left + anchors.horizontalCenter: animateInTriangle.width > radius? animateInTriangle.right : undefined + anchors.top: animateInTriangle.top + anchors.topMargin: -3 + width: 14 + height: 14 + radius: 7 + color: 'black' + border.width: 2 + border.color: 'white' + opacity: enabled? 0.7 : 0 + Drag.active: animateInMouseArea.drag.active + MouseArea { + id: animateInMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + drag.target: parent + drag.axis: Drag.XAxis + property int startX + property int startFadeIn + onPressed: { + root.stopScrolling = true + startX = parent.x + startFadeIn = animateIn + parent.anchors.left = undefined + parent.anchors.horizontalCenter = undefined + } + onReleased: { + root.stopScrolling = false + if (animateInTriangle.width > parent.radius) + parent.anchors.horizontalCenter = animateInTriangle.right + else + parent.anchors.left = animateInTriangle.left + bubbleHelp.hide() + } + onPositionChanged: { + if (mouse.buttons === Qt.LeftButton) { + var delta = Math.round((parent.x - startX) / timeScale) + var duration = startFadeIn + delta + filter.animateIn = duration + + // Show fade duration as time in a "bubble" help. + var s = application.timecode(Math.max(duration, 0)) + bubbleHelp.show(clipRoot.x, trackRoot.y + clipRoot.height, s.substring(6)) + } + } + onDoubleClicked: filter.animateIn = (filter.animateIn > 0) ? 0 : Math.round(profile.fps) + } + SequentialAnimation on scale { + loops: Animation.Infinite + running: animateInMouseArea.containsMouse + NumberAnimation { + from: 1.0 + to: 1.5 + duration: 250 + easing.type: Easing.InOutQuad + } + NumberAnimation { + from: 1.5 + to: 1.0 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + + TimelineTriangle { + id: animateOutTriangle + visible: !isBlank + width: parent.animateOut * timeScale + height: parent.height - parent.border.width * 2 + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: parent.border.width + opacity: 0.5 + transform: Scale { xScale: -1; origin.x: animateOutTriangle.width / 2} + onWidthChanged: { + if (width === 0) { + animateOutControl.anchors.horizontalCenter = undefined + animateOutControl.anchors.right = animateOutTriangle.right + } else if (animateOutControl.anchors.right && !animateOutMouseArea.pressed) { + animateOutControl.anchors.right = undefined + animateOutControl.anchors.horizontalCenter = animateOutTriangle.left + } + } + } + Rectangle { + id: animateOutControl + visible: metadata !== null && metadata.keyframes.allowAnimateOut + enabled: !isBlank + anchors.right: animateOutTriangle.width > radius? undefined : animateOutTriangle.right + anchors.horizontalCenter: animateOutTriangle.width > radius? animateOutTriangle.left : undefined + anchors.top: animateOutTriangle.top + anchors.topMargin: -3 + width: 14 + height: 14 + radius: 7 + color: 'black' + border.width: 2 + border.color: 'white' + opacity: enabled? 0.7 : 0 + Drag.active: animateOutMouseArea.drag.active + MouseArea { + id: animateOutMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + drag.target: parent + drag.axis: Drag.XAxis + property int startX + property int startFadeOut + onPressed: { + root.stopScrolling = true + startX = parent.x + startFadeOut = animateOut + parent.anchors.right = undefined + parent.anchors.horizontalCenter = undefined + } + onReleased: { + root.stopScrolling = false + if (animateOutTriangle.width > parent.radius) + parent.anchors.horizontalCenter = animateOutTriangle.left + else + parent.anchors.right = animateOutTriangle.right + bubbleHelp.hide() + } + onPositionChanged: { + if (mouse.buttons === Qt.LeftButton) { + var delta = Math.round((startX - parent.x) / timeScale) + var duration = startFadeOut + delta + filter.animateOut = duration + + // Show fade duration as time in a "bubble" help. + var s = application.timecode(Math.max(duration, 0)) + bubbleHelp.show(clipRoot.x + clipRoot.width, trackRoot.y + clipRoot.height, s.substring(6)) + } + } + onDoubleClicked: filter.animateOut = (filter.animateOut > 0) ? 0 : Math.round(profile.fps) + } + SequentialAnimation on scale { + loops: Animation.Infinite + running: animateOutMouseArea.containsMouse + NumberAnimation { + from: 1.0 + to: 1.5 + duration: 250 + easing.type: Easing.InOutQuad + } + NumberAnimation { + from: 1.5 + to: 1.0 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + id: trimIn + visible: metadata !== null && metadata.keyframes.allowTrim + enabled: !isBlank + anchors.left: parent.left + anchors.leftMargin: 0 + height: parent.height + width: 5 + color: isAudio? 'green' : 'lawngreen' + opacity: enabled? 0.5 : 0 + Drag.active: trimInMouseArea.drag.active + Drag.proposedAction: Qt.MoveAction + + MouseArea { + id: trimInMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.SizeHorCursor + drag.target: parent + drag.axis: Drag.XAxis + property double startX + + onPressed: { + root.stopScrolling = true + startX = mapToItem(null, x, y).x + originalX = 0 // reusing originalX to accumulate delta for bubble help + parent.anchors.left = undefined + } + onReleased: { + root.stopScrolling = false + parent.anchors.left = clipRoot.left + clipRoot.trimmedIn(clipRoot) + } + onPositionChanged: { + if (mouse.buttons === Qt.LeftButton) { + var newX = mapToItem(null, x, y).x + var delta = Math.round((newX - startX) / timeScale) + if (Math.abs(delta) > 0) { + if (clipDuration + originalX + delta > 0) + originalX += delta + clipRoot.trimmingIn(clipRoot, delta, mouse) + startX = newX + } + } + } + } + } + Rectangle { + id: trimOut + visible: metadata !== null && metadata.keyframes.allowTrim + enabled: !isBlank + anchors.right: parent.right + anchors.rightMargin: 0 + height: parent.height + width: 5 + color: 'red' + opacity: enabled? 0.5 : 0 + Drag.active: trimOutMouseArea.drag.active + Drag.proposedAction: Qt.MoveAction + + MouseArea { + id: trimOutMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.SizeHorCursor + drag.target: parent + drag.axis: Drag.XAxis + property int duration + + onPressed: { + root.stopScrolling = true + duration = clipDuration + originalX = 0 // reusing originalX to accumulate delta for bubble help + parent.anchors.right = undefined + } + onReleased: { + root.stopScrolling = false + parent.anchors.right = clipRoot.right + clipRoot.trimmedOut(clipRoot) + } + onPositionChanged: { + if (mouse.buttons === Qt.LeftButton) { + var newDuration = Math.round((parent.x + parent.width) / timeScale) + var delta = duration - newDuration + if (Math.abs(delta) > 0) { + if (clipDuration - originalX - delta > 0) + originalX += delta + clipRoot.trimmingOut(clipRoot, delta, mouse) + duration = newDuration + } + } + } + } + } + Menu { + id: menu + MenuItem { + visible: !isBlank && settings.timelineShowWaveforms + text: qsTr('Rebuild Audio Waveform') + onTriggered: producer.remakeAudioLevels() + } + onPopupVisibleChanged: { + if (visible && application.OS !== 'OS X' && __popupGeometry.height > 0) { + // Try to fix menu running off screen. This only works intermittently. + menu.__yOffset = Math.min(0, Screen.height - (__popupGeometry.y + __popupGeometry.height + 40)) + menu.__xOffset = Math.min(0, Screen.width - (__popupGeometry.x + __popupGeometry.width)) + } + } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframe.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframe.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframe.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframe.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018 Meltytech, 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 . + */ + +import QtQuick 2.0 +import org.shotcut.qml 1.0 +import QtQuick.Controls 1.0 +import Shotcut.Controls 1.0 +import QtQuick.Window 2.2 +import 'Keyframes.js' as Logic + +Rectangle { + id: keyframeRoot + property int position: 0 + property int interpolation: KeyframesModel.DiscreteInterpolation // rectangle for discrete + property bool isSelected: false + property string name: '' + property double value + property int parameterIndex + property bool isCurve: metadata !== null? metadata.keyframes.parameters[parameterIndex].isCurve : false + property int trackHeight: Logic.trackHeight(isCurve) + property double minimum: metadata !== null? metadata.keyframes.parameters[parameterIndex].minimum : 0.0 + property double maximum: metadata !== null? metadata.keyframes.parameters[parameterIndex].maximum : 1.0 + property double trackValue: (0.5 - (value - minimum) / (maximum - minimum)) * (trackHeight - height - 2.0 * border.width) + property double minDragX: activeClip.x - width/2 + property double maxDragX: activeClip.x + activeClip.width - width/2 + property double minDragY: activeClip.y - width/2 + property double maxDragY: activeClip.y + activeClip.height - width/2 + + signal clicked(var keyframe) + + SystemPalette { id: activePalette } + + x: position * timeScale - width/2 + anchors.verticalCenter: parameterRoot.verticalCenter + anchors.verticalCenterOffset: isCurve ? trackValue : 0 + height: 10 + width: height + color: isSelected? 'red' : activePalette.buttonText + border.color: activePalette.button + border.width: 1 + radius: (interpolation === KeyframesModel.SmoothInterpolation)? height/2 : 0 // circle for smooth + rotation: (interpolation === KeyframesModel.LinearInterpolation)? 45 : 0 // diamond for linear + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) + menu.popup() + else + producer.position = position + } + onDoubleClicked: removeMenuItem.trigger() + drag { + target: parent + axis: isCurve? Drag.XAndYAxis : Drag.XAxis + threshold: 0 + minimumX: minDragX + maximumX: maxDragX + minimumY: minDragY + maximumY: maxDragY + } + onPressed: { + parent.clicked(keyframeRoot) + if (isCurve) { + if (mouse.modifiers & Qt.ControlModifier) + drag.axis = Drag.YAxis + else if (mouse.modifiers & Qt.AltModifier) + drag.axis = Drag.XAxis + else + drag.axis = Drag.XAndYAxis + } + } + onEntered: if (isCurve) parent.anchors.verticalCenter = undefined + onReleased: if (isCurve) parent.anchors.verticalCenter = parameterRoot.verticalCenter + onPositionChanged: { + var newPosition = Math.round(parent.x / timeScale + (parent.width/2)) + if (newPosition !== keyframeRoot.position) + parameters.setPosition(parameterIndex, index, newPosition - (filter.in - producer.in)) + if (isCurve) { + var trackValue = Math.min(Math.max(0, 1.0 - parent.y / (parameterRoot.height - parent.height)), 1.0) + var newValue = minimum + trackValue * (maximum - minimum) + if (trackValue !== newValue) + parameters.setKeyframe(parameterIndex, newValue, newPosition - (filter.in - producer.in), interpolation) + } + } + } + + ToolTip { + id: tooltip + text: name + cursorShape: Qt.PointingHandCursor + } + + Menu { + id: menu + Menu { + id: keyframeTypeSubmenu + title: qsTr('Keyframe Type') + ExclusiveGroup { id: keyframeTypeGroup } + MenuItem { + text: qsTr('Discrete') + checkable: true + checked: interpolation === KeyframesModel.DiscreteInterpolation + exclusiveGroup: keyframeTypeGroup + onTriggered: parameters.setInterpolation(parameterIndex, index, KeyframesModel.DiscreteInterpolation) + } + MenuItem { + text: qsTr('Linear') + checkable: true + checked: interpolation === KeyframesModel.LinearInterpolation + exclusiveGroup: keyframeTypeGroup + onTriggered: parameters.setInterpolation(parameterIndex, index, KeyframesModel.LinearInterpolation) + } + MenuItem { + text: qsTr('Smooth') + checkable: true + checked: interpolation === KeyframesModel.SmoothInterpolation + exclusiveGroup: keyframeTypeGroup + onTriggered: parameters.setInterpolation(parameterIndex, index, KeyframesModel.SmoothInterpolation) + } + } + MenuItem { + id: removeMenuItem + text: qsTr('Remove') + onTriggered: { + parameters.remove(parameterIndex, index) + root.selection = [] + } + } + onPopupVisibleChanged: { + if (visible && application.OS !== 'OS X' && __popupGeometry.height > 0) { + // Try to fix menu running off screen. This only works intermittently. + menu.__yOffset = Math.min(0, Screen.height - (__popupGeometry.y + __popupGeometry.height + 40)) + menu.__xOffset = Math.min(0, Screen.width - (__popupGeometry.x + __popupGeometry.width)) + } + } + onAboutToShow: tooltip.isVisible = false + onAboutToHide: tooltip.isVisible = true + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframes.js shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframes.js --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframes.js 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Keyframes.js 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +function trackHeight(isCurves) { + return isCurves? (multitrack.trackHeight * 2) : 36 +} + +function scrollIfNeeded() { + var x = producer.position * timeScale; + if (!scrollView) return; + if (x > scrollView.flickableItem.contentX + scrollView.width - 50) + scrollView.flickableItem.contentX = x - scrollView.width + 50; + else if (x < 50) + scrollView.flickableItem.contentX = 0; + else if (x < scrollView.flickableItem.contentX + 50) + scrollView.flickableItem.contentX = x - 50; +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/keyframes.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/keyframes.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/keyframes.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/keyframes.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2017-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.5 +import QtQml.Models 2.1 +import QtQuick.Controls 1.3 +import Shotcut.Controls 1.0 +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.2 +import 'Keyframes.js' as Logic + +Rectangle { + id: root + width: 400 + SystemPalette { id: activePalette } + color: activePalette.window + + property int selectedIndex: -1 + property int headerWidth: 140 + property int currentTrack: 0 + property color selectedTrackColor: Qt.rgba(0.8, 0.8, 0, 0.3); + property bool stopScrolling: false + property color shotcutBlue: Qt.rgba(23/255, 92/255, 118/255, 1.0) + property double timeScale: 1.0 + property var selection: [] + + signal keyframeClicked() + + onTimeScaleChanged: redrawWaveforms() + + function redrawWaveforms() { + Logic.scrollIfNeeded() + beforeClip.generateWaveform() + activeClip.generateWaveform() + afterClip.generateWaveform() + } + + function setZoom(value) { + keyframesToolbar.scaleSlider.value = value + } + + function adjustZoom(by) { + setZoom(keyframesToolbar.scaleSlider.value + by) + } + + function zoomIn() { + adjustZoom(0.0625) + } + + function zoomOut() { + adjustZoom(-0.0625) + } + + function resetZoom() { + setZoom(1.0) + } + + function zoomByWheel(wheel) { + if (wheel.modifiers & Qt.ControlModifier) { + adjustZoom(wheel.angleDelta.y / 720) + } + if (wheel.modifiers & Qt.ShiftModifier) { + multitrack.trackHeight = Math.max(30, multitrack.trackHeight + wheel.angleDelta.y / 5) + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: menu.popup() + } + + KeyframesToolbar { + id: keyframesToolbar + width: parent.width + height: ruler.height + 6 + anchors.top: parent.top + z: 1 + } + + Row { + anchors.top: keyframesToolbar.bottom + Column { + z: 1 + + Rectangle { + // Padding between toolbar and track headers. + width: headerWidth + height: ruler.height + color: activePalette.window + z: 1 + } + Flickable { + // Non-slider scroll area for the track headers. + contentY: scrollView.flickableItem.contentY + width: headerWidth + height: trackHeaders.height + interactive: false + + Column { + id: trackHeaders + + ParameterHead { + id: clipHead + visible: metadata !== null + trackName: metadata !== null? metadata.name : '' + width: headerWidth + height: Logic.trackHeight(true) + selected: false +// onIsLockedChanged: parametersRepeater.itemAt(index).isLocked = isLocked + } + Repeater { + id: trackHeaderRepeater + model: parameters + ParameterHead { + trackName: model.name + delegateIndex: index +// isLocked: model.locked + width: headerWidth + height: Logic.trackHeight(metadata !== null && typeof(metadata.keyframes.parameters[index]) !== 'undefined' && metadata.keyframes.parameters[index].isCurve) + current: false // index === currentTrack +// onIsLockedChanged: parametersRepeater.itemAt(index).isLocked = isLocked + onClicked: { + currentTrack = index +// timeline.selectTrackHead(currentTrack) + } + } + } + } + Rectangle { + // thin dividing line between headers and tracks + visible: metadata !== null + color: activePalette.windowText + width: 1 + x: parent.x + parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + } + } + } + MouseArea { + id: tracksArea + width: root.width - headerWidth + height: root.height + + // This provides continuous scrubbing and scimming at the left/right edges. + focus: true + hoverEnabled: true + onClicked: producer.position = (scrollView.flickableItem.contentX + mouse.x) / timeScale + onDoubleClicked: { + // Figure out which parameter row that is in. + for (var i = 0; i < parametersRepeater.count; i++) { + // Only for parameters with curves. + if (metadata.keyframes.parameters[i].isCurve) { + var point = tracksArea.mapToItem(parametersRepeater.itemAt(i), mouse.x, mouse.y) + var position = Math.round(point.x / timeScale) - (filter.in - producer.in) + var trackHeight = parametersRepeater.itemAt(i).height + var interpolation = 1 + // Get the interpolation from the previous keyframe if any. + for (var j = 0; j < parametersRepeater.itemAt(i).getKeyframeCount(); j++) { + var k = parametersRepeater.itemAt(i).getKeyframe(j) + if (k.position - (filter.in - producer.in) < position) + interpolation = k.interpolation + else + break + } + // If click position is within range. + if (position >= 0 && position < filter.duration + && point.y > 0 && point.y < trackHeight) { + // Determine the value to set. + var keyframeHeight = 10 + var trackValue = Math.min(Math.max(0, 1.0 - (point.y - keyframeHeight/2) / (trackHeight - keyframeHeight)), 1.0) + var minimum = metadata.keyframes.parameters[i].minimum + var maximum = metadata.keyframes.parameters[i].maximum + var value = minimum + trackValue * (maximum - minimum) + //console.log('clicked parameter ' + i + ' frame ' + position + ' trackValue ' + trackValue + ' value ' + value) + parameters.addKeyframe(i, value, position, interpolation) + break + } + } + } + } + + property bool scim: false + onReleased: scim = false + onExited: scim = false + onPositionChanged: { + if (mouse.modifiers === Qt.ShiftModifier || mouse.buttons === Qt.LeftButton) { + producer.position = (scrollView.flickableItem.contentX + mouse.x) / timeScale + scim = true + } + else + scim = false + } + Timer { + id: scrubTimer + interval: 25 + repeat: true + running: parent.scim && parent.containsMouse + && (parent.mouseX < 50 || parent.mouseX > parent.width - 50) + && (producer.position * timeScale >= 50) + onTriggered: { + if (parent.mouseX < 50) + producer.position -= 10 + else + producer.position += 10 + } + } + + Column { + Flickable { + // Non-slider scroll area for the Ruler. + contentX: scrollView.flickableItem.contentX + width: root.width - headerWidth + height: ruler.height + interactive: false + + Ruler { + id: ruler + width: producer.duration * timeScale + index: index + } + } + ScrollView { + id: scrollView + width: root.width - headerWidth + height: root.height - ruler.height - keyframesToolbar.height + + Item { + width: tracksContainer.width + headerWidth + height: trackHeaders.height + 30 // 30 is padding + Column { + Rectangle { + width: 1 + visible: metadata !== null + height: clipHead.height + } + // These make the striped background for the tracks. + // It is important that these are not part of the track visual hierarchy; + // otherwise, the clips will be obscured by the Track's background. + Repeater { + model: parameters + delegate: Rectangle { + width: tracksContainer.width + color: (false /*index === currentTrack*/)? selectedTrackColor : (index % 2)? activePalette.alternateBase : activePalette.base + height: Logic.trackHeight(metadata !== null && typeof(metadata.keyframes.parameters[index]) !== 'undefined' && metadata.keyframes.parameters[index].isCurve) + } + } + } + Column { + id: tracksContainer + Rectangle { + id: trackRoot + width: clipRow.width + height: Logic.trackHeight(true) + color: 'transparent' + Row { + id: clipRow + Clip { + id: beforeClip + visible: metadata !== null && filter !== null && filter.out > 0 && filter.in > 0 + isBlank: true + clipResource: producer.resource + mltService: producer.mlt_service + inPoint: producer.in + outPoint: filter !== null? filter.in - 1 : 0 + audioLevels: producer.audioLevels + height: trackRoot.height + hash: producer.hash + speed: producer.speed + outThumbnailVisible: false + } + Clip { + id: activeClip + visible: metadata !== null && filter !== null && filter.out > 0 + clipName: producer.name + clipResource: producer.resource + mltService: producer.mlt_service + inPoint: filter !== null? filter.in : 0 + outPoint: filter !== null? filter.out : 0 + animateIn: filter !== null? filter.animateIn : 0 + animateOut: filter !== null? filter.animateOut : 0 + audioLevels: producer.audioLevels + height: trackRoot.height + hash: producer.hash + speed: producer.speed + onTrimmingIn: { + var n = filter.in + delta + if (delta != 0 && n >= producer.in && n <= filter.out) { + filter.in = n + // Show amount trimmed as a time in a "bubble" help. + var s = application.timecode(Math.abs(clip.originalX)) + s = '%1%2 = %3'.arg((clip.originalX < 0)? '-' : (clip.originalX > 0)? '+' : '') + .arg(s.substring(3)) + .arg(application.timecode(n)) + bubbleHelp.show(clip.x, trackRoot.y + trackRoot.height, s) + } + } + onTrimmedIn: bubbleHelp.hide() + onTrimmingOut: { + var n = filter.out - delta + if (delta != 0 && n >= filter.in && n <= producer.out) { + filter.out = n + // Show amount trimmed as a time in a "bubble" help. + var s = application.timecode(Math.abs(clip.originalX)) + s = '%1%2 = %3'.arg((clip.originalX < 0)? '+' : (clip.originalX > 0)? '-' : '') + .arg(s.substring(3)) + .arg(application.timecode(n)) + bubbleHelp.show(clip.x + clip.width, trackRoot.y + trackRoot.height, s) + } + } + onTrimmedOut: bubbleHelp.hide() + } + Clip { + id: afterClip + visible: metadata !== null && filter !== null && filter.out > 0 + isBlank: true + clipResource: producer.resource + mltService: producer.mlt_service + inPoint: filter !== null? filter.out + 1 : 0 + outPoint: producer.out + audioLevels: producer.audioLevels + height: trackRoot.height + hash: producer.hash + speed: producer.speed + inThumbnailVisible: false + } + } + } + + Repeater { id: parametersRepeater; model: parameterDelegateModel } + } + } + } + } + + Rectangle { + id: cursor + visible: producer.position > -1 && metadata !== null + color: activePalette.text + width: 1 + height: root.height - scrollView.__horizontalScrollBar.height - keyframesToolbar.height + x: producer.position * timeScale - scrollView.flickableItem.contentX + y: 0 + } + TimelinePlayhead { + id: playhead + visible: producer.position > -1 && metadata !== null + x: producer.position * timeScale - scrollView.flickableItem.contentX - 5 + y: 0 + width: 11 + height: 5 + } + } + } + + Rectangle { + id: bubbleHelp + property alias text: bubbleHelpLabel.text + color: application.toolTipBaseColor + width: bubbleHelpLabel.width + 8 + height: bubbleHelpLabel.height + 8 + radius: 4 + states: [ + State { name: 'invisible'; PropertyChanges { target: bubbleHelp; opacity: 0} }, + State { name: 'visible'; PropertyChanges { target: bubbleHelp; opacity: 1} } + ] + state: 'invisible' + transitions: [ + Transition { + from: 'invisible' + to: 'visible' + OpacityAnimator { target: bubbleHelp; duration: 200; easing.type: Easing.InOutQuad } + }, + Transition { + from: 'visible' + to: 'invisible' + OpacityAnimator { target: bubbleHelp; duration: 200; easing.type: Easing.InOutQuad } + } + ] + Label { + id: bubbleHelpLabel + color: application.toolTipTextColor + anchors.centerIn: parent + } + function show(x, y, text) { + bubbleHelp.x = x + tracksArea.x - scrollView.flickableItem.contentX - bubbleHelpLabel.width + bubbleHelp.y = y + tracksArea.y - scrollView.flickableItem.contentY - bubbleHelpLabel.height + bubbleHelp.text = text + if (bubbleHelp.state !== 'visible') + bubbleHelp.state = 'visible' + } + function hide() { + bubbleHelp.state = 'invisible' + bubbleHelp.opacity = 0 + } + } + DropShadow { + source: bubbleHelp + anchors.fill: bubbleHelp + opacity: bubbleHelp.opacity + horizontalOffset: 3 + verticalOffset: 3 + radius: 8 + color: '#80000000' + transparentBorder: true + fast: true + } + + Menu { + id: menu + MenuItem { + text: qsTr('Show Audio Waveforms') + checkable: true + checked: settings.timelineShowWaveforms + onTriggered: { + if (checked) { + if (settings.timelineShowWaveforms) { + settings.timelineShowWaveforms = checked + redrawWaveforms() + } else { + settings.timelineShowWaveforms = checked + producer.remakeAudioLevels() + } + } else { + settings.timelineShowWaveforms = checked + } + } + } + MenuItem { + text: qsTr('Show Video Thumbnails') + checkable: true + checked: settings.timelineShowThumbnails + onTriggered: settings.timelineShowThumbnails = checked + } + MenuItem { + text: qsTr('Reload') + onTriggered: parameters.reload() + } + onPopupVisibleChanged: { + if (visible && application.OS === 'Windows' && __popupGeometry.height > 0) { + // Try to fix menu running off screen. This only works intermittently. + menu.__yOffset = Math.min(0, Screen.height - (__popupGeometry.y + __popupGeometry.height + 40)) + menu.__xOffset = Math.min(0, Screen.width - (__popupGeometry.x + __popupGeometry.width)) + } + } + } + + DelegateModel { + id: parameterDelegateModel + model: parameters + Parameter { + model: parameters + rootIndex: parameterDelegateModel.modelIndex(index) + width: producer.duration * timeScale + height: Logic.trackHeight(metadata !== null && typeof(metadata.keyframes.parameters[index]) !== 'undefined' && metadata.keyframes.parameters[index].isCurve) + onClicked: { + currentTrack = parameter.DelegateModel.itemsIndex + root.selection = [keyframe.DelegateModel.itemsIndex] + root.keyframeClicked() + } + } + } + + Connections { + target: producer + onPositionChanged: if (!stopScrolling) { + var x = position * timeScale; + if (!scrollView) return; + if (x > scrollView.flickableItem.contentX + scrollView.width - 50) + scrollView.flickableItem.contentX = x - scrollView.width + 50; + else if (x < 50) + scrollView.flickableItem.contentX = 0; + else if (x < scrollView.flickableItem.contentX + 50) + scrollView.flickableItem.contentX = x - 50; + } + } + + Connections { + target: filter + onChanged: { + var parameterIndex = parameters.parameterIndex(name) + if (parameterIndex > -1) { + currentTrack = parameterIndex + var keyframeIndex = parameters.keyframeIndex(parameterIndex, producer.position + producer.in) + if (keyframeIndex > -1) + selection = [keyframeIndex] + } + } + } + + // This provides continuous scrolling at the left/right edges. + Timer { + id: scrollTimer + interval: 25 + repeat: true + triggeredOnStart: true + property var item + property bool backwards + onTriggered: { + var delta = backwards? -10 : 10 + if (item) item.x += delta + scrollView.flickableItem.contentX += delta + if (scrollView.flickableItem.contentX <= 0) + stop() + } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/KeyframesToolbar.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/KeyframesToolbar.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/KeyframesToolbar.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/KeyframesToolbar.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.2 +import QtQuick.Controls 1.3 +import QtQuick.Layouts 1.0 + +ToolBar { + property alias scaleSlider: scaleSlider + + SystemPalette { id: activePalette } + + width: 200 + height: 24 + anchors.margins: 0 + + RowLayout { + ToolButton { + action: menuAction + implicitWidth: 28 + implicitHeight: 24 + } + Button { // separator + enabled: false + implicitWidth: 1 + implicitHeight: 20 + } + ToolButton { + action: zoomOutAction + implicitWidth: 28 + implicitHeight: 24 + } + ZoomSlider { + id: scaleSlider + } + ToolButton { + action: zoomInAction + implicitWidth: 28 + implicitHeight: 24 + } + } + + Action { + id: menuAction + tooltip: qsTr('Display a menu of additional actions') + iconName: 'format-justify-fill' + iconSource: 'qrc:///icons/oxygen/32x32/actions/format-justify-fill.png' + onTriggered: menu.popup() + } + Action { + id: zoomOutAction + tooltip: qsTr("Zoom timeline out (-)") + iconName: 'zoom-out' + iconSource: 'qrc:///icons/oxygen/32x32/actions/zoom-out.png' + onTriggered: root.zoomOut() + } + Action { + id: zoomInAction + tooltip: qsTr("Zoom timeline in (+)") + iconName: 'zoom-in' + iconSource: 'qrc:///icons/oxygen/32x32/actions/zoom-in.png' + onTriggered: root.zoomIn() + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ParameterHead.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ParameterHead.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ParameterHead.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ParameterHead.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2017-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0 +import Shotcut.Controls 1.0 as Shotcut + +Rectangle { + id: paramHeadRoot + property string trackName: '' + property bool isLocked: false + property bool selected: false + property bool current: false + property int delegateIndex: -1 + signal clicked() + + SystemPalette { id: activePalette } + color: selected ? selectedTrackColor : (delegateIndex % 2)? activePalette.alternateBase : activePalette.base + border.color: selected? 'red' : 'transparent' + border.width: selected? 1 : 0 + clip: true + state: 'normal' + states: [ + State { + name: 'selected' + when: paramHeadRoot.selected + PropertyChanges { + target: paramHeadRoot + color: root.shotcutBlue + } + }, + State { + name: 'current' + when: paramHeadRoot.current + PropertyChanges { + target: paramHeadRoot + color: selectedTrackColor + } + }, + State { + name: 'normal' + when: !paramHeadRoot.selected && !paramHeadRoot.current + PropertyChanges { + target: paramHeadRoot + color: (delegateIndex % 2)? activePalette.alternateBase : activePalette.base + } + } + ] + transitions: [ + Transition { + to: '*' + ColorAnimation { target: paramHeadRoot; duration: 100 } + } + ] + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + parent.clicked() + if (mouse.button == Qt.RightButton) + menu.popup() + } + } + Column { + id: trackHeadColumn + spacing: (paramHeadRoot.height < 50)? 0 : 6 + anchors { + top: parent.top + left: parent.left + margins: (paramHeadRoot.height < 50)? 0 : 4 + } + + Label { + text: trackName + color: activePalette.windowText + elide: Qt.ElideRight + x: 4 + y: 3 + width: paramHeadRoot.width - trackHeadColumn.anchors.margins * 2 - 8 + } + RowLayout { + spacing: 8 + ToolButton { + id: previousButton + implicitWidth: 20 + implicitHeight: 20 + iconName: 'media-skip-backward' + iconSource: 'qrc:///icons/oxygen/32x32/actions/media-skip-backward.png' + onClicked: { + if (delegateIndex >= 0) { + root.selection = [keyframes.seekPrevious()] + root.currentTrack = delegateIndex + } else { + var position = producer.position + producer.in + if (position > filter.out) + position = filter.out + else if (position > filter.out - filter.animateOut) + position = filter.out - filter.animateOut + else if (producer.position > filter.in + filter.animateIn) + position = filter.in + filter.animateIn + else if (producer.position > filter.in) + position = filter.in + else + position = 0 + producer.position = position - producer.in + } + } + tooltip: (delegateIndex >= 0) ? qsTr('Seek to previous keyframe') : qsTr('Seek backwards') + } + + ToolButton { + id: deleteButton + visible: delegateIndex >= 0 + enabled: root.selection.length > 0 + implicitWidth: 20 + implicitHeight: 20 + iconName: 'edit-delete' + iconSource: 'qrc:///icons/oxygen/32x32/actions/edit-delete.png' + opacity: enabled? 1.0 : 0.5 + onClicked: { + parameters.remove(root.currentTrack, root.selection[0]) + root.selection = [] + } + tooltip: qsTr('Delete the selected keyframe') + } + Item { + visible: delegateIndex < 0 + width: 20 + height: 20 + } + + ToolButton { + id: nextButton + implicitWidth: 20 + implicitHeight: 20 + iconName: 'media-skip-forward' + iconSource: 'qrc:///icons/oxygen/32x32/actions/media-skip-forward.png' + onClicked: { + if (delegateIndex >= 0) { + root.selection = [keyframes.seekNext()] + root.currentTrack = delegateIndex + } else { + var position = producer.position + producer.in + if (position < filter.in) + position = filter.in + else if (position < filter.in + filter.animateIn) + position = filter.in + filter.animateIn + else if (position < filter.out - filter.animateOut) + position = filter.out - filter.animateOut + else if (position < filter.out) + position = filter.out + else + position = producer.out + producer.position = position - producer.in + } + } + tooltip: (delegateIndex >= 0) ? qsTr('Seek to next keyframe') : qsTr('Seek forwards') + } + + ToolButton { + id: lockButton + visible: false && delegateIndex >= 0 + implicitWidth: 20 + implicitHeight: 20 + iconName: isLocked ? 'object-locked' : 'object-unlocked' + iconSource: isLocked ? 'qrc:///icons/oxygen/32x32/status/object-locked.png' : 'qrc:///icons/oxygen/32x32/status/object-unlocked.png' +// onClicked: timeline.setTrackLock(index, !isLocked) + tooltip: isLocked? qsTr('Unlock track') : qsTr('Lock track') + } + } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Parameter.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Parameter.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Parameter.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Parameter.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018 Meltytech, 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 . + */ + +import QtQuick 2.0 +import QtQml.Models 2.1 +import org.shotcut.qml 1.0 + +Item { + id: parameterRoot + property alias model: keyframeDelegateModel.model + property alias rootIndex: keyframeDelegateModel.rootIndex + property bool isLocked: false + + signal clicked(var keyframe, var parameter) + + function getKeyframeCount() { + return keyframesRepeater.count + } + + function getKeyframe(keyframeIndex) { + if (keyframeIndex < keyframesRepeater.count) + return keyframesRepeater.itemAt(keyframeIndex) + else + return null + } + + Repeater { id: keyframesRepeater; model: keyframeDelegateModel; onCountChanged: canvas.requestPaint() } + + Canvas { + id: canvas + visible: metadata !== null && metadata.keyframes.parameters[parameterRoot.DelegateModel.itemsIndex].isCurve + anchors.fill: parent + + function catmullRomToBezier(context, i) { + var a = 1.0 / 6.0 + var g = i-2 >= 0 ? i-2 : i + var h = i-1 >= 0 ? i-1 : i + var j = i+1 < keyframesRepeater.count ? i+1 : i + var widthOffset = keyframesRepeater.itemAt(0).width / 2 + var heightOffset = keyframesRepeater.itemAt(0).height / 2 + var points = [ + {x: keyframesRepeater.itemAt(g).x + widthOffset, y: keyframesRepeater.itemAt(g).y + heightOffset}, + {x: keyframesRepeater.itemAt(h).x + widthOffset, y: keyframesRepeater.itemAt(h).y + heightOffset}, + {x: keyframesRepeater.itemAt(i).x + widthOffset, y: keyframesRepeater.itemAt(i).y + heightOffset}, + {x: keyframesRepeater.itemAt(j).x + widthOffset, y: keyframesRepeater.itemAt(j).y + heightOffset}, + ] + context.bezierCurveTo(-a*points[0].x + points[1].x + a*points[2].x, + -a*points[0].y + points[1].y + a*points[2].y, + a*points[1].x + points[2].x - a*points[3].x, + a*points[1].y + points[2].y - a*points[3].y, + points[2].x, points[2].y) + } + + onPaint: { + var ctx = getContext("2d") + ctx.strokeStyle = activePalette.buttonText + ctx.lineWidth = 1.0 + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.beginPath() + if (keyframesRepeater.count) { + var widthOffset = keyframesRepeater.itemAt(0).width / 2 + var heightOffset = keyframesRepeater.itemAt(0).height / 2 + // Draw extent before first keyframe. + var startX = (filter.in - producer.in) * timeScale + ctx.moveTo(startX, keyframesRepeater.itemAt(0).y + heightOffset) + ctx.lineTo(keyframesRepeater.itemAt(0).x + widthOffset, keyframesRepeater.itemAt(0).y + heightOffset) + // Draw lines between keyframes. + for (var i = 1; i < keyframesRepeater.count; i++) { + switch (keyframesRepeater.itemAt(i - 1).interpolation) { + case KeyframesModel.LinearInterpolation: + ctx.lineTo(keyframesRepeater.itemAt(i).x + widthOffset, keyframesRepeater.itemAt(i).y + heightOffset) + break + case KeyframesModel.SmoothInterpolation: + catmullRomToBezier(ctx, i) + ctx.moveTo(keyframesRepeater.itemAt(i).x + widthOffset, keyframesRepeater.itemAt(i).y + heightOffset) + break + default: // KeyframesModel.DiscreteInterpolation + ctx.lineTo(keyframesRepeater.itemAt(i).x + widthOffset, keyframesRepeater.itemAt(i - 1).y + heightOffset) + ctx.moveTo(keyframesRepeater.itemAt(i).x + widthOffset, keyframesRepeater.itemAt(i).y + heightOffset) + break + } + } + // Draw extent after last keyframe. + ctx.lineTo((filter.out - producer.in + 1) * timeScale, keyframesRepeater.itemAt(i - 1).y + heightOffset) + } + ctx.stroke() + } + } + + DelegateModel { + id: keyframeDelegateModel + Keyframe { + position: (filter.in - producer.in) + model.frame + interpolation: model.interpolation + name: model.name + value: model.value + minDragX: (filter.in - producer.in + model.minimumFrame) * timeScale - width/2 + maxDragX: (filter.in - producer.in + model.maximumFrame) * timeScale - width/2 + isSelected: root.currentTrack === parameterRoot.DelegateModel.itemsIndex && root.selection.indexOf(index) !== -1 + parameterIndex: parameterRoot.DelegateModel.itemsIndex + onClicked: parameterRoot.clicked(keyframe, parameterRoot) + onInterpolationChanged: canvas.requestPaint() + onPositionChanged: canvas.requestPaint() + onValueChanged: canvas.requestPaint() + } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Ruler.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Ruler.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/Ruler.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/Ruler.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.0 + +Rectangle { + property int index: 0 + property int stepSize: Math.round(100 * Math.max(1.0, timeScale / 3.0)) + + SystemPalette { id: activePalette } + + id: rulerTop + enabled: false + height: 24 + color: activePalette.base + + Repeater { + model: parent.width / stepSize + Rectangle { + anchors.bottom: rulerTop.bottom + height: 18 + width: 1 + color: activePalette.windowText + x: index * stepSize + Label { + anchors.left: parent.right + anchors.leftMargin: 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: 2 + color: activePalette.windowText + x: index * stepSize + 2 + text: application.timecode(index * stepSize / timeScale) + font.pointSize: 7.5 + } + } + } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ToggleButton.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ToggleButton.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ToggleButton.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ToggleButton.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import Shotcut.Controls 1.0 as Shotcut + +CheckBox { + property string iconName + property url iconSource + property alias tooltip: tooltip.text + + anchors.verticalCenter: parent.verticalCenter + style: CheckBoxStyle { + background: Rectangle { + implicitWidth: 28 + implicitHeight: 24 + radius: 3 + SystemPalette { id: activePalette } + color: control.checked? activePalette.highlight : activePalette.button + border.color: activePalette.shadow + border.width: 1 + } + indicator: ToolButton { + x: 3 + implicitWidth: 24 + implicitHeight: 20 + iconName: control.iconName + iconSource: control.iconSource + } + } + Shotcut.ToolTip { id: tooltip } +} diff -Nru shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ZoomSlider.qml shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ZoomSlider.qml --- shotcut-18.03.06/Shotcut.app/share/shotcut/qml/views/keyframes/ZoomSlider.qml 1970-01-01 00:00:00.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/share/shotcut/qml/views/keyframes/ZoomSlider.qml 2018-06-02 07:58:01.000000000 +0000 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-2018 Meltytech, LLC + * Author: Dan Dennedy + * + * 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 . + */ + +import QtQuick 2.2 +import QtQuick.Controls 1.0 + +Rectangle { + property alias value: slider.value + + SystemPalette { id: activePalette } + + color: activePalette.window + width: 200 + height: 24 + + Slider { + id: slider + orientation: Qt.Horizontal + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + leftMargin: 4 + rightMargin: 4 + } + minimumValue: 0 + maximumValue: 3.0 + value: 1 + function setScaleFactor() { + timeScale = Math.pow(value, 3) + 0.01 + } + onValueChanged: { + if (!pressed) + setScaleFactor() + } + onPressedChanged: { + if (!pressed) + setScaleFactor() + } + } +} Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_ca.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_ca.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_cs.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_cs.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_da.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_da.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_de.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_de.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_el.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_el.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_en.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_en.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_es.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_es.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_et.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_et.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_fr.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_fr.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_gd.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_gd.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_hu.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_hu.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_it.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_it.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_ja.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_ja.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_nb.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_nb.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_ne.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_ne.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_nl.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_nl.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_oc.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_oc.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_pl.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_pl.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_pt_BR.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_pt_BR.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_pt_PT.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_pt_PT.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_ru.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_ru.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_sk.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_sk.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_sl.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_sl.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_tr.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_tr.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_uk.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_uk.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_zh_CN.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_zh_CN.qm differ Binary files /tmp/tmpRNjcbs/JExeL5eyvz/shotcut-18.03.06/Shotcut.app/share/shotcut/translations/shotcut_zh_TW.qm and /tmp/tmpRNjcbs/te75Rr7hRI/shotcut-18.06.02/Shotcut.app/share/shotcut/translations/shotcut_zh_TW.qm differ diff -Nru shotcut-18.03.06/Shotcut.app/versions shotcut-18.06.02/Shotcut.app/versions --- shotcut-18.03.06/Shotcut.app/versions 2018-03-06 10:02:12.000000000 +0000 +++ shotcut-18.06.02/Shotcut.app/versions 2018-06-02 09:10:35.000000000 +0000 @@ -1,12 +1,13 @@ -vid.stab afc8ea9fb0632e0cdf781725a770edb1de788fed v1.1.0-3-gafc8ea9 +vid.stab 38ecbaf8ece45edd907994660ecd50f0db817b98 v1.1.0-16-g38ecbaf +nv-codec-headers 91d9e20b82af90569a42fac429f5685051b0ccd5 n8.1.24.2-1-g91d9e20 opus defbc370ec764d3d0dc43c45911e455b5e483b30 v1.2.1 -libvpx e0b88b5c00b8026876da72e2f6c6fadf479d394d v1.7.0-134-ge0b88b5 -x265 d2c11daee6e4814a5ed99627dad3dc0ca066c41a 2.7-2-gd2c11da +libvpx 3f7e6cc020446ee29439f1cd7d3d5c39adaf64c0 v1.7.0-428-g3f7e6cc +x265 1755d3d39478135361101c1d6ce22902498b422d 2.8-346-g1755d3d x264 e9a5903edf8ca59ef20e6f4894c196f135af735e libepoxy e2c33af5bfcfc9d168f9e776156dd47c33f428b3 v1.3.1 -movit 3e8b4ebb796bcbe7e9727a2d7f2d0ba8f0170dfa -frei0r 2479e7556094db271b9b10893a1d65ba1a155859 v1.6.1-9-g2479e75 -FFmpeg e4b95f710ba4df47e0437d6f90065cc6d4b92c1d n3.4.2-3-ge4b95f7 -mlt 14aaea69be922cbb4eb4ba46c4bf2c94fb0858da v6.6.0-21-g14aaea6 -shotcut a3efc0ab07c518ecf3c0dae97c2823fafb8fe7e6 v18.03-9-ga3efc0a +movit d8b565fbe6e67c01586819aeefd5e7466c8f82fd +frei0r 4b363c644e505ce34c79b27d2d664713cbb3dbaa v1.6.1-11-g4b363c6 +FFmpeg c8b57d4333b1bc5c4125fdac8fbf4625c399fc9c n4.0-40-gc8b57d4 +mlt ee1b9cbdd311a24dc724215f15dc0116725dee00 v6.8.0-32-gee1b9cb +shotcut a135d38d53cb30809f90ea0b247441b6b6395c65 v18.06-2-ga135d38 webvfx d65a42d25a887d11fd5cf5e485c1561dc802b671 0.4.4-61-gd65a42d diff -Nru shotcut-18.03.06/Shotcut.desktop shotcut-18.06.02/Shotcut.desktop --- shotcut-18.03.06/Shotcut.desktop 2018-03-06 10:02:12.000000000 +0000 +++ shotcut-18.06.02/Shotcut.desktop 2018-05-19 01:26:34.000000000 +0000 @@ -1,12 +1,8 @@ -#!/usr/bin/env xdg-open [Desktop Entry] Type=Application Name=Shotcut -Name[de]=Shotcut GenericName=Video Editor -GenericName[de]=Video Bearbeitungsprogramm Comment=Video Editor -Comment[de]=Programm zum Bearbeiten und Abspielen von Videodateien. Terminal=false Exec=sh -c "$(dirname "%k")/Shotcut.app/shotcut "%F"" Icon=applications-multimedia