From 40f82866427ea9b56fadbd688f0e20b32239f6bf Mon Sep 17 00:00:00 2001 From: tpearson Date: Wed, 24 Feb 2010 18:16:21 +0000 Subject: [PATCH] Added KDE3 version of MLT git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/libraries/mlt@1095634 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- COPYING | 504 +++++ ChangeLog | 58 + GPL | 340 +++ Makefile | 61 + README | 65 + config.log | 2 + configure-stamp | 0 demo/README | 217 ++ demo/circle.png | Bin 0 -> 5238 bytes demo/circle.svg | 1 + demo/consumers.ini | 12 + demo/demo | 106 + demo/demo.ini | 28 + demo/demo.kino | 13 + demo/entity.westley | 11 + demo/luma1.pgm | Bin 0 -> 414735 bytes demo/mlt_all | 3 + demo/mlt_attributes | 7 + demo/mlt_audio_stuff | 6 + demo/mlt_avantika_title | 3 + demo/mlt_bouncy | 10 + demo/mlt_bouncy_ball | 14 + demo/mlt_clock_in_and_out | 7 + demo/mlt_composite_transition | 7 + demo/mlt_effect_in_middle | 4 + demo/mlt_fade_black | 9 + demo/mlt_fade_in_and_out | 9 + demo/mlt_intro | 9 + demo/mlt_jcut | 10 + demo/mlt_lcut | 12 + demo/mlt_levels | 5 + demo/mlt_my_name_is | 10 + demo/mlt_news | 18 + demo/mlt_obscure | 5 + demo/mlt_push | 18 + demo/mlt_slideshow | 4 + demo/mlt_slideshow_black | 3 + demo/mlt_squeeze | 9 + demo/mlt_squeeze_box | 9 + demo/mlt_ticker | 15 + demo/mlt_title_over_gfx | 25 + demo/mlt_titleshadow_watermark | 10 + demo/mlt_voiceover | 36 + demo/mlt_watermark | 6 + demo/new.westley | 51 + demo/pango.westley | 34 + demo/svg.westley | 50 + demo/watermark1.png | Bin 0 -> 1352 bytes docs/TODO | 1 + docs/dvcp.txt | 339 +++ docs/framework.txt | 1341 ++++++++++++ docs/inigo.txt | 378 ++++ docs/install.txt | 187 ++ docs/policies.txt | 52 + docs/services.txt | 1588 ++++++++++++++ docs/testing-20040110.txt | 35 + docs/testing.txt | 599 ++++++ docs/valerie.txt | 861 ++++++++ docs/westley.txt | 574 +++++ mlt-config-template | 32 + mlt-framework.pc | 14 + mlt-framework.pc.in | 7 + mlt-miracle.pc | 14 + mlt-miracle.pc.in | 7 + mlt-valerie.pc | 14 + mlt-valerie.pc.in | 7 + profiles/Makefile | 18 + profiles/atsc_1080i_60 | 10 + profiles/atsc_720p_30 | 10 + profiles/cif_ntsc | 10 + profiles/cif_pal | 10 + profiles/cvd_ntsc | 10 + profiles/cvd_pal | 10 + profiles/dv_ntsc | 10 + profiles/dv_ntsc_wide | 10 + profiles/dv_pal | 10 + profiles/dv_pal_wide | 10 + profiles/hdv_1080_50i | 10 + profiles/hdv_1080_60i | 10 + profiles/hdv_720_25p | 10 + profiles/hdv_720_30p | 10 + profiles/qcif_ntsc | 10 + profiles/qcif_pal | 10 + profiles/quarter_ntsc | 10 + profiles/quarter_ntsc_wide | 10 + profiles/quarter_pal | 10 + profiles/quarter_pal_wide | 10 + profiles/square_ntsc | 10 + profiles/square_ntsc_wide | 10 + profiles/square_pal | 10 + profiles/square_pal_wide | 10 + profiles/svcd_ntsc | 10 + profiles/svcd_ntsc_wide | 10 + profiles/svcd_pal | 10 + profiles/svcd_pal_wide | 10 + profiles/vcd_ntsc | 10 + profiles/vcd_pal | 10 + setenv | 25 + setenv_mc | 9 + src/albino/Makefile | 36 + src/albino/albino.c | 110 + src/framework/Makefile | 97 + src/framework/config.h | 9 + src/framework/configure | 2 + src/framework/mlt.h | 51 + src/framework/mlt_consumer.c | 797 +++++++ src/framework/mlt_consumer.h | 80 + src/framework/mlt_deque.c | 297 +++ src/framework/mlt_deque.h | 51 + src/framework/mlt_events.c | 403 ++++ src/framework/mlt_events.h | 52 + src/framework/mlt_factory.c | 304 +++ src/framework/mlt_factory.h | 38 + src/framework/mlt_field.c | 193 ++ src/framework/mlt_field.h | 37 + src/framework/mlt_filter.c | 213 ++ src/framework/mlt_filter.h | 62 + src/framework/mlt_frame.c | 1310 ++++++++++++ src/framework/mlt_frame.h | 118 ++ src/framework/mlt_geometry.c | 700 +++++++ src/framework/mlt_geometry.h | 73 + src/framework/mlt_multitrack.c | 436 ++++ src/framework/mlt_multitrack.h | 65 + src/framework/mlt_parser.c | 243 +++ src/framework/mlt_parser.h | 52 + src/framework/mlt_playlist.c | 1500 +++++++++++++ src/framework/mlt_playlist.h | 109 + src/framework/mlt_pool.c | 355 ++++ src/framework/mlt_pool.h | 31 + src/framework/mlt_producer.c | 846 ++++++++ src/framework/mlt_producer.h | 79 + src/framework/mlt_profile.c | 241 +++ src/framework/mlt_profile.h | 49 + src/framework/mlt_properties.c | 912 ++++++++ src/framework/mlt_properties.h | 79 + src/framework/mlt_property.c | 340 +++ src/framework/mlt_property.h | 87 + src/framework/mlt_repository.c | 209 ++ src/framework/mlt_repository.h | 39 + src/framework/mlt_service.c | 529 +++++ src/framework/mlt_service.h | 69 + src/framework/mlt_tokeniser.c | 169 ++ src/framework/mlt_tokeniser.h | 46 + src/framework/mlt_tractor.c | 474 +++++ src/framework/mlt_tractor.h | 52 + src/framework/mlt_transition.c | 326 +++ src/framework/mlt_transition.h | 70 + src/framework/mlt_types.h | 110 + src/humperdink/Makefile | 38 + src/humperdink/client.c | 1025 +++++++++ src/humperdink/client.h | 66 + src/humperdink/io.c | 205 ++ src/humperdink/io.h | 36 + src/humperdink/remote.c | 73 + src/inigo/Makefile | 37 + src/inigo/configure | 0 src/inigo/inigo.c | 381 ++++ src/inigo/io.c | 196 ++ src/inigo/io.h | 45 + src/miracle/Makefile | 71 + src/miracle/configure | 2 + src/miracle/miracle.c | 122 ++ src/miracle/miracle_commands.c | 248 +++ src/miracle/miracle_commands.h | 52 + src/miracle/miracle_connection.c | 292 +++ src/miracle/miracle_connection.h | 92 + src/miracle/miracle_local.c | 597 ++++++ src/miracle/miracle_local.h | 41 + src/miracle/miracle_log.c | 57 + src/miracle/miracle_log.h | 43 + src/miracle/miracle_server.c | 323 +++ src/miracle/miracle_server.h | 76 + src/miracle/miracle_unit.c | 769 +++++++ src/miracle/miracle_unit.h | 82 + src/miracle/miracle_unit_commands.c | 485 +++++ src/miracle/miracle_unit_commands.h | 61 + src/modules/Makefile | 32 + src/modules/avformat/Makefile | 62 + src/modules/avformat/config.mak | 1 + src/modules/avformat/configure | 159 ++ src/modules/avformat/consumer_avformat.c | 1192 +++++++++++ src/modules/avformat/consumer_avformat.h | 28 + src/modules/avformat/factory.c | 128 ++ src/modules/avformat/ffmpeg.patch | 15 + src/modules/avformat/filter_avcolour_space.c | 243 +++ src/modules/avformat/filter_avcolour_space.h | 28 + src/modules/avformat/filter_avdeinterlace.c | 350 ++++ src/modules/avformat/filter_avdeinterlace.h | 28 + src/modules/avformat/filter_avresample.c | 197 ++ src/modules/avformat/filter_avresample.h | 28 + src/modules/avformat/mmx.h | 243 +++ src/modules/avformat/producer_avformat.c | 1067 ++++++++++ src/modules/avformat/producer_avformat.h | 28 + src/modules/configure | 36 + src/modules/core/Makefile | 61 + src/modules/core/composite_line_yuv_mmx.S | 211 ++ src/modules/core/configure | 42 + src/modules/core/consumer_null.c | 183 ++ src/modules/core/consumer_null.h | 28 + src/modules/core/factory.c | 112 + src/modules/core/filter_brightness.c | 103 + src/modules/core/filter_brightness.h | 28 + src/modules/core/filter_channelcopy.c | 97 + src/modules/core/filter_channelcopy.h | 28 + src/modules/core/filter_data.h | 30 + src/modules/core/filter_data_feed.c | 178 ++ src/modules/core/filter_data_show.c | 344 +++ src/modules/core/filter_gamma.c | 89 + src/modules/core/filter_gamma.h | 28 + src/modules/core/filter_greyscale.c | 63 + src/modules/core/filter_greyscale.h | 28 + src/modules/core/filter_luma.c | 128 ++ src/modules/core/filter_luma.h | 28 + src/modules/core/filter_mirror.c | 336 +++ src/modules/core/filter_mirror.h | 28 + src/modules/core/filter_mono.c | 95 + src/modules/core/filter_mono.h | 28 + src/modules/core/filter_obscure.c | 310 +++ src/modules/core/filter_obscure.h | 28 + src/modules/core/filter_region.c | 88 + src/modules/core/filter_region.h | 28 + src/modules/core/filter_rescale.c | 321 +++ src/modules/core/filter_rescale.h | 28 + src/modules/core/filter_resize.c | 201 ++ src/modules/core/filter_resize.h | 28 + src/modules/core/filter_transition.c | 114 + src/modules/core/filter_transition.h | 28 + src/modules/core/filter_watermark.c | 263 +++ src/modules/core/filter_watermark.h | 28 + src/modules/core/producer_colour.c | 253 +++ src/modules/core/producer_colour.h | 28 + src/modules/core/producer_noise.c | 177 ++ src/modules/core/producer_noise.h | 28 + src/modules/core/producer_ppm.c | 274 +++ src/modules/core/producer_ppm.h | 28 + src/modules/core/transition_composite.c | 1215 +++++++++++ src/modules/core/transition_composite.h | 31 + src/modules/core/transition_luma.c | 586 ++++++ src/modules/core/transition_luma.h | 28 + src/modules/core/transition_mix.c | 166 ++ src/modules/core/transition_mix.h | 28 + src/modules/core/transition_region.c | 452 ++++ src/modules/core/transition_region.h | 28 + src/modules/data_fx.properties | 250 +++ src/modules/disable-avformat | 0 src/modules/disable-dv | 0 src/modules/disable-jackrack | 0 src/modules/disable-mmx | 0 src/modules/dv/Makefile | 36 + src/modules/dv/configure | 19 + src/modules/dv/consumer_libdv.c | 449 ++++ src/modules/dv/consumer_libdv.h | 28 + src/modules/dv/factory.c | 49 + src/modules/dv/producer_libdv.c | 525 +++++ src/modules/dv/producer_libdv.h | 31 + src/modules/effectv/Makefile | 35 + src/modules/effectv/configure | 19 + src/modules/effectv/factory.c | 44 + src/modules/effectv/filter_burn.c | 214 ++ src/modules/effectv/filter_burn.h | 27 + src/modules/effectv/gpl | 0 src/modules/effectv/image.c | 307 +++ src/modules/effectv/utils.c | 58 + src/modules/effectv/utils.h | 48 + src/modules/feeds/Makefile | 15 + src/modules/feeds/NTSC/data_fx.properties | 250 +++ src/modules/feeds/NTSC/obscure.properties | 26 + src/modules/feeds/PAL/border.properties | 22 + src/modules/feeds/PAL/data_fx.properties | 76 + src/modules/feeds/PAL/etv.properties | 186 ++ src/modules/feeds/PAL/example.properties | 12 + src/modules/feeds/PAL/obscure.properties | 35 + src/modules/feeds/configure | 0 src/modules/fezzik.dict | 38 + src/modules/fezzik.ini | 13 + src/modules/fezzik/Makefile | 36 + src/modules/fezzik/configure | 12 + src/modules/fezzik/factory.c | 49 + src/modules/fezzik/producer_fezzik.c | 179 ++ src/modules/fezzik/producer_fezzik.h | 28 + src/modules/fezzik/producer_hold.c | 203 ++ src/modules/fezzik/producer_hold.h | 28 + src/modules/gtk2/Makefile | 60 + src/modules/gtk2/config.h | 4 + src/modules/gtk2/config.mak | 4 + src/modules/gtk2/configure | 43 + src/modules/gtk2/consumer_gtk2.c | 56 + src/modules/gtk2/consumer_gtk2.h | 29 + src/modules/gtk2/factory.c | 93 + src/modules/gtk2/filter_rescale.c | 158 ++ src/modules/gtk2/filter_rescale.h | 28 + src/modules/gtk2/have_mmx.S | 53 + src/modules/gtk2/pixops.c | 769 +++++++ src/modules/gtk2/pixops.h | 72 + src/modules/gtk2/producer_pango.c | 699 +++++++ src/modules/gtk2/producer_pango.h | 37 + src/modules/gtk2/producer_pixbuf.c | 499 +++++ src/modules/gtk2/producer_pixbuf.h | 28 + src/modules/gtk2/scale_line_22_yuv_mmx.S | 227 ++ src/modules/inigo/Makefile | 33 + src/modules/inigo/configure | 12 + src/modules/inigo/factory.c | 48 + src/modules/inigo/producer_inigo.c | 442 ++++ src/modules/inigo/producer_inigo.h | 29 + src/modules/jackrack/Makefile | 47 + src/modules/jackrack/configure | 31 + src/modules/jackrack/factory.c | 48 + src/modules/jackrack/filter_jackrack.c | 373 ++++ src/modules/jackrack/filter_jackrack.h | 28 + src/modules/jackrack/filter_ladspa.c | 191 ++ src/modules/jackrack/filter_ladspa.h | 28 + src/modules/jackrack/gpl | 0 src/modules/jackrack/jack_rack.c | 359 ++++ src/modules/jackrack/jack_rack.h | 72 + src/modules/jackrack/lock_free_fifo.c | 117 ++ src/modules/jackrack/lock_free_fifo.h | 53 + src/modules/jackrack/plugin.c | 596 ++++++ src/modules/jackrack/plugin.h | 88 + src/modules/jackrack/plugin_desc.c | 417 ++++ src/modules/jackrack/plugin_desc.h | 81 + src/modules/jackrack/plugin_mgr.c | 321 +++ src/modules/jackrack/plugin_mgr.h | 53 + src/modules/jackrack/plugin_settings.c | 395 ++++ src/modules/jackrack/plugin_settings.h | 76 + src/modules/jackrack/process.c | 600 ++++++ src/modules/jackrack/process.h | 76 + src/modules/kdenlive/Makefile | 37 + src/modules/kdenlive/configure | 8 + src/modules/kdenlive/factory.c | 50 + src/modules/kdenlive/filter_boxblur.c | 233 +++ src/modules/kdenlive/filter_boxblur.h | 27 + src/modules/kdenlive/filter_wave.c | 139 ++ src/modules/kdenlive/filter_wave.h | 27 + src/modules/kdenlive/producer_framebuffer.c | 280 +++ src/modules/kdenlive/producer_framebuffer.h | 27 + src/modules/kino/Makefile | 45 + src/modules/kino/avi.cc | 1860 +++++++++++++++++ src/modules/kino/avi.h | 447 ++++ src/modules/kino/configure | 24 + src/modules/kino/endian_types.h | 265 +++ src/modules/kino/error.cc | 103 + src/modules/kino/error.h | 51 + src/modules/kino/factory.c | 46 + src/modules/kino/filehandler.cc | 940 +++++++++ src/modules/kino/filehandler.h | 217 ++ src/modules/kino/gpl | 0 src/modules/kino/kino_wrapper.cc | 110 + src/modules/kino/kino_wrapper.h | 45 + src/modules/kino/producer_kino.c | 145 ++ src/modules/kino/producer_kino.h | 28 + src/modules/kino/riff.cc | 711 +++++++ src/modules/kino/riff.h | 143 ++ src/modules/lumas/.compress | 0 src/modules/lumas/Makefile | 23 + src/modules/lumas/configure | 26 + src/modules/lumas/create_lumas | 48 + src/modules/lumas/luma.c | 441 ++++ src/modules/motion_est/Makefile | 55 + src/modules/motion_est/README | 97 + src/modules/motion_est/arrow_code.c | 165 ++ src/modules/motion_est/arrow_code.h | 26 + src/modules/motion_est/configure | 23 + src/modules/motion_est/factory.c | 45 + .../motion_est/filter_autotrack_rectangle.c | 326 +++ src/modules/motion_est/filter_crop_detect.c | 244 +++ src/modules/motion_est/filter_motion_est.c | 1115 ++++++++++ src/modules/motion_est/filter_motion_est.h | 42 + src/modules/motion_est/filter_vismv.c | 144 ++ src/modules/motion_est/gpl | 0 src/modules/motion_est/producer_slowmotion.c | 418 ++++ src/modules/motion_est/sad_sse.h | 429 ++++ src/modules/normalize/Makefile | 33 + src/modules/normalize/configure | 10 + src/modules/normalize/factory.c | 45 + src/modules/normalize/filter_volume.c | 458 ++++ src/modules/normalize/filter_volume.h | 28 + src/modules/normalize/gpl | 0 src/modules/plus/Makefile | 37 + src/modules/plus/configure | 23 + src/modules/plus/factory.c | 57 + src/modules/plus/filter_affine.c | 133 ++ src/modules/plus/filter_affine.h | 28 + src/modules/plus/filter_charcoal.c | 175 ++ src/modules/plus/filter_charcoal.h | 28 + src/modules/plus/filter_invert.c | 90 + src/modules/plus/filter_invert.h | 28 + src/modules/plus/filter_sepia.c | 101 + src/modules/plus/filter_sepia.h | 28 + src/modules/plus/transition_affine.c | 609 ++++++ src/modules/plus/transition_affine.h | 28 + src/modules/qimage/Makefile | 33 + src/modules/qimage/configure | 80 + src/modules/qimage/factory.c | 46 + src/modules/qimage/gpl | 0 src/modules/qimage/producer_qimage.c | 287 +++ src/modules/qimage/producer_qimage.h | 28 + src/modules/qimage/qimage_wrapper.cpp | 259 +++ src/modules/qimage/qimage_wrapper.h | 50 + src/modules/resample/Makefile | 35 + src/modules/resample/configure | 18 + src/modules/resample/factory.c | 46 + src/modules/resample/filter_resample.c | 201 ++ src/modules/resample/filter_resample.h | 28 + src/modules/resample/gpl | 0 src/modules/sdl/Makefile | 52 + src/modules/sdl/config.mak | 1 + src/modules/sdl/configure | 27 + src/modules/sdl/consumer_sdl.c | 837 ++++++++ src/modules/sdl/consumer_sdl.h | 30 + src/modules/sdl/consumer_sdl_osx_hack.h | 37 + src/modules/sdl/consumer_sdl_preview.c | 423 ++++ src/modules/sdl/consumer_sdl_still.c | 640 ++++++ src/modules/sdl/factory.c | 58 + src/modules/sdl/producer_sdl_image.c | 245 +++ src/modules/sdl/producer_sdl_image.h | 28 + src/modules/sox/Makefile | 34 + src/modules/sox/config.mak | 2 + src/modules/sox/configure | 46 + src/modules/sox/factory.c | 45 + src/modules/sox/filter_sox.c | 457 ++++ src/modules/sox/filter_sox.h | 28 + src/modules/valerie/Makefile | 33 + src/modules/valerie/configure | 11 + src/modules/valerie/consumer_valerie.c | 175 ++ src/modules/valerie/consumer_valerie.h | 28 + src/modules/valerie/factory.c | 46 + src/modules/vmfx/Makefile | 37 + src/modules/vmfx/configure | 23 + src/modules/vmfx/factory.c | 57 + src/modules/vmfx/filter_chroma.c | 100 + src/modules/vmfx/filter_chroma.h | 28 + src/modules/vmfx/filter_chroma_hold.c | 101 + src/modules/vmfx/filter_chroma_hold.h | 28 + src/modules/vmfx/filter_mono.c | 97 + src/modules/vmfx/filter_mono.h | 28 + src/modules/vmfx/filter_shape.c | 229 ++ src/modules/vmfx/filter_shape.h | 28 + src/modules/vmfx/producer_pgm.c | 209 ++ src/modules/vmfx/producer_pgm.h | 28 + src/modules/vorbis/Makefile | 35 + src/modules/vorbis/configure | 18 + src/modules/vorbis/factory.c | 46 + src/modules/vorbis/producer_vorbis.c | 365 ++++ src/modules/vorbis/producer_vorbis.h | 28 + src/modules/westley/Makefile | 37 + src/modules/westley/configure | 19 + src/modules/westley/consumer_westley.c | 745 +++++++ src/modules/westley/consumer_westley.h | 28 + src/modules/westley/factory.c | 51 + src/modules/westley/producer_westley.c | 1512 ++++++++++++++ src/modules/westley/producer_westley.h | 28 + src/modules/westley/westley.dtd | 64 + src/modules/xine/Makefile | 35 + src/modules/xine/attributes.h | 46 + src/modules/xine/configure | 18 + src/modules/xine/cpu_accel.c | 232 ++ src/modules/xine/deinterlace.c | 860 ++++++++ src/modules/xine/deinterlace.h | 47 + src/modules/xine/factory.c | 46 + src/modules/xine/filter_deinterlace.c | 106 + src/modules/xine/filter_deinterlace.h | 28 + src/modules/xine/gpl | 0 src/modules/xine/xineutils.h | 1098 ++++++++++ src/tests/Makefile | 40 + src/tests/charlie.c | 193 ++ src/tests/clock16ntsc.pgm | Bin 0 -> 691217 bytes src/tests/clock16pal.pgm | Bin 0 -> 829457 bytes src/tests/dan.c | 111 + src/tests/dissolve.c | 71 + src/tests/hello.c | 136 ++ src/tests/io.c | 208 ++ src/tests/io.h | 45 + src/tests/luma.c | 75 + src/tests/pango.c | 77 + src/tests/pixbuf.c | 72 + src/tests/setenv | 7 + src/tests/test.png | Bin 0 -> 1352 bytes src/valerie/Makefile | 66 + src/valerie/configure | 2 + src/valerie/valerie.c | 969 +++++++++ src/valerie/valerie.h | 264 +++ src/valerie/valerie_notifier.c | 126 ++ src/valerie/valerie_notifier.h | 60 + src/valerie/valerie_parser.c | 147 ++ src/valerie/valerie_parser.h | 76 + src/valerie/valerie_remote.c | 309 +++ src/valerie/valerie_remote.h | 41 + src/valerie/valerie_response.c | 244 +++ src/valerie/valerie_response.h | 61 + src/valerie/valerie_socket.c | 177 ++ src/valerie/valerie_socket.h | 56 + src/valerie/valerie_status.c | 160 ++ src/valerie/valerie_status.h | 85 + src/valerie/valerie_tokeniser.c | 172 ++ src/valerie/valerie_tokeniser.h | 55 + src/valerie/valerie_util.c | 77 + src/valerie/valerie_util.h | 37 + 497 files changed, 74783 insertions(+) create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 GPL create mode 100644 Makefile create mode 100644 README create mode 100644 config.log create mode 100644 configure-stamp create mode 100644 demo/README create mode 100644 demo/circle.png create mode 100644 demo/circle.svg create mode 100644 demo/consumers.ini create mode 100755 demo/demo create mode 100644 demo/demo.ini create mode 100644 demo/demo.kino create mode 100644 demo/entity.westley create mode 100644 demo/luma1.pgm create mode 100644 demo/mlt_all create mode 100644 demo/mlt_attributes create mode 100644 demo/mlt_audio_stuff create mode 100644 demo/mlt_avantika_title create mode 100644 demo/mlt_bouncy create mode 100644 demo/mlt_bouncy_ball create mode 100644 demo/mlt_clock_in_and_out create mode 100644 demo/mlt_composite_transition create mode 100644 demo/mlt_effect_in_middle create mode 100644 demo/mlt_fade_black create mode 100644 demo/mlt_fade_in_and_out create mode 100644 demo/mlt_intro create mode 100644 demo/mlt_jcut create mode 100644 demo/mlt_lcut create mode 100644 demo/mlt_levels create mode 100644 demo/mlt_my_name_is create mode 100644 demo/mlt_news create mode 100644 demo/mlt_obscure create mode 100644 demo/mlt_push create mode 100644 demo/mlt_slideshow create mode 100644 demo/mlt_slideshow_black create mode 100644 demo/mlt_squeeze create mode 100644 demo/mlt_squeeze_box create mode 100644 demo/mlt_ticker create mode 100644 demo/mlt_title_over_gfx create mode 100644 demo/mlt_titleshadow_watermark create mode 100644 demo/mlt_voiceover create mode 100644 demo/mlt_watermark create mode 100644 demo/new.westley create mode 100644 demo/pango.westley create mode 100644 demo/svg.westley create mode 100644 demo/watermark1.png create mode 100644 docs/TODO create mode 100644 docs/dvcp.txt create mode 100644 docs/framework.txt create mode 100644 docs/inigo.txt create mode 100644 docs/install.txt create mode 100644 docs/policies.txt create mode 100644 docs/services.txt create mode 100644 docs/testing-20040110.txt create mode 100644 docs/testing.txt create mode 100644 docs/valerie.txt create mode 100644 docs/westley.txt create mode 100644 mlt-config-template create mode 100644 mlt-framework.pc create mode 100644 mlt-framework.pc.in create mode 100644 mlt-miracle.pc create mode 100644 mlt-miracle.pc.in create mode 100644 mlt-valerie.pc create mode 100644 mlt-valerie.pc.in create mode 100644 profiles/Makefile create mode 100644 profiles/atsc_1080i_60 create mode 100644 profiles/atsc_720p_30 create mode 100644 profiles/cif_ntsc create mode 100644 profiles/cif_pal create mode 100644 profiles/cvd_ntsc create mode 100644 profiles/cvd_pal create mode 100644 profiles/dv_ntsc create mode 100644 profiles/dv_ntsc_wide create mode 100644 profiles/dv_pal create mode 100644 profiles/dv_pal_wide create mode 100644 profiles/hdv_1080_50i create mode 100644 profiles/hdv_1080_60i create mode 100644 profiles/hdv_720_25p create mode 100644 profiles/hdv_720_30p create mode 100644 profiles/qcif_ntsc create mode 100644 profiles/qcif_pal create mode 100644 profiles/quarter_ntsc create mode 100644 profiles/quarter_ntsc_wide create mode 100644 profiles/quarter_pal create mode 100644 profiles/quarter_pal_wide create mode 100644 profiles/square_ntsc create mode 100644 profiles/square_ntsc_wide create mode 100644 profiles/square_pal create mode 100644 profiles/square_pal_wide create mode 100644 profiles/svcd_ntsc create mode 100644 profiles/svcd_ntsc_wide create mode 100644 profiles/svcd_pal create mode 100644 profiles/svcd_pal_wide create mode 100644 profiles/vcd_ntsc create mode 100644 profiles/vcd_pal create mode 100644 setenv create mode 100644 setenv_mc create mode 100644 src/albino/Makefile create mode 100644 src/albino/albino.c create mode 100644 src/framework/Makefile create mode 100644 src/framework/config.h create mode 100755 src/framework/configure create mode 100644 src/framework/mlt.h create mode 100644 src/framework/mlt_consumer.c create mode 100644 src/framework/mlt_consumer.h create mode 100644 src/framework/mlt_deque.c create mode 100644 src/framework/mlt_deque.h create mode 100644 src/framework/mlt_events.c create mode 100644 src/framework/mlt_events.h create mode 100644 src/framework/mlt_factory.c create mode 100644 src/framework/mlt_factory.h create mode 100644 src/framework/mlt_field.c create mode 100644 src/framework/mlt_field.h create mode 100644 src/framework/mlt_filter.c create mode 100644 src/framework/mlt_filter.h create mode 100644 src/framework/mlt_frame.c create mode 100644 src/framework/mlt_frame.h create mode 100644 src/framework/mlt_geometry.c create mode 100644 src/framework/mlt_geometry.h create mode 100644 src/framework/mlt_multitrack.c create mode 100644 src/framework/mlt_multitrack.h create mode 100644 src/framework/mlt_parser.c create mode 100644 src/framework/mlt_parser.h create mode 100644 src/framework/mlt_playlist.c create mode 100644 src/framework/mlt_playlist.h create mode 100644 src/framework/mlt_pool.c create mode 100644 src/framework/mlt_pool.h create mode 100644 src/framework/mlt_producer.c create mode 100644 src/framework/mlt_producer.h create mode 100644 src/framework/mlt_profile.c create mode 100644 src/framework/mlt_profile.h create mode 100644 src/framework/mlt_properties.c create mode 100644 src/framework/mlt_properties.h create mode 100644 src/framework/mlt_property.c create mode 100644 src/framework/mlt_property.h create mode 100644 src/framework/mlt_repository.c create mode 100644 src/framework/mlt_repository.h create mode 100644 src/framework/mlt_service.c create mode 100644 src/framework/mlt_service.h create mode 100644 src/framework/mlt_tokeniser.c create mode 100644 src/framework/mlt_tokeniser.h create mode 100644 src/framework/mlt_tractor.c create mode 100644 src/framework/mlt_tractor.h create mode 100644 src/framework/mlt_transition.c create mode 100644 src/framework/mlt_transition.h create mode 100644 src/framework/mlt_types.h create mode 100644 src/humperdink/Makefile create mode 100644 src/humperdink/client.c create mode 100644 src/humperdink/client.h create mode 100644 src/humperdink/io.c create mode 100644 src/humperdink/io.h create mode 100644 src/humperdink/remote.c create mode 100644 src/inigo/Makefile create mode 100755 src/inigo/configure create mode 100644 src/inigo/inigo.c create mode 100644 src/inigo/io.c create mode 100644 src/inigo/io.h create mode 100644 src/miracle/Makefile create mode 100755 src/miracle/configure create mode 100644 src/miracle/miracle.c create mode 100644 src/miracle/miracle_commands.c create mode 100644 src/miracle/miracle_commands.h create mode 100644 src/miracle/miracle_connection.c create mode 100644 src/miracle/miracle_connection.h create mode 100644 src/miracle/miracle_local.c create mode 100644 src/miracle/miracle_local.h create mode 100644 src/miracle/miracle_log.c create mode 100644 src/miracle/miracle_log.h create mode 100644 src/miracle/miracle_server.c create mode 100644 src/miracle/miracle_server.h create mode 100644 src/miracle/miracle_unit.c create mode 100644 src/miracle/miracle_unit.h create mode 100644 src/miracle/miracle_unit_commands.c create mode 100644 src/miracle/miracle_unit_commands.h create mode 100644 src/modules/Makefile create mode 100644 src/modules/avformat/Makefile create mode 100644 src/modules/avformat/config.mak create mode 100755 src/modules/avformat/configure create mode 100644 src/modules/avformat/consumer_avformat.c create mode 100644 src/modules/avformat/consumer_avformat.h create mode 100644 src/modules/avformat/factory.c create mode 100644 src/modules/avformat/ffmpeg.patch create mode 100644 src/modules/avformat/filter_avcolour_space.c create mode 100644 src/modules/avformat/filter_avcolour_space.h create mode 100644 src/modules/avformat/filter_avdeinterlace.c create mode 100644 src/modules/avformat/filter_avdeinterlace.h create mode 100644 src/modules/avformat/filter_avresample.c create mode 100644 src/modules/avformat/filter_avresample.h create mode 100644 src/modules/avformat/mmx.h create mode 100644 src/modules/avformat/producer_avformat.c create mode 100644 src/modules/avformat/producer_avformat.h create mode 100755 src/modules/configure create mode 100644 src/modules/core/Makefile create mode 100644 src/modules/core/composite_line_yuv_mmx.S create mode 100755 src/modules/core/configure create mode 100644 src/modules/core/consumer_null.c create mode 100644 src/modules/core/consumer_null.h create mode 100644 src/modules/core/factory.c create mode 100644 src/modules/core/filter_brightness.c create mode 100644 src/modules/core/filter_brightness.h create mode 100644 src/modules/core/filter_channelcopy.c create mode 100644 src/modules/core/filter_channelcopy.h create mode 100644 src/modules/core/filter_data.h create mode 100644 src/modules/core/filter_data_feed.c create mode 100644 src/modules/core/filter_data_show.c create mode 100644 src/modules/core/filter_gamma.c create mode 100644 src/modules/core/filter_gamma.h create mode 100644 src/modules/core/filter_greyscale.c create mode 100644 src/modules/core/filter_greyscale.h create mode 100644 src/modules/core/filter_luma.c create mode 100644 src/modules/core/filter_luma.h create mode 100644 src/modules/core/filter_mirror.c create mode 100644 src/modules/core/filter_mirror.h create mode 100644 src/modules/core/filter_mono.c create mode 100644 src/modules/core/filter_mono.h create mode 100644 src/modules/core/filter_obscure.c create mode 100644 src/modules/core/filter_obscure.h create mode 100644 src/modules/core/filter_region.c create mode 100644 src/modules/core/filter_region.h create mode 100644 src/modules/core/filter_rescale.c create mode 100644 src/modules/core/filter_rescale.h create mode 100644 src/modules/core/filter_resize.c create mode 100644 src/modules/core/filter_resize.h create mode 100644 src/modules/core/filter_transition.c create mode 100644 src/modules/core/filter_transition.h create mode 100644 src/modules/core/filter_watermark.c create mode 100644 src/modules/core/filter_watermark.h create mode 100644 src/modules/core/producer_colour.c create mode 100644 src/modules/core/producer_colour.h create mode 100644 src/modules/core/producer_noise.c create mode 100644 src/modules/core/producer_noise.h create mode 100644 src/modules/core/producer_ppm.c create mode 100644 src/modules/core/producer_ppm.h create mode 100644 src/modules/core/transition_composite.c create mode 100644 src/modules/core/transition_composite.h create mode 100644 src/modules/core/transition_luma.c create mode 100644 src/modules/core/transition_luma.h create mode 100644 src/modules/core/transition_mix.c create mode 100644 src/modules/core/transition_mix.h create mode 100644 src/modules/core/transition_region.c create mode 100644 src/modules/core/transition_region.h create mode 100644 src/modules/data_fx.properties create mode 100644 src/modules/disable-avformat create mode 100644 src/modules/disable-dv create mode 100644 src/modules/disable-jackrack create mode 100644 src/modules/disable-mmx create mode 100644 src/modules/dv/Makefile create mode 100755 src/modules/dv/configure create mode 100644 src/modules/dv/consumer_libdv.c create mode 100644 src/modules/dv/consumer_libdv.h create mode 100644 src/modules/dv/factory.c create mode 100644 src/modules/dv/producer_libdv.c create mode 100644 src/modules/dv/producer_libdv.h create mode 100644 src/modules/effectv/Makefile create mode 100755 src/modules/effectv/configure create mode 100644 src/modules/effectv/factory.c create mode 100644 src/modules/effectv/filter_burn.c create mode 100644 src/modules/effectv/filter_burn.h create mode 100644 src/modules/effectv/gpl create mode 100644 src/modules/effectv/image.c create mode 100644 src/modules/effectv/utils.c create mode 100644 src/modules/effectv/utils.h create mode 100644 src/modules/feeds/Makefile create mode 100644 src/modules/feeds/NTSC/data_fx.properties create mode 100644 src/modules/feeds/NTSC/obscure.properties create mode 100644 src/modules/feeds/PAL/border.properties create mode 100644 src/modules/feeds/PAL/data_fx.properties create mode 100644 src/modules/feeds/PAL/etv.properties create mode 100644 src/modules/feeds/PAL/example.properties create mode 100644 src/modules/feeds/PAL/obscure.properties create mode 100755 src/modules/feeds/configure create mode 100644 src/modules/fezzik.dict create mode 100644 src/modules/fezzik.ini create mode 100644 src/modules/fezzik/Makefile create mode 100755 src/modules/fezzik/configure create mode 100644 src/modules/fezzik/factory.c create mode 100644 src/modules/fezzik/producer_fezzik.c create mode 100644 src/modules/fezzik/producer_fezzik.h create mode 100644 src/modules/fezzik/producer_hold.c create mode 100644 src/modules/fezzik/producer_hold.h create mode 100644 src/modules/gtk2/Makefile create mode 100644 src/modules/gtk2/config.h create mode 100644 src/modules/gtk2/config.mak create mode 100755 src/modules/gtk2/configure create mode 100644 src/modules/gtk2/consumer_gtk2.c create mode 100644 src/modules/gtk2/consumer_gtk2.h create mode 100644 src/modules/gtk2/factory.c create mode 100644 src/modules/gtk2/filter_rescale.c create mode 100644 src/modules/gtk2/filter_rescale.h create mode 100644 src/modules/gtk2/have_mmx.S create mode 100644 src/modules/gtk2/pixops.c create mode 100644 src/modules/gtk2/pixops.h create mode 100644 src/modules/gtk2/producer_pango.c create mode 100644 src/modules/gtk2/producer_pango.h create mode 100644 src/modules/gtk2/producer_pixbuf.c create mode 100644 src/modules/gtk2/producer_pixbuf.h create mode 100644 src/modules/gtk2/scale_line_22_yuv_mmx.S create mode 100644 src/modules/inigo/Makefile create mode 100755 src/modules/inigo/configure create mode 100644 src/modules/inigo/factory.c create mode 100644 src/modules/inigo/producer_inigo.c create mode 100644 src/modules/inigo/producer_inigo.h create mode 100644 src/modules/jackrack/Makefile create mode 100755 src/modules/jackrack/configure create mode 100644 src/modules/jackrack/factory.c create mode 100644 src/modules/jackrack/filter_jackrack.c create mode 100644 src/modules/jackrack/filter_jackrack.h create mode 100644 src/modules/jackrack/filter_ladspa.c create mode 100644 src/modules/jackrack/filter_ladspa.h create mode 100644 src/modules/jackrack/gpl create mode 100644 src/modules/jackrack/jack_rack.c create mode 100644 src/modules/jackrack/jack_rack.h create mode 100644 src/modules/jackrack/lock_free_fifo.c create mode 100644 src/modules/jackrack/lock_free_fifo.h create mode 100644 src/modules/jackrack/plugin.c create mode 100644 src/modules/jackrack/plugin.h create mode 100644 src/modules/jackrack/plugin_desc.c create mode 100644 src/modules/jackrack/plugin_desc.h create mode 100644 src/modules/jackrack/plugin_mgr.c create mode 100644 src/modules/jackrack/plugin_mgr.h create mode 100644 src/modules/jackrack/plugin_settings.c create mode 100644 src/modules/jackrack/plugin_settings.h create mode 100644 src/modules/jackrack/process.c create mode 100644 src/modules/jackrack/process.h create mode 100644 src/modules/kdenlive/Makefile create mode 100755 src/modules/kdenlive/configure create mode 100644 src/modules/kdenlive/factory.c create mode 100644 src/modules/kdenlive/filter_boxblur.c create mode 100644 src/modules/kdenlive/filter_boxblur.h create mode 100644 src/modules/kdenlive/filter_wave.c create mode 100644 src/modules/kdenlive/filter_wave.h create mode 100644 src/modules/kdenlive/producer_framebuffer.c create mode 100644 src/modules/kdenlive/producer_framebuffer.h create mode 100644 src/modules/kino/Makefile create mode 100644 src/modules/kino/avi.cc create mode 100644 src/modules/kino/avi.h create mode 100755 src/modules/kino/configure create mode 100644 src/modules/kino/endian_types.h create mode 100644 src/modules/kino/error.cc create mode 100644 src/modules/kino/error.h create mode 100644 src/modules/kino/factory.c create mode 100644 src/modules/kino/filehandler.cc create mode 100644 src/modules/kino/filehandler.h create mode 100644 src/modules/kino/gpl create mode 100644 src/modules/kino/kino_wrapper.cc create mode 100644 src/modules/kino/kino_wrapper.h create mode 100644 src/modules/kino/producer_kino.c create mode 100644 src/modules/kino/producer_kino.h create mode 100644 src/modules/kino/riff.cc create mode 100644 src/modules/kino/riff.h create mode 100644 src/modules/lumas/.compress create mode 100644 src/modules/lumas/Makefile create mode 100755 src/modules/lumas/configure create mode 100755 src/modules/lumas/create_lumas create mode 100644 src/modules/lumas/luma.c create mode 100644 src/modules/motion_est/Makefile create mode 100644 src/modules/motion_est/README create mode 100644 src/modules/motion_est/arrow_code.c create mode 100644 src/modules/motion_est/arrow_code.h create mode 100755 src/modules/motion_est/configure create mode 100644 src/modules/motion_est/factory.c create mode 100644 src/modules/motion_est/filter_autotrack_rectangle.c create mode 100644 src/modules/motion_est/filter_crop_detect.c create mode 100644 src/modules/motion_est/filter_motion_est.c create mode 100644 src/modules/motion_est/filter_motion_est.h create mode 100644 src/modules/motion_est/filter_vismv.c create mode 100644 src/modules/motion_est/gpl create mode 100644 src/modules/motion_est/producer_slowmotion.c create mode 100644 src/modules/motion_est/sad_sse.h create mode 100644 src/modules/normalize/Makefile create mode 100755 src/modules/normalize/configure create mode 100644 src/modules/normalize/factory.c create mode 100644 src/modules/normalize/filter_volume.c create mode 100644 src/modules/normalize/filter_volume.h create mode 100644 src/modules/normalize/gpl create mode 100644 src/modules/plus/Makefile create mode 100755 src/modules/plus/configure create mode 100644 src/modules/plus/factory.c create mode 100644 src/modules/plus/filter_affine.c create mode 100644 src/modules/plus/filter_affine.h create mode 100644 src/modules/plus/filter_charcoal.c create mode 100644 src/modules/plus/filter_charcoal.h create mode 100644 src/modules/plus/filter_invert.c create mode 100644 src/modules/plus/filter_invert.h create mode 100644 src/modules/plus/filter_sepia.c create mode 100644 src/modules/plus/filter_sepia.h create mode 100644 src/modules/plus/transition_affine.c create mode 100644 src/modules/plus/transition_affine.h create mode 100644 src/modules/qimage/Makefile create mode 100755 src/modules/qimage/configure create mode 100644 src/modules/qimage/factory.c create mode 100644 src/modules/qimage/gpl create mode 100644 src/modules/qimage/producer_qimage.c create mode 100644 src/modules/qimage/producer_qimage.h create mode 100644 src/modules/qimage/qimage_wrapper.cpp create mode 100644 src/modules/qimage/qimage_wrapper.h create mode 100644 src/modules/resample/Makefile create mode 100755 src/modules/resample/configure create mode 100644 src/modules/resample/factory.c create mode 100644 src/modules/resample/filter_resample.c create mode 100644 src/modules/resample/filter_resample.h create mode 100644 src/modules/resample/gpl create mode 100644 src/modules/sdl/Makefile create mode 100644 src/modules/sdl/config.mak create mode 100755 src/modules/sdl/configure create mode 100644 src/modules/sdl/consumer_sdl.c create mode 100644 src/modules/sdl/consumer_sdl.h create mode 100644 src/modules/sdl/consumer_sdl_osx_hack.h create mode 100644 src/modules/sdl/consumer_sdl_preview.c create mode 100644 src/modules/sdl/consumer_sdl_still.c create mode 100644 src/modules/sdl/factory.c create mode 100644 src/modules/sdl/producer_sdl_image.c create mode 100644 src/modules/sdl/producer_sdl_image.h create mode 100644 src/modules/sox/Makefile create mode 100644 src/modules/sox/config.mak create mode 100755 src/modules/sox/configure create mode 100644 src/modules/sox/factory.c create mode 100644 src/modules/sox/filter_sox.c create mode 100644 src/modules/sox/filter_sox.h create mode 100644 src/modules/valerie/Makefile create mode 100755 src/modules/valerie/configure create mode 100644 src/modules/valerie/consumer_valerie.c create mode 100644 src/modules/valerie/consumer_valerie.h create mode 100644 src/modules/valerie/factory.c create mode 100644 src/modules/vmfx/Makefile create mode 100755 src/modules/vmfx/configure create mode 100644 src/modules/vmfx/factory.c create mode 100644 src/modules/vmfx/filter_chroma.c create mode 100644 src/modules/vmfx/filter_chroma.h create mode 100644 src/modules/vmfx/filter_chroma_hold.c create mode 100644 src/modules/vmfx/filter_chroma_hold.h create mode 100644 src/modules/vmfx/filter_mono.c create mode 100644 src/modules/vmfx/filter_mono.h create mode 100644 src/modules/vmfx/filter_shape.c create mode 100644 src/modules/vmfx/filter_shape.h create mode 100644 src/modules/vmfx/producer_pgm.c create mode 100644 src/modules/vmfx/producer_pgm.h create mode 100644 src/modules/vorbis/Makefile create mode 100755 src/modules/vorbis/configure create mode 100644 src/modules/vorbis/factory.c create mode 100644 src/modules/vorbis/producer_vorbis.c create mode 100644 src/modules/vorbis/producer_vorbis.h create mode 100644 src/modules/westley/Makefile create mode 100755 src/modules/westley/configure create mode 100644 src/modules/westley/consumer_westley.c create mode 100644 src/modules/westley/consumer_westley.h create mode 100644 src/modules/westley/factory.c create mode 100644 src/modules/westley/producer_westley.c create mode 100644 src/modules/westley/producer_westley.h create mode 100644 src/modules/westley/westley.dtd create mode 100644 src/modules/xine/Makefile create mode 100644 src/modules/xine/attributes.h create mode 100755 src/modules/xine/configure create mode 100644 src/modules/xine/cpu_accel.c create mode 100644 src/modules/xine/deinterlace.c create mode 100644 src/modules/xine/deinterlace.h create mode 100644 src/modules/xine/factory.c create mode 100644 src/modules/xine/filter_deinterlace.c create mode 100644 src/modules/xine/filter_deinterlace.h create mode 100644 src/modules/xine/gpl create mode 100644 src/modules/xine/xineutils.h create mode 100644 src/tests/Makefile create mode 100644 src/tests/charlie.c create mode 100644 src/tests/clock16ntsc.pgm create mode 100644 src/tests/clock16pal.pgm create mode 100644 src/tests/dan.c create mode 100644 src/tests/dissolve.c create mode 100644 src/tests/hello.c create mode 100644 src/tests/io.c create mode 100644 src/tests/io.h create mode 100644 src/tests/luma.c create mode 100644 src/tests/pango.c create mode 100644 src/tests/pixbuf.c create mode 100644 src/tests/setenv create mode 100644 src/tests/test.png create mode 100644 src/valerie/Makefile create mode 100755 src/valerie/configure create mode 100644 src/valerie/valerie.c create mode 100644 src/valerie/valerie.h create mode 100644 src/valerie/valerie_notifier.c create mode 100644 src/valerie/valerie_notifier.h create mode 100644 src/valerie/valerie_parser.c create mode 100644 src/valerie/valerie_parser.h create mode 100644 src/valerie/valerie_remote.c create mode 100644 src/valerie/valerie_remote.h create mode 100644 src/valerie/valerie_response.c create mode 100644 src/valerie/valerie_response.h create mode 100644 src/valerie/valerie_socket.c create mode 100644 src/valerie/valerie_socket.h create mode 100644 src/valerie/valerie_status.c create mode 100644 src/valerie/valerie_status.h create mode 100644 src/valerie/valerie_tokeniser.c create mode 100644 src/valerie/valerie_tokeniser.h create mode 100644 src/valerie/valerie_util.c create mode 100644 src/valerie/valerie_util.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..ec47efc --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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 of the License, or (at your option) any later version. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c5c26e1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,58 @@ +$Id: ChangeLog 1008 2007-07-15 01:19:30Z ddennedy $ + +USING svn log NOW + +2007-04-09 Dan Dennedy + Cleanup copyrights and attributions, and move Jean-Baptiste's services + to a new kdenlive module. + +2007-03-30 Dan Dennedy + Add support for sox 13.0.0. + +2007-03-30 Jean-Baptiste Mardelle + Fix boxblur and wave filters license. + +2007-03-29 Dan Dennedy + Cleanup license declarations and remove dv1394d references. + Change registration of vmfx/mono to threshold to disambiguate with + core/mono. + +2007-03-27 Dan Dennedy + Fix ffmpeg swscale code enabled with mmx flags and fix --enable-swscale + in conjunction with --avformat-static. + +2007-03-16 Dan Dennedy + Added docs/policies.txt. + +2007-02-19 Jean-Baptiste Mardelle + Blur and wave filters: fix typos and make functions static (patch from Stephane Fillod) + +2007-02-18 Jean-Baptiste Mardelle + Add blur and wave filters from Leny Grisel + +2007-02-07 Dan Dennedy + Added ffmpeg libswscale support to avformat module (requires configure + option --avformat-swscale) + +2006-12-07 Dan Dennedy + applied audio frequency and audio channels initialization patch from Jean-Baptiste + +2006-09-27 Zachary Drew + applied amd64 patch from gentoo folks to fix compilation of motion_est + on amd64 (thanks for the heads-up Jean-Michel) + + +2006-09-25 Dan Dennedy + - src/modules/sdl/Makefile: fix compilation on some systems using + modular x.org. + +2006-08-08 Dan Dennedy + enhance producer_westley to parse Kino 0.9.1 SMIL (clock) time values. + +2006-08-08 Dan Dennedy + convert --avformat-cvs to svn and rename option as --avformat-svn (--avformat-cvs is an undocumented alias). diff --git a/GPL b/GPL new file mode 100644 index 0000000..623b625 --- /dev/null +++ b/GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b4d5ffa --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +SUBDIRS = src/framework \ + src/inigo \ + src/valerie \ + src/miracle \ + src/humperdink \ + src/albino \ + src/modules \ + profiles + +all clean: + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) -s -C $$subdir depend || exit 1; \ + $(MAKE) -C $$subdir $@ || exit 1; \ + done + +distclean: + rm mlt-config packages.dat; \ + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) -C $$subdir $@ || exit 1; \ + done; \ + rm config.mak; + +dist-clean: distclean + +include config.mak + +install: + install -d "$(DESTDIR)$(prefix)/bin" + install -d "$(DESTDIR)$(prefix)/include" + install -d "$(DESTDIR)$(libdir)" + install -d "$(DESTDIR)$(libdir)/pkgconfig" + install -d "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -d "$(DESTDIR)$(prefix)/share/mlt/modules" + install -c -m 755 mlt-config "$(DESTDIR)$(bindir)" + install -c -m 644 *.pc "$(DESTDIR)$(libdir)/pkgconfig" + install -m 644 packages.dat "$(DESTDIR)$(prefix)/share/mlt/" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + done; \ +# if test -z "$(DESTDIR)"; then \ +# /sbin/ldconfig || true; \ +# fi + +uninstall: + rm -f "$(DESTDIR)$(bindir)"/mlt-config + rm -f "$(DESTDIR)$(libdir)/pkgconfig/mlt-*.pc" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + done + rm -rf "$(DESTDIR)$(prefix)/include/mlt" + rm -rf "$(DESTDIR)$(prefix)/share/mlt" + +dist: + [ -d "mlt-$(version)" ] && rm -rf "mlt-$(version)" || echo + svn export . "mlt-$(version)" + svn log > "mlt-$(version)/ChangeLog" + tar -cvzf "mlt-$(version).tar.gz" "mlt-$(version)" diff --git a/README b/README new file mode 100644 index 0000000..aaac6e2 --- /dev/null +++ b/README @@ -0,0 +1,65 @@ +MLT/Miracle README +------------------ + + Sponsored by Ushodaya Enterprises Limited + Written by Charles Yates + and Dan Dennedy + + MLT is a LGPL multimedia framework designed for television broadcasting, + and Miracle is a GPL multi-unit video playout server with realtime + effects. + + This document provides a quick reference for the minimal configuration, + build and installation of MLT. See the docs directory for usage and + development details. + + +Configuration +------------- + + Configuration is triggered by running: + + ./configure + + More information on usage is found by running: + + ./configure --help + + NB: This script must be run to register new services after a CVS checkout + or subsequent update. + + +Compilation +----------- + + Once configured, it should be sufficient to run: + + make + + to compile the system. + + +Testing +------- + + To execute the mlt tools without installation, or to test a new version + on a system with an already installed mlt version, you should run: + + . setenv + + NB: This applies to your current shell only and it assumes a bash or + regular bourne shell is in use. + + +Installation +------------ + + The install is triggered by running: + + make install + + +More Information +---------------- + + For more detailed information, please refer to docs/install.txt. diff --git a/config.log b/config.log new file mode 100644 index 0000000..c6b91b1 --- /dev/null +++ b/config.log @@ -0,0 +1,2 @@ +Wed Feb 24 12:10:50 CST 2010 +./configure --enable-gpl --luma-compress --disable-mmx --enable-motion-est --avformat-shared=/usr --avformat-swscale --prefix=/opt/kde3 --with-extra-libs=/opt/kde3/lib --with-extra-includes=/opt/kde3/include/kde diff --git a/configure-stamp b/configure-stamp new file mode 100644 index 0000000..e69de29 diff --git a/demo/README b/demo/README new file mode 100644 index 0000000..a9a5a08 --- /dev/null +++ b/demo/README @@ -0,0 +1,217 @@ +MLT Demo Notes + +Before running the demo script, make sure you '. setenv' from the parent +directory. Also, please create clips clip1.dv, clip2.dv, clip3.dv, clip1.mpeg, +clip2.mpeg, clip3.mpeg, and music1.ogg. Please make sure clips are at least 500 +frames duration. + +These notes explain the the concepts presented in each demonstration and +what details to look for. + +First, a note on consumers. When you start the script, the main menu asks +you to choose a consumer. A consumer is like a viewer, but it could also +write to a stream/file. The "SDL" consumer is the popular Simple DirectMedia +Layer audio and video output. The "Westley" consumer generates an XML +representation of the service network. That can be played directly due to the +westley producer plugin. See docs/westley.txt for more information. The +"MainConcept DV" consumer refers to the proprietary MLT plugin required to +use MLT with MainConcept DV, DVCPro, and MPEG codecs. "/dev/dv1394/0" refers +to a device file for transmitting DV over FireWire using the Linux dv1394 kernel +module. The "BlueFish444" consumer is another proprietary plugin to use +the BlueFish444 manufactured SDI video/audio output cards with MLT. + +And now the demos... + +All clips + + Simply builds a playlist containing each video clip, and you can transport + between them using j and k keys. + +Filter in/out + + A video filter can be applied to a portion of a producer (clip, playlist, + or multitrack). This examples shows the greyscale filter. + +Watermark + + A graphic can overlay video in realtime with support for alpha channel. + This example uses a PNG file with an alpha channel. Distortion is explicitly + enabled here so the otherwise circular graphic is scaled to fill the + compositing region. By default, compositing honours the aspect ratio of the + overlay. + +My name is... + + Titles are very easy to composite in realtime. The titler uses Pango + with the FreeType2 rendering backend. This means it supports high + quality scalable font rendering with anti-aliasing, unicode (UTF-8), + and Pango markup capabilities. The compsiting here respects the aspect + ratio of the rendered title in the first two title pieces but distorts + the final one. This demo also shows the motion and scaling capabilities + of the compositor in conjunction with honouring aspect. The compositor + is doing field-based rendering. So, when displayed non-progressively + with SDL, you can see motion artifacts during animation. + +A composite transition + + The compositor also handles video over video as demonstrated in this + usage of the compositor to create a special transition. This demonstration + also crossfades the audio during the transition! Progressive rendering + is explicitly enabled on the compositor due to the poor results that + would otherwise occur due to scaling an interleaved video frame and moving + the video in a reverse direction horizontally. + +Fade in and out + + A simple series of transitions betwen 3 clips using dissolves and audio + crossfades. This is easy :-). + +Clock in and out + + Wipe transitions are very easy and highly extensible as they are generated + using a very convenient lookup table based upon the luma of an image. + This image can be a 16 bit PGM (grayscale bitmap) or the luma channel of + any video producer. A number of high quality wipes can be downloaded from + http://mlt.sf.net/. It also performs field rendering. + The second wipe demonstrates the ability to control the direction of the + wipe as well. + +Obscure + + A popular requirement in news production is to obscure a face, obscenity, + or trademarked logo. This demonstrates using a simple rectangular + obscure filter applied to a region of the image. The second example is more + advanced and shows using the "region" filter to select the image area and a + property of the region filter to "shape" the region using the alpha channel + of another image (circle.png) and another property to "filter" the region + using the obscure filter. + +Audio Stuff + + A music bed sound track can be mixed with a video. The sound track of the + video clip has a "floating" amplitude normalisation filter applied. + Typically, audio normalisation applies a constant gain factor across the + entire duration of an audio segment from a single source where the + gain factor is automatically determined by anaylsing the maximum "power" + or peak levels. However, in news production, a popular requirement is to + to dynamically boost the amplitude in soft areas and reduce the amplitude + in louder areas. Thus, the gain analysis is performed using a "sliding + window" approach. This example also applies a constant gain factor of + 0.5 (50%) to the normalised audio of the video clip (to get a nicer + mix level). + +Audio and Video Levels + + Audio can be normalised by setting a target amplitude level in decibels. + A gamma curve can be applied to the luma channel of video. + +Shadowed Title and Watermark + + Two instances of the titler are used to create a shadow effect. + The aspect ratio of the watermark in this example is not distorted. Since + the original image is a circle with square pixels--a computer-generated + image--and ITU BT.601 video is not composed of square samples. Therefore, + the compositor normalises the pixel aspect ratio of the overlay to the + destination image, and the circular image remains circular on the analog + video output. Finally, a greyscale filter is applied to the watermark + while its opacity is set at 30%. + +Station Promo into Story? + + Here is fun demo that might show using a still graphic with some music + to introduce a show. A luma wipe with an audio crossfade transitions from + the show title or station promotional material. + +Voiceover 2 clips with title + + A common news production requirement to have a "voiceover" audio track + to a clip or even multiple clips as demonstrated here. Likewise, it is + common to place a title caption on the video at the same time! This + demo has a little fun with the titler at the sake of practicality :-) + The foreground of the title is transparent while the opacity of the + background is reduced to blend with the video. Meanwhile, the compositor + stretches the image to fill the bottom slice of the video--not suitable + for overscan displays ;-) + + Also, pay close attention to the mixing levels of the audio tracks. + The audio of the video fades out as the voiceover track (just music + in this demo) fades in. Then, the voiceover remains mixed with the + ambient audio at a 60% level. Finally, the voiceover fades out smoothly + from the 60% level to nothing. + +GJ-TTAvantika title + + This demo requires a special TrueType font called Avantika. If you have the + font, register it with fontconfig using the fc-cache utility. This + demonstrates i18n capabilities of the titler and the alignment capabilities + of both the titler and the compositor. The titler centre aligns + the two lines of text, and the compositor centre aligns the title + horizontally on the frame. + +Title over graphic + + You can superimpose a title over a graphic over video! Also, + you can apply a luma wipe to the compositor! + +Slideshow + + This demo requires any number of JPEG images with the extension ".jpg" + in a subdirectory named "Scotland." + +Bouncy, Bouncy + + The "watermark" filter encapsulates the compositor, and you have full + control over the compositor properties. Who says a watermark can not + also be a video?! + +Bouncy, Bouncy Ball + + A variation on the above Bouncy, Bouncy demo that applies a shape, or + alpha producer, to the the compositing region. + +Breaking News + + This demonstrates layout capabilities of the compositor. + +Squeeze Transitions + + This demonstrates a distorting barndoor-like wipe. + + +J Cut + + A J cut is an edit where the audio cuts before the video. + It gets its name from the way it looks on a NLE timeline user interface. + When the audio cuts over, it does an audio crossfade over the duration of + one frame. This makes the audio cut slightly less abrupt and avoids any + "click" due to mismatched sample levels at the edit point. The video edit + is a hard cut. + +L Cut + + An L cut is an edit where the video cuts before the audio. + It gets its name from the way it looks on a NLE timeline user interface. + This demo shows a very quick dissolve over 5 frames for a soft video cut. + Like the J Cut demo, an audio crossfade for the duration of one frame makes + an audio edit nearly instantaneous while being slightly softened and + avoiding aberrations. + +Fade from/to black/silence + + Of course, it is possible using MLT to fade from black on video and silence + on audio as well fade to black and silence. + +Push wipe + + A push wipe is a somewhat fancier transition than most standard wipes + because it involves motion. The new video clip "pushes" the old video + clip off one edge. If you can preview on an analog monitor you will notice + how smooth the motion is due to field-based rendering. + +Ticker tape + + A very minimal reverse crawling title neard the bottom of the screen. + The goal of the demo is show fluid motion of the field-based rendering of + the compositor when viewed on an analog monitor using a DV or BlueFish444 + consumer. The demo also shows the potientional for using and extending the + existing set of services for a full blown news ticker implementation. diff --git a/demo/circle.png b/demo/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..968b74aff550fc95775a1c59105c9727f15d88d6 GIT binary patch literal 5238 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 literal 0 HcmV?d00001 diff --git a/demo/circle.svg b/demo/circle.svg new file mode 100644 index 0000000..476ef3c --- /dev/null +++ b/demo/circle.svg @@ -0,0 +1 @@ + diff --git a/demo/consumers.ini b/demo/consumers.ini new file mode 100644 index 0000000..b02695f --- /dev/null +++ b/demo/consumers.ini @@ -0,0 +1,12 @@ +SDL Default sdl +SDL Half D1 sdl:360x288 rescale=nearest resize=1 +SDL High Latency sdl buffer=12 rescale=none +SDL Progressive sdl progressive=1 +Westley to Terminal westley +Westley to File westley: +MainConcept DV to /dev/dv1394/0 mcdv:/dev/dv1394/0 rescale=nearest buffer=25 +libdv to /dev/dv1394/0 libdv:/dev/dv1394/0 rescale=nearest buffer=25 +BlueFish444 PAL bluefish:1 +BlueFish444 NTSC bluefish:1 standard=NTSC +BlueFish444 PAL Prog LL bluefish:1 progressive=1 buffer=1 frames=4 +BlueFish444 NTSC Prog LL bluefish:1 standard=NTSC progressive=1 buffer=1 frames=4 diff --git a/demo/demo b/demo/demo new file mode 100755 index 0000000..98777ca --- /dev/null +++ b/demo/demo @@ -0,0 +1,106 @@ +#!/bin/bash + +function show_consumers( ) +{ + awk -F '\t' '{ printf( "%d. %s\n", ++ i, $1 ); }' < consumers.ini +} + +function get_consumer( ) +{ + option=$1 + [ "$option" != "" ] && [ $option -gt 0 ] && sed 's/\t\+/\t/g' < consumers.ini | cut -f 2 | head -n $option | tail -n -1 +} + +function show_menu( ) +{ + sed 's/\t\+/\t/g' < demo.ini | + awk -F '\t' '{ printf( "%2d. %-30.30s", ++ i, $2 ); if ( i % 2 == 0 ) printf( "\n" ); } END { if ( i % 2 == 1 ) printf( "\n" ); }' +} + +function check_dependencies( ) +{ + option=$1 + if [ $option -gt 0 ] + then + deps=`sed 's/\t\+/\t/g' < demo.ini | cut -f 3 | head -n $option | tail -n -1` + if [ "$deps" != "" ] + then + echo "$deps" | + tr ',' '\n' | + while read dep + do + ls $dep > /dev/null 2>&1 + val=$? + [ $val != 0 ] && echo Failed to find $dep >&2 && echo $val + done + fi + echo 0 + fi +} + +function get_demo( ) +{ + option=$1 + if [ $option -gt 0 ] + then + cut -f 1 demo.ini | head -n $option | tail -n -1 + fi +} + +while [ 1 ] +do + + echo Select Consumer + echo + + show_consumers + + echo + echo 0. Exit + echo + echo -n "Option: " + read option + echo + + [ "$option" == "0" ] && break + + export MLT_CONSUMER=`get_consumer $option` + + while [ "$option" != "0" -a "$MLT_CONSUMER" != "" ] + do + echo Choose Demo + echo + + show_menu + + echo + echo -n "Option: " + read option + echo + + [ "$option" == "" ] && break + + demo=`get_demo $option` + usable=`check_dependencies $option` + + if [ "$usable" = "0" -a "$demo" != "" ] + then + if [ "$MLT_CONSUMER" == "westley:" ] + then export WESTLEY_CONSUMER="westley:$demo.westley" + bash $demo -consumer $WESTLEY_CONSUMER + inigo +$demo.txt out=100 $demo.westley $demo.westley -filter watermark:watermark1.png composite.fill=1 composite.geometry=85%,5%:10%x10% + elif [ "$MLT_CONSUMER" == "westley" ] + then bash $demo -consumer $MLT_CONSUMER | less + else bash $demo -consumer $MLT_CONSUMER + fi + elif [ "$usable" != "" ] + then + echo + echo Unable to locate suitable files for the demo - please provide them. + read pause + fi + + stty sane + done + +done diff --git a/demo/demo.ini b/demo/demo.ini new file mode 100644 index 0000000..b01b049 --- /dev/null +++ b/demo/demo.ini @@ -0,0 +1,28 @@ +mlt_all All clips clip* +mlt_effect_in_middle Filter in/out clip1.mpeg +mlt_watermark Watermark clip2.dv,watermark1.png +mlt_my_name_is My name is... clip3.dv +mlt_composite_transition A composite transition clip1.dv,clip2.mpeg +mlt_fade_in_and_out Fade in and out clip1.dv,clip2.mpeg,clip3.dv +mlt_clock_in_and_out Clock in and out clip2.dv,clip1.dv,clip3.mpeg +mlt_obscure Obscure clip2.mpeg,circle.png +mlt_audio_stuff Audio Stuff clip*.dv,music1.ogg +mlt_levels Audio and Video Levels clip*.dv +mlt_titleshadow_watermark Shadowed Title and Watermark clip3.dv +mlt_intro Station Promo into Story? watermark1.png,clip3.mpeg,music1.ogg +mlt_voiceover Voiceover 2 clips with title clip1.dv,clip2.mpeg,music1.ogg +mlt_avantika_title GJ-TTAvantika title pango.westley +mlt_title_over_gfx Title over graphic watermark1.png,clip1.dv +mlt_slideshow Slideshow Scotland +mlt_bouncy Bouncy, Bouncy clip1.dv,clip3.dv +mlt_bouncy_ball Bouncy, Bouncy Ball clip1.mpeg,clip3.mpeg,circle.png +mlt_news Breaking News clip1.dv,clip2.dv +mlt_squeeze Squeeze Transitions clip1.dv,clip2.dv,clip3.dv +mlt_squeeze_box Squeeze Box clip1.dv,clip2.dv,clip3.dv +mlt_jcut J Cut clip1.dv,clip2.dv +mlt_lcut L Cut clip1.dv,clip2.dv +mlt_fade_black Fade from/to black/silence clip3.mpeg +mlt_push Push wipe clip1.mpeg, clip2.mpeg +mlt_ticker Ticker tape clip1.dv +mlt_attributes Attributes clip1.dv +mlt_slideshow_black Composite slideshow Scotland diff --git a/demo/demo.kino b/demo/demo.kino new file mode 100644 index 0000000..99e8767 --- /dev/null +++ b/demo/demo.kino @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/demo/entity.westley b/demo/entity.westley new file mode 100644 index 0000000..0f6a6b9 --- /dev/null +++ b/demo/entity.westley @@ -0,0 +1,11 @@ + + +]> + + + pango + Hello &name;, +My name is Inigo Montoya. + + diff --git a/demo/luma1.pgm b/demo/luma1.pgm new file mode 100644 index 0000000000000000000000000000000000000000..ac689e5bd7b1afea69b2619d440722853caa1405 GIT binary patch literal 414735 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 + + + + + clip2.mpeg + + + + + + + + clip3.mpeg + + + + + + greyscale + + + luma + + + + + + + + + + + + + + + + + + + + + luma + + + 1 + + + + diff --git a/demo/pango.westley b/demo/pango.westley new file mode 100644 index 0000000..4684d48 --- /dev/null +++ b/demo/pango.westley @@ -0,0 +1,34 @@ + + + + clip1.dv + + + pango + +.txt + GJ-TTAvantika 36 + 1 + 0xffffddff + 0x8c101080 + 8 + + + + + + + + + composite + 1 + 0 + -70%,65%:100%x35%:0 + 0,65%:100%x35%:100 + 0,65%:100%x35%:100 + 0,65%:100%x35%:0 + centre + centre + + + diff --git a/demo/svg.westley b/demo/svg.westley new file mode 100644 index 0000000..a41ff4a --- /dev/null +++ b/demo/svg.westley @@ -0,0 +1,50 @@ + + + + + + + + +]> + + + + + pixbuf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/watermark1.png b/demo/watermark1.png new file mode 100644 index 0000000000000000000000000000000000000000..5a961f8797d74d4e2457e533ab73521a534664a5 GIT binary patch literal 1352 zcmV-O1-JT%P)Q2Ue;Z@vP-uW*q~F{#Qkvx3{iucMMBURf(ro+ z64WDz%3wsI1WiZ?IwmqfE=>#|LBSY)C;}3QD2(82BPa;o3Yo5hZNR#99qZa2f1I_H ztzGq(@2_*d%lmxq_4@>AL{Ystz*E3%(8@tLfC8W$@PYO#i2cA(x63(@PM;VBR0zBc zsDLq@BESzc0Gr${XE4` z1|ntJ0SGg&TT#7n-^VOm1LP#zVloOEUoK*PO)iZ~?3fIKZ+`3pz?LmDX}oDv8-aU) zg>j&wdadZGpWHfwAkV6w>%po_8W!8RaAkmwZr!a5X4uFyCr$Q5SsnWjit2q9*fZMd z9E-%}x?(0zFayy1bCC8MVV<6D1t1cIGrt5mcsjs$=YoX7S~4|NZkO}C?5JM08kjs9 zFcj7}?&(9Ruwb_tC@nHlV3Wzp&@Gb$l-iBdRA*89h+Zx?15rA=BZ+GM_Ia94Nx-DU z(X0%KbuZZY^sTXc{HBAN>MR7%+aKlCO;@>aMekmAo1O=n2FE>pM54M?)+~wIN386A z!@<`ZO3BHLyL%FV1ezl@Qc-H=;5*}ad`cE~jWtnOW@f!=XG>i%7PB-03LpgRJkrgK zm8~@Hzs_%eh2nF2kNN2hMq|GY&|??@B#T+_$(pf@D>UNoi*oREFSDj)W6PES@O6cG zzQISAKSH)eVoO3yTQ2rfx8)jxL$RwUH8HWo#NqD)3BJ?ZZddsf3AC#Ki)PvwS7_vV zPlQ>kTG{mZ4d$(FC{RK4U6@X0l3r}8U_Op9YZb^ z?EobOdXH}F3^OpOpR~&#;idQ5Is3;g0LqGtY+F%+HA@-+eW)sv4_+%Ctr4^j0XYY7 zvscfk`zD&Hz9%ETYG6=f>HAk{KCgF|3B^V}tSceIBt}3_pAVuXtq6#5WJmQffO&tm z4KioC6{A5=qhxd9Y#-eLJ>Zcj9P4fIE8+;<%SgDg_rhe<@N zjeW(caw9`Q-F)B%2Tu0h0Ud9@ed)`Ro)sFm@%;ckPgB@Yz03nV1IVX- z3Q~}3pt8)2#Vk?#s1;cf6pb= zFFs*IeR9kQEtgscSoCgtywFof2zc1-a<(K6j;NZUx5y5_3^1j_LjBx4${i-c5sh7k z{d{z&n{Z@QSSK*Y?Q)(RK9RIc71cWpv@b#AgWfOAMnO12IuO9Oz-+h6c{ADMXzx-P zSOlyB%F|JXKpPSh +Last Revision: 2004-03-20 + + +General Format +-------------- + DVCP is an ASCII-based request/response TCP protocol much like FTP and + inspired by the SGI MVCP (Multiport Video Computer Protocol). Each + command is three to eight characters long followed by zero or more + arguments. Every item (command or argument) in the request is delimited + by a space and terminated with a new line. Arguments that contain spaces + must be surrounded by double quotation marks. The new line must contain + a line feed optionally preceeded by a carriage return. There are no + request header lines or body. + + +Response Codes +-------------- + Responses consist of a numeric result code followed by a space folowed + by a brief textual description of the result. No quoting is applied to + descriptions regardless if it contains spaces. The result codes are + grouped by the hundreds into general categories of responses. Anything + in the 200-299 range is considered a success and anything 300 and above + is an error or exception. Most responses do not contain a body except + some of the success results that report information and sometimes the + 500 Server Error returns specific information. + + A 200 result code contains no body. + A 201 result code contains one or more lines in the body, and an empty + line terminates the response. + A 202 result code contains only a single response line in the body. + + Errors in the 400 range indicate a normally handled error where the + command could not perform its action due to protocol syntax errors or + problems with validation of one or more of the arguments. This usually + indicates that the client is responsible for performing an illegal + request. + + Errors in the 500 range indicate a server error or exception. + + The following is a list of response codes and their descriptions: + 200 OK + 201 OK + 202 OK + 400 Unknown command + 401 Operation timed out + 402 Argument missing + 403 Unit not found + 404 Failed to locate or open clip + 405 Argument value out of range + 500 Server Error + + +Establishing a Connection +------------------------- + One can connect to the miracle server using telnet or a custom client, + preferrably one developed using the valerie client API. The default port + is 5250. Connections can be broken at will or use the BYE command to + request the server to terminate the connection. + + +General Command Information +--------------------------- + + All commands are case insensitive. Arguments may or may not be case + sensitive. There are two categories of commands: global and unit. Global + commands operate at the server level. Unit commands address a specific + unit. miracle is a multi-unit system. Units are named as U? where ? + is the unit number, for example, U0. As units are added to the server, + the unit number increases; the first unit is U0. + + The command HELP lists all commands known to the server with a brief + description of their purpose and arguments. Most commands take zero or + one argument outside of the unit name. Sometimes an argument is + optional, and an optional argument always follows required arguments. + All units command required a unit name argument. + + {} = required argument + [] = optional argument + () = one of a set of pre-defined values + + +Global Commands +--------------- + +HELP + List the commands and their brief description. + +BYE + Close the connection. + +SHUTDOWN + Shutdown the server and all client connections. + +SET {key=value} + Set a global server configuration property. + Currently, the only planned key is "root" to set the base directory + path for the CLS and LOAD commands. The default root value is /. + +GET {key} + Get the current value of a configuration property. + The value is returned by itself in the body of the response. + +CLS {path} + List the clips and subdirectories at {path} on the server. + Only subdirectories, non-hidden regular files, symbolic links, and NFS + shares are supported. + The response body contains one line per item. + The name of the subdirectory/file is always surrounded by double + quotation marks in case it contains spaces. + Subdirectories are listed before files and have a trailing / in their + name. + File entries have a size value in bytes in the second column position. + +RUN {file} + Process the commands in a file located on the server. + Commands are executed one after the other with no delay until the end + of file is reached or a command returns a response code not in the 200 + range. + The response body contains each command sent along with its arguments, + followed by each command's response status code and response body. + + +STATUS + Responds with the output of USTA for each unit and accepts no further + input. Each time the state of the unit changes, a new row is returned by + the server containing the state of the unit. + +Unit Management + + The following global commands manage the DV units within the server. + Currently there is a maximum of four units, and units can not be + removed. Each unit may be in an online or offline state. Offline units + can not be used, and any unit commands issued against an offline unit + results in a 403 response. + +NLS + * NOT IMPLEMENTED IN MIRACLE YET * + + +UADD mlt-consumer[:argument] + Add a unit based upon the mlt-consumer id and optional constructor + argument. + If the consumer is not found, then it still added but in an + offline manner. Later, by adding the device to the bus, the unit will + automatically become online. + The response body contains the name of the new unit: U0, U1, U2, or U3. + Channel is an optional setting. + +ULS + List the units. + The response body contains a space-delimited row for each unit in the + server containing the following columns: + - unit name (one of U0, U1, U2, or U3) + - mlt-consumer[:argument] from uadd + - 1394 node GUID (defunt - always 0 with miracle for now) + - online flag (1 = online, 0 = offline) + +SHUTDOWN + Shutdown the server. + + +Unit Commands +------------- + + The first argument of any unit command is the unit name (U0 - U3). A + unit must be loaded with a file before it can play anything. A "clip" + refers to the presence of a file loaded into the unit. A clip can + contain an in and out point to set the playback region. The default in + point is 0, and the default out point is the number of frames in the + file minus one. Therefore, all frame positions are zero-based. + +USET {unit} {key=value} + Set a unit's configuration property. + Key is one of the following: eof, points. + + Property "eof" determines what the playback engine does when it reaches + the end of a clip. The eof property takes one of the following values: + stop, loop, continue or pause. The default is pause. + + Property "points" determines whether the playback engine restricts the + playback region to the in and out points. It takes one of the following + values: use, ignore. + +UGET {unit} {key} + Get a unit's configuration property. + Key is one of the following: eof, points. + The response body contains only the key's value. See USET for information + about each property. + +LIST {unit} + List the clips associated to the unit. + The response body consists of two sections - the first section is a single row + containing the generation number of the playlist associated to the unit (an + integer starting from 0 which is incremented on each action which changes the + playlist). The second sections contais a space-delimited row for each clip in the + units playlistcontaining the following columns: + - clip index (starts from 0) + - file name + - in point + - out point + - real length of the files + - calculated length of file + When USET points=use is specified (default), the calculated size is (out-in)+1. + When points are ignored, the real length of the file is returned. + +LOAD {unit} {filename} [in out] + Load a clip into the unit. + Optionally set the in and out points to the specified absolute frame numbers. + Sets the current position to the first frame in the clip. + Preface the filename with '!' to tell the disk reader thread to remove only + duplicate frames from the tail of its buffer queue (from a previously loaded + and playing clip). Otherwise, miracle flushes all of its buffers upon LOAD + to make the effect of LOAD instantaneous. The LOAD !, USET eof=pause, and + extended USTA information can be used for client-side playlists (see the + demo programs). + +APND {unit} {filename} [in out] + Append a clip onto the unit's playlist. + Optionally set the in and out points to the specified absolute frame numbers. + +INSERT {unit} {filename} [ [+|-]clip [ in out ] ] + Insert a clip into the units playlist at the specified clip index or relative + to the currently playing clip index. + +REMOVE {unit} [ [+|-]clip ] + Removes a clip from the specified clip index or position relative to the + currently playing clip index. + +CLEAN {unit} + Removes all by the playing clip. + +WIPE {unit} + Removes all clips before the playing clip. + +MOVE {unit} [+|-]clip [ [+|-]clip ] + Move a clip in the playlist to position specified or position relative to the + currently playing clip. + +PLAY {unit} [speed] + Commence unit playback from the current position. + The default speed is 100% if not specified. + Speed is represented as a percentage value multiplied by 10. Therefore + the default playback speed is 1000 (1X or 100%), 2X is 2000. + Negative speed values play in reverse. + +STOP {unit} + Terminate the unit playback resulting in no video being sent. + +PAUSE {unit} + Pause the unit playback causing the current frame position to he held + indefinitely. + +REW {unit} + Rewind the unit. + If the unit it playing, then REW sets the playback speed to -2000 + (200%). + If the unit is stopped, then the frame position is reset to the first + frame. First frame depends upon the "points" unit configuration property + and whether an in point has been established for the clip using the SIN + command. + Set the currently loaded clip's in point. + Frame is zero-based and absolute. It is not dependent upon the clip's + current in point. + A frame-number of -1, resets the in point to 0. + +FF {unit} + Fast forward the unit. + If the unit it playing, then FF sets the playback speed to 2000 (200% + in reverse). + If the unit is stopped, then the frame position is reset to the first + frame. First frame depends upon the "points" unit configuration property + and whether an in point has been established for the clip using the SIN + command. + +STEP {unit} {number-of-frames} + Adjust the current frame position by the number of frames specified. + Number-of-frames can accept positive or negative values. + +GOTO {unit} {frame-number} [ [+|-]clip ] + Set the current frame position to frame-number. + Frame-number is zero-based and absolute within the clip, which means it is + relative to the file beginning and not the clip in point. + It does not alter the playback status of the unit. + +SIN {unit} {frame-number} [ [+|-]clip ] + Set the currently loaded clip's in point. + The in point is the logical starting frame of the clip. + Frame is zero-based and absolute. It is not dependent upon the clip's + current in point. + A frame-number of -1, resets the in point to 0. + +SOUT {unit} {frame-number} [ [+|-]clip ] + Set the currently loaded clip's out point. + The out point is the logical last frame of the clip. + Frame is zero-based and absolute. It is not dependent upon the clip's + current out point. + A frame-number of -1, resets the out point to the number of frames in + the file minus 1. + +USTA {unit} + Get the unit status report. + The response body contains the following fields delimited by spaces: + - unit number: U0, U1, U2, or U3 without the "U" prefix + - mode: (offline|not_loaded|playing|stopped|paused|disconnected|unknown) + "unknown" means the unit has not been added + "disconnected" means the server has closed the connection to the client. + - current clip name: filename + - current position: in absolute frame number units + - speed: playback rate in (percent * 10) + - fps: frames-per-second of loaded clip + - current in-point: starting frame number + - current out-point: ending frame number + - length of the clip + - buffer tail clip name: filename + - buffer tail position: in absolute frame number units + - buffer tail in-point: starting frame number + - buffer tail out-point: ending frame number + - buffer tail length: length of clip in buffer tail + - seekable flag: indicates if the current clip is seekable (relates to head) + - playlist generation number + - current clip index (relates to head) + + The status contains information based not only on the current frame being + output (current above) but also based upon the most recent frame read by + the disk reader thread and added to the tail of the input buffer queue + (buffer tail above). + +XFER {unit} {target-unit} + Transfer the unit's clip to the target unit. + The clip inherently includes the in- and out-point information. + The target unit's "points" configuration property is set to "use." + + + + diff --git a/docs/framework.txt b/docs/framework.txt new file mode 100644 index 0000000..1c2bafb --- /dev/null +++ b/docs/framework.txt @@ -0,0 +1,1341 @@ +Framework Documentation + +Copyright (C) 2004 Ushodaya Enterprises Limited +Author: Charles Yates +Last Revision: 2004-10-08 + + +MLT FRAMEWORK +------------- + +Preamble: + + MLT is a multimedia framework designed for television broadcasting. As such, + it provides a pluggable architecture for the inclusion of new audio/video + sources, filters, transitions and playback devices. + + The framework provides the structure and utility functionality on which + all of the MLT applications and services are defined. + + On its own, the framework provides little more than 'abstract classes' and + utilities for managing resources, such as memory, properties, dynamic object + loading and service instantiation. + + This document is split roughly into 3 sections. The first section provides a + basic overview of MLT, the second section shows how it's used and the final + section shows structure and design, with an emphasis on how the system is + extended. + + +Target Audience: + + This document is provided as a 'road map' for the framework and should be + considered mandatory reading for anyone wishing to develop code at the MLT + level. + + This includes: + + 1. framework maintainers; + 2. module developers; + 3. application developers; + 4. anyone interested in MLT. + + The emphasis of the document is in explaining the public interfaces, as + opposed to the implementation details. + + It is not required reading for the MLT client/server integration - please + refer to valerie.txt and dvcp.txt for more details on this area. + + +SECTION 1 - BASIC OVERVIEW +-------------------------- + +Basic Design Information: + + MLT is written in C. + + The framework has no dependencies other than the standard C99 and POSIX + libraries. + + It follows a basic Object Oriented design paradigm, and as such, much of the + design is loosely based on the Producer/Consumer design pattern. + + It employs Reverse Polish Notation for the application of audio and video FX. + + The framework is designed to be colour space neutral - the currently + implemented modules, however, are very much 8bit YUV422 oriented. In theory, + the modules could be entirely replaced. + + A vague understanding of these terms is assumed throughout the remainder of + this document. + + +Structure and Flow: + + The general structure of an MLT 'network' is simply the connection of a + 'producer' to a 'consumer': + + +--------+ +--------+ + |Producer|-->|Consumer| + +--------+ +--------+ + + A typical consumer requests MLT Frame objects from the producer, does + something with them and when finished with a frame, closes it. + + /\ A common confusion with the producer/consumer terminology used here is + /!!\ that a consumer may 'produce' something. For example, the libdv consumer + \!!/ produces DV and the libdv producer seems to consume DV. However, the + \/ naming conventions refer only to producers and consumers of MLT Frames. + + To put it another way - a producer produces MLT Frame objects and a consumer + consumes MLT Frame objects. + + An MLT Frame essentially provides an uncompressed image and its associated + audio samples. + + Filters may also be placed between the producer and the consumer: + + +--------+ +------+ +--------+ + |Producer|-->|Filter|-->|Consumer| + +--------+ +------+ +--------+ + + A service is the collective name for producers, filters, transitions and + consumers. + + The communications between a connected consumer and producer or service are + carried out in 3 phases: + + * get the frame + * get the image + * get the audio + + MLT employs 'lazy evaluation' - the image and audio need not be extracted + from the source until the get image and audio methods are invoked. + + In essence, the consumer pulls from what it's connected to - this means that + threading is typically in the domain of the consumer implementation and some + basic functionality is provided on the consumer class to ensure realtime + throughput. + + +SECTION 2 - USAGE +----------------- + +Hello World: + + Before we go in to the specifics of the framework architecture, a working + example of usage is provided. + + The following simply provides a media player: + + #include + #include + #include + + int main( int argc, char *argv[] ) + { + // Initialise the factory + if ( mlt_factory_init( NULL ) == 0 ) + { + // Create the default consumer + mlt_consumer hello = mlt_factory_consumer( NULL, NULL ); + + // Create via the default producer + mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] ); + + // Connect the producer to the consumer + mlt_consumer_connect( hello, mlt_producer_service( world ) ); + + // Start the consumer + mlt_consumer_start( hello ); + + // Wait for the consumer to terminate + while( !mlt_consumer_is_stopped( hello ) ) + sleep( 1 ); + + // Close the consumer + mlt_consumer_close( hello ); + + // Close the producer + mlt_producer_close( world ); + + // Close the factory + mlt_factory_close( ); + } + else + { + // Report an error during initialisation + fprintf( stderr, "Unable to locate factory modules\n" ); + } + + // End of program + return 0; + } + + This is a simple example - it doesn't provide any seeking capabilities or + runtime configuration options. + + The first step of any MLT application is the factory initialisation - this + ensures that the environment is configured and MLT can function. The factory + is covered in more detail below. + + All services are instantiated via the factories, as shown by the + mlt_factory_consumer and mlt_factory_producer calls above. There are similar + factories for filters and transitions. There are details on all the standard + services in services.txt. + + The defaults requested here are a special case - the NULL usage requests + that we use the default producers and consumers. + + The default producer is "fezzik". This producer matches file names to + locate a service to use and attaches 'normalising filters' (such as scalers, + deinterlacers, resamplers and field normalisers) to the loaded content - + these filters ensure that the consumer gets what it asks for. + + The default consumer is "sdl". The combination of fezzik and sdl will + provide a media player. + + In this example, we connect the producer and then start the consumer. We + then wait until the consumer is stopped (in this case, by the action of the + user closing the SDL window) and finally close the consumer, producer and + factory before exiting the application. + + Note that the consumer is threaded - waiting for an event of some sort is + always required after starting and before stopping or closing the consumer. + + Also note, you can override the defaults as follows: + + $ MLT_CONSUMER=westley ./hello file.avi + + This will create a westley xml document on stdout. + + $ MLT_CONSUMER=westley MLT_PRODUCER=avformat ./hello file.avi + + This will play the video using the avformat producer directly, thus it will + bypass the normalising functions. + + $ MLT_CONSUMER=libdv ./hello file.avi > /dev/dv1394 + + This might, if you're lucky, do on the fly, realtime conversions of file.avi + to DV and broadcast it to your DV device. + + +Factories: + + As shown in the 'Hello World' example, factories create service objects. + + The framework itself provides no services - they are provided in the form of + a plugin structure. A plugin is organised in the form of a 'module' and a + module can provide many services of different types. + + Once the factory is initialised, all the configured services are available + for use. + + The complete set of methods associated to the factory are as follows: + + int mlt_factory_init( char *prefix ); + const char *mlt_factory_prefix( ); + char *mlt_environment( char *name ); + mlt_producer mlt_factory_producer( char *name, void *input ); + mlt_filter mlt_factory_filter( char *name, void *input ); + mlt_transition mlt_factory_transition( char *name, void *input ); + mlt_consumer mlt_factory_consumer( char *name, void *input ); + void mlt_factory_close( ); + + The mlt_factory_prefix returns the path to the location of the installed + modules directory. This can be specified in the mlt_factory_init call + itself, or it can be specified via the MLT_REPOSITORY environment variable, + or in the absence of either of those, it will default to the install + prefix/shared/mlt/modules. + + The mlt_environment provides read only access to a collection of name=value + pairs as shown in the following table: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |MLT_NORMALISATION |The normalisation of the system |PAL or NTSC | + +------------------+------------------------------------+------------------+ + |MLT_PRODUCER |The default producer |"fezzik" or other | + +------------------+------------------------------------+------------------+ + |MLT_CONSUMER |The default consumer |"sdl" or other | + +------------------+------------------------------------+------------------+ + |MLT_TEST_CARD |The default test card producer |any producer | + +------------------+------------------------------------+------------------+ + + These values are initialised from the environment variables of the same + name. + + As shown above, a producer can be created using the 'default normalising' + producer, and they can also be requested by name. Filters and transitions + are always requested by name - there is no concept of a 'default' for these. + + +Service Properties: + + As shown in the services.txt document, all services have their own set of + properties than can be manipulated to affect their behaviour. + + In order to set properties on a service, we need to retrieve the properties + object associated to it. For producers, this is done by invoking: + + mlt_properties properties = mlt_producer_properties( producer ); + + All services have a similar method associated to them. + + Once retrieved, setting and getting properties can be done directly on this + object, for example: + + mlt_properties_set( properties, "name", "value" ); + + A more complete description of the properties object is found below. + + +Playlists: + + So far, we've shown a simple producer/consumer configuration - the next + phase is to organise producers in playlists. + + Let's assume that we're adapting the Hello World example, and wish to queue + a number of files for playout, ie: + + hello *.avi + + Instead of invoking mlt_factory_producer directly, we'll create a new + function called create_playlist. This function is responsible for creating + the playlist, creating each producer and appending to the playlist. + + mlt_producer create_playlist( int argc, char **argv ) + { + // We're creating a playlist here + mlt_playlist playlist = mlt_playlist_init( ); + + // We need the playlist properties to ensure clean up + mlt_properties properties = mlt_playlist_properties( playlist ); + + // Loop through each of the arguments + int i = 0; + for ( i = 1; i < argc; i ++ ) + { + // Create the producer + mlt_producer producer = mlt_factory_producer( NULL, argv[ i ] ); + + // Add it to the playlist + mlt_playlist_append( playlist, producer ); + + // Close the producer (see below) + mlt_producer_close( producer ); + } + + // Return the playlist as a producer + return mlt_playlist_producer( playlist ); + } + + Notice that we close the producer after the append. Actually, what we're + doing is closing our reference to it - the playlist creates its own reference + to the producer on append and insert, and it will close its reference + when the playlist is destroyed[*]. + + Note also that if you append multiple instances of the same producer, it + will create multiple references to it. + + Now all we need do is to replace these lines in the main function: + + // Create a normalised producer + mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] ); + + with: + + // Create a playlist + mlt_producer world = create_playlist( argc, argv ); + + and we have a means to play multiple clips. + + [*] This reference functionality was introduced in mlt 0.1.2 - it is 100% + compatable with the early mechanism of registering the reference and + destructor with the properties of the playlist object. + + +Filters: + + Inserting filters between the producer and consumer is just a case of + instantiating the filters, connecting the first to the producer, the next + to the previous filter and the last filter to the consumer. + + For example: + + // Create a producer from something + mlt_producer producer = mlt_factory_producer( ... ); + + // Create a consumer from something + mlt_consumer consumer = mlt_factory_consumer( ... ); + + // Create a greyscale filter + mlt_filter filter = mlt_factory_filter( "greyscale", NULL ); + + // Connect the filter to the producer + mlt_filter_connect( filter, mlt_producer_service( producer ), 0 ); + + // Connect the consumer to filter + mlt_consumer_connect( consumer, mlt_filter_service( filter ) ); + + As with producers and consumers, filters can be manipulated via their + properties object - the mlt_filter_properties method can be invoked and + properties can be set as needed. + + The additional argument in the filter connection is an important one as it + dictates the 'track' on which the filter operates. For basic producers and + playlists, there's only one track (0), and as you will see in the next + section, even multiple tracks have a single track output. + + +Attached Filters: + + All services can have attached filters. + + Consider the following example: + + // Create a producer + mlt_producer producer = mlt_factory_producer( NULL, clip ); + + // Get the service object of the producer + mlt_producer service = mlt_producer_service( producer ); + + // Create a filter + mlt_filter filter = mlt_factory_filter( "greyscale" ); + + // Create a playlist + mlt_playlist playlist = mlt_playlist_init( ); + + // Attach the filter to the producer + mlt_service_attach( producer, filter ); + + // Construct a playlist with various cuts from the producer + mlt_playlist_append_io( producer, 0, 99 ); + mlt_playlist_append_io( producer, 450, 499 ); + mlt_playlist_append_io( producer, 200, 399 ); + + // We can close the producer and filter now + mlt_producer_close( producer ); + mlt_filter_close( filter ); + + When this is played out, the greyscale filter will be executed for each frame + in the playlist which comes from that producer. + + Further, each cut can have their own filters attached which are executed after + the producer's filters. As an example: + + // Create a new filter + filter = mlt_factory_filter( "invert", NULL ); + + // Get the second 'clip' in the playlist + producer = mlt_playlist_get_clip( 1 ); + + // Get the service object of the clip + service = mlt_producer_service( producer ); + + // Attach the filter + mlt_service_attach( producer, filter ); + + // Close the filter + mlt_filter_close( filter ); + + Even the playlist itself can have an attached filter: + + // Create a new filter + filter = mlt_factory_filter( "watermark", "+Hello.txt" ); + + // Get the service object of the playlist + service = mlt_playlist_service( playlist ); + + // Attach the filter + mlt_service_attach( service, filter ); + + // Close the filter + mlt_filter_close( filter ); + + And, of course, the playlist, being a producer, can be cut up and placed on + another playlist, and filters can be attached to those cuts or on the new + playlist itself and so on ad nauseum. + + The main advantage of attached filters is that they remain attached and don't + suffer from the maintenance problems associated with items being inserted and + displacing calculated in/out points - this being a major issue if you + exclusively use the connect or insert detached filters in a multitrack field + (described below). + + +Introducing the Mix: + + The mix is the simplest way to introduce transitions between adjacent clips + on a playlist. + + Consider the following playlist: + + +-+----------------------+----------------------------+-+ + |X|A |B |X| + +-+----------------------+----------------------------+-+ + + Let's assume that the 'X' is a 'black clip' of 50 frames long. + + When you play this out, you'll get a 50 frames of black, abrupt cut into + A, followed by an abrupt cut into B, and finally into black again. + + The intention is to convert this playlist into something like: + + +-+---------------------+-+------------------------+-+ + |X|A |A|B |B| + |A| |B| |X| + +-+---------------------+-+------------------------+-+ + + Where the clips which refer to 2 clips represent a transition. Notice that + the representation of the second playlist is shorter than the first - this is + to be expected - a single transition of 50 frames between two clips will + reduce the playtime of the result by 50 frames. + + This is done via the use of the mlt_playlist_mix method. So, assuming you get + a playlist as shown in the original diagram, to do the first mix, you could do + something like: + + // Create a transition + mlt_transition transition = mlt_factor_transition( "luma", NULL ); + + // Mix the first and second clips for 50 + mlt_playlist_mix( playlist, 0, 50, transition ); + + // Close the transition + mlt_transition_close( transition ); + + This would give you the first transition, subsequently, you would apply a similar + technique to mix clips 1 and 2. Note that this would create a new clip on the + playlist, so the next mix would be between 3 and 4. + + As a general hint, to simplify the requirement to know the next clip index, + you might find the following simpler: + + // Get the number of clips on the playlist + int i = mlt_playlist_count( ); + + // Iterate through them in reverse order + while ( i -- ) + { + // Create a transition + mlt_transition transition = mlt_factor_transition( "luma", NULL ); + + // Mix the first and second clips for 50 + mlt_playlist_mix( playlist, i, 50, transition ); + + // Close the transition + mlt_transition_close( transition ); + } + + There are other techniques, like using the mlt_playlist_join between the + current clip and the newly created one (you can determine if a new clip was + created by comparing the playlist length before and after the mix call). + + Internally, the mlt_playlist_mix call generates a tractor and multitrack as + described below. Like the attached filters, the mix makes life very simple + when you're inserting items into the playlist. + + Also note that it allows a simpler user interface - instead of enforcing the + use of a complex multitrack object, you can do many operations on a single + track. Thus, additional tracks can be used to introduce audio dubs, mixes + or composites which are independently positioned and aren't affected by + manipulations on other tracks. But hey, if you want a bombastic, confusing + and ultimately frustrating traditional NLE experience, that functionality + is provided too ;-). + + +Practicalities and Optimisations: + + In the previous two sections I've introduced some powerful functionality + designed to simplify MLT usage. However, a general issue comes into this - + what happens when you introduce a transition between two cuts from the same + bit of video footage? + + Anyone who is familiar with video compression will be aware that seeking + isn't always without consequence from a performance point of view. So if + you happen to require two frames from the same clip for a transition, the + processing is going to be excessive and the result will undoubtedly be very + unpleasant, especially if you're rendering in realtime... + + So how do we get round this? + + Actually, it's very simple - you invoke mlt_producer_optimise on the top + level object after a modification and MLT will determine how to handle it. + Internally, it determines the maximum number of overlapping instances + throughout the object and creates clones and assigns clone indexes as + required. + + In the mix example above, you can simply call: + + // Optimise the playlist + mlt_producer_optimise( mlt_playlist_producer( playlist ) ); + + after the mix calls have be done. Note that this is automatically applied + to deserialised westleys. + + +Multiple Tracks and Transitions: + + MLT's approach to multiple tracks is governed by two requirements: + + 1) The need for a consumer and producer to communicate with one another via + a single frame; + 2) The desire to be able to serialise and manipulate a 'network' (or filter + graph if you prefer). + + We can visualise a multitrack in the way that an NLE presents it: + + +-----------------+ +-----------------------+ + 0: |a1 | |a2 | + +---------------+-+--------------------------+-+---------------------+ + 1: |b1 | + +------------------------------+ + + The overlapping areas of track 0 and 1 would (presumably) have some kind of + transition - without a transition, the frames from b1 and b2 would be shown + during the areas of overlap (ie: by default, the higher numbered track takes + precedence over the lower numbered track). + + MLT has a multitrack object, but it is not a producer in the sense that it + can be connected directly to a consumer and everything will work correctly. + A consumer would treat it precisely as it would a normal producer, and, in + the case of the multitrack above, you would never see anything from track 1 + other than the transitions between the clips - the gap between a1 and a2 + would show test frames. + + This happens because a consumer pulls one frame from the producer it's + connected to while a multitrack will provide one frame per track. + Something, somewhere, must ensure that all frames are pulled from the + multitrack and elect the correct frame to pass on. + + Hence, MLT provides a wrapper for the multitrack, which is called a + 'tractor', and its the tractors task to ensure that all tracks are pulled + evenly, the correct frame is output and that we have 'producer like' + behaviour. + + Thus, a multitrack is conceptually 'pulled' by a tractor as shown here: + + +----------+ + |multitrack| + | +------+ | +-------+ + | |track0|-|--->|tractor| + | +------+ | |\ | + | | | \ | + | +------+ | | \ | + | |track1|-|--->|---o---|---> + | +------+ | | / | + | | | / | + | +------+ | |/ | + | |track2|-|--->| | + | +------+ | +-------+ + +----------+ + + With a combination of the two, we can now connect multitracks to consumers. + The last non-test card will be retrieved and passed on. + + The tracks can be producers, playlists, or even other tractors. + + Now we wish to insert filters and transitions between the multitrack and the + tractor. We can do this directly by inserting filters directly between the + tractor and the multitrack, but this involves a lot of connecting and + reconnecting left and right producers and consumers, and it seemed only fair + that we should be able to automate that process. + + So in keeping with our agricultural theme, the concept of the 'field' was + born. We 'plant' filters and transitions in the field and the tractor pulls + the multitrack (think of a combine harvester :-)) over the field and + produces a 'bail' (sorry - kidding - frame :-)). + + Conceptually, we can see it like this: + + +----------+ + |multitrack| + | +------+ | +-------------+ +-------+ + | |track0|-|--->|field |--->|tractor| + | +------+ | | | |\ | + | | | filters | | \ | + | +------+ | | and | | \ | + | |track1|-|--->| transitions |--->|---o---|---> + | +------+ | | | | / | + | | | | | / | + | +------+ | | | |/ | + | |track2|-|--->| |--->| | + | +------+ | +-------------+ +-------+ + +----------+ + + So, we need to create the tractor first, and from that we obtain the + multitrack and field objects. We can populate these and finally + connect the tractor to a consumer. + + In essence, this is how it looks to the consumer: + + +-----------------------------------------------+ + |tractor +--------------------------+ | + | +----------+ | +-+ +-+ +-+ +-+ | | + | |multitrack| | |f| |f| |t| |t| | | + | | +------+ | | |i| |i| |r| |r| | | + | | |track0|-|--->| |l|- ->|l|- ->|a|--->|a|\| | + | | +------+ | | |t| |t| |n| |n| | | + | | | | |e| |e| |s| |s| |\ | + | | +------+ | | |r| |r| |i| |i| | \| + | | |track1|-|- ->| |0|--->|1|--->|t|--->|t|-|--o---> + | | +------+ | | | | | | |i| |i| | /| + | | | | | | | | |o| |o| |/ | + | | +------+ | | | | | | |n| |n| | | + | | |track2|-|- ->| | |- ->| |--->|0|- ->|1|/| | + | | +------+ | | | | | | | | | | | | + | +----------+ | +-+ +-+ +-+ +-+ | | + | +--------------------------+ | + +-----------------------------------------------+ + + An example will hopefully clarify this. + + Let's assume that we want to provide a 'watermark' to our hello world + example. We have already extended the example to play multiple clips, + and now we will place a text based watermark, reading 'Hello World' in + the top left hand corner: + + mlt_producer create_tracks( int argc, char **argv ) + { + // Create the tractor + mlt_tractor tractor = mlt_tractor_new( ); + + // Obtain the field + mlt_field field = mlt_tractor_field( tractor ); + + // Obtain the multitrack + mlt_multitrack multitrack = mlt_tractor_multitrack( tractor ); + + // Create a composite transition + mlt_transition transition = mlt_factory_transition( "composite", "10%,10%:15%x15%" ); + + // Create track 0 + mlt_producer track0 = create_playlist( argc, argv ); + + // Create the watermark track - note we NEED fezzik for scaling here + mlt_producer track1 = mlt_factory_producer( "fezzik", "pango" ); + + // Get the length of track0 + mlt_position length = mlt_producer_get_playtime( track0 ); + + // Set the properties of track1 + mlt_properties properties = mlt_producer_properties( track1 ); + mlt_properties_set( properties, "text", "Hello\nWorld" ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + mlt_properties_set_position( properties, "length", length ); + mlt_properties_set_int( properties, "a_track", 0 ); + mlt_properties_set_int( properties, "b_track", 1 ); + + // Now set the properties on the transition + properties = mlt_transition_properties( transition ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + + // Add our tracks to the multitrack + mlt_multitrack_connect( multitrack, track0, 0 ); + mlt_multitrack_connect( multitrack, track1, 1 ); + + // Now plant the transition + mlt_field_plant_transition( field, transition, 0, 1 ); + + // Close our references + mlt_producer_close( track0 ); + mlt_producer_close( track1 ); + mlt_transition_close( transition ); + + // Return the tractor + return mlt_tractor_producer( tractor ); + } + + Now all we need do is to replace these lines in the main function: + + // Create a playlist + mlt_producer world = create_playlist( argc, argv ); + + with: + + // Create a watermarked playlist + mlt_producer world = create_tracks( argc, argv ); + + and we have a means to play multiple clips with a horribly obtrusive + watermark - just what the world needed, right? ;-) + + Incidentally, the same thing could be achieved with the more trivial + watermark filter inserted between the producer and the consumer. + + +SECTION 3 - STRUCTURE AND DESIGN +-------------------------------- + +Class Hierarchy: + + The mlt framework consists of an OO class hierarchy which consists of the + following public classes and abstractions: + + mlt_properties + mlt_frame + mlt_service + mlt_producer + mlt_playlist + mlt_tractor + mlt_filter + mlt_transition + mlt_consumer + mlt_deque + mlt_pool + mlt_factory + + Each class defined above can be read as extending the classes above and to + the left. + + The following sections describe the properties, stacking/queuing and memory + pooling functionality provided by the framework - these are key components + and a basic understanding of these is required for the remainder of the + documentation. + + +mlt_properties: + + The properties class is the base class for the frame and service classes. + + It is designed to provide an efficient lookup table for various types of + information, such as strings, integers, floating points values and pointers + to data and data structures. + + All properties are indexed by a unique string. + + The most basic use of properties is as follows: + + // 1. Create a new, empty properties set; + mlt_properties properties = mlt_properties_new( ); + + // 2. Assign the value "world" to the property "hello"; + mlt_properties_set( properties, "hello", "world" ); + + // 3. Retrieve and print the value of "hello"; + printf( "%s\n", mlt_properties_get( properties, "hello" ) ); + + // 4. Reassign "hello" to "world!"; + mlt_properties_set( properties, "hello", "world!" ); + + // 5. Retrieve and print the value of "hello"; + printf( "%s\n", mlt_properties_get( properties, "hello" ) ); + + // 6. Assign the value "0" to "int"; + mlt_properties_set( properties, "int", "0" ); + + // 7. Retrieve and print the integer value of "int"; + printf( "%d\n", mlt_properties_get_int( properties, "int" ) ); + + // 8. Assign the integer value 50 to "int2"; + mlt_properties_set_int( properties, "int2", 50 ); + + // 9. Retrieve and print the double value of "int2"; + printf( "%s\n", mlt_properties_get( properties, "int2" ) ); + + Steps 2 through 5 demonstrate that the "name" is unique - set operations on + an existing "name" change the value. They also free up memory associated to + the previous value. Note that it also possible to change type in this way + too. + + Steps 6 and 7 demonstrate that the properties object handles deserialisation + from strings. The string value of "0" is set, the integer value of 0 is + retrieved. + + Steps 8 and 9 demonstrate that the properties object handles serialisation + to strings. + + To show all the name/value pairs in a properties, it is possible to iterate + through them: + + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + printf( "%s = %s\n", mlt_properties_get_name( properties, i ), + mlt_properties_get_value( properties, i ) ); + + Note that properties are retrieved in the order in which they are set. + + Properties are also used to hold pointers to memory. This is done via the + set_data call: + + uint8_t *image = malloc( size ); + mlt_properties_set_data( properties, "image", image, size, NULL, NULL ); + + In this example, we specify that the pointer can be retrieved from + properties by a subsequent request to get_data: + + image = mlt_properties_get_data( properties, "image", &size ); + + or: + + image = mlt_properties_get_data( properties, "image", NULL ); + + if we don't wish to retrieve the size. + + Two points here: + + 1) The allocated memory remains after the properties object is closed unless + you specify a destructor. In the case above, this can be done with: + + mlt_properties_set_data( properties, "image", image, size, free, NULL ); + + When the properties are closed, or the value of "image" is changed, the + destructor is invoked. + + 2) The string value returned by mlt_properties_get is NULL. Typically, you + wouldn't wish to serialise an image as a string, but other structures + might need such functionality - you can specify a serialiser as the last + argument if required (declaration is char *serialise( void * )). + + Properties also provides some more advanced usage capabilities. + + It has the ability to inherit all serialisable values from another properties + object: + + mlt_properties_inherit( this, that ); + + It has the ability to mirror properties set on this on another set of + properties: + + mlt_properties_mirror( this, that ); + + After this call, all serialisable values set on this are passed on to that. + + +mlt_deque: + + Stacks and queues are essential components in the MLT framework. Being of a + lazy disposition, we elected to implement a 'Double Ended Queue' (deque) - + this encapsulates the functionality of both. + + The API of the deque is defined as follows: + + mlt_deque mlt_deque_init( ); + int mlt_deque_count( mlt_deque this ); + int mlt_deque_push_back( mlt_deque this, void *item ); + void *mlt_deque_pop_back( mlt_deque this ); + int mlt_deque_push_front( mlt_deque this, void *item ); + void *mlt_deque_pop_front( mlt_deque this ); + void *mlt_deque_peek_back( mlt_deque this ); + void *mlt_deque_peek_front( mlt_deque this ); + void mlt_deque_close( mlt_deque this ); + + The stacking operations are used in a number of places: + + * Reverse Polish Notation (RPN) image and audio operations + * memory pooling + + The queuing operations are used in: + + * the consumer base class; + * consumer implementations may require further queues. + + +mlt_pool: + + The MLT framework provides memory pooling capabilities through the mlt_pool + API. Once initilialised, these can be seen as a straightforward drop in + replacement for malloc/realloc/free functionality. + + The background behind this API is that malloc/free operations are + notoriously inefficient, especially when dealing with large blocks of memory + (such as an image). On linux, malloc is optimised for memory allocations + less than 128k - memory blocks allocated of these sizes or less are retained + in the process heap for subsequent reuse, thus bypassing the kernel calls + for repeated allocation/frees for small blocks of memory. However, blocks of + memory larger than that require kernel calls and this has a detrimental + impact on performance. + + The mlt_pool design is simply to hold a list of stacks - there is one stack + per 2^n bytes (where n is between 8 and 31). When an alloc is called, the + requested size is rounded to the next 2^n, the stack is retrieved for that + size, and an item is popped or created if the stack is empty. + + Each item has a 'header', situated immediately before the returned address - + this holds the 'stack' to which the item belongs. + + When an item is released, we retrieve the header, obtain the stack and push + it back. + + Thus, from the programmers point of view, the API is the same as the + traditional malloc/realloc/free calls: + + void *mlt_pool_alloc( int size ); + void *mlt_pool_realloc( void *ptr, int size ); + void mlt_pool_release( void *release ); + + +mlt_frame: + + A frame object is essentially defined as: + + +------------+ + |frame | + +------------+ + | properties | + | image stack| + | audio stack| + +------------+ + + The life cycle of a frame can be represented as follows: + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 0.0 | | |Request frame | + +-----+----------------------+-----------------------+---------------------+ + | 0.1 | |Receives request | | + | | |Request frame | | + +-----+----------------------+-----------------------+---------------------+ + | 0.2 |Receives request | | | + | |Generates frame for | | | + | |current position | | | + | |Increments position | | | + +-----+----------------------+-----------------------+---------------------+ + | 0.3 | |Receives frame | | + | | |Updates frame | | + +-----+----------------------+-----------------------+---------------------+ + | 0.4 | | |Receives frame | + +-----+----------------------+-----------------------+---------------------+ + + Note that neither the filter nor the consumer have any conception of + 'position' until they receive a frame. Speed and position are properties of + the producer, and they are assigned to the frame object when the producer + creates it. + + Step 0.3 is a critical one here - if the filter determines that the frame is + of interest to it, then it should manipulate the image and/or audio stacks + and properties as required. + + Assuming that the filter deals with both image and audio, then it should + push data and methods on to the stacks which will deal with the processing. + This can be done with the mlt_frame_push_image and audio methods. In order for + the filter to register interest in the frame, the stacks should hold: + + image stack: + [ producer_get_image ] [ data1 ] [ data2 ] [ filter_get_image ] + + audio stack: + [ producer_get_audio ] [ data ] [ filter_get_audio ] + + The filter_get methods are invoked automatically when the consumer invokes a + get_image on the frame. + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 1.0 | | |frame_get_image | + +-----+----------------------+-----------------------+---------------------+ + | 1.1 | |filter_get_image: | | + | | | pop data2 and data1 | | + | | | frame_get_image | | + +-----+----------------------+-----------------------+---------------------+ + | 1.2 |producer_get_image | | | + | | Generates image | | | + +-----+----------------------+-----------------------+---------------------+ + | 1.3 | |Receives image | | + | | |Updates image | | + +-----+----------------------+-----------------------+---------------------+ + | 1.4 | | |Receives image | + +-----+----------------------+-----------------------+---------------------+ + + Obviously, if the filter isn't interested in the image, then it should leave + the stack alone, and then the consumer will retrieve its image directly from + the producer. + + Similarly, audio is handled as follows: + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 2.0 | | |frame_get_audio | + +-----+----------------------+-----------------------+---------------------+ + | 2.1 | |filter_get_audio: | | + | | | pop data | | + | | | frame_get_audio | | + +-----+----------------------+-----------------------+---------------------+ + | 2.2 |producer_get_audio | | | + | | Generates audio | | | + +-----+----------------------+-----------------------+---------------------+ + | 2.3 | |Receives audio | | + | | |Updates audio | | + +-----+----------------------+-----------------------+---------------------+ + | 2.4 | | |Receives audio | + +-----+----------------------+-----------------------+---------------------+ + + And finally, when the consumer is done with the frame, it should close it. + + Note that a consumer may not evaluate both image and audio for any given + frame, especially in a realtime environment. See 'Realtime Considerations' + below. + + By default, a frame has the following properties: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |_position |The producers frame position |0 to n | + +------------------+------------------------------------+------------------+ + |_speed |The producers speed |double | + +------------------+------------------------------------+------------------+ + |image |The generated image |NULL or pointer | + +------------------+------------------------------------+------------------+ + |alpha |The generated alpha mask |NULL or pointer | + +------------------+------------------------------------+------------------+ + |width |The width of the image | | + +------------------+------------------------------------+------------------+ + |height |The height of the image | | + +------------------+------------------------------------+------------------+ + |normalised_width |The normalised width of the image |720 | + +------------------+------------------------------------+------------------+ + |normalised_height |The normalised height of the image |576 or 480 | + +------------------+------------------------------------+------------------+ + |progressive |Indicates progressive/interlaced |0 or 1 | + +------------------+------------------------------------+------------------+ + |top_field_first |Indicates top field first |0 or 1 | + +------------------+------------------------------------+------------------+ + |audio |The generated audio |NULL or pointer | + +------------------+------------------------------------+------------------+ + |frequency |The frequency of the audio | | + +------------------+------------------------------------+------------------+ + |channels |The channels of the audio | | + +------------------+------------------------------------+------------------+ + |samples |The samples of the audio | | + +------------------+------------------------------------+------------------+ + |aspect_ratio |The sample aspect ratio of the image|double | + +------------------+------------------------------------+------------------+ + |test_image |Used to indicate no image available |0 or 1 | + +------------------+------------------------------------+------------------+ + |test_audio |Used to indicate no audio available |0 or 1 | + +------------------+------------------------------------+------------------+ + + The consumer can attach the following properties which affect the default + behaviour of a frame: + + +------------------+------------------------------------+------------------+ + |test_card_producer|Synthesise test images from here |NULL or pointer | + +------------------+------------------------------------+------------------+ + |consumer_aspect_ |Apply this aspect ratio to the test |double | + |ratio |card producer | | + +------------------+------------------------------------+------------------+ + |rescale.interp |Use this scale method for test image|"string" | + +------------------+------------------------------------+------------------+ + + While most of these are mainly self explanatory, the normalised_width and + normalised_height values require a little explanation. These are required + to ensure that effects are consistently handled as PAL or NTSC, regardless + of the consumers or producers width/height image request. + + The test_image and audio flags are used to determine when images and audio + should be synthesised. + + Additional properties may be provided by the producer implementation, and + filters, transitions and consumers may add additional properties to + communicate specific requests. These are documented in modules.txt. + + The complete API for the mlt frame is as follows: + + mlt_frame mlt_frame_init( ); + mlt_properties mlt_frame_properties( mlt_frame this ); + int mlt_frame_is_test_card( mlt_frame this ); + int mlt_frame_is_test_audio( mlt_frame this ); + double mlt_frame_get_aspect_ratio( mlt_frame this ); + int mlt_frame_set_aspect_ratio( mlt_frame this, double value ); + mlt_position mlt_frame_get_position( mlt_frame this ); + int mlt_frame_set_position( mlt_frame this, mlt_position value ); + int mlt_frame_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); + uint8_t *mlt_frame_get_alpha_mask( mlt_frame this ); + int mlt_frame_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); + int mlt_frame_push_get_image( mlt_frame this, mlt_get_image get_image ); + mlt_get_image mlt_frame_pop_get_image( mlt_frame this ); + int mlt_frame_push_frame( mlt_frame this, mlt_frame that ); + mlt_frame mlt_frame_pop_frame( mlt_frame this ); + int mlt_frame_push_service( mlt_frame this, void *that ); + void *mlt_frame_pop_service( mlt_frame this ); + int mlt_frame_push_audio( mlt_frame this, void *that ); + void *mlt_frame_pop_audio( mlt_frame this ); + void mlt_frame_close( mlt_frame this ); + + +mlt_service: + + The service base class extends properties and allows 0 to m inputs and 0 to + n outputs and is represented as follows: + + +-----------+ + - ->| |- -> + - ->| Service |- -> + - ->| | + +-----------+ + | properties| + +-----------+ + + Descendents of service impose restrictions on how inputs and outputs can be + connected and will provide a basic set of properties. Typically, the service + instance is encapsulated by the descendent in order for it to ensure that + its connection rules are followed. + + A service does not define any properties when constructed. It should be + noted that producers, filters and transitions my be serialised (say, via the + westley consumer), and care should be taken to distinguish between + serialisable and transient properties. The convention used is to prefix + transient properties with an underscore. + + The public interface is defined by the following functions: + + int mlt_service_init( mlt_service this, void *child ); + mlt_properties mlt_service_properties( mlt_service this ); + int mlt_service_connect_producer( mlt_service this, mlt_service producer, int index ); + int mlt_service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + void mlt_service_close( mlt_service this ); + + Typically, only direct descendents of services need invoke these methods and + developers are encouraged to use those extensions when defining new services. + + +mlt_producer: + + A producer has 0 inputs and 1 output: + + +-----------+ + | | + | Producer |---> + | | + +-----------+ + | service | + +-----------+ + + A producer provides an abstraction for file readers, pipes, streams or any + other image or audio input. + + When instantiated, a producer has the following properties: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |mlt_type |The producers type |mlt_producer | + +------------------+------------------------------------+------------------+ + |_position |The producers frame position |0 to n | + +------------------+------------------------------------+------------------+ + |_speed |The producers speed |double | + +------------------+------------------------------------+------------------+ + |fps |The output frames per second |25 or 29.97 | + +------------------+------------------------------------+------------------+ + |in |The in point in frames |0 to length - 1 | + +------------------+------------------------------------+------------------+ + |out |The out point in frames |in to length - 1 | + +------------------+------------------------------------+------------------+ + |length |The length of the input in frames |0 to n | + +------------------+------------------------------------+------------------+ + |aspect_ratio |aspect_ratio of the source |0 to n | + +------------------+------------------------------------+------------------+ + |eof |end of clip behaviour |"pause" or "loop" | + +------------------+------------------------------------+------------------+ + |resource |Constructor argument (ie: file name)|"" | + +------------------+------------------------------------+------------------+ + + Additional properties may be provided by the producer implementation. + + The public interface is defined by the following functions: + + mlt_producer mlt_producer_new( ); + int mlt_producer_init( mlt_producer this, void *child ); + mlt_service mlt_producer_service( mlt_producer this ); + mlt_properties mlt_producer_properties( mlt_producer this ); + int mlt_producer_seek( mlt_producer this, mlt_position position ); + mlt_position mlt_producer_position( mlt_producer this ); + mlt_position mlt_producer_frame( mlt_producer this ); + int mlt_producer_set_speed( mlt_producer this, double speed ); + double mlt_producer_get_speed( mlt_producer this ); + double mlt_producer_get_fps( mlt_producer this ); + int mlt_producer_set_in_and_out( mlt_producer this, mlt_position in, mlt_position out ); + mlt_position mlt_producer_get_in( mlt_producer this ); + mlt_position mlt_producer_get_out( mlt_producer this ); + mlt_position mlt_producer_get_playtime( mlt_producer this ); + mlt_position mlt_producer_get_length( mlt_producer this ); + void mlt_producer_prepare_next( mlt_producer this ); + void mlt_producer_close( mlt_producer this ); + + +mlt_filter: + + The public interface is defined by the following functions: + + int mlt_filter_init( mlt_filter this, void *child ); + mlt_filter mlt_filter_new( ); + mlt_service mlt_filter_service( mlt_filter this ); + mlt_properties mlt_filter_properties( mlt_filter this ); + mlt_frame mlt_filter_process( mlt_filter this, mlt_frame that ); + int mlt_filter_connect( mlt_filter this, mlt_service producer, int index ); + void mlt_filter_set_in_and_out( mlt_filter this, mlt_position in, mlt_position out ); + int mlt_filter_get_track( mlt_filter this ); + mlt_position mlt_filter_get_in( mlt_filter this ); + mlt_position mlt_filter_get_out( mlt_filter this ); + void mlt_filter_close( mlt_filter ); + + +mlt_transition: + + The public interface is defined by the following functions: + + int mlt_transition_init( mlt_transition this, void *child ); + mlt_transition mlt_transition_new( ); + mlt_service mlt_transition_service( mlt_transition this ); + mlt_properties mlt_transition_properties( mlt_transition this ); + int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track ); + void mlt_transition_set_in_and_out( mlt_transition this, mlt_position in, mlt_position out ); + int mlt_transition_get_a_track( mlt_transition this ); + int mlt_transition_get_b_track( mlt_transition this ); + mlt_position mlt_transition_get_in( mlt_transition this ); + mlt_position mlt_transition_get_out( mlt_transition this ); + mlt_frame mlt_transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ); + void mlt_transition_close( mlt_transition this ); + + +mlt_consumer: + + The public interface is defined by the following functions: + + int mlt_consumer_init( mlt_consumer this, void *child ); + mlt_service mlt_consumer_service( mlt_consumer this ); + mlt_properties mlt_consumer_properties( mlt_consumer this ); + int mlt_consumer_connect( mlt_consumer this, mlt_service producer ); + int mlt_consumer_start( mlt_consumer this ); + mlt_frame mlt_consumer_get_frame( mlt_consumer this ); + mlt_frame mlt_consumer_rt_frame( mlt_consumer this ); + int mlt_consumer_stop( mlt_consumer this ); + int mlt_consumer_is_stopped( mlt_consumer this ); + void mlt_consumer_close( mlt_consumer ); + + +Specialised Producers: + + There are two major types of specialised producers - playlists and tractors. + + The following sections describe these. + + +mlt_playlist: + + mlt_playlist mlt_playlist_init( ); + mlt_producer mlt_playlist_producer( mlt_playlist this ); + mlt_service mlt_playlist_service( mlt_playlist this ); + mlt_properties mlt_playlist_properties( mlt_playlist this ); + int mlt_playlist_count( mlt_playlist this ); + int mlt_playlist_clear( mlt_playlist this ); + int mlt_playlist_append( mlt_playlist this, mlt_producer producer ); + int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out ); + int mlt_playlist_blank( mlt_playlist this, mlt_position length ); + mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index ); + int mlt_playlist_current_clip( mlt_playlist this ); + mlt_producer mlt_playlist_current( mlt_playlist this ); + int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index ); + int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out ); + int mlt_playlist_remove( mlt_playlist this, int where ); + int mlt_playlist_move( mlt_playlist this, int from, int to ); + int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out ); + void mlt_playlist_close( mlt_playlist this ); + +mlt_tractor: diff --git a/docs/inigo.txt b/docs/inigo.txt new file mode 100644 index 0000000..b18af6c --- /dev/null +++ b/docs/inigo.txt @@ -0,0 +1,378 @@ +Inigo Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Author: Charles Yates +Last Revision: 2004-03-20 + + +INIGO +----- + +Preamble: + + inigo was developed as a test tool for the MLT framework. It can be thought + of as a powerful, if somewhat obscure, multitrack command line oriented + video editor. + + The following details the usage of the tool and as a result, provides a lot + of insight into the workings of the MLT framework. + + +Usage: + + inigo [ -group [ name=value ]* ] + [ -consumer id[:arg] [ name=value ]* ] + [ -filter filter[:arg] [ name=value ] * ] + [ -attach filter[:arg] [ name=value ] * ] + [ -mix length [ -mixer transition ]* ] + [ -transition id[:arg] [ name=value ] * ] + [ -blank frames ] + [ -track ] + [ -split relative-frame ] + [ -join clips ] + [ -repeat times ] + [ producer [ name=value ] * ]+ + + +General rules: + + 1. Order is incredibly important; + + 2. Error checking on command line parsing is weak; + + 3. Please refer to services.txt for details on services available; + + 4. The MLT framework, from which inigo has inherited its naming convention, + is very mlt-centric. Producers produce MLT frame objects and consumers + consume MLT frame objects. The distinction is important - a DV producer + does not produce DV, it produces MLT frames from a DV source, and similarly + a DV consumer does not consume DV, it consumes MLT frames and produces DV + frames. + + +Terminology: + + 'Producers' typically refer to files but may also indicate devices (such as + dv1394 input or video4linux). Hence, the more generic term is used [the more + generic usage is out of scope for now...]. + + 'Filters' are frame modifiers - they always guarantee that for every frame + they receive, they output *precisely* one frame. Never more, never less, + ever. Nothing says that a filter cannot generate frames though + + 'Transitions' collect frames from two tracks (a and b) and output 1 + modified frame on their 'a track', and 1 unmodified frame on their 'b track'. + Never more, never less, ever. + + 'Consumers' collect frames from a producer, do something with them and + destroy them. + + Collectively, these are known as 'services'. + + All services have 'properties' associated to them. These are typically + defaulted or evaluated and may be overriden on a case by case basis. + + All services except consumers obey in and out properties. + + Consumers have no say in the flow of frames [though they may give the + illusion that they do]. They get frames from a connected producer, use them, + destroy them and get more. + + +Basics: + + To play a file with the default SDL PAL consumer, usage is: + + $ inigo file + + Note that 'file' can be anything that inigo has a known 'producer' mapping + for (so this can be anything from .dv to .txt). + + You can also specify the producer directly, for example: + + $ inigo avformat:file.mpeg + + Would force the direct use of avformat for loading the file. + + +Properties: + + Properties can be assigned to the producer by adding additional name=value + pairs after the producer: + + $ inigo file in=50 out=100 something="something else" + + Note that while some properties have meaning to all producers (for example: + in, out and length are guaranteed to be valid for all, though typically, + length is determined automatically), the validity of others are dependent on + the producer - however, properties will always be assigned and silently + ignored if they won't be used. + + +Multiple Files: + + Multiple files of different types can be used: + + $ inigo a.dv b.mpg c.png + + Properties can be assigned to each file: + + $ inigo a.dv in=50 out=100 b.mpg out=500 c.png out=500 + + MLT will take care of 'normalising' the output of a producer to ensure + that the consumer gets what it needs. So, in the case above, the mlt + framework will ensure that images are rescaled and audio resampled to meet + the requirements of your configuration (which, by default, will be PAL). + See 'Appendix A: Normalisation Rules' below. + + +Filters: + + Filters are frame modifiers - they can change the contents of the audio or + the images associated to a frame. + + $ inigo a.dv -filter greyscale + + As with producers, properties may be specified on filters too. + + Again, in and out properties are common to all, so to apply a filter to a + range of frames, you would use something like: + + $ inigo a.dv -filter greyscale in=0 out=50 + + Again, filters have their own set of rules about properties and will + silently ignore properties that do not apply. + + +Groups: + + The -group switch is provided to force default properties on the following + 'services'. For example: + + $ inigo -group in=0 out=49 clip* + + would play the first 50 frames of all clips that match the wild card + pattern. + + Note that the last -group settings also apply to the following filters, + transitions and consumers, so: + + $ inigo -group in=0 out=49 clip* -filter greyscale + + is *probably not* what you want (ie: the greyscale filter would only be + applied to the first 50 frames). + + To shed the group properties, you can use any empty group: + + $ inigo -group in=0 out=49 clip* -group -filter greyscale + + +Attached Filters: + + As described above, the -filter switch applies filters to an entire track. To + localise filters to a specific clip on a track, you have to know information + about the lengths of the clip and all clips leading up to it. In practise, + this is horrifically impractical, especially at a command line level (and not + even that practical from a programing point of view...). + + The -attach family of switches simplify things enormously. By default, -attach + will attach a filter to the last service created, so: + + $ inigo clip1.dv clip2.dv -attach greyscale clip3.dv + + would only apply the filter to clip2.dv. You can further narrow down the area of + the effect by specifying in/out points on the attached filter. + + This might seem simple so far, but there is a catch... consider the following: + + $ ingo clip1.dv -attach watermark:+hello.txt -attach invert + + The second attached filter is actually attached to the watermark. You might + think, yay, nice (and it is :-)), but, it might not be what you want. For example + you might want to attach both to clip1.dv. To do that, you can use: + + $ ingo clip1.dv -attach-cut watermark:+hello.txt -attach-cut invert + + As you shall see below, there are still another couple of gotchas associated to + -attach, and even another variant :-). + + +Mixes: + + The -mix switch provides the simplest means to introduce transitions between + adjacent clips. + + For example: + + $ inigo clip1.dv clip2.dv -mix 25 -mixer luma -mixer mix:-1 + + would provide both an audio and video transition between clip1 and clip2. + + This functionality supercedes the enforced use of the -track and -transition + switches from earlier versions of inigo and makes life a lot easier :-). + + These can be used in combination, so you can for example do a fade from black + and to black using the following: + + $ inigo colour:black out=24 clip1.dv -mix 25 -mixer luma \ + colour:black out=24 -mix 25 -mixer luma + + while this may not be immediately obvious, consider what's happening as the + command line is being parsed from left to right: + + Input: Track + ----------------------- ----------------------------------------------------- + colour:black out=24 [black] + clip1.dv [black][clip1.dv] + -mix 25 [black+clip1.dv][clip1.dv] + -mixer luma [luma:black+clip1.dv][clip1.dv] + colour:black out=24 [luma:black+clip1.dv][clip1.dv][black] + -mix 25 [luma:black+clip1.dv][clip1.dv][clip1.dv+black] + -mixer luma [luma:black+clip1.dv][clip1.dv][luma:clip1.dv+black] + + Obviously, the clip1.dv instances refer to different parts of the clip, but + hopefully that will demonstrate what happens as we construct the track. + + You will find more details on the mix in the framework.txt. + + +Mix and Attach: + + As noted, -attach normally applies to the last created service - so, you can + attach a filter to the transition region using: + + $ inigo clip1.dv clip2.dv -mix 25 -mixer luma -attach watermark:+Transition.txt + + Again, nice, but take care - if you want the attached filter to be associated + to the region following the transition, use -attach-cut instead. + + +Splits, Joins, Removes and Swaps: + + COMPLEX - needs simplification.... + + +Introducing Tracks and Blanks: + + So far, all of the examples have shown the definition of a single + playlist, or more accurately, track. + + When multiple tracks exist, the consumer will receive a frame + from the 'highest numbered' track that is generating a non-blank + frame. + + It is best to visualise a track arrangement, so we'll start with + an example: + + $ inigo a.dv -track b.dv in=0 out=49 + + This can be visualised as follows: + + +------------------+ + |a | + +-------+----------+ + |b | + +-------+ + + Playout will show the first 50 frames of b and the 51st frame shown will be + the 51st frame of a. + + This rule also applies to audio only producers on the second track, for + example, the following would show the video from the a track, but the audio + would come from the second track: + + $ inigo a.dv -track b.mp3 in=0 out=49 + + To have the 51st frame be the first frame of b, we can use the -blank switch: + + $ inigo a.dv out=49 -track -blank 49 b.dv + + Which we can visualise as: + + +-------+ + |a | + +-------+-------------------+ + |b | + +-------------------+ + + Now playout will continue as though a and b clips are on the + same track (which on its own, is about as useful as reversing the + process of slicing bread). + + +Transitions: + + Where tracks become useful is in the placing of transitions. + + Here we need tracks to overlap, so a useful multitrack + definition could be given as: + + $ inigo a.dv out=49 \ + -track \ + -blank 24 b.dv \ + -transition luma in=25 out=49 a_track=0 b_track=1 + + Now we're cooking - our visualisation would be something like: + + +-------+ + |a | + +---+---+--------------+ + |b | + +------------------+ + + Playout will now show the first 25 frames of a and then a fade + transition for 25 frames between a and b, and will finally + playout the remainder of b. + + +Reversing a Transition: + + When we visualise a track definition, we also see situations + like: + + +-------+ +----------+ + |a1 | |a2 | + +---+---+--------------+----+-----+ + |b | + +-----------------------+ + + In this case, we have two transitions, a1 to b and b to a2. + + In this scenario, we define a command line as follows: + + $ inigo a.dv out=49 -blank 49 a2.dv \ + -track \ + -blank 24 b.dv out=99 \ + -transition luma in=25 out=49 a_track=0 b_track=1 \ + -transition luma in=100 out=124 reverse=1 a_track=0 b_track=1 + + +Serialisation: + + Inigo has a built in serialisation mechanism - you can build up + your command, test it via any consumer and then add a -serialise + file.inigo switch to save it. + + The saved file can be subsequently used as a clip by either + miracle or inigo. Take care though - paths to files are saved as + provided on the command line.... + + A more expressive serialisation can be obtained with the westley consumer + - this will provide an xml document which can be used freely in inigo and + miracle. + + See westley.txt for more information. + + +Missing Features: + + Some filters/transitions should be applied on the output frame regardless + of which track it comes from - for example, you might have a 3rd text + track or a watermark which you want composited on every frame, and of + course, there's the obscure filter.... + + inigo only supports this in two invocations - as a simple example: + + $ inigo a.dv -track -blank 100 b.dv -consumer westley:basic.westley + $ inigo basic.westley -filter watermark:watermark.png + diff --git a/docs/install.txt b/docs/install.txt new file mode 100644 index 0000000..d71d849 --- /dev/null +++ b/docs/install.txt @@ -0,0 +1,187 @@ +Installation Documentation + +Copyright (C) 2004 Ushodaya Enterprises Limited +Author: Charles Yates +Last Revision: 2004-04-13 + + +INSTALL +------- + + This document provides a description of the MLT project installation and + organisation. + + +Directories +----------- + + The directory heirarchy is defined as follows: + + + demo - A selection of samples to show off capabilities. + + docs - Location of all documentation + + src - All project source is provided here + + framework - The mlt media framework + + modules - All services are defined here + + avformat - libavformat dependent services + + bluefish - Bluefish dependent services (*) + + core - Independent MLT services + + dv - libdv dependent services + + fezzik - A giant (meta) service to load and normalise media + + gtk2 - pango and pixbuf dependent services + + mainconcept - mainconcept dependent services (*) + + normalize - audio normalisation functions (**) + + plus - throwaway silliness + + resample - libresample dependent services (**) + + sdl - SDL dependent services + + vorbis - vorbis dependenent services + + westley - Nice and clever XML services + + xine - Xine-derived sources (**) + + albino - A simple console (protocol level) example (**) + + inigo - A media playing test application (**) + + humperdink - A terminal-based example client + + miracle - The server implementation (**) + + tests - Reserved for regression and unit tests + + valerie - Client API to access the server (**) + + Additional subdirectories may be nested below those shown and should be + documented in their parent. + + (*) Not posted to CVS due to licensing issues. + (**) Contains GPL dependencies or code. + + +Dependencies +------------ + + The MLT core is dependent on: + + * a C99 compliant C compiler + * posix threading + * standard posix libraries + + The MLT applications and libraries provided are all dependent on the core. + + The modules have the following dependencies: + + ----------- ---------------------------------------------------------- + MODULE DESCRIPTION + ----------- ---------------------------------------------------------- + avformat Provided from ffmpeg CVS and compiled as a shared library. + URL: http://ffmpeg.sf.net + ----------- ---------------------------------------------------------- + bluefish Bluefish hardware and software development kit + URL: http://www.bluefish444.com + ----------- ---------------------------------------------------------- + dv libdv 0.102 or later. + URL: http://libdv.sf.net + ----------- ---------------------------------------------------------- + gtk2 GTK2 and associated dependencies. + URL: http://www.gtk.org + ----------- ---------------------------------------------------------- + mainconcept Mainconcept MPEG and DVCPRO Release SDKs. + URL: http://www.mainconcept.com + ----------- ---------------------------------------------------------- + resample libsamplerate 0.15 or later + URL: http://www.mega-nerd.com/SRC/ (GPL) + ----------- ---------------------------------------------------------- + sdl SDL 1.2 or later. + URL: http://www.libsdl.org + ----------- ---------------------------------------------------------- + vorbis libvorbis 1.0.1 or later. + URL: http://www.vorbis.com/ + ----------- ---------------------------------------------------------- + westley libxml2 2.5 or later. + URL: http://www.xmlsoft.org/ + ----------- ---------------------------------------------------------- + + +Configuration +------------- + + Configuration is triggered from the top level directory via a + ./configure script. + + Each source bearing subdirectory shown above have their own configure + script which are called automatically from the top level. + + Typically, new modules can be introduced without modification to the + configure script and arguments are accepted and passed through to all + subdirectories. + + More information on usage is found by running: + + ./configure --help + + NB: This script must be run to register new services after a CVS checkout + or subsequent update. + + +Compilation +----------- + + Makefiles are generated during configuration and these are based on + a per directory template which must be provided by the developer. + + +Testing +------- + + To execute the mlt tools without installation, or to test a new version + on a system with an already installed mlt version, you should run: + + . setenv + + NB: This applies to your current shell only and it assumes sh or bash. + + +Installation +------------ + + The install is triggered by running make install from the top level + directory. + + The framework produces a single shared object which is installed in + $prefix/lib/ and public header files which are installed in + $prefix/include/mlt/framework. + + Valerie produces a single shared object which is installed in + $prefix/lib/ and public header which are installed in + $prefix/include/mlt/valerie. + + Miracle produces a single exectuable which is installed in + $prefix/bin/, a library in $prefix/lib and associated header files in + $prefix/include. + + The modules produce a shared object per module and update text files + containing a list of modules provided by this build. These are installed + in $prefix/share/mlt/modules. It is at the discretion of the module to + install additional support files. + + To allow the development of external components, mlt-config and scripts + are generated and installed in $prefix/bin. + + After install, only those modules listed are usable by the server. No + module is loaded unless explicitly requested via server configuration + or usage. + + External modules are also placed in this $prefix/share/mlt/modules, and the + installation of those must modify the text file accordingly before they + will be considered at runtime. + + +Development +----------- + + All compilation in the project has {top-level-dir}/src on the include path. + All headers are included as: + + #include + + All external modules have {prefix}/include/mlt on the include path. All + headers should also be included as: + + #include + + This allows migration of source between external and internal modules. + The configuration and Makefile template requirements will require + attention though. diff --git a/docs/policies.txt b/docs/policies.txt new file mode 100644 index 0000000..5a5d2ce --- /dev/null +++ b/docs/policies.txt @@ -0,0 +1,52 @@ +Open Source Development Policies and Procedures for MLT +by Dan Dennedy + +Policies +-------- + +Any contribution to the "core" module must assign copyright to Ushodaya +Enterprises Limited because they need license control over that module. + +The framework and valerie client libraries are covered under LGPL. Miracle, +inigo, albino, and humperdink applications are covered under GPL. Modules +should strive to be LGPL to make them available through the framework as LGPL. + +Comments in the framework and valerie header files must be C-style, not C++. + +Coding Style: +There are not a lot of rules, but we prefer brackets on their own line, +indents using tabs, liberal usage of spaces in statements and expressions, and +no hard line length. The code in src/framework serves as a good example. + +Commit messages must be prefaced with the name of the changed files. This makes +the Subversion log more useful as a ChangeLog. For example, + docs/policies.txt: added policy about commit message + +Increment the version number in ./configure on the first commit after a release +as well as just prior to a new release. This way we can track if someone is +using an unreleased version from the source code repository. + +Procedures +---------- + +Update services.txt when you add or update a service. + +Setting Copyright on Appropriated Code: +You do not want to be accused of copying someone's code and changing copyright +or license without permission. The license is straightforward: you must retain +the original author's license unless you receive explicit permission. There are +a few ways to approach the copyright declaration depending upon the +intermingling and changes. If you heavily comingle original and new code or +lightly modifiy the original code, you can retain the original's copyright +including the years, and then add your copyright for the current year. If you +can separate the MLT integration from the core subroutines, then you can put +the core subroutines into a separate file with the original copyright and just +copyright the MLT integration code as your own. However, if you have heavily +modified the original code beyond nearly all recognition, you can copyright it +as your own and attribute the original author as inspiration. + +Bug Reporting: +First preference is to use the SourceForge tracker: +http://sourceforge.net/tracker/?group_id=96039&atid=613414 +Second preference is in the mailing list: +mlt-devel@lists.sourceforge.net diff --git a/docs/services.txt b/docs/services.txt new file mode 100644 index 0000000..23e6b5c --- /dev/null +++ b/docs/services.txt @@ -0,0 +1,1588 @@ +Service Documentation + +Authors: Charles Yates + Dan Dennedy +Last Revision: $Date: 2007-03-30 08:55:55 +0200 (ven, 30 mar 2007) $ + + +SERVICES +-------- + + Services marked as "(Proprietary)" are not distributed with the LGPL + version of mlt. + +Producers +--------- + + avformat + + Description + + ffmpeg libavformat based producer for video and audio. + + Constructor Argument + + 'file' - a filename specification or URL in the form: + [{protocol}|{format}]:{resource}[?{format-parameter}[&{format-parameter}...]] + For example, video4linux:/dev/video1?width:320&height:240 + Note: on the bash command line, & must be escaped as '\&'. + Also, note the use of ':' instead of '=' for parameters. + Use 'ffmpeg -formats' to see a list of supported protocols + and formats. + + Details + + Format parameters only appear to be useful with 'video4linux' or + 'audio_device' formats. For 'video4linux' the parameters are + width, height, frame_rate, frame_rate_base, and standard (ntsc|pal). + For 'audio_device' the parameters are channels and sample_rate. + + Initialisation Properties + + int video_index - index of video stream to use (-1 is off) + int audio_index - index of audio stream to use (-1 is off) + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - this is fixed at 25 for PAL currently + double source_fps - the framerate of the resource + double aspect_ratio - sample aspect ratio of the resource + - this is determined on every frame read + + Dependencies + + ffmpeg must be configured as --enable-shared and installed prior + to compilation of mlt. + + Known Bugs + + Audio sync discrepancy with some content. + Not all libavformat supported formats are seekable. + Ogg Vorbis is currently broken. + MPEG seeking is inaccurate - doesn't seek to i-frames so you may + get junk for a few frames. + RAW DV seeking not supported. + Fails to play beyond first frame of video of sources with PTS not + starting at 0 (video4linux). + + fezzik + + Description + + A friendly giant that likes to rhyme and throw rocks + + Constructor Argument + + 'file' - a filename specification: + [{mlt-service}:]{resource} | {mlt-service} + - can also be the name of a producer service that can + accept the resource specified post construction. + + Initialisation Properties + + int in - in point + int out - out point + + all producer initialising properties + + Read Only Properties + + string resource - file location + + all producer read only properties + + Details + + This producer has two roles: + + 1. it handles the mappings of all file names to the other + producers; + 2. it attaches normalising filters (rescale, resize and resample) + to the producers (when necessary). + + This producer simplifies many aspects of use. Essentially, it + ensures that a consumer will receive images and audio precisely as + they request them. + + Dependencies + + all. + + Known Bugs + + None. + + + colour + + Description + + A simple colour generator. + + Constructor Argument + + colour - A colour value is a hexadecimal representation of RGB plus + alpha channel as 0xrrggbbaa. + - Also colours can be the words: white, black, red, green, + or blue. + - The default colour is black. + + Initialisation Properties + + none + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + none + + + libdv + + Description + + libdv based decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + int length - duration of resource (in frames) + + Mutable Properties + + string quality - one of "best," "fast" or anything else chooses + medium. + + Dependencies + + libdv. + + Known Bugs + + DVCPRO is incorrectly identified as 16:9 aspect ratio. You must use + libdv from CVS or a post 0.101 release. + + mcdv (Proprietary) + + Description + + MainConcept based dv decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + int length - duration of resource (in frames) + + Dependencies + + MainConcept DV or DVCPRO SDK, libdv. + "dv_sdk" installed parallel to mlt. + + Known Bugs + + None + + mcmpeg (Proprietary) + + Description + + MainConcept based mpeg decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + double aspect_ratio - sample aspect ratio of video + int length - duration of resource (in frames) + + Dependencies + + MainConcept MPEG SDK. + "mpeg_sdk_release" installed parallel to mlt. + + Known Bugs + + None. + + noise + + Description + + White noise producer + + Constructor Argument + + none + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + double aspect_ratio - sample aspect ratio of video + int length - duration of resource (in frames) + + Dependencies + + none + + Known Bugs + + none + + pango + + Description + + A title generator that uses the Pango international text layout + and Freetype2 font renderer. + + Constructor Argument + + string file - a text file containing Pango markup, see: + http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html + - requires xml-like encoding special chars from: + <, >, & -to- <, >, & + + Details + + Supplying a filename with extension ".txt" causes the Fezzik + producer to load with pango. If the filename begins with "+" the + pango producer interprets the filename as pango text. This is a + shortcut to embed titles in inigo commands. For westley, it is + recommended that you embed the title text in the property value. + + Pango has builtin scaling. It will rescale the originally rendered + title to whatever the consumer requests. Therefore, it will lose + its aspect ratio if so requested, and it is up to the consumer to + request a proper width and height that maintains the image aspect. + + Initialisation Properties + + int in - in point + int out - out point + + Mutable Properties + + string markup - a string containing Pango markup see: + http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html + - requires xml-like encoding special chars from: + <, >, & -to- <, >, & + string fgcolour - an RGBA colour specification of the text + (i.e. 0xrrggbbaa) + string bgcolour - an RGBA colour of the background rectangle + string align - paragraph alignment: left, centre, right + - also, numbers 0, 1 and 2 can be used respectively. + int pad - the number of pixels to pad the background rectangle + beyond edges of text. default 0. + string markup - see constructor argument + string text - non-markup string in UTF-8 encoding (can contain + markup chars un-encoded) + string font - the default typeface to use when not using markup. + default "Sans 48". FreeType2 renders at 72 dpi. + string encoding - the text encoding type of the input if not UTF-8. + - see 'iconv --list' for a list of possible inputs. + int weight - the weight of the font (default is 400) + + Read Only Properties + + string resource - the text/markup file or "pango" if no file. + int real_width - the original, unscaled width of the rendered title. + int real_height - the original, unscaled height of the title. + int width - the last requested scaled image width. + int height - the last requested scaled image height. + + Dependencies + + libpango-1.0, libpangoft2-1.0, libfreetype, libgdk_pixbuf-2.0, + libglib-2.0, libgobject-2.0, libgmodule-2.0, libfontconfig. + + Known Bugs + + The foreground and background Pango markup span attributes are not + supported. + Word wrapping is not supported. + + pixbuf + + Description + + A still graphics to video generator using gdk-pixbuf + + Constructor Argument + + 'file' - The name of a graphics file loadable by + a gdk-pixbuf loader. see /usr/lib/gdk-pixbuf/loaders + definitely png, jpeg, tiff, pnm, and xpm + - If "%" in filename, the filename is used with sprintf + generate a filename from a counter for multi-file/flipbook + animation. The file sequence ends when numeric + discontinuity >100. + - If filename contains "/.all.", suffix with an extension to + load all pictures with matching extension from a directory. + - If filename contains the string "2 channels. + + rescale + + Description + + Scale the producer video frame size to match the consumer. + This filter is designed for use as a Fezzik normaliser. + + Constructor Argument + + None. + + Initialisation Properties + + int in - in point + int out - out point + + Mutable Properties + + If a property "consumer_aspect_ratio" exists on the frame, then + rescaler normalises the producer's aspect ratio and maximises the + size of the frame, but may not produce the consumer's requested + dimension. Therefore, this option works best in conjunction with the + resize filter. This behavior can be disabled by another service by + either removing the property, setting it to zero, or setting + frame property "distort" to 1. + + Dependencies + + none + + Known Bugs + + none but... it only implements a nearest neighbour scaling - it is + used as the base class for the gtkrescale and mcrescale filters. + + resize + + Description + + Image scaling and padding and field order adjustment. + + Details + + Normally resize is used to pad the producer's + output to what the consumer has requested after an upstream rescale + filter first scales the image to maximise usage of the image area. + This filter also adjusts the field order to lower field first + if the frame property "top_field_first" has been set to 1. + Therefore, when done, it sets the top_field_first to 0. + This filter is automatically invoked by Fezzik as part of image + sample aspect ratio normalisation. + + Constructor Argument + + scale - "affine" to use affine transform scaling, otherwise + center and pad. + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Assumes lower field first output. + + volume + + Description + + Adjust an audio stream's volume level + - based on the 'normalize' utility + + Constructor Argument + + gain - a string containing one of: + - a floating point value of the gain adjustment + - a numeric value with the suffix "dB" to adjust in terms of decibels + - "normalise" to normalise the volume to the target amplitude -12dBFS + + Initialisation Properties + + int in - in point + int out - out point + int window - the number of video frames over which to smooth normalisation. + defaults to 75. + + Mutable Properties + + string gain - same as constructor argument above + + string normalise - normalise the volume to the amplitude: + - a numeric value with the suffix "dB" to set amplitude in decibels + - a floating point value of the relative volume + - an unspecified value uses the default -12dBFS + + string limiter - limit all samples above: + - a numeric value with the suffix "dB" + - a floating point value ( dB = 20 * log10(x) ) + - an unspecified value uses the default -6dBFS + + double max_gain - a floating point or decibel value of the maximum gain that + can be applied during normalisation. + - an unspecified value uses the default 20dB + + string end - a gain value just like the gain property above. + This causes the gain to be interpolated from 'gain' to 'end' + over the duration. + + int window - the size of the normalising smoothing buffer in video frame units. + - the smoothing buffer prevents erratic gain changes. + - the default value is 75 video frames. + + gain can be applied as a factor to the normalise amplitude! + + Dependencies + + none + + Known Bugs + + none + + watermark + + Description + + Add a watermark to the frames. + + Constructor Argument + + resource - the producer to use (ie: a .png) + + Initialisation Properties + + string resource - the producer to use + string factory - producer required for the resource ('fezzik') + string geometry - composite geometry + string distort - control scaling + int in - in point + int out - out point + + Mutable Properties + + none + + Dependencies + + mlt core modules and optionally, fezzik + + Known Bugs + + none + +Transitions +----------- + + composite + + Description + + A key-framable alpha-channel compositor for two frames. + + Details + + Performs dissolves and luma wipes in addition to alpha compositing. + + By default, the aspect ratio of the B frame is respected and the + size portion of the geometry specification simply defines a + bounding rectangle. + + This performs field-based rendering unless the A frame property + "progressive" or "consumer_progressive" or the transition property + "progressive" is set to 1. + + Constructor Argument + + none[*] + + Initialisation Properties + + int in - in point + int out - out point + string factory - The name of a factory service used as a non-PGM + producer loader. The default is fezzik. + + Read Only Properties + + none + + Mutable Properties + + + string geometry - key frame specification + - this is a ; delimited form of the deprecated start, + key[n], end properties + int progressive - set to 1 to disable field-based rendering. + string distort - when set, causes the B frame image to fill the WxH + completely with no regard to B's aspect ratio. + string halign - when not distorting, set the horizontal alignment + of B within the geometry rectangle. + - one of: left (0), centre (1), or right (2). + - the default is left. + string valign - when not distorting, set the vertical alignment of + B within the geometry rectangle. + - one of: top (0), middle (1), or bottom (2). + - the default is top. + string luma - the luma map file name. If not supplied, a dissolve. + double softness - only when using a luma map, how soft to make the + edges between A and B. 0.0 = no softness. 1.0 = + too soft. + Any property starting with "luma." is passed to the non-PGM luma + producer. + + Deprecated Properties + + string start - a geometry specification as X,Y:WxH[!][:mix] + - X, Y, W, H are assumed to pixel units unless they + have the suffix '%' + - '!' is a shortcut to specify distort, see below. + - mix is always a 2 digit percentage, defaults to 100. + - default is "85%,5%:10%x10%" + string end - the ending size and position. + string key[F] - X,Y:WxH[:mix] - set a key frame for geometry between + the in and out. F is a frame number and can be + negative to make it relative to the out point. + + Dependencies + + none + + Known Bugs + + Assumes lower field first during field rendering. + + luma + + Description + + A generic dissolve and wipe transition processor. + + Details + + luma gets its name + from how it uses a grayscale "map" file. As the luma value varies + over time, a threshold filter is applied to the map to determine + what parts of frame A vs. frame B to show. It reads PGM files + up to 16 bits! Alternatively, it can use the first frame from any + producer that outputs yuv, but it will be limited to the luma + gamut of 220 values. + This performs field-based rendering unless the A frame property + "progressive" or "consumer_progressive" or the transition property + "progressive" is set to 1. + + Constructor Argument + + string resource - the luma map file name - either PGM or any other + producable video. + - If not supplied, a dissolve. + + Initialisation Properties + + int in - in point + int out - out point + string factory - The name of a factory service used as a non-PGM + producer loader. The default is Fezzik. + + Mutable Properties + + string resource - same as above + double softness - only when using a luma map, how soft to make the + edges between A and B. 0.0 = no softness. 1.0 = + too soft. + int reverse - reverse the direction of the transition. + Any property starting with "producer." is passed to the non-PGM luma + producer. + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Assumes lower field first output. + + mix + + Description + + An two stream audio mixer. + + Constructor Argument + + start - see below + + Initalisation Properties + + int in - in point + int out - out point + + Mutable Properties + + double start - the mix level to apply to the second frame. + - any negative value causes an automatic crossfade from 0 to 1. + double end - the ending value of the mix level. mix level will be interpolated + from start to end over the in-out range. + int reverse - set to 1 to reverse the direction of the mix. + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Samples from the longer of the two frames are discarded. + + + region + + Description + + Apply zero or more filters to B frame as it is composited onto + a region of the A frame. The "shape" of the region can be defined + by the alpha channel of a third producer. + + Constructor Argument + + resource - a shape producer + + Initialisation Properties + + string resource - nothing is rectangle, "circle" is a pixbuf- + generated SVG circle, anything else is loaded by the factory. + string factory - the service that creates the shape producer. + - the default is fezzik. + string filter[N] - one or more filters to apply. All filter + properties are passed using the same filter "key" + Any property starting with "composite." is passed to the + encapsulated composite transition. + + Read Only Properties + + none + + Dependencies + + transition_composite + + Known Bugs + + none + + +Consumers +--------- + + avformat + + Description + + Multiformat transcoding consumer. + + Constructor Argument + + string target - the filename to write to, e.g. test.mpeg. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + libavformat + + Known Bugs + + Plenty. + + bluefish (Proprietary) + + Description + + BlueFish444 audio and video output module. + + Constructor Argument + + card - a numeric card id starting at 1, default is 1. + + Initialisation Properties + + string standard - "PAL" (default) or "NTSC" + - default is based upon MLT_NORMALISATION + environment variable, which defaults to PAL. + int frames - the number of DMA video frames. default is 8. + minimum is 2. maximum on my system is 11. + int buffer - the number of frames to buffer within MLT, minimum 1, + default 25. + string rescale - a rescale method, see the Filters/rescale. + + Read Only Properties + + none + + Dependencies + + BlueVelvet SDK installed parallel to mlt in "bluefish." + + Known Bugs + + Does not work with any service that uses pipes! + + If mlt crashes, you might need to reload the BlueDriver kernel + module due to unreleased DMA buffers. + + Only supports 2 channel audio at the moment. + + libdv + + Description + + libdv dv producer. + + Constructor Argument + + string target - the filename to write to, e.g. /dev/dv1394. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + + Mutable Properties + + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + libdv + + Known Bugs + + none + + mcmpeg + + Description + + Mainconcept MPEG encoder. + + Constructor Argument + + string target - the filename to write to. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + string format - vcd [default], svcd or dvd provide base settings + int motion_search_type - 0 to 16 - reduces quality/cpu usage + int gop - group of picture size (default: format dependent) + + Mutable Properties + + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + Mainconcept MPEG SDK + + Known Bugs + + none + + sdl + + Description + + Simple DirectMedia Layer audio and video output module. + + Constructor Argument + + string video_standard - "PAL" (default), "NTSC", or "WxH" + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + - Hint: "none" works very good with SDL output. + + Mutable Properties + + double volume - audio level factor + int video_off - if 1, disable video output + int audio_off - if 1, disable audio output + int resize - TODO + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + int audio_buffer - size of the sdl audio buffer (default: 1024) + + Read Only Properties + + none + + Dependencies + + libSDL-1.2, libasound, libaudio, libXt, libX11, libXext, libSM, libICE + + Known Bugs + + none + + westley + + Description + + Serialise the service network to XML. + See docs/westley.txt for more information. + + Constructor Argument + + resource - the name of a file in which to store the XML. + - stdout is default. + + Initialisation Properties + + string resource - same as above. + + Dependencies + + libxml2 + + Known Bugs + + Untested arbitrary nesting of multitracks and playlists. + Property "id" is generated as service type followed by number if + no property named "id" exists, but it fails to guarantee uniqueness. diff --git a/docs/testing-20040110.txt b/docs/testing-20040110.txt new file mode 100644 index 0000000..14c8dcd --- /dev/null +++ b/docs/testing-20040110.txt @@ -0,0 +1,35 @@ +On 1/10/2004, Dan Dennedy ran the testing.txt against mlt albino and miracle. + + +NOTE: Discrepancies cited here may have impact on related functionality. + + +General +------------------------------------------------------------------------------ +Server side error checks and related response error codes are not stringently enforced. + + +Not Implemented +------------------------------------------------------------------------------ +NLS +USET points=ignore +USET eof=terminate + + +Incorrect Behaviour +------------------------------------------------------------------------------ + + +Different Intentional Behaviour +------------------------------------------------------------------------------ + +Different forced Behaviour +------------------------------------------------------------------------------ +killall miracle does not work when the SDL consumer is in use. requires killall -HUP + +MLT Bugs +------------------------------------------------------------------------------ +Please check the services.txt doc for known bugs related to MLT components. + + + diff --git a/docs/testing.txt b/docs/testing.txt new file mode 100644 index 0000000..2886989 --- /dev/null +++ b/docs/testing.txt @@ -0,0 +1,599 @@ +Miracle Test Procedure + +Copyright (C) 2003 Ushodaya Enterprised Limited +Author: Dan Dennedy +Last Revision: 2004-03-20 + + +NOTE: THIS DOCUMENT REQUIRES REVISION TO NEW, EXPECTED BEHAVIOR FROM MIRACLE. + +Tests are divided into 9 sections: + + 1. Command Line Usage + 2. Unit Management + 3. Server Configuration + 4. Simple Playback + 5. Multi-unit Playback + 6. Unit Configuration + 7. Advanced Playback + 8. Bus Reset + 9. Server Side Queuing + +Each section contains many tests which I've divided into a minimum of two lines: + +n.m action to carry out +--> expected result + +Further lines may appear to show the actual results when they deviate from what +I expected or if there are special cases to consider. + +Sequential tests are indicated as: + +n.m.o action to carry out +--> expected result + +It is suggested that you run top during the testing and note cpu hikes +or any excessive memory usage related to an operation. + + +0. Introduction +--------------- + +The tests following are by no means exhaustive, but they should cover typical +use cases - creativity is encouraged with more cases being added where necessary. +This document should also be maintained to dictate actual state, especially with +regard to a final release. Unit test cases are encouraged, but are excluded from +this document. + +It is important to carry out the full test cycle when preparing a final release. +In this situation, please resist the temptation to bug fix a given test case and +resume the tests from that point onward - it is better to repeat from the +beginning (but you can of course employ common sense in this situation). + +Before starting the final tests, please delete/backup your current +/etc/dv139d.conf file. This (more or less) ensures that tests are carried out +for a virgin install. + + +1. Command Line Usage +--------------------- + +Run these from the top level project directory + +1.1.0 Start miracle in interactive mode: src/miracle/miracle -test +--> miracle starts interactively and reports: +(5) Starting server on 5250. +(5) miracle version 0.0.1 listening on port 5250 + +1.1.1 Stop the server by pressing Ctrl-C +--> miracle returns the following and returns control to the console: +(5) miracle version 0.0.1 server terminated. + +1.2.2 Start miracle as a daemon: src/miracle/miracle +--> control returns to the console + +1.2.3 Verify miracle is running: ps ax +--> several miracle processes are running + +1.2.4 Verify successful miracle startup using syslog: sudo tail /var/log/syslog +--> miracle: miracle version 0.0.1 listening on port 5250 + +1.2.5 Verify connectivity on port 5250: telnet localhost 5250 +--> 100 VTR Ready + +1.2.6 Test clean disconnect: BYE +--> Connection closed by foreign host. + +1.2.7 Stop the daemon: killall miracle +--> no errors + +1.2.8 Verify a clean server shutdown: sudo tail /var/log/syslog +--> miracle: shutdown complete. + +1.3.0 Start miracle on a different port: src/miracle/miracle -port 5260 + +1.3.1 Verify successful miracle startup using syslog: sudo tail /var/log/syslog +--> miracle: miracle version 0.0.1 listening on port 5260 + +1.3.2 Verify connectivity on port 5260: telnet localhost 5260 +--> 100 VTR Ready + +1.3.3 Test clean disconnect: BYE +--> Connection closed by foreign host. + +1.3.4 Stop the daemon: killall miracle +--> no errors + + +2. Unit Management +------------------ + +Start the miracle server and connect to it with telnet or a protocol- +level client (albino). + +2.1 List the AV/C devices on the bus: NLS +--> 201 OK +--> one or more lines listing devices with GUID in second column + +2.2 Add a device as a miracle unit: UADD {sdl, bluefish} +--> 201 OK +--> U0 + +2.3 List the units: ULS +--> 201 OK +--> U0 ?? {sdl, bluefish} 1 +--> It is important that the last column is '1' to indicate it is online. + +2.4 List the units: ULS +--> 201 OK +--> U0 ?? {sdl, bluefish} 1 + +2.5 Attempt unit commands for a unit that does not exist: LOAD U6 foo +--> 403 Unit not found + +2.6 Attempt unit commands without specifying a unit: PLAY +--> 402 Argument missing + +2.7 Attempt unit commands for a unit: PLAY U0 +--> 200 OK + +2.8.0 Load a clip into an unit: LOAD U0 test.dv +--> 200 OK + +2.7.1 Verify the status of the unit: USTA U0 +--> 202 OK +--> 0 online "test.dv" 0 1000 25.00 0 ... +--> only the first 3 columns are relevant in this test + + +3. Server Configuration +----------------------- + +Start miracle if not already started from a previous test. + +3.1 Get the hard-coded default root property value: GET root +--> 202 OK +--> / + +3.2 List the files and subdirectories at the root: CLS / +--> 201 OK +--> "bin/" +--> ... + +3.3 Change the server root to a place where clips are stored: e.g., + SET root=/tmp +--> 200 OK + +3.4 Get the new value of the root property value: GET root +--> 202 OK +--> /tmp/ +--> Notice that if you did not use a trailing slash in step 2.3, one is + added for you and appears in this step. This is normal and correct. + +3.5 List the files and subdirectories at the root: CLS / +--> 201 OK +--> zero or more lines listing subdirectories followed by files. + +3.6 Try to set a property that does not exist: SET foo=bar +--> 405 Argument value out of range + +3.7 Try to set no property or value: SET +--> 402 Argument missing + +3.8 Attempt a bogus command: FOO +--> 400 Unknown command + +XXX 3.9 Attempt the incorrect case for a command: get root +XXX --> 400 Unknown command + +3.10 Attempt case insensitivity of property key: GET Root +--> 202 OK +--> /tmp/ + + +4. Simple Playback +------------------- + +Start miracle or restart if already started. +Add an online unit. +Set the server root property if desired. + +4.1.0 Load a clip into the unit: LOAD U0 test.dv +--> 200 OK + +4.1.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 stopped "test.dv" 0 1000 25.00 0 ... +--> Only the first 3 columns are relevant in this test. +--> The remaining columns are only relevant to the tester. + +4.2.0 Play the clip: PLAY U0 +--> 200 OK +--> Verify audio and video output + +4.2.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 1739 1000 25.00 0 ... +--> Only the first 3 columns are relevant in this test. +--> The remaining columns are only relevant to the tester. + +4.3.0 Pause playback: PAUSE U0 +--> 200 OK +--> Verify video continues, but audio is muted. + +4.3.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 1739 0 25.00 0 ... +--> The fifth column --------^ should be 0; it indicates speed. + +4.3.2 Stop playback: STOP U0 +--> 200 OK +--> The analog video output stops + +4.3.3 Pause playback: PAUSE U0 +--> 200 OK +--> Analog video starts again, but it is held on the same frame + paused in 4.3.0. + +4.3.4 Stop playback: STOP U0 +--> 200 OK +--> The analog video signal ceases. + +4.3.5 Rewind the unit: REW U0 +--> 200 OK + +4.3.6 Play the unit: PLAY U0 +--> 200 OK +--> Analog audio and video are produced from the beginning of the file. + +4.4 Stop the server during playback and ensure clean shutdown. + + +5. Multi-unit Playback +----------------------- + +Start miracle or restart if already started. +Add *2* online units. +Set the server root property if desired. + +5.1.0 Load a clip into one unit: LOAD U0 test.dv +--> 200 OK + +5.1.1 Load a clip into the other unit: LOAD U1 test.dv +--> 200 OK + +5.1.2 Start playing one unit: PLAY U0 +--> 200 OK +--> Verify audio and video output + +5.1.3 Start playing the other unit: PLAY U1 +--> 200 OK +--> Verify audio and video output of both units + +5.2 Verify independence of units by pausing one of them: PAUSE U0 +--> 200 OK +--> Verify video continues, but audio is muted on the first unit only. + +5.3 Stop the server during multi-unit playback and ensure clean shutdown. + + +6. Advanced Playback +-------------------- + +Start miracle or restart if already started. +Add *2* online units. +Set the server root property if desired. + +Trick play modes: + +6.1.0 Load a clip: LOAD U0 test.dv +--> 200 OK + +6.1.1 Start playback by pausing on the first frame: PAUSE U0 +--> 200 OK +--> analog video starts, but audio is muted. + +6.1.2 Play fast forward: FF U0 +--> 200 OK +--> verify video is playing fast in the forward direction. + +6.1.3 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 219 2000 25.00 0 ... +--> The important column is --^, indicates speed + +6.1.4 Play fast reverse: REW U0 +--> 200 OK +--> verify analog video output is fast in the reverse direction. + +6.1.5 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 4621 -2000 25.00 0 ... +--> The important column is ---^, negative mean reverse + +6.1.6 Play slow forward: PLAY U0 500 +--> 200 OK +--> Verify the analog video output is slow in the forward direction. + +6.1.7 Play reverse normal speed: PLAY U0 -1000 +--> 200 OK +--> Verify the analog video output is at a normal speed in the reverse direction. +--> Audio output is reverse, but not the field order of video + +Loading while playing: + +6.2.0 Stop the unit (might be playing): STOP U0 +--> 200 OK + +6.2.1 Rewing the unit: REW U0 +--> 200 OK + +6.2.2 Start playing: PLAY U0 +--> 200 OK +--> verify analog audio and video output + +6.2.3 Load another clip: LOAD test002.dv +--> 200 OK +--> playback seamlessly switches to the new clip +--> verify the analog appearance of the video makes a clean switch + +6.2.4 Load another clip, this time with in and out points: + LOAD test.dv 100 500 (whatever works for your test footage) +--> 200 OK +--> verify the analog appearance of the video makes a clean switch + +6.2.5 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 403 1000 25.00 100 ... +--> verify position -----^ is beyond --^ in point, last column is the out + point specified in the previous step. + +Edit points: + +6.3.0 Load a clip: LOAD U0 test.dv +--> 200 OK + +6.3.1 Pause the playback unit: PAUSE U0 +--> 200 OK + +6.3.2 Set the in point: SIN U0 100 +--> 200 OK +--> verify the frame displayed in analog video out changes + +6.3.4 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 100 ... +--> verify position ---^ and in ---^ + +6.3.5 Change the mode of the unit to not restrict playback to the edit + region: USET U0 points=ignore +--> 200 OK + +6.3.6 Jump to a frame before the in frame: GOTO U0 50 +--> 200 OK + +6.3.7 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 50 0 25.00 100 ... +--> position ----------^ preceeds -^ (in) + +6.3.8 Set the unit mode to restrict playback to the edit region: USET U0 points=use +--> 200 OK +--> verify frame on analog video output changes + +6.3.9 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 100 ... +--> verify position ---^ and in ---^ + +6.3.10 Clear the in point: SIN U0 -1 +--> 200 OK + +6.3.11 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 0 ... +--> verify the in point is reset --^ + +The above sequence should be repeated in a similar manner for the out point +using the SOUT command. + +Transfer: + +6.4.0 Load a clip into the first unit: LOAD U0 test.dv +--> 200 OK + +6.4.1 Load a clip into the second unit: LOAD U1 test002.dv +--> 200 OK + +6.4.2 Start playing the first unit: PLAY U0 +--> 200 OK +--> verify audio and video analog output + +6.4.3 Set an in point on the clip in the second unit: SIN U1 100 +--> 200 OK + +6.4.4 Play the second unit: PLAY U1 +--> 200 OK +--> note the beginning footage + +6.4.5 Transfer the clip from the second to the first unit: XFER U1 U0 +--> 200 OK +--> verify a clean switch on the analog audio and video output of the first unit. +--> upon transfer it should play the same footage previewed in step 6.4.4 + +6.4.5 Get the first unit's status: USTA U0 +--> 202 OK +--> 0 playing "test002.dv" 963 1000 29.97 100 2502 +--> note the in point set from U1 ---------^ + + +7. Unit Configuration +--------------------- + +Start miracle or restart if already started. +Add an online unit. +Set the server root property if desired. + +7.1.0 Load a short clip: LOAD U0 test.dv +--> 200 OK + +7.1.1 Play a clip: PLAY U0 +--> 200 OK +--> Wait until it gets to the end, and it should pause on the last frame. + +7.1.2 Make the clip start looping: USET U0 eof=loop +--> 200 OK +--> verify the clip starts playing from the beginning and loops + +7.2.0 Set the in point: SIN U0 10 +--> 200 OK +--> playback pauses at in point (verify with USTA U0) + +7.2.1 Set the out point: SOUT U0 200 +--> 200 OK +--> playback pauses at in point (verify with USTA U0) + +7.2.2 Start playing again: PLAY U0 +--> 200 OK +--> verify playback loops between in and out points + +7.3 Tell the unit to ignore the edit points: USET U0 points=ignore +--> 200 OK +--> verify playback loops over entire video file + +7.4 Get the current value of the points property: UGET U0 points +--> 202 OK +--> ignore + + +9. Server Side Queuing +---------------------- + +Only one unit is used for these test cases, and +users are encouraged to test with multiple units online. It is assumed that a +number of dv files are available for use in the servers ROOT directory - this +document assumes that they are named test001.dv and up. + +9.1.0 Start miracle in interactive mode and add a unit (all tests will assume U0) +--> server started with unit 0 available + +9.1.1 Obtain a miracle shell (via telnet or albino). +--> 100 VTR (if reported by the client) + +9.1.2 Load a clip with LOAD U0 test001.dv and PAUSE U0 +--> 200 OK + +9.1.3 List the clips with LIST U0 +--> 201 OK +--> 1 +--> 0 "test001.dv" 0 6999 7000 7000 25.00 +--> The 1 on the second line denotes the number of times the list has been changed + via user commands (known as the 'generation' number). +--> The third line and beyond reports the clip index (from 0 to n), file loaded, in point, + out point, real size of the file and the calculated size (out - in + 1 ). + +9.1.4 Check the unit status with USTA U0 +--> 202 OK +--> 0 paused "test001.dv" 0 0 25.00 0 6999 7000 "test001.dv" 0 0 6999 7000 1 1 0 +--> The last two fields indicate the generation number and current clip resp. + +9.1.5 Append a clip with APND U0 test002.dv followed by LIST U0 +--> 201 OK +--> 2 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 2 and current clip of 0 + +9.1.6 Move clip 1 to clip 0 with MOVE U0 1 0 followed by LIST U0 +--> 201 OK +--> 3 +--> 0 "test002.dv" 0 6999 7000 7000 +--> 1 "test001.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 3 and current clip of 1 + +9.1.7 Move clip 0 to clip 1 with MOVE U0 0 1 followed by LIST U0 +--> 201 OK +--> 4 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 4 and current clip of 0 +--> Note that the order in which you run 9.1.6 and 9.1.7 shouldn't matter as the + result will be identical + +9.1.8 Change the position to the next clip with GOTO U0 0 +1 +--> 200 OK +--> Check that USTA U0 reports a generation of 4 and current clip of 1 + +9.1.9 Remove all but the playing clip with CLEAN U0 followed by LIST U0 +--> 201 OK +--> 5 +--> 0 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 5 and current clip of 0 + +9.1.10 Insert test001.dv back into the list using INSERT U0 test001.dv and run LIST U0 +--> 201 OK +--> 6 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 6 and current clip of 1 + +9.1.11 Insert test003.dv at position 2 using INSERT U0 test001.dv 3 and run LIST U0 +--> 201 OK +--> 7 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> 2 "test003.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 7 and current clip of 1 + +9.1.12 Change the in point of the current clip using SIN U0 5000 and run LIST U0 +--> 201 OK +--> 8 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 8 and current clip of 1 + +9.1.13 Change the out point of the following clip using SOUT U0 5000 +1 and run LIST U0 +--> 201 OK +--> 9 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 0 5000 7000 5001 +--> Check that USTA U0 reports a generation of 9 and current clip of 2 + +9.1.14 Change the in point of the current clip to 1000 using SIN U0 1000 and run LIST U0 +--> 201 OK +--> 10 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 1000 5000 7000 4001 +--> Check that USTA U0 reports a generation of 10 and current clip of 2 + +9.1.15 Ignore the in/out points by running USET U0 points=ignore and run LIST U0 +--> 201 OK +--> 11 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 7000 +--> 2 "test003.dv" 1000 5000 7000 7000 +--> Check that USTA U0 reports a generation of 11 and current clip of 2 + +9.1.16 Turn the in/out points on again by running USET U0 points=use and run LIST U0 +--> 201 OK +--> 12 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 1000 5000 7000 4001 +--> Check that USTA U0 reports a generation of 12 and current clip of 2 + +9.1.17 Remove the current clip using REMOVE U0 and run LIST U0 +--> 201 OK +--> 13 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> Check that USTA U0 reports a generation of 13 and current clip of 0 + +9.1.17 Remove the next clip using REMOVE U0 +1 and run LIST U0 +--> 201 OK +--> 14 +--> 0 "test001.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 14 and current clip of 0 diff --git a/docs/valerie.txt b/docs/valerie.txt new file mode 100644 index 0000000..9a90c86 --- /dev/null +++ b/docs/valerie.txt @@ -0,0 +1,861 @@ +Valerie API Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Author: Charles Yates +Last Revision: 2004-03-20 + + +TABLE OF CONTENTS +----------------- + + 0. Overview + 0.1. Intended Audience + 0.2. Terminology + 1. Definition of a Parser + 1.1. Construction of a Local Parser + 1.2. Construction of a Remote Parser + 1.3. Using the Parser + 1.4. Closing the Parser + 2. The High Level Parser Wrapper + 2.1. Connecting + 2.2. valerie_error_code + 2.3. Using the High Level Wrapper + 2.4. Obtaining Directory Contents + 2.5. Obtaining the Node List + 2.6. Obtaining the Unit List + 2.7. Unit Status Information + 2.8. Server Side Queuing APIs + 2.9. Accessing the Low Level Parser Directly + 2.10. Cleaning up + 2.11. Examples + 3. The Low Level Parser API + 3.1. Executing a Command + 3.2. Interpreting valerie_response + 3.3. Accessing Unit Status + APPENDIX A - COMPILATION AND LINKING + APPENDIX B - COMPLETE HIGH LEVEL PARSER WRAPPER API LISTING + APPENDIX C - COMPLETE LOW LEVEL PARSER API LISTING + APPENDIX D - REFERENCES + + +0. Overview +----------- + + This document details how applications interface to DVCP functionality. + + +0.1. Intended Audience +---------------------- + + This document draws heavily upon the DVCP design (1) and assumes a basic + knowledge of the functionality provided by the DVCP core. + + It is aimed at developers who wish to use or maintain the API. + + +0.2. Terminology +---------------- + + The API is designed to allow client applications the ability to communicate + to a standalone miracle server or entirely embed the DVCP core in an + instance of a client application. + + The distinction between the two is defined by the construction of the + 'parser'. + + This 'parser' can be used to issue DVCP commands and receive responses and + a 'high level parser wrapper' is provided to simplify the usage and + decouple the application from the DVCP command set. + + +1. Definition of a Parser +------------------------- + + The parser provides a low level API which allows text DVCP commands to be + executed with responses being returned to the caller. Commands and + responses are ASCII formatted text. + + Two parsers are provided - local and remote. + + The local parser is the physical implementation which takes commands and + executes them. + + The remote parser is a network abstraction that forwards commands to a + miracle instance that hosts a local parser. + + +1.1. Construction of a Local Parser +----------------------------------- + + To construct a local parser you must have: + + #include + + and code to initialise the parser is as follows: + + valerie_parser parser = miracle_parser_init_local( ); + + See Appendix A for compilation and linking details. + + +1.2. Construction of a Remote Parser +------------------------------------ + + To construct a remote parser you must have: + + #include + + and code to initialise the parser is as follows: + + valerie_parser parser = valerie_parser_init_remote( "server", port ); + + See Appendix A for compilation and linking details. + + +1.3. Using the Parser +--------------------- + + Although the parser can be used directly to send commands and receive + responses, this low level usage puts the onus on the developer to parse the + responses in a meaningful way. + + Although this usage is not strictly forbidden by applications, it is + discouraged as construction of commands and meaningful parsing of responses + leads to the clients being unnecessarily dependent on the servers input and + output. + + As a result, a higher level Parser Wrapper API is provided - this API + encapsulates the command construction and response parsing. + + The following 2 sections provide details on these modes of access. + + +1.4. Closing the Parser +----------------------- + + Regardless of use, it is the constructors responsibility to close the + parser before it goes out of scope. This is done via: + + valerie_parser_close( parser ); + + +2. The High Level Parser Wrapper +-------------------------------- + + The recommended way to access the parser, is via the valerie API. To use + this API, you must have: + + #include + + and code to construct the wrapper is: + + valerie dv = valerie_init( parser ); + + Note that either remote or local parsers can be used here and there is no + difference in usage, though some error returns will not be applicable to + both. + + It is recommended that applications honour and deal with the error returns + of both as this allows applications to interchange parsers. + + Also note that valerie is not threadsafe, so you should not use the same + structure in multiple threads. The correct solution to this is to create a + valerie per thread - you may safely use the same parser for each thread ie: + + /* valerie for the application */ + valerie dv = valerie_init( parser ); + /* valerie for the status handling thread. */ + valerie dv_status = valerie_init( parser ); + + For the purposes of simplification, the remainder of this section assumes + that a remote parser is in use. + + +2.1. Connecting +--------------- + + Once constructed, the next thing to do is 'connect': + + valerie_error_code error = valerie_connect( dv ); + + This function call initialises the parser (ie: if it's remote, it + establishes a connection to the server, or if it's local, it initialises + the state of the units and supporting objects). + + Note that if you have multiple valerie instances on the same parser you + should only connect one of the instances. + + +2.2. valerie_error_code +---------------------- + + All but a couple of the functions that make up the valerie API return a + valerie_error_code. + + These are defined as follows: + + valerie_ok = 0, + valerie_malloc_failed, + valerie_unknown_error, + valerie_no_response, + valerie_invalid_command, + valerie_server_timeout, + valerie_missing_argument, + valerie_server_unavailable, + valerie_unit_creation_failed, + valerie_unit_unavailable, + valerie_invalid_file, + valerie_invalid_position + + In most cases, it is sufficient to check on a return of valerie_ok. + + To obtain a textual description of a particular error, you can use: + + char *valerie_error_description( valerie_error_code ); + + +2.3. Using the High Level Wrapper +--------------------------------- + + The following code snippet assumes that dv is an initialised and connected + valerie structure: + + valerie_error_code error = valerie_unit_play( dv, 0 ); + if ( error == valerie_ok ) + fprintf( stderr, "Unit 0 is now playing\n" ); + else + fprintf( stderr, "Play on unit 0 failed: %s\n", + valerie_error_description( error ) ); + + The complete interface to valerie is listed in Appendix B of this document. + + +2.4. Obtaining Directory Contents +-------------------------------- + + To obtain a list of files and subdirectories in a given directory relative + to the ROOT property of the server, DVCP provides the CLS command. + + A valid execution of CLS would be something like: + + CLS "/Stuff" + + would provide a response formatted as follows: + + 201 OK + "More Stuff/" + "file0001.dv" 15552000 + "file0002.dv" 15552000 + + with a trailing empty line. + + The first line indicates the error value, the second line shows an example + of a subdirectory and the 3rd and 4th line lists two files that happen to + exist in the directory. + + valerie provides a high level view on this which automatically parses the + response from the server correctly via the valerie_dir structures and + related functions. + + An example of use is as follows: + + valerie_dir dir = valerie_dir_init( dv, "/Stuff" ); + valerie_error_code error = valerie_dir_get_error_code( dir ); + if ( error == valerie_ok ) + { + if ( valerie_dir_count( dir ) > 0 ) + { + valerie_dir_entry_t entry; + int index = 0; + for ( index = 0; index < valerie_dir_count( dir ); index ++ ) + { + valerie_dir_get( dir, index, &entry ); + if ( entry.dir ) + printf( "<%s>\n", entry.name ); + else + printf( "%30s %8d", entry.name, entry.size ); + } + } + else + { + fprintf( stderr, "Directory is empty\n" ); + } + } + else + { + fprintf( stderr, "Directory listing failed: %s\n", + valerie_error_description( error ) ); + } + valerie_dir_close( dir ); + + Note that entry.name provides the name of the file or directory without the + directory prefix. As a convenience, entry.full provides the prefixed name, + so you could subsequently use: + + error = valerie_unit_load( dv, 0, entry.full ); + + to load unit 0 with an entry. + + +2.5. Obtaining the Node List +---------------------------- + + Currently not defined by miracle. + +2.6. Obtaining the Unit List +---------------------------- + + To obtain a list of defined units, DVCP provides the ULS command. + + A valid execution of ULS would be: + + ULS + + and would provide a response formatted as follows: + + 201 OK + U0 00 sdl:360x288 1 + + with a trailing empty line. + + The fields of each record in the response dictate unit, node, mlt consumer and + online status respectively. + + valerie provides a high level view on this which automatically parses the + response from the server correctly via the valerie_units structures and + related functions. + + An example of use is as follows: + + valerie_units units = valerie_units_init( dv ); + valerie_error_code error = valerie_units_get_error_code( units ); + if ( error == valerie_ok ) + { + if ( valerie_units_count( units ) > 0 ) + { + valerie_unit_entry_t entry; + int index = 0; + for ( index = 0; index < valerie_units_count( units ); index ++ ) + { + valerie_units_get( units, index, &entry ); + printf( "U%d %02d %s %s\n", + entry.unit, + entry.node, + entry.guid, + entry.online ? "online" : "offline" ); + } + } + else + { + fprintf( stderr, "Unit list is empty\n" ); + } + } + else + { + fprintf( stderr, "Unit listing failed: %s\n", + valerie_error_description( error ) ); + } + valerie_units_close( units ); + + +2.7. Unit Status Information +---------------------------- + + There are two methods for a client to obtain unit status information. + + The first is via the DVCP USTA command, which would normally be accessed + via: + + USTA U0 + + and would provide a response formated as follows: + + 202 OK + 0 playing "a.dv" 58 1000 25.00 0 6999 7000 "a.dv" 157 0 6999 7000 1 4 0 + + with no trailing empty line. + + The entries in the record are: + + * Unit + * State (undefined, offline, not_loaded, stopped, playing, + paused, disconnected [when server dies]) + * Name of Clip + * Position in clip + * Speed * 1000 + * Frames per second + * Start of clip (in point) + * End of clip (out point) + * Length of clip + * Read ahead clip + * Read ahead position + * Read ahead clip in + * Read ahead clip out + * Read ahead clip length + * Seekable flag + * Playlist generation + * Clip index + + Again, valerie provides a high level means for obtaining this via the + valerie_unit_status function and valerie_status structures: + + valerie_status_t status; + valerie_error_code error = valerie_unit_status( dv, 0, &status ); + if ( error == valerie_ok ) + { + switch( status.status ) + { + case unit_offline: + printf( "offline " ); + break; + case unit_undefined: + printf( "undefined " ); + break; + case unit_not_loaded: + printf( "unloaded " ); + break; + case unit_stopped: + printf( "stopped " ); + break; + case unit_playing: + printf( "playing " ); + break; + default: + printf( "unknown " ); + break; + } + + printf( "%06lld %06lld %06lld %s\n", status.in, + status.position, + status.out, + status.clip ); + } + else + { + fprintf( stderr, "Unit status failed: %s\n", + valerie_error_description( error ) ); + } + + The second approach for obtaining a units status is via automatic + notification. + + This is done via the valerie_notifier API. To obtain the notifier from the + high level API, you can use: + + valerie_notifier notifier = valerie_get_notifier( dv ); + + To obtain the last status associated to a unit, you can use: + + int unit = 1; + valerie_status_t status; + valerie_notifier_get( notifier, &status, unit ); + + To wait for the next status from any unit, you can use: + + valerie_notifier_wait( notifier, &status ); + + If you wish to trigger the action associated to your applications wait + handling of a particular unit, you can use: + + valerie_notifier_get( notifier, &status, unit ); + valerie_notifier_put( notifier, &status ); + + See Examples below for details on this. + + The complete list of fields in the status structure are: + + int unit; + unit_status status; + char clip[ 2048 ]; + int64_t position; + int speed; + double fps; + int64_t in; + int64_t out; + int64_t length; + char tail_clip[ 2048 ]; + int64_t tail_position; + int64_t tail_in; + int64_t tail_out; + int64_t tail_length; + int seekable; + int generation; + int clip_index; + + You will always receive a status record for every frame output. + + The read ahead information is provided for client side queuing. Client side + queuing assumes that uset eof=pause is applied to the unit. A client can + detect when the previously scheduled clip is played out by using the read + ahead information and schedule the next clip. While this mode of operation + is still supported, it is recommended that new clients use the server side + queuing mechanism which is described in the following section. + + +2.8. Server Side Queueing APIs +------------------------------ + + This section describes the APIs available to provide server side queueing. + + The concept is that each unit maintains its own playlist, containing multiple + clips. Associated to the playlist is a generation number which is incremented + on each modification to the playlist. The current playlist generation is + provided in the status record in order for a client to know when to refresh + its presentation of the list. The status record also indicates which clip is + currently active. + + Actions that can be carried out on the playlist are summarised as: + + * list - list all the clips and associated in/out points and size + * loading a clip - a load will wipe the current list and replace it with the + specified clip + * appending a clip - append will always place the specified clip at the end + of the playlist + * inserting a clip - insert will place a new clip at the specified position + in the playlist + * moving a clip - move will allow clips can be moved in the playlist + * removing a clip - remove will remove the specified clip from the playlist + * clean - clean will remove all but the playing clip from the playlist + + Additionally, the following existing actions are clip aware: + + * goto allows you to move the current play position to a specific clip position + * set in/out points allows you to modify clip in and out points + + Backward compatability has been maintained by the addition of a clip-aware + family of APIs which have the naming convention of valerie_unit_clip_*. + + These are listed in Appendix B. + + The following shows an example of obtaining the clips queued on unit 0: + + valerie_list list = valerie_list_init( dv, 0 ); + valerie_list_entry_t entry; + int index; + + printf( "Generation = %d\n", list->generation ); + for ( index = 0; index < valerie_list_count( list ); index ++ ) + { + valerie_list_get( list, index, &entry ); + printf( "%d %s %d %d %d %d\n", + entry.clip, + entry.full, + entry.in, + entry.out, + entry.max, + entry.size ); + } + valerie_list_close( list ); + + To load a clip on unit 0: + + valerie_unit_load( dv, 0, "/path/clip.dv" ); + + To append a clip on unit 0: + + valerie_unit_append( dv, 0, "/path/clip.dv", -1, -1 ); + + Note that the last two arguments specify the in and out points of the clip + with -1 denoting dfaults of the entirety of the file. + + To insert a clip at position 0 on unit 0, we can use the following: + + valerie_unit_clip_insert( dv, 0, clip_absolute, 0, "/path/clip.dv", -1, -1 ); + + The 3rd and 4th arguments here are common to all the valerie_unit_clip functions. + They take the form of either [clip_absolute, n] to indicate an absolute clip + index, or [clip_relative, n] to indicate a clip index relative to the + currently playing clip. + + So, to insert a clip immediately before the currently playing clip, we can + use: + + valerie_unit_clip_insert( dv, 0, clip_relative, -1, "/path/clip.dv", -1, -1 ); + + To move the current clip to the next position in the list: + + valerie_unit_clip_move( dv, 0, clip_relative, 0, clip_relative, 1 ); + + To remove a specific clip: + + valerie_unit_clip_remove( dv, 0, clip_absolute, index ); + + To remove all but the currently playing clip: + + valerie_unit_clean( dv, 0 ); + + To goto the first frame in the first clip, you can use: + + valerie_unit_clip_goto( dv, 0, clip_absolute, 0, 0 ); + + To set the in and out points on the current clip: + + valerie_unit_clip_set_in( dv, 0, clip_relative, 0, 0 ); + valerie_unit_clip_set_out( dv, 0, clip_relative, 0, 1000 ); + + A more complete example of use of the server side can queuing can be found + at: + + http://users.pandora.be/acp/rugen + + The demo client provided with valerie is used for retaining backward + compatability with the client side queuing API. + + +2.9. Accessing the Low Level Parser Directly +-------------------------------------------- + + The low level parser and its associated structures can be accessed directly + from the high level API, but is very occasionally actually needed. + + The methods are provided via a pair of high level methods: + + valerie_error_code error = valerie_execute( dv, 1024, "USTA U%d", unit ); + valerie_response response = valerie_get_last_response( dv ); + int index = 0; + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%d: %s\n", index, valerie_response_get_line( response,index ) ); + + More details on the valerie_response structure can be found in section 3 of this + document. + + +2.10. Cleaning up +----------------- + + Before the valerie and parser go out of scope, you need to run: + + valerie_close( dv ); + valerie_parser_close( parser ); + + Note that you should close all valerie instances before closing the parser. + + +2.11. Examples +-------------- + + Please refer to albino and humperdink source for examples provided with + the project. Additional examples can be found via google with gdv1394 and + poldo. + + +3. The Low Level Parser API +--------------------------- + + The low level parser API provides a very simple mechanism for constructing + commands and receiving responses. + + As described in section 2, a parser is constructed as local or remote and + this is sufficient for constructing the low level parser. + + +3.1. Executing a Command +------------------------ + + All commands can be executed via the single variable argument function + valerie_parser_executef and this function returns a valerie_response, ie: + + valerie_response response = valerie_parser_executef( parser, "CLS \"%s\"", dir ); + + Note that no carriage return/line feed is required (adding this is + erroneous). + + It is the receiver of the response who is responsible for closing it. + + valerie_response_close( response ); + + +3.2. Interpreting valerie_response +----------------------------- + + The response received can be NULL, but it is safe to call: + + int error = valerie_response_get_error_code( response ); + + which will return: + + * -1 if response is NULL, + * -2 if there is no content to the response, + * 0 if the responses first line does not correspond to a valid DVCP response + * or the DVCP protocol error code returned on the first line of the response + + A simple use of a valerie_response structure is as follows: + + valerie_response response = valerie_parser_executef( parser, "CLS \"%s\"", dir ); + int error = valerie_response_get_error_code( response ); + if ( error >= 0 ) + { + int index = 0; + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%3d: %s\n", index, valerie_response_get_line( response, index ) ); + } + else + { + /* interpret error */ + } + valerie_response_close( response ); + + Note that it is safe to call valerie_response_close regardless of the error + condition indicated. + + +3.3. Accessing Unit Status +-------------------------- + + As with the high level parser, there are two alternatives to obtain unit + status information - either via the USTA DVCP command or via the + valerie1394_notifier. + + The latter is the recommended way for any applications which wish to extract + meaningful information from the status while avoiding the requirement to + duplicate the parsing process in a specific client. + + The notifier can be obtained by: + + valerie_notifier notifier = valerie_parser_get_notifier( parser ); + + The use of the notifier with the low level parser is identical to that + dictated in Section 2 - to obtain the last status associated to a unit, + you can use: + + int unit = 1; + valerie_status_t status; + valerie_notifier_get( notifier, &status, unit ); + + To wait for the next status from any unit, you can use: + + valerie_notifier_wait( notifier, &status ); + + +APPENDIX A - COMPILATION AND LINKING +------------------------------------ + + Compilation flags are: + + -I /include + + where prefix defaults to /usr/local. + + Linking flags for a client are: + + -L /lib/ -lvalerie + + Or for a local parser: + + -L /lib/ -lmiracle + + Note that you never need both libs. + + +APPENDIX B - COMPLETE HIGH LEVEL PARSER WRAPPER API LISTING +----------------------------------------------------------- + + valerie valerie_init( valerie_parser ); + + valerie_error_code valerie_connect( valerie ); + + valerie_error_code valerie_set( valerie, char *, char * ); + valerie_error_code valerie_get( valerie, char *, char *, int ); + + valerie_error_code valerie_unit_add( valerie, char * ); + valerie_error_code valerie_unit_load( valerie, int, char * ); + valerie_error_code valerie_unit_load_clipped( valerie,int,char *,long,long ); + valerie_error_code valerie_unit_load_back( valerie, int, char * ); + valerie_error_code valerie_unit_load_back_clipped(valerie,int,char *,long,long) + valerie_error_code valerie_unit_play( valerie, int ); + valerie_error_code valerie_unit_play_at_speed( valerie, int, int ); + valerie_error_code valerie_unit_stop( valerie, int ); + valerie_error_code valerie_unit_pause( valerie, int ); + valerie_error_code valerie_unit_rewind( valerie, int ); + valerie_error_code valerie_unit_fast_forward( valerie, int ); + valerie_error_code valerie_unit_step( valerie, int, int ); + valerie_error_code valerie_unit_goto( valerie, int, int ); + valerie_error_code valerie_unit_set_in( valerie, int, int ); + valerie_error_code valerie_unit_set_out( valerie, int, int ); + valerie_error_code valerie_unit_clear_in( valerie, int ); + valerie_error_code valerie_unit_clear_out( valerie, int ); + valerie_error_code valerie_unit_clear_in_out( valerie, int ); + valerie_error_code valerie_unit_set( valerie, int, char *, char * ); + valerie_error_code valerie_unit_get( valerie, int, char * ); + + valerie_error_code valerie_unit_status( valerie, int, valerie_status ); + valerie_notifier valerie_get_notifier( valerie ); + + valerie_dir valerie_dir_init( valerie, char * ); + valerie_error_code valerie_dir_get( valerie_dir, int, valerie_dir_entry ); + int valerie_dir_count( valerie_dir ); + void valerie_dir_close( valerie_dir ); + + valerie_nodes valerie_nodes_init( valerie ); + valerie_error_code valerie_nodes_get(valerie_nodes,int,valerie_node_entry); + int valerie_nodes_count( valerie_nodes ); + void valerie_nodes_close( valerie_nodes ); + + valerie_units valerie_units_init( valerie ); + valerie_error_code valerie_units_get(valerie_units,int,valerie_unit_entry); + int valerie_units_count( valerie_units ); + void valerie_units_close( valerie_units ); + + valerie_response valerie_get_last_response( valerie ); + + valerie_error_code valerie_execute( valerie, size_t, char *, ... ); + + void valerie_close( valerie ); + + Notifier Functions + ------------------ + + void valerie_notifier_get( valerie_notifier, valerie_status, int ); + void valerie_notifier_put( valerie_notifier, valerie_status ); + int valerie_notifier_wait( valerie_notifier, valerie_status ); + void valerie_notifier_close( valerie_notifier ); + + Server Side Queuing + ------------------- + + valerie_list valerie_list_init( valerie, int ) + valerie_error_code valerie_list_get_error_code( valerie_list ) + valerie_error_code valerie_list_get( valerie_list, int, valerie_list_entry ) + int valerie_list_count( valerie_list ) + void valerie_list_close( valerie_list ) + + valerie_error_code valerie_unit_clean( valerie dv, int unit ) + valerie_error_code valerie_unit_append( valerie dv, int unit, char *file, int in, int out ) + valerie_error_code valerie_unit_remove_current_clip( valerie dv, int unit ) + + valerie_error_code valerie_unit_clip_goto( valerie dv, int unit, valerie_clip_offset offset, int clip, int position ) + valerie_error_code valerie_unit_clip_set_in( valerie dv, int unit, valerie_clip_offset offset, int clip, int in ) + valerie_error_code valerie_unit_clip_set_out( valerie dv, int unit, valerie_clip_offset offset, int clip, int in ) + valerie_error_code valerie_unit_clip_move( valerie dv, int unit, valerie_clip_offset offset, int src, valerie_clip_offset offset, int dest ) + valerie_error_code valerie_unit_clip_remove( valerie dv, int unit, valerie_clip_offset offset, int clip ) + valerie_error_code valerie_unit_clip_insert( valerie dv, int unit, valerie_clip_offset offset, int clip, char *file, int in, int out ) + + + +APPENDIX C - COMPLETE LOW LEVEL PARSER API LISTING +-------------------------------------------------- + + valerie_response valerie_parser_connect( valerie_parser ); + valerie_response valerie_parser_execute( valerie_parser, char * ); + valerie_response valerie_parser_executef( valerie_parser, char *, ... ); + valerie_response valerie_parser_run( valerie_parser, char * ); + valerie_notifier valerie_parser_get_notifier( valerie_parser ); + void valerie_parser_close( valerie_parser ); + + valerie_response valerie_response_init( ); + valerie_response valerie_response_clone( valerie_response ); + int valerie_response_get_error_code( valerie_response ); + char *valerie_response_get_error_string( valerie_response ); + char *valerie_response_get_line( valerie_response, int ); + int valerie_response_count( valerie_response ); + void valerie_response_set_error( valerie_response, int, char * ); + int valerie_response_printf( valerie_response, size_t, char *, ... ); + int valerie_response_write( valerie_response, char *, int ); + void valerie_response_close( valerie_response ); + + +APPENDIX D - REFERENCES +----------------------- + + (1) doc/dvcp.txt - DVCP protocol + (2) doc/testing.txt - Test procedures diff --git a/docs/westley.txt b/docs/westley.txt new file mode 100644 index 0000000..1f42538 --- /dev/null +++ b/docs/westley.txt @@ -0,0 +1,574 @@ +Westley Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Authors: Charles Yates +Last Revision: 2004-03-20 + + +WESTLEY +------- + +Preamble: + + Westley is the MLT projects XML serialisation/deserialisation format - + as such, it closely mirrors the internal structure of the MLT API. + + If you just want to go straight to the DTD, then see + mlt/src/modules/westley/westley.dtd, which gets installed at + $(prefix)/share/mlt/modules/westley.dtd. Currently, the westley parser is + non-validating. + + +Introduction: + + A westley document is essentially a list of 'producers' - a producer is + an mlt object which generates mlt frames (images and associated audio + samples). + + There are 3 types of producer: + + * Basic Producers - these are typically file or device oriented feeds; + * Playlists - these are arrangements of multiple producers; + * Multitracks - these are the fx encapsulators. + + In the mlt model, producers are created and attached to 'consumers' - + consumers are software playback components (such as SDL), or wrappers for + hardware drivers (such as bluefish) or even the westley serialising + consumer itself (the latter doesn't receive frames - it merely + interrogates the connected producer for its configuration). + + Although westley was defined as a serialisation mechanism for instantiated + MLT components, this document will concentrate on the hand authoring of + westley documents. + + +Rules: + + As shall become apparent through the remainder of this document, the basic + tenet of westley authoring is to organise the document in the following + manner: + + 1) create producer elements for each unique media clip in the project; + 2) create playlists for each track; + 3) create a multitrack and specify filters and transitions; + 4) adding global filters. + + While other uses of westley exist, the approach taken here is to maximise + efficiency for complex projects. + + +Basic Producers: + + The simplest westley document is: + + + + clip1.dv + + + + The westley wrapping is of course superfluous here - loading this document + with MLT is identical to loading the clip directly. + + Of course, you can specify additional properties. For example, consider an + MPEG file with multiple soundtracks - you could define a westley document to + ensure that the second audio track is loaded: + + + + clip1.mpeg + 1 + + + + NB: This relies on the mpeg being handled by the avformat producer, rather + than the mcmpeg one. See services.txt for more details. + + A more useful example comes with the pango producer for a text producer. + + TODO: pango example... + + Notes: + + 1) It is better not to specify in/out points when defining basic producers + as these can be specified in the playlists. The reasoning is that in/out + restricts the amount of the clip available, and could lead to the same clip + being loaded multiple times if you need different regions of the clip + elsewhere; + 2) A westley can be specified as a resource, so westleys can naturally + encapsulate other westleys. + + +Playlists: + + Playlists provide a 'collection' structure for producers. These can be used + to define 'tracks' in the multitrack approach, or simple playlists for + sequential, single track playout. + + As an example, the following defines two basic producers and a playlist with 3 + items: + + + + clip1.dv + + + clip2.dv + + + + + + + + + Here we see how the playlist defines the in/out points of the basic + producers. + + Notes: + + 1) All in/out points are absolute frame positions relative to the producer + being appended to the playlist; + 2) Westley documents are currently authored for a specific normalisation; + 3) The last 'producer' in the document is the default for play out; + 4) Playlists can reference the same producer multiple times. In/out regions + do not need to be contiguous - duplication and skipping is acceptable. + + +Interlude - Introducing Multitracks: + + So far we've defined basic producers and playlists/tracks - the tractor is + the element that allows us to arrange our tracks and specify filters and + transitions. Similarly to a playlist, a tractor is a container. + + Note that MLT doesn't see a filter or a transition as a producer in the + normal sense - filters and transitions are passive when it comes to seeking. + Internally, seeks are carried out on the producers. This is an important + point - MLT does not follow a traditional graph oriented model. + + Visualising an MLT tractor and it's interaction with the consumer will + assist here: + + +----------------------------------------------+ + |tractor | + | +----------+ +-+ +-+ +-+ +-+ | + | |multitrack| |f| |f| |t| |t| | + | | +------+ | |i| |i| |r| |r| | + | | |track0|-|--->|l|- ->|l|- ->|a|--->|a|\ | + | | +------+ | |t| |t| |n| |n| \ | + | | | |e| |e| |s| |s| \ | + | | +------+ | |r| |r| |i| |i| \ | +--------+ + | | |track1|-|- ->|0|--->|1|--->|t|--->|t|-----|--->|consumer| + | | +------+ | | | | | |i| |i| / | +--------+ + | | | | | | | |o| |o| / | ^ + | | +------+ | | | | | |n| |n| / | | + | | |track2|-|- ->| |- ->| |--->|0|- ->|1|/ | | + | | +------+ | | | | | | | | | | | + | +----------+ +-+ +-+ +-+ +-+ | | + +----------------------------------------------+ | + ^ | + | | + +-----------+ | + |APPLICATION|--------------------------------------------+ + +-----------+ + + Internally, all frames from all tracks pass through all the filters and + transitions - these are told which tracks to deal and which regions of the + tracks to work on. + + Note that the application communicates with the producer - it can alter + playback speed, position, or even which producer is connected to which + consumer. + + The consumer receives the first non-blank frame (see below). It has no say + in the order in which gets them (the sdl consumer when used with inigo might + appear to be an exception - it isn't - it simply has a route back to the + application to allow the application to interpret key presses). + + +Tractors: + + To create a multitrack westley, we can use two playlists and introduce a + tractor. For the purposes of demonstration, I'll add a filter here too: + + + + clip1.dv + + + clip2.dv + + + + + + + + + + + + + + + + 0 + greyscale + + + + + Here we see that blank frames are inserted into the first playlist and a + blank is provided at the beginning of the second - this can be visualised in + the traditional timeline widget as follows: + + +-------+ +-------------+ + |a | |a | + +-------+---+-------------+ + |b | + +---+ + + Adding the filter on the top track, gives us: + + +-------+ +-------------+ + |a | |a | + +-------+---+-------------+ + |greyscale | + --------+---+-------------+ + |b | + +---+ + + Note that it's only applied to the visible parts of the top track. + + The requirement to apply a filter to the output, as opposed to a specific + track leads us to the final item in the Rules section above. As an example, + let's assume we wish to watermark all output, then we could use the + following: + + + + clip1.dv + + + clip2.dv + + + + + + + + + + + + + + + + 0 + greyscale + + + + + + + + watermark + watermark1.png + + + + + Here we employ another tractor and we define a single track (being the + tractor we previously defined) and apply a watermarking filter there. + + This is simply provided as an example - the watermarking functionality could + be better handled at the playout stage itself (ie: as a filter automatically + placed between all producers and the consumer). + + Tracks act like "layers" in an image processing program like the GIMP. The + bottom-most track takes highest priority and higher layers are overlays + and do not appear unless there are gaps in the lower layers or unless + a transition is applied that merges the tracks on the specifed region. + Practically speaking, for A/B video editing it does not mean too much, + and it will work as expected; however, as a general rule apply any CGI + (graphic overlays with pixbuf or titles with pango) on tracks higher than + your video tracks. Also, this means that any audio-only tracks that are + lower than your video tracks will play rather than the audio from the video + clip. Remember, nothing is affected like mixing or compositing until one + applies a transition or appropriate filter. + + + + clip1.dv + + + + + + clip2.mpeg + + + + + + + + + + + + 0 + 1 + luma + + + 0 + 1 + mix + 0.0 + 1.0 + + + + + A "luma" transition is a video wipe processor that takes a greyscale bitmap + for the wipe definition. When one does not specify a bitmap, luma performs + a dissolve. The "mix" transition does an audio mix, but it interpolates + between the gain scaling factors between the start and end properties - + in this example, from 0.0 (none of track B) to 1.0 (all of track B). + Because the bottom track starts out with a gap specified using the + element, the upper track appears during the blank segment. See the demos and + services.txt to get an idea of the capabilities of the included transitions. + +Flexibility: + + The information presented above is considered the MLT Westley "normal" + form. This is the output generated by the westley consumer, for example, + when used with inigo. It is the output generated when you use the + "Westley to File" consumer in the demo script, which beginners will find + most useful for learning to use westley XML. This section describes + alternative forms the westley producer accepts. + + First of all, the normal form is more of a linear format with producers + and playlists defined prior to their usage in a multitrack. Westley + also accepts a hierarchical format with producers as children of tracks + or playlist entries and with playlists as children of tracks: + + + + + + + + + clip1.dv + + + + + + + + + Obviously, this example is meant to demonstrate hierarchy and not effective + use of playlist or multitrack! + + Secondly, as part of error handling, westley is forgiving if you fail to + supply , , and where one can be understood. This + affords an abbreviated syntax that is less verbose and perhaps less + intimidating for a human to read and understand. One can simplify the + above example as: + + + + + + clip1.dv + + + + + + Yes, filters and transitions can be added to the above example after the + closing multitrack tag () because it is still enclosed within + the westley body tags. + + If you specify in and out on a producer and it has been enclosed within + an or , then the edit points apply to the playlist + entry and not to the producer itself. This facilitates re-use of media: + + + + clip1.dv + + + + + In the above example, the producer attribute of the entry element is + a reference to the preceding producer. All references must follow the + definition. The edit points supplied on the producer above will not affect + the entry that references it below because westley knows the clip is a + playlist entry and optimises this situation. The advantage is that one + does not need to determine every clip to be included ahead of time + and specify them outside the context of the mutlitrack timeline. + + This form of authoring will be easier for many to visualise as a non-linear + editor's timeline. Here is a more complex example: + + + + + + clip2.mpeg + + + + + + + + clip3.mpeg + + + + + + + + + + + + Did you notice something different in the last example? Properties can be + expressed using XML attributes on the element as well. However, only + non-service-specific properties are supported in this way. For example, + "mlt_service" is available to any producer, filter, or transition. However, + "resource" is actually service-specific. Notice the syntax of the last + property, on the last transition. Westley accepts property values using + the "value" attribute as well as using element text. + + We have seen a few different ways of expressing property values. There are + a couple more for properties that can accept XML data. For example, the + GDK pixbuf producer with librsvg can handle embedded SVG, and the Pango + producer can handle embedded Pango markup. You can enclose the embedded XML + using a CDATA section: + + ... ]]> + + Please ensure the opening CDATA tag immediately follows the opening + property tag and that the section closing tag immediately precedes the + closing property tag. + + However, westley can also accept inline embedded XML: + + + + + + + Currently, there is no namespace handling so a conflict will occur only on + any embedded XML that contains an element named "property" because + westley collects embedded XML until it reaches a closing property tag. + + +Entities and Parameterisation: + + The westley producer parser supports XML entities. An example: + + + + ]> + + + pango + &msg; + + + + If you are embedding another XML document into a property value not using + a CNODE section, then any DOCTYPE section must be relocated before any of + the xml elements to be well-formed. See demo/svg.westley for an example. + + Entities can be used to parameterise westley! Using the above example, the + entity declared serves as the default value for &msg;. The entity content + can be overridden from the resource property supplied to the westley + producer. The syntax is the familiar, url-encoded query string used with + HTTP, e.g.: file?name=value&name=value... + + There are a couple of rules of usage. The Miracle LOAD command and inigo + command line tool require you to preface the URL with "westley:" because + the query string destroys the filename extension matching peformed by + Fezzik. Also, inigo looks for '=' to tokenise property settings. Therefore, + one uses ':' between name and value instead of '='. Finally, since inigo + is run from the shell, one must enclose the URL within single quotes to + prevent shell filename expansion, or similar. + + Needless to say, the ability to parameterise westley XML compositions is + an extremely powerful tool. An example for you to play with is available in + demo/entity.westley. Try overriding the name from inigo: + inigo 'westley:entity.westley?name:Charlie' + + Technically, the entity declaration is not needed in the head of the XML + document if you always supply the parameter. However, you run the risk + of unpredictable behviour without one. Therefore, it is safest and a best + practice to always supply an entity declaration. It is improves the + readability as one does not need to search for the entity references to + see what parameters are available. + + +Tips and Technique: + + If one finds the above hierarchical, abbreviated format intuitive, + start with a simple template and fill and extend as needed: + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + By using a playlist for each track, it is easier to iteratively add new + clips and blank regions as you develop the project. You will not have to + use or later add when necessary. + + A more advanced template that allows sequencing multitracks is: + + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + + If you end up making a collection of templates for various situations, then + consider using XML Entities to make the template more effective by moving + anything that should parameterised into an entity. + + If you want to have a silent, black background for audio and video fades, + then make the top track simply . Then, + use composite and volume effects. See the "Fade from/to black/silence" + demo for an example (demo/mlt_fade_black). + + If you apply the reverse=1 property to a transition like "luma," then + be careful because it also inherently swaps the roles of A and B tracks. + Therefore, you need to might need to swap the a_track and b_track values + if it did not turn out the way you expected. See the "Clock in and out" + for an example (demo/mlt_clock_in_and_out). diff --git a/mlt-config-template b/mlt-config-template new file mode 100644 index 0000000..5559bf7 --- /dev/null +++ b/mlt-config-template @@ -0,0 +1,32 @@ +export package=framework +export field=0 + +while [ "$1" != "" ] +do + case $1 in + --help ) field=0 ;; + --version ) field=-1 ;; + --prefix ) field=-2 ;; + --prefix=* ) prefix="${i#--prefix=}" ;; + --cflags ) field=2 ;; + --libs ) field=3 ;; + --list ) field=1; package="" ;; + * ) package=$1 ;; + esac + shift +done + +if [ "$field" = "0" ] +then echo "Usage: mlt-config [ --version ] | [ --prefix=dir ] [ [ package ] [ --cflags ] [ --libs ] ]" +elif [ "$field" = "-1" ] +then echo $version +elif [ "$field" = "-2" ] +then config=`which mlt-config` + dir=`dirname $config` + dir=`dirname $dir` + echo $dir +elif [ -f "$prefix/share/mlt/packages.dat" ] +then grep "^$package" $prefix/share/mlt/packages.dat | cut -f $field +else echo mlt-config cannot find package $package. +fi + diff --git a/mlt-framework.pc b/mlt-framework.pc new file mode 100644 index 0000000..2c9cd9f --- /dev/null +++ b/mlt-framework.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include -I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lmlt + +Name: mlt-framework +Description: MLT multimedia framework +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-framework.pc.in b/mlt-framework.pc.in new file mode 100644 index 0000000..5748867 --- /dev/null +++ b/mlt-framework.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-framework +Description: MLT multimedia framework +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-miracle.pc b/mlt-miracle.pc new file mode 100644 index 0000000..94aabe2 --- /dev/null +++ b/mlt-miracle.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lmiracle + +Name: mlt-miracle +Description: MLT Miracle server API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-miracle.pc.in b/mlt-miracle.pc.in new file mode 100644 index 0000000..593055e --- /dev/null +++ b/mlt-miracle.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-miracle +Description: MLT Miracle server API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-valerie.pc b/mlt-valerie.pc new file mode 100644 index 0000000..9cd6ac1 --- /dev/null +++ b/mlt-valerie.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lvalerie + +Name: mlt-valerie +Description: MLT Valerie client API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-valerie.pc.in b/mlt-valerie.pc.in new file mode 100644 index 0000000..7750ba3 --- /dev/null +++ b/mlt-valerie.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-valerie +Description: MLT Valerie client API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/profiles/Makefile b/profiles/Makefile new file mode 100644 index 0000000..4da1114 --- /dev/null +++ b/profiles/Makefile @@ -0,0 +1,18 @@ +include ../config.mak + +all: + +depend: + +distclean: + +clean: + +install: all uninstall + install -d "$(DESTDIR)$(prefix)/share/mlt/profiles" + install -m 644 * "$(DESTDIR)$(prefix)/share/mlt/profiles" + rm -f "$(DESTDIR)$(prefix)/share/mlt/profiles/*~" + rm -f "$(DESTDIR)$(prefix)/share/mlt/profiles/Makefile" + +uninstall: + rm -rf "$(DESTDIR)$(prefix)/share/mlt/profiles" diff --git a/profiles/atsc_1080i_60 b/profiles/atsc_1080i_60 new file mode 100644 index 0000000..3c95886 --- /dev/null +++ b/profiles/atsc_1080i_60 @@ -0,0 +1,10 @@ +description=ATSC 1080i 60Hz +frame_rate_num=30000 +frame_rate_den=1001 +width=1920 +height=1080 +progressive=0 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/atsc_720p_30 b/profiles/atsc_720p_30 new file mode 100644 index 0000000..beb6cc9 --- /dev/null +++ b/profiles/atsc_720p_30 @@ -0,0 +1,10 @@ +description=ATSC 720p 30Hz +frame_rate_num=30000 +frame_rate_den=1001 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/cif_ntsc b/profiles/cif_ntsc new file mode 100644 index 0000000..48279a5 --- /dev/null +++ b/profiles/cif_ntsc @@ -0,0 +1,10 @@ +description=CIF NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=288 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cif_pal b/profiles/cif_pal new file mode 100644 index 0000000..a7f66d4 --- /dev/null +++ b/profiles/cif_pal @@ -0,0 +1,10 @@ +description=CIF PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=288 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cvd_ntsc b/profiles/cvd_ntsc new file mode 100644 index 0000000..508b3cc --- /dev/null +++ b/profiles/cvd_ntsc @@ -0,0 +1,10 @@ +description=CVD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=480 +progressive=0 +sample_aspect_num=20 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cvd_pal b/profiles/cvd_pal new file mode 100644 index 0000000..2415c77 --- /dev/null +++ b/profiles/cvd_pal @@ -0,0 +1,10 @@ +description=CVD PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=27 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_ntsc b/profiles/dv_ntsc new file mode 100644 index 0000000..c5462dd --- /dev/null +++ b/profiles/dv_ntsc @@ -0,0 +1,10 @@ +description=DV NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=720 +height=480 +progressive=0 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_ntsc_wide b/profiles/dv_ntsc_wide new file mode 100644 index 0000000..6c98e4f --- /dev/null +++ b/profiles/dv_ntsc_wide @@ -0,0 +1,10 @@ +description=DV NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=720 +height=480 +progressive=0 +sample_aspect_num=40 +sample_aspect_den=33 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/dv_pal b/profiles/dv_pal new file mode 100644 index 0000000..33e82bc --- /dev/null +++ b/profiles/dv_pal @@ -0,0 +1,10 @@ +description=DV PAL +frame_rate_num=25 +frame_rate_den=1 +width=720 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_pal_wide b/profiles/dv_pal_wide new file mode 100644 index 0000000..a4b669c --- /dev/null +++ b/profiles/dv_pal_wide @@ -0,0 +1,10 @@ +description=DV PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=720 +height=576 +progressive=0 +sample_aspect_num=118 +sample_aspect_den=81 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_1080_50i b/profiles/hdv_1080_50i new file mode 100644 index 0000000..6544470 --- /dev/null +++ b/profiles/hdv_1080_50i @@ -0,0 +1,10 @@ +description=HDV 1080 50i +frame_rate_num=25 +frame_rate_den=1 +width=1440 +height=1080 +progressive=0 +sample_aspect_num=4 +sample_aspect_den=3 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_1080_60i b/profiles/hdv_1080_60i new file mode 100644 index 0000000..9c3e6fc --- /dev/null +++ b/profiles/hdv_1080_60i @@ -0,0 +1,10 @@ +description=HDV 1080 60i +frame_rate_num=30000 +frame_rate_den=1001 +width=1440 +height=1080 +progressive=0 +sample_aspect_num=4 +sample_aspect_den=3 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_720_25p b/profiles/hdv_720_25p new file mode 100644 index 0000000..7bd6eed --- /dev/null +++ b/profiles/hdv_720_25p @@ -0,0 +1,10 @@ +description=HDV 720 25p +frame_rate_num=25 +frame_rate_den=1 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_720_30p b/profiles/hdv_720_30p new file mode 100644 index 0000000..68caaf9 --- /dev/null +++ b/profiles/hdv_720_30p @@ -0,0 +1,10 @@ +description=HDV 720 30p +frame_rate_num=30000 +frame_rate_den=1001 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/qcif_ntsc b/profiles/qcif_ntsc new file mode 100644 index 0000000..0e90157 --- /dev/null +++ b/profiles/qcif_ntsc @@ -0,0 +1,10 @@ +description=QCIF NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=176 +height=144 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/qcif_pal b/profiles/qcif_pal new file mode 100644 index 0000000..21667ee --- /dev/null +++ b/profiles/qcif_pal @@ -0,0 +1,10 @@ +description=QCIF PAL +frame_rate_num=25 +frame_rate_den=1 +width=176 +height=144 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_ntsc b/profiles/quarter_ntsc new file mode 100644 index 0000000..fb37cd7 --- /dev/null +++ b/profiles/quarter_ntsc @@ -0,0 +1,10 @@ +description=Quarter Square NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=320 +height=240 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_ntsc_wide b/profiles/quarter_ntsc_wide new file mode 100644 index 0000000..8c7be94 --- /dev/null +++ b/profiles/quarter_ntsc_wide @@ -0,0 +1,10 @@ +description=Quarter Square NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=426 +height=240 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/quarter_pal b/profiles/quarter_pal new file mode 100644 index 0000000..beec207 --- /dev/null +++ b/profiles/quarter_pal @@ -0,0 +1,10 @@ +description=Quarter Square PAL +frame_rate_num=25 +frame_rate_den=1 +width=384 +height=288 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_pal_wide b/profiles/quarter_pal_wide new file mode 100644 index 0000000..195410a --- /dev/null +++ b/profiles/quarter_pal_wide @@ -0,0 +1,10 @@ +description=Quarter Square PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=512 +height=288 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/square_ntsc b/profiles/square_ntsc new file mode 100644 index 0000000..e139c3e --- /dev/null +++ b/profiles/square_ntsc @@ -0,0 +1,10 @@ +description=Square NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=640 +height=480 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/square_ntsc_wide b/profiles/square_ntsc_wide new file mode 100644 index 0000000..da2de3e --- /dev/null +++ b/profiles/square_ntsc_wide @@ -0,0 +1,10 @@ +description=Square NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=854 +height=480 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/square_pal b/profiles/square_pal new file mode 100644 index 0000000..a69c8d6 --- /dev/null +++ b/profiles/square_pal @@ -0,0 +1,10 @@ +description=Square PAL +frame_rate_num=25 +frame_rate_den=1 +width=768 +height=576 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/square_pal_wide b/profiles/square_pal_wide new file mode 100644 index 0000000..316a80d --- /dev/null +++ b/profiles/square_pal_wide @@ -0,0 +1,10 @@ +description=Square PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=1024 +height=576 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/svcd_ntsc b/profiles/svcd_ntsc new file mode 100644 index 0000000..fd6f13a --- /dev/null +++ b/profiles/svcd_ntsc @@ -0,0 +1,10 @@ +description=SVCD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=480 +height=480 +progressive=0 +sample_aspect_num=15 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/svcd_ntsc_wide b/profiles/svcd_ntsc_wide new file mode 100644 index 0000000..174960a --- /dev/null +++ b/profiles/svcd_ntsc_wide @@ -0,0 +1,10 @@ +description=SVCD NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=480 +height=480 +progressive=0 +sample_aspect_num=20 +sample_aspect_den=11 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/svcd_pal b/profiles/svcd_pal new file mode 100644 index 0000000..2049270 --- /dev/null +++ b/profiles/svcd_pal @@ -0,0 +1,10 @@ +description=SVCD PAL +frame_rate_num=25 +frame_rate_den=1 +width=480 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=36 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/svcd_pal_wide b/profiles/svcd_pal_wide new file mode 100644 index 0000000..3aea87a --- /dev/null +++ b/profiles/svcd_pal_wide @@ -0,0 +1,10 @@ +description=SVCD PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=480 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=27 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/vcd_ntsc b/profiles/vcd_ntsc new file mode 100644 index 0000000..e58f4f2 --- /dev/null +++ b/profiles/vcd_ntsc @@ -0,0 +1,10 @@ +description=VCD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=240 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/vcd_pal b/profiles/vcd_pal new file mode 100644 index 0000000..5f4e03f --- /dev/null +++ b/profiles/vcd_pal @@ -0,0 +1,10 @@ +description=VCD PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=288 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/setenv b/setenv new file mode 100644 index 0000000..dd38115 --- /dev/null +++ b/setenv @@ -0,0 +1,25 @@ + +# Environment variable settings to allow execution without install + +export MLT_REPOSITORY=`pwd`/src/modules +export MLT_PROFILES_PATH=`pwd`/profiles + +export LD_LIBRARY_PATH=\ +`pwd`/src/framework:\ +`pwd`/src/valerie:\ +`pwd`/src/miracle:\ +`pwd`/src/modules/bluefish:\ +`pwd`/../BlueLinuxDriver/install/lib:\ +`pwd`/../mpeg_sdk_release/bin:\ +`pwd`/../dvcpro_sdk_release/lib:\ +`pwd`/../sr_sdk_release:\ +$LD_LIBRARY_PATH + +[ $(uname -s) = Darwin ] && export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH + +export PATH=\ +`pwd`/src/albino:\ +`pwd`/src/inigo:\ +`pwd`/src/humperdink:\ +`pwd`/src/miracle:\ +$PATH diff --git a/setenv_mc b/setenv_mc new file mode 100644 index 0000000..705d4e2 --- /dev/null +++ b/setenv_mc @@ -0,0 +1,9 @@ + +# Environment variable settings to allow execution without install + +export LD_LIBRARY_PATH=\ +`pwd`/../mpeg_sdk_release/bin:\ +`pwd`/../dvcpro_sdk_release/lib:\ +`pwd`/../sr_sdk_release/lib:\ +$LD_LIBRARY_PATH + diff --git a/src/albino/Makefile b/src/albino/Makefile new file mode 100644 index 0000000..45b7c80 --- /dev/null +++ b/src/albino/Makefile @@ -0,0 +1,36 @@ +include ../../config.mak + +TARGET = albino + +OBJS = albino.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../miracle -lmiracle -L../valerie -lvalerie -L../miracle -lmiracle -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/albino/albino.c b/src/albino/albino.c new file mode 100644 index 0000000..d4f0856 --- /dev/null +++ b/src/albino/albino.c @@ -0,0 +1,110 @@ +/* + * albino.c -- Local Valerie/Miracle Test Utility + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include + +/* Application header files */ +#include +#include +#include + +char *prompt( char *command, int length ) +{ + printf( "> " ); + return fgets( command, length, stdin ); +} + +void report( valerie_response response ) +{ + int index = 0; + if ( response != NULL ) + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%4d: %s\n", index, valerie_response_get_line( response, index ) ); +} + +int main( int argc, char **argv ) +{ + valerie_parser parser = NULL; + valerie_response response = NULL; + char temp[ 1024 ]; + int index = 1; + + if ( argc > 2 && !strcmp( argv[ 1 ], "-s" ) ) + { + printf( "Miracle Client Instance\n" ); + parser = valerie_parser_init_remote( argv[ 2 ], 5250 ); + response = valerie_parser_connect( parser ); + index = 3; + } + else + { + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + printf( "Miracle Standalone Instance\n" ); + parser = miracle_parser_init_local( ); + response = valerie_parser_connect( parser ); + } + + if ( response != NULL ) + { + /* process files on command lines before going into console mode */ + for ( ; index < argc; index ++ ) + { + valerie_response_close( response ); + response = valerie_parser_run( parser, argv[ index ] ); + report( response ); + } + + while ( response != NULL && prompt( temp, 1024 ) ) + { + valerie_util_trim( valerie_util_chomp( temp ) ); + if ( !strcasecmp( temp, "BYE" ) ) + { + break; + } + else if ( strcmp( temp, "" ) ) + { + valerie_response_close( response ); + response = valerie_parser_execute( parser, temp ); + report( response ); + } + } + } + else + { + fprintf( stderr, "Unable to connect to a Miracle instance.\n" ); + } + + printf( "\n" ); + valerie_parser_close( parser ); + + return 0; +} diff --git a/src/framework/Makefile b/src/framework/Makefile new file mode 100644 index 0000000..f5df34c --- /dev/null +++ b/src/framework/Makefile @@ -0,0 +1,97 @@ +include ../../config.mak + +NAME = libmlt$(LIBSUF) +TARGET = $(NAME).$(version) + +ifneq ($(targetos), Darwin) +NAME = libmlt$(LIBSUF) +TARGET = $(NAME).$(version) +SHFLAGS += -Wl,-soname,$(TARGET) +else +NAME = libmlt$(LIBSUF) +TARGET = libmlt.$(version)$(LIBSUF) +SHFLAGS += -install_name $(libdir)/$(TARGET) +endif + +OBJS = mlt_frame.o \ + mlt_geometry.o \ + mlt_deque.o \ + mlt_property.o \ + mlt_properties.o \ + mlt_events.o \ + mlt_parser.o \ + mlt_service.o \ + mlt_producer.o \ + mlt_multitrack.o \ + mlt_playlist.o \ + mlt_consumer.o \ + mlt_filter.o \ + mlt_transition.o \ + mlt_field.o \ + mlt_tractor.o \ + mlt_factory.o \ + mlt_repository.o \ + mlt_pool.o \ + mlt_tokeniser.o \ + mlt_profile.o + +INCS = mlt_consumer.h \ + mlt_factory.h \ + mlt_filter.h \ + mlt.h \ + mlt_multitrack.h \ + mlt_pool.h \ + mlt_properties.h \ + mlt_events.h \ + mlt_parser.h \ + mlt_repository.h \ + mlt_tractor.h \ + mlt_types.h \ + mlt_deque.h \ + mlt_field.h \ + mlt_frame.h \ + mlt_geometry.h \ + mlt_playlist.h \ + mlt_producer.h \ + mlt_property.h \ + mlt_service.h \ + mlt_transition.h \ + mlt_tokeniser.h \ + mlt_profile.h + +SRCS := $(OBJS:.o=.c) + +CFLAGS += $(RDYNAMIC) -DPREFIX="\"$(prefix)\"" + +LDFLAGS += -lm $(LIBDL) -lpthread + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + ln -sf $(TARGET) $(NAME) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) $(NAME) + +install: + install -d $(DESTDIR)$(libdir) + install -m 755 $(TARGET) $(DESTDIR)$(libdir) + ln -sf $(TARGET) $(DESTDIR)$(libdir)/$(NAME) + install -d "$(DESTDIR)$(prefix)/include/mlt/framework" + install -m 644 $(INCS) "$(DESTDIR)$(prefix)/include/mlt/framework" + +uninstall: + rm -f "$(DESTDIR)$(libdir)/$(TARGET)" + rm -f "$(DESTDIR)$(libdir)/$(NAME)" + rm -rf "$(DESTDIR)$(prefix)/include/mlt/framework" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/framework/config.h b/src/framework/config.h new file mode 100644 index 0000000..214b97b --- /dev/null +++ b/src/framework/config.h @@ -0,0 +1,9 @@ +/** GENERATED FILE - DON'T EDIT */ + +#ifndef _MLT_CONFIG_H_ +#define _MLT_CONFIG_H_ + +#define PREFIX_DATA PREFIX "/lib/mlt/modules" + +#endif + diff --git a/src/framework/configure b/src/framework/configure new file mode 100755 index 0000000..52655ae --- /dev/null +++ b/src/framework/configure @@ -0,0 +1,2 @@ +#!/bin/sh +echo "framework -I$prefix/include -I$prefix/include/mlt -D_REENTRANT -L$libdir -lmlt" >> ../../packages.dat diff --git a/src/framework/mlt.h b/src/framework/mlt.h new file mode 100644 index 0000000..9c99a2a --- /dev/null +++ b/src/framework/mlt.h @@ -0,0 +1,51 @@ +/* + * mlt.h -- header file for lazy client and implementation code :-) + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_H_ +#define _MLT_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "mlt_factory.h" +#include "mlt_frame.h" +#include "mlt_deque.h" +#include "mlt_multitrack.h" +#include "mlt_producer.h" +#include "mlt_transition.h" +#include "mlt_consumer.h" +#include "mlt_filter.h" +#include "mlt_playlist.h" +#include "mlt_properties.h" +#include "mlt_field.h" +#include "mlt_tractor.h" +#include "mlt_tokeniser.h" +#include "mlt_parser.h" +#include "mlt_geometry.h" +#include "mlt_profile.h" + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/framework/mlt_consumer.c b/src/framework/mlt_consumer.c new file mode 100644 index 0000000..4fc9d71 --- /dev/null +++ b/src/framework/mlt_consumer.c @@ -0,0 +1,797 @@ +/* + * mlt_consumer.c -- abstraction for all consumer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_consumer.h" +#include "mlt_factory.h" +#include "mlt_producer.h" +#include "mlt_frame.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +static void mlt_consumer_frame_render( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); +static void mlt_consumer_frame_show( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); +static void mlt_consumer_property_changed( mlt_service owner, mlt_consumer this, char *name ); +static void apply_profile_properties( mlt_profile profile, mlt_properties properties ); + +static mlt_event g_event_listener = NULL; + +/** Public final methods +*/ + +int mlt_consumer_init( mlt_consumer this, void *child ) +{ + int error = 0; + memset( this, 0, sizeof( struct mlt_consumer_s ) ); + this->child = child; + error = mlt_service_init( &this->parent, this ); + if ( error == 0 ) + { + // Get the properties from the service + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + + // Apply the profile to properties for legacy integration + apply_profile_properties( mlt_profile_get(), properties ); + + // Default rescaler for all consumers + mlt_properties_set( properties, "rescale", "bilinear" ); + + // Default read ahead buffer size + mlt_properties_set_int( properties, "buffer", 25 ); + + // Default audio frequency and channels + mlt_properties_set_int( properties, "frequency", 48000 ); + mlt_properties_set_int( properties, "channels", 2 ); + + // Default of all consumers is real time + mlt_properties_set_int( properties, "real_time", 1 ); + + // Default to environment test card + mlt_properties_set( properties, "test_card", mlt_environment( "MLT_TEST_CARD" ) ); + + // Hmm - default all consumers to yuv422 :-/ + this->format = mlt_image_yuv422; + + mlt_events_register( properties, "consumer-frame-show", ( mlt_transmitter )mlt_consumer_frame_show ); + mlt_events_register( properties, "consumer-frame-render", ( mlt_transmitter )mlt_consumer_frame_render ); + mlt_events_register( properties, "consumer-stopped", NULL ); + + // Register a property-changed listener to handle the profile property - + // subsequent properties can override the profile + g_event_listener = mlt_events_listen( properties, this, "property-changed", ( mlt_listener )mlt_consumer_property_changed ); + + // Create the push mutex and condition + pthread_mutex_init( &this->put_mutex, NULL ); + pthread_cond_init( &this->put_cond, NULL ); + + } + return error; +} + +static void apply_profile_properties( mlt_profile profile, mlt_properties properties ) +{ + mlt_event_block( g_event_listener ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( profile ) ); + mlt_properties_set_int( properties, "frame_rate_num", profile->frame_rate_num ); + mlt_properties_set_int( properties, "frame_rate_den", profile->frame_rate_den ); + mlt_properties_set_int( properties, "width", profile->width ); + mlt_properties_set_int( properties, "height", profile->height ); + mlt_properties_set_int( properties, "progressive", profile->progressive ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( profile ) ); + mlt_properties_set_int( properties, "sample_aspect_num", profile->sample_aspect_num ); + mlt_properties_set_int( properties, "sample_aspect_den", profile->sample_aspect_den ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( profile ) ); + mlt_properties_set_int( properties, "display_aspect_num", profile->display_aspect_num ); + mlt_properties_set_int( properties, "display_aspect_num", profile->display_aspect_num ); + mlt_event_unblock( g_event_listener ); +} + +static void mlt_consumer_property_changed( mlt_service owner, mlt_consumer this, char *name ) +{ + if ( !strcmp( name, "profile" ) ) + { + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Locate the profile + mlt_profile_select( mlt_properties_get( properties, "profile" ) ); + + // Apply to properties + apply_profile_properties( mlt_profile_get(), properties ); + } + else if ( !strcmp( name, "frame_rate_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->frame_rate_num = mlt_properties_get_int( properties, "frame_rate_num" ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( NULL ) ); + } + else if ( !strcmp( name, "frame_rate_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->frame_rate_den = mlt_properties_get_int( properties, "frame_rate_den" ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( NULL ) ); + } + else if ( !strcmp( name, "width" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->width = mlt_properties_get_int( properties, "width" ); + } + else if ( !strcmp( name, "height" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->height = mlt_properties_get_int( properties, "height" ); + } + else if ( !strcmp( name, "progressive" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->progressive = mlt_properties_get_int( properties, "progressive" ); + } + else if ( !strcmp( name, "sample_aspect_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->sample_aspect_num = mlt_properties_get_int( properties, "sample_aspect_num" ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + } + else if ( !strcmp( name, "sample_aspect_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->sample_aspect_den = mlt_properties_get_int( properties, "sample_aspect_den" ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + } + else if ( !strcmp( name, "display_aspect_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->display_aspect_num = mlt_properties_get_int( properties, "display_aspect_num" ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( NULL ) ); + } + else if ( !strcmp( name, "display_aspect_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->display_aspect_den = mlt_properties_get_int( properties, "display_aspect_den" ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( NULL ) ); + } +} + +static void mlt_consumer_frame_show( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( mlt_frame )args[ 0 ] ); +} + +static void mlt_consumer_frame_render( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( mlt_frame )args[ 0 ] ); +} + +/** Create a new consumer. +*/ + +mlt_consumer mlt_consumer_new( ) +{ + // Create the memory for the structure + mlt_consumer this = malloc( sizeof( struct mlt_consumer_s ) ); + + // Initialise it + if ( this != NULL ) + mlt_consumer_init( this, NULL ); + + // Return it + return this; +} + +/** Get the parent service object. +*/ + +mlt_service mlt_consumer_service( mlt_consumer this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the consumer properties. +*/ + +mlt_properties mlt_consumer_properties( mlt_consumer this ) +{ + return this != NULL ? MLT_SERVICE_PROPERTIES( &this->parent ) : NULL; +} + +/** Connect the consumer to the producer. +*/ + +int mlt_consumer_connect( mlt_consumer this, mlt_service producer ) +{ + return mlt_service_connect_producer( &this->parent, producer, 0 ); +} + +/** Start the consumer. +*/ + +int mlt_consumer_start( mlt_consumer this ) +{ + // Stop listening to the property-changed event + mlt_event_block( g_event_listener ); + + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Determine if there's a test card producer + char *test_card = mlt_properties_get( properties, "test_card" ); + + // Just to make sure nothing is hanging around... + mlt_frame_close( this->put ); + this->put = NULL; + this->put_active = 1; + + // Deal with it now. + if ( test_card != NULL ) + { + if ( mlt_properties_get_data( properties, "test_card_producer", NULL ) == NULL ) + { + // Create a test card producer + mlt_producer producer = mlt_factory_producer( NULL, test_card ); + + // Do we have a producer + if ( producer != NULL ) + { + // Test card should loop I guess... + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" ); + //mlt_producer_set_speed( producer, 0 ); + //mlt_producer_set_in_and_out( producer, 0, 0 ); + + // Set the test card on the consumer + mlt_properties_set_data( properties, "test_card_producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + } + } + else + { + // Allow the hash table to speed things up + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + } + + // Check and run an ante command + if ( mlt_properties_get( properties, "ante" ) ) + system( mlt_properties_get( properties, "ante" ) ); + + // Set the real_time preference + this->real_time = mlt_properties_get_int( properties, "real_time" ); + + // Start the service + if ( this->start != NULL ) + return this->start( this ); + + return 0; +} + +/** An alternative method to feed frames into the consumer - only valid if + the consumer itself is not connected. +*/ + +int mlt_consumer_put_frame( mlt_consumer this, mlt_frame frame ) +{ + int error = 1; + + // Get the service assoicated to the consumer + mlt_service service = MLT_CONSUMER_SERVICE( this ); + + if ( mlt_service_producer( service ) == NULL ) + { + struct timeval now; + struct timespec tm; + pthread_mutex_lock( &this->put_mutex ); + while ( this->put_active && this->put != NULL ) + { + gettimeofday( &now, NULL ); + tm.tv_sec = now.tv_sec + 1; + tm.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait( &this->put_cond, &this->put_mutex, &tm ); + } + if ( this->put_active && this->put == NULL ) + this->put = frame; + else + mlt_frame_close( frame ); + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + } + else + { + mlt_frame_close( frame ); + } + + return error; +} + +/** Protected method for consumer to get frames from connected service +*/ + +mlt_frame mlt_consumer_get_frame( mlt_consumer this ) +{ + // Frame to return + mlt_frame frame = NULL; + + // Get the service assoicated to the consumer + mlt_service service = MLT_CONSUMER_SERVICE( this ); + + // Get the consumer properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the frame + if ( mlt_service_producer( service ) == NULL && mlt_properties_get_int( properties, "put_mode" ) ) + { + struct timeval now; + struct timespec tm; + pthread_mutex_lock( &this->put_mutex ); + while ( this->put_active && this->put == NULL ) + { + gettimeofday( &now, NULL ); + tm.tv_sec = now.tv_sec + 1; + tm.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait( &this->put_cond, &this->put_mutex, &tm ); + } + frame = this->put; + this->put = NULL; + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + if ( frame != NULL ) + mlt_service_apply_filters( service, frame, 0 ); + } + else if ( mlt_service_producer( service ) != NULL ) + { + mlt_service_get_frame( service, &frame, 0 ); + } + else + { + frame = mlt_frame_init( ); + } + + if ( frame != NULL ) + { + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the test card producer + mlt_producer test_card = mlt_properties_get_data( properties, "test_card_producer", NULL ); + + // Attach the test frame producer to it. + if ( test_card != NULL ) + mlt_properties_set_data( frame_properties, "test_card_producer", test_card, 0, NULL, NULL ); + + // Attach the rescale property + mlt_properties_set( frame_properties, "rescale.interp", mlt_properties_get( properties, "rescale" ) ); + + // Aspect ratio and other jiggery pokery + mlt_properties_set_double( frame_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "consumer_deinterlace", mlt_properties_get_int( properties, "progressive" ) | mlt_properties_get_int( properties, "deinterlace" ) ); + mlt_properties_set( frame_properties, "deinterlace_method", mlt_properties_get( properties, "deinterlace_method" ) ); + } + + // Return the frame + return frame; +} + +static inline long time_difference( struct timeval *time1 ) +{ + struct timeval time2; + time2.tv_sec = time1->tv_sec; + time2.tv_usec = time1->tv_usec; + gettimeofday( time1, NULL ); + return time1->tv_sec * 1000000 + time1->tv_usec - time2.tv_sec * 1000000 - time2.tv_usec; +} + +int mlt_consumer_profile( mlt_properties properties, char *profile ) +{ + mlt_profile p = mlt_profile_select( profile ); + if ( p ) + { + apply_profile_properties( p, properties ); + return 1; + } + else + { + return 0; + } +} + +static void *consumer_read_ahead_thread( void *arg ) +{ + // The argument is the consumer + mlt_consumer this = arg; + + // Get the properties of the consumer + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + + // See if video is turned off + int video_off = mlt_properties_get_int( properties, "video_off" ); + int preview_off = mlt_properties_get_int( properties, "preview_off" ); + int preview_format = mlt_properties_get_int( properties, "preview_format" ); + + // Get the audio settings + mlt_audio_format afmt = mlt_audio_pcm; + int counter = 0; + double fps = mlt_properties_get_double( properties, "fps" ); + int channels = mlt_properties_get_int( properties, "channels" ); + int frequency = mlt_properties_get_int( properties, "frequency" ); + int samples = 0; + int16_t *pcm = NULL; + + // See if audio is turned off + int audio_off = mlt_properties_get_int( properties, "audio_off" ); + + // Get the maximum size of the buffer + int buffer = mlt_properties_get_int( properties, "buffer" ) + 1; + + // General frame variable + mlt_frame frame = NULL; + uint8_t *image = NULL; + + // Time structures + struct timeval ante; + + // Average time for get_frame and get_image + int count = 1; + int skipped = 0; + int64_t time_wait = 0; + int64_t time_frame = 0; + int64_t time_process = 0; + int skip_next = 0; + mlt_service lock_object = NULL; + + if ( preview_off && preview_format != 0 ) + this->format = preview_format; + + // Get the first frame + frame = mlt_consumer_get_frame( this ); + + // Get the lock object + lock_object = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "consumer_lock_service", NULL ); + + // Lock it + if ( lock_object ) mlt_service_lock( lock_object ); + + // Get the image of the first frame + if ( !video_off ) + { + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-frame-render", frame, NULL ); + mlt_frame_get_image( frame, &image, &this->format, &width, &height, 0 ); + } + + if ( !audio_off ) + { + samples = mlt_sample_calculator( fps, frequency, counter++ ); + mlt_frame_get_audio( frame, &pcm, &afmt, &frequency, &channels, &samples ); + } + + // Unlock the lock object + if ( lock_object ) mlt_service_unlock( lock_object ); + + // Mark as rendered + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + + // Get the starting time (can ignore the times above) + gettimeofday( &ante, NULL ); + + // Continue to read ahead + while ( this->ahead ) + { + // Fetch width/height again + width = mlt_properties_get_int( properties, "width" ); + height = mlt_properties_get_int( properties, "height" ); + + // Put the current frame into the queue + pthread_mutex_lock( &this->mutex ); + while( this->ahead && mlt_deque_count( this->queue ) >= buffer ) + pthread_cond_wait( &this->cond, &this->mutex ); + mlt_deque_push_back( this->queue, frame ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + + time_wait += time_difference( &ante ); + + // Get the next frame + frame = mlt_consumer_get_frame( this ); + time_frame += time_difference( &ante ); + + // If there's no frame, we're probably stopped... + if ( frame == NULL ) + continue; + + // Attempt to fetch the lock object + lock_object = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "consumer_lock_service", NULL ); + + // Increment the count + count ++; + + // Lock if there's a lock object + if ( lock_object ) mlt_service_lock( lock_object ); + + // All non normal playback frames should be shown + if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "_speed" ) != 1 ) + { + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "consumer_deinterlace", 1 ); + skipped = 0; + time_frame = 0; + time_process = 0; + time_wait = 0; + count = 1; + skip_next = 0; + } + + // Get the image + if ( !skip_next ) + { + // Get the image, mark as rendered and time it + if ( !video_off ) + { + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-frame-render", frame, NULL ); + mlt_frame_get_image( frame, &image, &this->format, &width, &height, 0 ); + } + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + } + else + { + // Increment the number of sequentially skipped frames + skipped ++; + skip_next = 0; + + // If we've reached an unacceptable level, reset everything + if ( skipped > 5 ) + { + skipped = 0; + time_frame = 0; + time_process = 0; + time_wait = 0; + count = 1; + } + } + + // Always process audio + if ( !audio_off ) + { + samples = mlt_sample_calculator( fps, frequency, counter++ ); + mlt_frame_get_audio( frame, &pcm, &afmt, &frequency, &channels, &samples ); + } + + // Increment the time take for this frame + time_process += time_difference( &ante ); + + // Determine if the next frame should be skipped + if ( mlt_deque_count( this->queue ) <= 5 && ( ( time_wait + time_frame + time_process ) / count ) > 40000 ) + skip_next = 1; + + // Unlock if there's a lock object + if ( lock_object ) mlt_service_unlock( lock_object ); + } + + // Remove the last frame + mlt_frame_close( frame ); + + return NULL; +} + +static void consumer_read_ahead_start( mlt_consumer this ) +{ + // We're running now + this->ahead = 1; + + // Create the frame queue + this->queue = mlt_deque_init( ); + + // Create the mutex + pthread_mutex_init( &this->mutex, NULL ); + + // Create the condition + pthread_cond_init( &this->cond, NULL ); + + // Create the read ahead + pthread_create( &this->ahead_thread, NULL, consumer_read_ahead_thread, this ); +} + +static void consumer_read_ahead_stop( mlt_consumer this ) +{ + // Make sure we're running + if ( this->ahead ) + { + // Inform thread to stop + this->ahead = 0; + + // Broadcast to the condition in case it's waiting + pthread_mutex_lock( &this->mutex ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + + // Broadcast to the put condition in case it's waiting + pthread_mutex_lock( &this->put_mutex ); + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + + // Join the thread + pthread_join( this->ahead_thread, NULL ); + + // Destroy the mutex + pthread_mutex_destroy( &this->mutex ); + + // Destroy the condition + pthread_cond_destroy( &this->cond ); + + // Wipe the queue + while ( mlt_deque_count( this->queue ) ) + mlt_frame_close( mlt_deque_pop_back( this->queue ) ); + + // Close the queue + mlt_deque_close( this->queue ); + } +} + +void mlt_consumer_purge( mlt_consumer this ) +{ + if ( this->ahead ) + { + pthread_mutex_lock( &this->mutex ); + while ( mlt_deque_count( this->queue ) ) + mlt_frame_close( mlt_deque_pop_back( this->queue ) ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + } +} + +mlt_frame mlt_consumer_rt_frame( mlt_consumer this ) +{ + // Frame to return + mlt_frame frame = NULL; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check if the user has requested real time or not + if ( this->real_time ) + { + int size = 1; + + // Is the read ahead running? + if ( this->ahead == 0 ) + { + int buffer = mlt_properties_get_int( properties, "buffer" ); + int prefill = mlt_properties_get_int( properties, "prefill" ); + consumer_read_ahead_start( this ); + if ( buffer > 1 ) + size = prefill > 0 && prefill < buffer ? prefill : buffer; + } + + // Get frame from queue + pthread_mutex_lock( &this->mutex ); + while( this->ahead && mlt_deque_count( this->queue ) < size ) + pthread_cond_wait( &this->cond, &this->mutex ); + frame = mlt_deque_pop_front( this->queue ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + } + else + { + // Get the frame in non real time + frame = mlt_consumer_get_frame( this ); + + // This isn't true, but from the consumers perspective it is + if ( frame != NULL ) + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + } + + return frame; +} + +/** Callback for the implementation to indicate a stopped condition. +*/ + +void mlt_consumer_stopped( mlt_consumer this ) +{ + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( this ), "running", 0 ); + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-stopped", NULL ); + mlt_event_unblock( g_event_listener ); +} + +/** Stop the consumer. +*/ + +int mlt_consumer_stop( mlt_consumer this ) +{ + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + char *debug = mlt_properties_get( MLT_CONSUMER_PROPERTIES( this ), "debug" ); + + // Just in case... + if ( debug ) fprintf( stderr, "%s: stopping put waiting\n", debug ); + pthread_mutex_lock( &this->put_mutex ); + this->put_active = 0; + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + + // Stop the consumer + if ( debug ) fprintf( stderr, "%s: stopping consumer\n", debug ); + if ( this->stop != NULL ) + this->stop( this ); + + // Check if the user has requested real time or not and stop if necessary + if ( debug ) fprintf( stderr, "%s: stopping read_ahead\n", debug ); + if ( mlt_properties_get_int( properties, "real_time" ) ) + consumer_read_ahead_stop( this ); + + // Kill the test card + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + + // Check and run a post command + if ( mlt_properties_get( properties, "post" ) ) + system( mlt_properties_get( properties, "post" ) ); + + if ( debug ) fprintf( stderr, "%s: stopped\n", debug ); + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +int mlt_consumer_is_stopped( mlt_consumer this ) +{ + // Check if the consumer is stopped + if ( this->is_stopped != NULL ) + return this->is_stopped( this ); + + return 0; +} + +/** Close the consumer. +*/ + +void mlt_consumer_close( mlt_consumer this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_CONSUMER_PROPERTIES( this ) ) <= 0 ) + { + // Get the childs close function + void ( *consumer_close )( ) = this->close; + + if ( consumer_close ) + { + // Just in case... + //mlt_consumer_stop( this ); + + this->close = NULL; + consumer_close( this ); + } + else + { + // Make sure it only gets called once + this->parent.close = NULL; + + // Destroy the push mutex and condition + pthread_mutex_destroy( &this->put_mutex ); + pthread_cond_destroy( &this->put_cond ); + + mlt_service_close( &this->parent ); + } + } +} diff --git a/src/framework/mlt_consumer.h b/src/framework/mlt_consumer.h new file mode 100644 index 0000000..8d0d66b --- /dev/null +++ b/src/framework/mlt_consumer.h @@ -0,0 +1,80 @@ +/* + * mlt_consumer.h -- abstraction for all consumer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_CONSUMER_H_ +#define _MLT_CONSUMER_H_ + +#include "mlt_service.h" +#include + +/** The interface definition for all consumers. +*/ + +struct mlt_consumer_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + int ( *start )( mlt_consumer ); + int ( *stop )( mlt_consumer ); + int ( *is_stopped )( mlt_consumer ); + void ( *close )( mlt_consumer ); + + /* Private data */ + void *local; + void *child; + + int real_time; + int ahead; + mlt_image_format format; + mlt_deque queue; + pthread_t ahead_thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + pthread_mutex_t put_mutex; + pthread_cond_t put_cond; + mlt_frame put; + int put_active; +}; + +/** Public final methods +*/ + +#define MLT_CONSUMER_SERVICE( consumer ) ( &( consumer )->parent ) +#define MLT_CONSUMER_PROPERTIES( consumer ) MLT_SERVICE_PROPERTIES( MLT_CONSUMER_SERVICE( consumer ) ) + +extern int mlt_consumer_init( mlt_consumer self, void *child ); +extern mlt_consumer mlt_consumer_new( ); +extern mlt_service mlt_consumer_service( mlt_consumer self ); +extern mlt_properties mlt_consumer_properties( mlt_consumer self ); +extern int mlt_consumer_connect( mlt_consumer self, mlt_service producer ); +extern int mlt_consumer_start( mlt_consumer self ); +extern void mlt_consumer_purge( mlt_consumer self ); +extern int mlt_consumer_put_frame( mlt_consumer self, mlt_frame frame ); +extern mlt_frame mlt_consumer_get_frame( mlt_consumer self ); +extern mlt_frame mlt_consumer_rt_frame( mlt_consumer self ); +extern int mlt_consumer_stop( mlt_consumer self ); +extern int mlt_consumer_is_stopped( mlt_consumer self ); +extern void mlt_consumer_stopped( mlt_consumer self ); +extern void mlt_consumer_close( mlt_consumer ); +extern int mlt_consumer_profile( mlt_properties properties, char *profile ); + +#endif diff --git a/src/framework/mlt_deque.c b/src/framework/mlt_deque.c new file mode 100644 index 0000000..75bb84b --- /dev/null +++ b/src/framework/mlt_deque.c @@ -0,0 +1,297 @@ +/* + * mlt_deque.c -- double ended queue + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "mlt_deque.h" + +// System header files +#include +#include + +typedef union +{ + void *addr; + int value; + double floating; +} +deque_entry; + +/** Private structure. +*/ + +struct mlt_deque_s +{ + deque_entry *list; + int size; + int count; +}; + +/** Create a deque. +*/ + +mlt_deque mlt_deque_init( ) +{ + mlt_deque this = malloc( sizeof( struct mlt_deque_s ) ); + if ( this != NULL ) + { + this->list = NULL; + this->size = 0; + this->count = 0; + } + return this; +} + +/** Return the number of items in the deque. +*/ + +int mlt_deque_count( mlt_deque this ) +{ + return this->count; +} + +/** Allocate space on the deque. +*/ + +static int mlt_deque_allocate( mlt_deque this ) +{ + if ( this->count == this->size ) + { + this->list = realloc( this->list, sizeof( deque_entry ) * ( this->size + 20 ) ); + this->size += 20; + } + return this->list == NULL; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back( mlt_deque this, void *item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].addr = item; + + return error; +} + +/** Pop an item. +*/ + +void *mlt_deque_pop_back( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].addr : NULL; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front( mlt_deque this, void *item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].addr = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +void *mlt_deque_pop_front( mlt_deque this ) +{ + void *item = NULL; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].addr; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +void *mlt_deque_peek_back( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].addr : NULL; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +void *mlt_deque_peek_front( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].addr : NULL; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back_int( mlt_deque this, int item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].value = item; + + return error; +} + +/** Pop an item. +*/ + +int mlt_deque_pop_back_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].value : 0; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front_int( mlt_deque this, int item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].value = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +int mlt_deque_pop_front_int( mlt_deque this ) +{ + int item = 0; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].value; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +int mlt_deque_peek_back_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].value : 0; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +int mlt_deque_peek_front_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].value : 0; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back_double( mlt_deque this, double item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].floating = item; + + return error; +} + +/** Pop an item. +*/ + +double mlt_deque_pop_back_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].floating : 0; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front_double( mlt_deque this, double item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].floating = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +double mlt_deque_pop_front_double( mlt_deque this ) +{ + double item = 0; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].floating; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +double mlt_deque_peek_back_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].floating : 0; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +double mlt_deque_peek_front_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].floating : 0; +} + +/** Close the queue. +*/ + +void mlt_deque_close( mlt_deque this ) +{ + free( this->list ); + free( this ); +} + diff --git a/src/framework/mlt_deque.h b/src/framework/mlt_deque.h new file mode 100644 index 0000000..5bc3d7d --- /dev/null +++ b/src/framework/mlt_deque.h @@ -0,0 +1,51 @@ +/* + * mlt_deque.h -- double ended queue + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_DEQUE_H_ +#define _MLT_DEQUE_H_ + +#include "mlt_types.h" + +extern mlt_deque mlt_deque_init( ); +extern int mlt_deque_count( mlt_deque self ); +extern int mlt_deque_push_back( mlt_deque self, void *item ); +extern void *mlt_deque_pop_back( mlt_deque self ); +extern int mlt_deque_push_front( mlt_deque self, void *item ); +extern void *mlt_deque_pop_front( mlt_deque self ); +extern void *mlt_deque_peek_back( mlt_deque self ); +extern void *mlt_deque_peek_front( mlt_deque self ); + +extern int mlt_deque_push_back_int( mlt_deque self, int item ); +extern int mlt_deque_pop_back_int( mlt_deque self ); +extern int mlt_deque_push_front_int( mlt_deque self, int item ); +extern int mlt_deque_pop_front_int( mlt_deque self ); +extern int mlt_deque_peek_back_int( mlt_deque self ); +extern int mlt_deque_peek_front_int( mlt_deque self ); + +extern int mlt_deque_push_back_double( mlt_deque self, double item ); +extern double mlt_deque_pop_back_double( mlt_deque self ); +extern int mlt_deque_push_front_double( mlt_deque self, double item ); +extern double mlt_deque_pop_front_double( mlt_deque self ); +extern double mlt_deque_peek_back_double( mlt_deque self ); +extern double mlt_deque_peek_front_double( mlt_deque self ); + +extern void mlt_deque_close( mlt_deque self ); + +#endif diff --git a/src/framework/mlt_events.c b/src/framework/mlt_events.c new file mode 100644 index 0000000..c74ab33 --- /dev/null +++ b/src/framework/mlt_events.c @@ -0,0 +1,403 @@ +/* + * mlt_events.h -- event handling + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "mlt_properties.h" +#include "mlt_events.h" + +/** Memory leak checks. +*/ + +//#define _MLT_EVENT_CHECKS_ + +#ifdef _MLT_EVENT_CHECKS_ +static int events_created = 0; +static int events_destroyed = 0; +#endif + +struct mlt_events_struct +{ + mlt_properties owner; + mlt_properties list; +}; + +typedef struct mlt_events_struct *mlt_events; + +struct mlt_event_struct +{ + mlt_events owner; + int ref_count; + int block_count; + mlt_listener listener; + void *service; +}; + +/** Increment the reference count on this event. +*/ + +void mlt_event_inc_ref( mlt_event this ) +{ + if ( this != NULL ) + this->ref_count ++; +} + +/** Increment the block count on this event. +*/ + +void mlt_event_block( mlt_event this ) +{ + if ( this != NULL && this->owner != NULL ) + this->block_count ++; +} + +/** Decrement the block count on this event. +*/ + +void mlt_event_unblock( mlt_event this ) +{ + if ( this != NULL && this->owner != NULL ) + this->block_count --; +} + +/** Close this event. +*/ + +void mlt_event_close( mlt_event this ) +{ + if ( this != NULL ) + { + if ( -- this->ref_count == 1 ) + this->owner = NULL; + if ( this->ref_count <= 0 ) + { +#ifdef _MLT_EVENT_CHECKS_ + events_destroyed ++; + fprintf( stderr, "Events created %d, destroyed %d\n", events_created, events_destroyed ); +#endif + free( this ); + } + } +} + +/** Forward declaration to private functions. +*/ + +static mlt_events mlt_events_fetch( mlt_properties ); +static void mlt_events_store( mlt_properties, mlt_events ); +static void mlt_events_close( mlt_events ); + +/** Initialise the events structure. +*/ + +void mlt_events_init( mlt_properties this ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events == NULL ) + { + events = malloc( sizeof( struct mlt_events_struct ) ); + events->list = mlt_properties_new( ); + mlt_events_store( this, events ); + } +} + +/** Register an event and transmitter. +*/ + +int mlt_events_register( mlt_properties this, char *id, mlt_transmitter transmitter ) +{ + int error = 1; + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + mlt_properties list = events->list; + char temp[ 128 ]; + error = mlt_properties_set_data( list, id, transmitter, 0, NULL, NULL ); + sprintf( temp, "list:%s", id ); + if ( mlt_properties_get_data( list, temp, NULL ) == NULL ) + mlt_properties_set_data( list, temp, mlt_properties_new( ), 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + return error; +} + +/** Fire an event. +*/ + +void mlt_events_fire( mlt_properties this, char *id, ... ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0; + va_list alist; + void *args[ 10 ]; + mlt_properties list = events->list; + mlt_properties listeners = NULL; + char temp[ 128 ]; + mlt_transmitter transmitter = mlt_properties_get_data( list, id, NULL ); + sprintf( temp, "list:%s", id ); + listeners = mlt_properties_get_data( list, temp, NULL ); + + va_start( alist, id ); + do + args[ i ] = va_arg( alist, void * ); + while( args[ i ++ ] != NULL ); + va_end( alist ); + + if ( listeners != NULL ) + { + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event event = mlt_properties_get_data_at( listeners, i, NULL ); + if ( event != NULL && event->owner != NULL && event->block_count == 0 ) + { + if ( transmitter != NULL ) + transmitter( event->listener, event->owner, event->service, args ); + else + event->listener( event->owner, event->service ); + } + } + } + } +} + +/** Register a listener. +*/ + +mlt_event mlt_events_listen( mlt_properties this, void *service, char *id, mlt_listener listener ) +{ + mlt_event event = NULL; + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + mlt_properties list = events->list; + mlt_properties listeners = NULL; + char temp[ 128 ]; + sprintf( temp, "list:%s", id ); + listeners = mlt_properties_get_data( list, temp, NULL ); + if ( listeners != NULL ) + { + int first_null = -1; + int i = 0; + for ( i = 0; event == NULL && i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->owner != NULL ) + { + if ( entry->service == service && entry->listener == listener ) + event = entry; + } + else if ( ( entry == NULL || entry->owner == NULL ) && first_null == -1 ) + { + first_null = i; + } + } + + if ( event == NULL ) + { + event = malloc( sizeof( struct mlt_event_struct ) ); + if ( event != NULL ) + { +#ifdef _MLT_EVENT_CHECKS_ + events_created ++; +#endif + sprintf( temp, "%d", first_null == -1 ? mlt_properties_count( listeners ) : first_null ); + event->owner = events; + event->ref_count = 0; + event->block_count = 0; + event->listener = listener; + event->service = service; + mlt_properties_set_data( listeners, temp, event, 0, ( mlt_destructor )mlt_event_close, NULL ); + mlt_event_inc_ref( event ); + } + } + + } + } + return event; +} + +/** Block all events for a given service. +*/ + +void mlt_events_block( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->service == service ) + mlt_event_block( entry ); + } + } + } + } +} + +/** Unblock all events for a given service. +*/ + +void mlt_events_unblock( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->service == service ) + mlt_event_unblock( entry ); + } + } + } + } +} + +/** Disconnect all events for a given service. +*/ + +void mlt_events_disconnect( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + char *name = mlt_properties_get_name( listeners, i ); + if ( entry != NULL && entry->service == service ) + mlt_properties_set_data( listeners, name, NULL, 0, NULL, NULL ); + } + } + } + } +} + +typedef struct +{ + int done; + pthread_cond_t cond; + pthread_mutex_t mutex; +} +condition_pair; + +static void mlt_events_listen_for( mlt_properties this, condition_pair *pair ) +{ + pthread_mutex_lock( &pair->mutex ); + if ( pair->done == 0 ) + { + pthread_cond_signal( &pair->cond ); + pthread_mutex_unlock( &pair->mutex ); + } +} + +mlt_event mlt_events_setup_wait_for( mlt_properties this, char *id ) +{ + condition_pair *pair = malloc( sizeof( condition_pair ) ); + pair->done = 0; + pthread_cond_init( &pair->cond, NULL ); + pthread_mutex_init( &pair->mutex, NULL ); + pthread_mutex_lock( &pair->mutex ); + return mlt_events_listen( this, pair, id, ( mlt_listener )mlt_events_listen_for ); +} + +void mlt_events_wait_for( mlt_properties this, mlt_event event ) +{ + if ( event != NULL ) + { + condition_pair *pair = event->service; + pthread_cond_wait( &pair->cond, &pair->mutex ); + } +} + +void mlt_events_close_wait_for( mlt_properties this, mlt_event event ) +{ + if ( event != NULL ) + { + condition_pair *pair = event->service; + event->owner = NULL; + pair->done = 0; + pthread_mutex_unlock( &pair->mutex ); + pthread_mutex_destroy( &pair->mutex ); + pthread_cond_destroy( &pair->cond ); + } +} + +/** Fetch the events object. +*/ + +static mlt_events mlt_events_fetch( mlt_properties this ) +{ + mlt_events events = NULL; + if ( this != NULL ) + events = mlt_properties_get_data( this, "_events", NULL ); + return events; +} + +/** Store the events object. +*/ + +static void mlt_events_store( mlt_properties this, mlt_events events ) +{ + if ( this != NULL && events != NULL ) + mlt_properties_set_data( this, "_events", events, 0, ( mlt_destructor )mlt_events_close, NULL ); +} + +/** Close the events object. +*/ + +static void mlt_events_close( mlt_events events ) +{ + if ( events != NULL ) + { + mlt_properties_close( events->list ); + free( events ); + } +} + diff --git a/src/framework/mlt_events.h b/src/framework/mlt_events.h new file mode 100644 index 0000000..e04de61 --- /dev/null +++ b/src/framework/mlt_events.h @@ -0,0 +1,52 @@ +/* + * mlt_events.h -- event handling + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_EVENTS_H_ +#define _MLT_EVENTS_H_ + +#include "mlt_types.h" + +#if GCC_VERSION >= 40000 +typedef void ( *mlt_transmitter )( void *, ... ); +typedef void ( *mlt_listener )( void *, ... ); +#else +typedef void ( *mlt_transmitter )( ); +typedef void ( *mlt_listener )( ); +#endif + +extern void mlt_events_init( mlt_properties self ); +extern int mlt_events_register( mlt_properties self, char *id, mlt_transmitter transmitter ); +extern void mlt_events_fire( mlt_properties self, char *id, ... ); +extern mlt_event mlt_events_listen( mlt_properties self, void *service, char *id, mlt_listener listener ); +extern void mlt_events_block( mlt_properties self, void *service ); +extern void mlt_events_unblock( mlt_properties self, void *service ); +extern void mlt_events_disconnect( mlt_properties self, void *service ); + +extern mlt_event mlt_events_setup_wait_for( mlt_properties self, char *id ); +extern void mlt_events_wait_for( mlt_properties self, mlt_event event ); +extern void mlt_events_close_wait_for( mlt_properties self, mlt_event event ); + +extern void mlt_event_inc_ref( mlt_event self ); +extern void mlt_event_block( mlt_event self ); +extern void mlt_event_unblock( mlt_event self ); +extern void mlt_event_close( mlt_event self ); + +#endif + diff --git a/src/framework/mlt_factory.c b/src/framework/mlt_factory.c new file mode 100644 index 0000000..686020a --- /dev/null +++ b/src/framework/mlt_factory.c @@ -0,0 +1,304 @@ +/* + * mlt_factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt.h" +#include "mlt_repository.h" + +#include +#include +#include + +/** Singleton repositories +*/ + +static char *mlt_prefix = NULL; +static mlt_properties global_properties = NULL; +static mlt_properties object_list = NULL; +static mlt_repository producers = NULL; +static mlt_repository filters = NULL; +static mlt_repository transitions = NULL; +static mlt_repository consumers = NULL; +static mlt_properties event_object = NULL; +static int unique_id = 0; + +/** Event transmitters. +*/ + +static void mlt_factory_create_request( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ], ( char * )args[ 1 ], ( mlt_service * )args[ 2 ] ); +} + +static void mlt_factory_create_done( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ], ( char * )args[ 1 ], ( mlt_service )args[ 2 ] ); +} + +/** Construct the factories. +*/ + +int mlt_factory_init( const char *prefix ) +{ + // Only initialise once + if ( mlt_prefix == NULL ) + { + // Allow user over rides + if ( prefix == NULL || !strcmp( prefix, "" ) ) + prefix = getenv( "MLT_REPOSITORY" ); + + // If no directory is specified, default to install directory + if ( prefix == NULL ) + prefix = PREFIX_DATA; + + // Store the prefix for later retrieval + mlt_prefix = strdup( prefix ); + + // Initialise the pool + mlt_pool_init( ); + + // Create and set up the events object + event_object = mlt_properties_new( ); + mlt_events_init( event_object ); + mlt_events_register( event_object, "producer-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "producer-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "filter-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "filter-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "transition-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "transition-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "consumer-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "consumer-create-done", ( mlt_transmitter )mlt_factory_create_done ); + + // Create the global properties + global_properties = mlt_properties_new( ); + + // Create the object list. + object_list = mlt_properties_new( ); + + // Create a repository for each service type + producers = mlt_repository_init( object_list, prefix, "producers", "mlt_create_producer" ); + filters = mlt_repository_init( object_list, prefix, "filters", "mlt_create_filter" ); + transitions = mlt_repository_init( object_list, prefix, "transitions", "mlt_create_transition" ); + consumers = mlt_repository_init( object_list, prefix, "consumers", "mlt_create_consumer" ); + + // Force a clean up when app closes + atexit( mlt_factory_close ); + } + + // Allow property refresh on a subsequent initialisation + if ( global_properties != NULL ) + { + mlt_properties_set_or_default( global_properties, "MLT_NORMALISATION", getenv( "MLT_NORMALISATION" ), "PAL" ); + mlt_properties_set_or_default( global_properties, "MLT_PRODUCER", getenv( "MLT_PRODUCER" ), "fezzik" ); + mlt_properties_set_or_default( global_properties, "MLT_CONSUMER", getenv( "MLT_CONSUMER" ), "sdl" ); + mlt_properties_set( global_properties, "MLT_TEST_CARD", getenv( "MLT_TEST_CARD" ) ); + mlt_properties_set_or_default( global_properties, "MLT_PROFILE", getenv( "MLT_PROFILE" ), "dv_pal" ); + + // Load the most appropriate profile + // MLT_PROFILE preferred + if ( getenv( "MLT_PROFILE" ) ) + mlt_profile_select( mlt_environment( "MLT_PROFILE" ) ); + // MLT_NORMALISATION backwards compatibility + else if ( strcmp( mlt_environment( "MLT_NORMALISATION" ), "PAL" ) ) + mlt_profile_select( "dv_ntsc" ); + else + mlt_profile_select( "dv_pal" ); + } + + + return 0; +} + +/** Fetch the events object. +*/ + +mlt_properties mlt_factory_event_object( ) +{ + return event_object; +} + +/** Fetch the prefix used in this instance. +*/ + +const char *mlt_factory_prefix( ) +{ + return mlt_prefix; +} + +/** Get a value from the environment. +*/ + +char *mlt_environment( const char *name ) +{ + return mlt_properties_get( global_properties, name ); +} + +/** Set a value in the environment. +*/ + +int mlt_environment_set( const char *name, const char *value ) +{ + return mlt_properties_set( global_properties, name, value ); +} + +/** Fetch a producer from the repository. +*/ + +mlt_producer mlt_factory_producer( const char *service, void *input ) +{ + mlt_producer obj = NULL; + + // Pick up the default normalising producer if necessary + if ( service == NULL ) + service = mlt_environment( "MLT_PRODUCER" ); + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "producer-create-request", service, input, &obj, NULL ); + + // Try to instantiate via the specified service + if ( obj == NULL ) + { + obj = mlt_repository_fetch( producers, service, input ); + mlt_events_fire( event_object, "producer-create-done", service, input, obj, NULL ); + if ( obj != NULL ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "producer" ); + if ( mlt_properties_get_int( properties, "_mlt_service_hidden" ) == 0 ) + mlt_properties_set( properties, "mlt_service", service ); + } + } + return obj; +} + +/** Fetch a filter from the repository. +*/ + +mlt_filter mlt_factory_filter( const char *service, void *input ) +{ + mlt_filter obj = NULL; + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "filter-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( filters, service, input ); + mlt_events_fire( event_object, "filter-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "filter" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Fetch a transition from the repository. +*/ + +mlt_transition mlt_factory_transition( const char *service, void *input ) +{ + mlt_transition obj = NULL; + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "transition-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( transitions, service, input ); + mlt_events_fire( event_object, "transition-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "transition" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Fetch a consumer from the repository +*/ + +mlt_consumer mlt_factory_consumer( const char *service, void *input ) +{ + mlt_consumer obj = NULL; + + if ( service == NULL ) + service = mlt_environment( "MLT_CONSUMER" ); + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "consumer-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( consumers, service, input ); + mlt_events_fire( event_object, "consumer-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "consumer" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Register an object for clean up. +*/ + +void mlt_factory_register_for_clean_up( void *ptr, mlt_destructor destructor ) +{ + char unique[ 256 ]; + sprintf( unique, "%08d", mlt_properties_count( global_properties ) ); + mlt_properties_set_data( global_properties, unique, ptr, 0, destructor, NULL ); +} + +/** Close the factory. +*/ + +void mlt_factory_close( ) +{ + if ( mlt_prefix != NULL ) + { + mlt_properties_close( event_object ); + mlt_repository_close( producers ); + mlt_repository_close( filters ); + mlt_repository_close( transitions ); + mlt_repository_close( consumers ); + mlt_properties_close( global_properties ); + mlt_properties_close( object_list ); + free( mlt_prefix ); + mlt_prefix = NULL; + mlt_pool_close( ); + mlt_profile_close(); + } +} diff --git a/src/framework/mlt_factory.h b/src/framework/mlt_factory.h new file mode 100644 index 0000000..46a5307 --- /dev/null +++ b/src/framework/mlt_factory.h @@ -0,0 +1,38 @@ +/* + * mlt_factory.h -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FACTORY_H +#define _MLT_FACTORY_H + +#include "mlt_types.h" + +extern int mlt_factory_init( const char *prefix ); +extern const char *mlt_factory_prefix( ); +extern char *mlt_environment( const char *name ); +extern int mlt_environment_set( const char *name, const char *value ); +extern mlt_properties mlt_factory_event_object( ); +extern mlt_producer mlt_factory_producer( const char *name, void *input ); +extern mlt_filter mlt_factory_filter( const char *name, void *input ); +extern mlt_transition mlt_factory_transition( const char *name, void *input ); +extern mlt_consumer mlt_factory_consumer( const char *name, void *input ); +extern void mlt_factory_register_for_clean_up( void *ptr, mlt_destructor destructor ); +extern void mlt_factory_close( ); + +#endif diff --git a/src/framework/mlt_field.c b/src/framework/mlt_field.c new file mode 100644 index 0000000..48e0470 --- /dev/null +++ b/src/framework/mlt_field.c @@ -0,0 +1,193 @@ +/* + * mlt_field.c -- A field for planting multiple transitions and filters + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_field.h" +#include "mlt_service.h" +#include "mlt_filter.h" +#include "mlt_transition.h" +#include "mlt_multitrack.h" +#include "mlt_tractor.h" + +#include +#include + +/** Private structures. +*/ + +struct mlt_field_s +{ + // This is the producer we're connected to + mlt_service producer; + + // Multitrack + mlt_multitrack multitrack; + + // Tractor + mlt_tractor tractor; +}; + +/** Constructor. + + We construct a multitrack and a tractor here. +*/ + +mlt_field mlt_field_init( ) +{ + // Initialise the field + mlt_field this = calloc( sizeof( struct mlt_field_s ), 1 ); + + // Initialise it + if ( this != NULL ) + { + // Construct a multitrack + this->multitrack = mlt_multitrack_init( ); + + // Construct a tractor + this->tractor = mlt_tractor_init( ); + + // The first plant will be connected to the mulitrack + this->producer = MLT_MULTITRACK_SERVICE( this->multitrack ); + + // Connect the tractor to the multitrack + mlt_tractor_connect( this->tractor, this->producer ); + } + + // Return this + return this; +} + +mlt_field mlt_field_new( mlt_multitrack multitrack, mlt_tractor tractor ) +{ + // Initialise the field + mlt_field this = calloc( sizeof( struct mlt_field_s ), 1 ); + + // Initialise it + if ( this != NULL ) + { + // Construct a multitrack + this->multitrack = multitrack; + + // Construct a tractor + this->tractor = tractor; + + // The first plant will be connected to the mulitrack + this->producer = MLT_MULTITRACK_SERVICE( this->multitrack ); + + // Connect the tractor to the multitrack + mlt_tractor_connect( this->tractor, this->producer ); + } + + // Return this + return this; +} + +/** Get the service associated to this field. +*/ + +mlt_service mlt_field_service( mlt_field this ) +{ + return MLT_TRACTOR_SERVICE( this->tractor ); +} + +/** Get the multi track. +*/ + +mlt_multitrack mlt_field_multitrack( mlt_field this ) +{ + return this != NULL ? this->multitrack : NULL; +} + +/** Get the tractor. +*/ + +mlt_tractor mlt_field_tractor( mlt_field this ) +{ + return this != NULL ? this->tractor : NULL; +} + +/** Get the properties associated to this field. +*/ + +mlt_properties mlt_field_properties( mlt_field this ) +{ + return MLT_SERVICE_PROPERTIES( mlt_field_service( this ) ); +} + +/** Plant a filter. +*/ + +int mlt_field_plant_filter( mlt_field this, mlt_filter that, int track ) +{ + // Connect the filter to the last producer + int result = mlt_filter_connect( that, this->producer, track ); + + // If sucessful, then we'll use this for connecting in the future + if ( result == 0 ) + { + // This is now the new producer + this->producer = MLT_FILTER_SERVICE( that ); + + // Reconnect tractor to new producer + mlt_tractor_connect( this->tractor, this->producer ); + + // Fire an event + mlt_events_fire( mlt_field_properties( this ), "service-changed", NULL ); + } + + return result; +} + +/** Plant a transition. +*/ + +int mlt_field_plant_transition( mlt_field this, mlt_transition that, int a_track, int b_track ) +{ + // Connect the transition to the last producer + int result = mlt_transition_connect( that, this->producer, a_track, b_track ); + + // If sucessful, then we'll use this for connecting in the future + if ( result == 0 ) + { + // This is now the new producer + this->producer = MLT_TRANSITION_SERVICE( that ); + + // Reconnect tractor to new producer + mlt_tractor_connect( this->tractor, this->producer ); + + // Fire an event + mlt_events_fire( mlt_field_properties( this ), "service-changed", NULL ); + } + + return 0; +} + +/** Close the field. +*/ + +void mlt_field_close( mlt_field this ) +{ + if ( this != NULL && mlt_properties_dec_ref( mlt_field_properties( this ) ) <= 0 ) + { + //mlt_tractor_close( this->tractor ); + //mlt_multitrack_close( this->multitrack ); + free( this ); + } +} + diff --git a/src/framework/mlt_field.h b/src/framework/mlt_field.h new file mode 100644 index 0000000..032b767 --- /dev/null +++ b/src/framework/mlt_field.h @@ -0,0 +1,37 @@ +/* + * mlt_field.h -- A field for planting multiple transitions and services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FIELD_H_ +#define _MLT_FIELD_H_ + +#include "mlt_types.h" + +extern mlt_field mlt_field_init( ); +extern mlt_field mlt_field_new( mlt_multitrack multitrack, mlt_tractor tractor ); +extern mlt_service mlt_field_service( mlt_field self ); +extern mlt_tractor mlt_field_tractor( mlt_field self ); +extern mlt_multitrack mlt_field_multitrack( mlt_field self ); +extern mlt_properties mlt_field_properties( mlt_field self ); +extern int mlt_field_plant_filter( mlt_field self, mlt_filter that, int track ); +extern int mlt_field_plant_transition( mlt_field self, mlt_transition that, int a_track, int b_track ); +extern void mlt_field_close( mlt_field self ); + +#endif + diff --git a/src/framework/mlt_filter.c b/src/framework/mlt_filter.c new file mode 100644 index 0000000..3fc2229 --- /dev/null +++ b/src/framework/mlt_filter.c @@ -0,0 +1,213 @@ +/* + * mlt_filter.c -- abstraction for all filter services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_filter.h" +#include "mlt_frame.h" + +#include +#include +#include + +static int filter_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + +/** Constructor method. +*/ + +int mlt_filter_init( mlt_filter this, void *child ) +{ + mlt_service service = &this->parent; + memset( this, 0, sizeof( struct mlt_filter_s ) ); + this->child = child; + if ( mlt_service_init( service, this ) == 0 ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + // Override the get_frame method + service->get_frame = filter_get_frame; + + // Define the destructor + service->close = ( mlt_destructor )mlt_filter_close; + service->close_object = this; + + // Default in, out, track properties + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "track", 0 ); + + return 0; + } + return 1; +} + +/** Create a new filter. +*/ + +mlt_filter mlt_filter_new( ) +{ + mlt_filter this = calloc( 1, sizeof( struct mlt_filter_s ) ); + if ( this != NULL ) + mlt_filter_init( this, NULL ); + return this; +} + +/** Get the service associated to this filter +*/ + +mlt_service mlt_filter_service( mlt_filter this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties associated to this filter. +*/ + +mlt_properties mlt_filter_properties( mlt_filter this ) +{ + return MLT_SERVICE_PROPERTIES( MLT_FILTER_SERVICE( this ) ); +} + +/** Connect this filter to a producers track. Note that a filter only operates + on a single track, and by default it operates on the entirety of that track. +*/ + +int mlt_filter_connect( mlt_filter this, mlt_service producer, int index ) +{ + int ret = mlt_service_connect_producer( &this->parent, producer, index ); + + // If the connection was successful, grab the producer, track and reset in/out + if ( ret == 0 ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "track", index ); + } + + return ret; +} + +/** Tune the in/out points. +*/ + +void mlt_filter_set_in_and_out( mlt_filter this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); +} + +/** Return the track that this filter is operating on. +*/ + +int mlt_filter_get_track( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_int( properties, "track" ); +} + +/** Get the in point. +*/ + +mlt_position mlt_filter_get_in( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_position( properties, "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_filter_get_out( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_position( properties, "out" ); +} + +/** Process the frame. +*/ + +mlt_frame mlt_filter_process( mlt_filter this, mlt_frame frame ) +{ + int disable = mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "disable" ); + if ( disable || this->process == NULL ) + return frame; + else + return this->process( this, frame ); +} + +/** Get a frame from this filter. +*/ + +static int filter_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + mlt_filter this = service->child; + + // Get coords in/out/track + int track = mlt_filter_get_track( this ); + int in = mlt_filter_get_in( this ); + int out = mlt_filter_get_out( this ); + + // Get the producer this is connected to + mlt_service producer = mlt_service_producer( &this->parent ); + + // If the frame request is for this filters track, we need to process it + if ( index == track || track == -1 ) + { + int ret = mlt_service_get_frame( producer, frame, index ); + if ( ret == 0 ) + { + mlt_position position = mlt_frame_get_position( *frame ); + if ( position >= in && ( out == 0 || position <= out ) ) + *frame = mlt_filter_process( this, *frame ); + return 0; + } + else + { + *frame = mlt_frame_init( ); + return 0; + } + } + else + { + return mlt_service_get_frame( producer, frame, index ); + } +} + +/** Close the filter. +*/ + +void mlt_filter_close( mlt_filter this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_FILTER_PROPERTIES( this ) ) <= 0 ) + { + if ( this->close != NULL ) + { + this->close( this ); + } + else + { + this->parent.close = NULL; + mlt_service_close( &this->parent ); + } + free( this ); + } +} diff --git a/src/framework/mlt_filter.h b/src/framework/mlt_filter.h new file mode 100644 index 0000000..06866a1 --- /dev/null +++ b/src/framework/mlt_filter.h @@ -0,0 +1,62 @@ +/* + * mlt_filter.h -- abstraction for all filter services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FILTER_H_ +#define _MLT_FILTER_H_ + +#include "mlt_service.h" + +/** The interface definition for all filters. +*/ + +struct mlt_filter_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + void ( *close )( mlt_filter ); + + /* protected filter method */ + mlt_frame ( *process )( mlt_filter, mlt_frame ); + + /* Protected */ + void *child; +}; + +/** Public final methods +*/ + +#define MLT_FILTER_SERVICE( filter ) ( &( filter )->parent ) +#define MLT_FILTER_PROPERTIES( filter ) MLT_SERVICE_PROPERTIES( MLT_FILTER_SERVICE( filter ) ) + +extern int mlt_filter_init( mlt_filter self, void *child ); +extern mlt_filter mlt_filter_new( ); +extern mlt_service mlt_filter_service( mlt_filter self ); +extern mlt_properties mlt_filter_properties( mlt_filter self ); +extern mlt_frame mlt_filter_process( mlt_filter self, mlt_frame that ); +extern int mlt_filter_connect( mlt_filter self, mlt_service producer, int index ); +extern void mlt_filter_set_in_and_out( mlt_filter self, mlt_position in, mlt_position out ); +extern int mlt_filter_get_track( mlt_filter self ); +extern mlt_position mlt_filter_get_in( mlt_filter self ); +extern mlt_position mlt_filter_get_out( mlt_filter self ); +extern void mlt_filter_close( mlt_filter ); + +#endif diff --git a/src/framework/mlt_frame.c b/src/framework/mlt_frame.c new file mode 100644 index 0000000..79fed6e --- /dev/null +++ b/src/framework/mlt_frame.c @@ -0,0 +1,1310 @@ +/* + * mlt_frame.c -- interface for all frame classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_frame.h" +#include "mlt_producer.h" +#include "mlt_factory.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +/** Constructor for a frame. +*/ + +mlt_frame mlt_frame_init( ) +{ + // Allocate a frame + mlt_frame this = calloc( sizeof( struct mlt_frame_s ), 1 ); + + if ( this != NULL ) + { + // Initialise the properties + mlt_properties properties = &this->parent; + mlt_properties_init( properties, this ); + + // Set default properties on the frame + mlt_properties_set_position( properties, "_position", 0.0 ); + mlt_properties_set_data( properties, "image", NULL, 0, NULL, NULL ); + mlt_properties_set_int( properties, "width", mlt_profile_get()->width ); + mlt_properties_set_int( properties, "height", mlt_profile_get()->height ); + mlt_properties_set_int( properties, "normalised_width", mlt_profile_get()->width ); + mlt_properties_set_int( properties, "normalised_height", mlt_profile_get()->height ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + mlt_properties_set_data( properties, "audio", NULL, 0, NULL, NULL ); + mlt_properties_set_data( properties, "alpha", NULL, 0, NULL, NULL ); + + // Construct stacks for frames and methods + this->stack_image = mlt_deque_init( ); + this->stack_audio = mlt_deque_init( ); + this->stack_service = mlt_deque_init( ); + } + + return this; +} + +/** Fetch the frames properties. +*/ + +mlt_properties mlt_frame_properties( mlt_frame this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Check if we have a way to derive something other than a test card. +*/ + +int mlt_frame_is_test_card( mlt_frame this ) +{ + return mlt_deque_count( this->stack_image ) == 0 || mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "test_image" ); +} + +/** Check if we have a way to derive something other than test audio. +*/ + +int mlt_frame_is_test_audio( mlt_frame this ) +{ + return mlt_deque_count( this->stack_audio ) == 0 || mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "test_audio" ); +} + +/** Get the aspect ratio of the frame. +*/ + +double mlt_frame_get_aspect_ratio( mlt_frame this ) +{ + return mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "aspect_ratio" ); +} + +/** Set the aspect ratio of the frame. +*/ + +int mlt_frame_set_aspect_ratio( mlt_frame this, double value ) +{ + return mlt_properties_set_double( MLT_FRAME_PROPERTIES( this ), "aspect_ratio", value ); +} + +/** Get the position of this frame. +*/ + +mlt_position mlt_frame_get_position( mlt_frame this ) +{ + int pos = mlt_properties_get_position( MLT_FRAME_PROPERTIES( this ), "_position" ); + return pos < 0 ? 0 : pos; +} + +/** Set the position of this frame. +*/ + +int mlt_frame_set_position( mlt_frame this, mlt_position value ) +{ + return mlt_properties_set_position( MLT_FRAME_PROPERTIES( this ), "_position", value ); +} + +/** Stack a get_image callback. +*/ + +int mlt_frame_push_get_image( mlt_frame this, mlt_get_image get_image ) +{ + return mlt_deque_push_back( this->stack_image, get_image ); +} + +/** Pop a get_image callback. +*/ + +mlt_get_image mlt_frame_pop_get_image( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a frame. +*/ + +int mlt_frame_push_frame( mlt_frame this, mlt_frame that ) +{ + return mlt_deque_push_back( this->stack_image, that ); +} + +/** Pop a frame. +*/ + +mlt_frame mlt_frame_pop_frame( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a service. +*/ + +int mlt_frame_push_service( mlt_frame this, void *that ) +{ + return mlt_deque_push_back( this->stack_image, that ); +} + +/** Pop a service. +*/ + +void *mlt_frame_pop_service( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a service. +*/ + +int mlt_frame_push_service_int( mlt_frame this, int that ) +{ + return mlt_deque_push_back_int( this->stack_image, that ); +} + +/** Pop a service. +*/ + +int mlt_frame_pop_service_int( mlt_frame this ) +{ + return mlt_deque_pop_back_int( this->stack_image ); +} + +/** Push an audio item on the stack. +*/ + +int mlt_frame_push_audio( mlt_frame this, void *that ) +{ + return mlt_deque_push_back( this->stack_audio, that ); +} + +/** Pop an audio item from the stack +*/ + +void *mlt_frame_pop_audio( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_audio ); +} + +/** Return the service stack +*/ + +mlt_deque mlt_frame_service_stack( mlt_frame this ) +{ + return this->stack_service; +} + +/** Replace image stack with the information provided. + + This might prove to be unreliable and restrictive - the idea is that a transition + which normally uses two images may decide to only use the b frame (ie: in the case + of a composite where the b frame completely obscures the a frame). + + The image must be writable and the destructor for the image itself must be taken + care of on another frame and that frame cannot have a replace applied to it... + Further it assumes that no alpha mask is in use. + + For these reasons, it can only be used in a specific situation - when you have + multiple tracks each with their own transition and these transitions are applied + in a strictly reversed order (ie: highest numbered [lowest track] is processed + first). + + More reliable approach - the cases should be detected during the process phase + and the upper tracks should simply not be invited to stack... +*/ + +void mlt_frame_replace_image( mlt_frame this, uint8_t *image, mlt_image_format format, int width, int height ) +{ + // Remove all items from the stack + while( mlt_deque_pop_back( this->stack_image ) ) ; + + // Update the information + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "image", image, 0, NULL, NULL ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "width", width ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "height", height ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "format", format ); + this->get_alpha_mask = NULL; +} + +/** Get the image associated to the frame. +*/ + +int mlt_frame_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_get_image get_image = mlt_frame_pop_get_image( this ); + mlt_producer producer = mlt_properties_get_data( properties, "test_card_producer", NULL ); + int error = 0; + + if ( get_image != NULL ) + { + mlt_properties_set_int( properties, "image_count", mlt_properties_get_int( properties, "image_count" ) - 1 ); + mlt_position position = mlt_frame_get_position( this ); + error = get_image( this, buffer, format, width, height, writable ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_frame_set_position( this, position ); + } + else if ( mlt_properties_get_data( properties, "image", NULL ) != NULL ) + { + *format = mlt_properties_get_int( properties, "format" ); + *buffer = mlt_properties_get_data( properties, "image", NULL ); + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + } + else if ( producer != NULL ) + { + mlt_frame test_frame = NULL; + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &test_frame, 0 ); + if ( test_frame != NULL ) + { + mlt_properties test_properties = MLT_FRAME_PROPERTIES( test_frame ); + mlt_properties_set_double( test_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "consumer_aspect_ratio" ) ); + mlt_properties_set( test_properties, "rescale.interp", mlt_properties_get( properties, "rescale.interp" ) ); + mlt_frame_get_image( test_frame, buffer, format, width, height, writable ); + mlt_properties_set_data( properties, "test_card_frame", test_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + mlt_properties_set_data( properties, "image", *buffer, *width * *height * 2, NULL, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_frame_get_aspect_ratio( test_frame ) ); + } + else + { + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + mlt_frame_get_image( this, buffer, format, width, height, writable ); + } + } + else + { + register uint8_t *p; + register uint8_t *q; + int size = 0; + + *width = *width == 0 ? 720 : *width; + *height = *height == 0 ? 576 : *height; + size = *width * *height; + + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "aspect_ratio", 0 ); + + switch( *format ) + { + case mlt_image_none: + size = 0; + *buffer = NULL; + break; + case mlt_image_rgb24: + size *= 3; + size += *width * 3; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + case mlt_image_rgb24a: + case mlt_image_opengl: + size *= 4; + size += *width * 4; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + case mlt_image_yuv422: + size *= 2; + size += *width * 2; + *buffer = mlt_pool_alloc( size ); + p = *buffer; + q = p + size; + while ( p != NULL && p != q ) + { + *p ++ = 235; + *p ++ = 128; + } + break; + case mlt_image_yuv420p: + size = size * 3 / 2; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + } + + mlt_properties_set_data( properties, "image", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "test_image", 1 ); + } + + mlt_properties_set_int( properties, "scaled_width", *width ); + mlt_properties_set_int( properties, "scaled_height", *height ); + + return error; +} + +uint8_t *mlt_frame_get_alpha_mask( mlt_frame this ) +{ + uint8_t *alpha = NULL; + if ( this != NULL ) + { + if ( this->get_alpha_mask != NULL ) + alpha = this->get_alpha_mask( this ); + if ( alpha == NULL ) + alpha = mlt_properties_get_data( &this->parent, "alpha", NULL ); + if ( alpha == NULL ) + { + int size = mlt_properties_get_int( &this->parent, "scaled_width" ) * mlt_properties_get_int( &this->parent, "scaled_height" ); + alpha = mlt_pool_alloc( size ); + memset( alpha, 255, size ); + mlt_properties_set_data( &this->parent, "alpha", alpha, size, mlt_pool_release, NULL ); + } + } + return alpha; +} + +int mlt_frame_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + mlt_get_audio get_audio = mlt_frame_pop_audio( this ); + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + int hide = mlt_properties_get_int( properties, "test_audio" ); + + if ( hide == 0 && get_audio != NULL ) + { + mlt_position position = mlt_frame_get_position( this ); + get_audio( this, buffer, format, frequency, channels, samples ); + mlt_frame_set_position( this, position ); + } + else if ( mlt_properties_get_data( properties, "audio", NULL ) ) + { + *buffer = mlt_properties_get_data( properties, "audio", NULL ); + *frequency = mlt_properties_get_int( properties, "audio_frequency" ); + *channels = mlt_properties_get_int( properties, "audio_channels" ); + *samples = mlt_properties_get_int( properties, "audio_samples" ); + } + else + { + int size = 0; + *samples = *samples <= 0 ? 1920 : *samples; + *channels = *channels <= 0 ? 2 : *channels; + *frequency = *frequency <= 0 ? 48000 : *frequency; + size = *samples * *channels * sizeof( int16_t ); + *buffer = mlt_pool_alloc( size ); + if ( *buffer != NULL ) + memset( *buffer, 0, size ); + mlt_properties_set_data( properties, "audio", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "test_audio", 1 ); + } + + mlt_properties_set_int( properties, "audio_frequency", *frequency ); + mlt_properties_set_int( properties, "audio_channels", *channels ); + mlt_properties_set_int( properties, "audio_samples", *samples ); + + if ( mlt_properties_get( properties, "meta.volume" ) ) + { + double value = mlt_properties_get_double( properties, "meta.volume" ); + + if ( value == 0.0 ) + { + memset( *buffer, 0, *samples * *channels * 2 ); + } + else if ( value != 1.0 ) + { + int total = *samples * *channels; + int16_t *p = *buffer; + while ( total -- ) + { + *p = *p * value; + p ++; + } + } + + mlt_properties_set( properties, "meta.volume", NULL ); + } + + return 0; +} + +unsigned char *mlt_frame_get_waveform( mlt_frame this, int w, int h ) +{ + int16_t *pcm = NULL; + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_audio_format format = mlt_audio_pcm; + int frequency = 32000; // lower frequency available? + int channels = 2; + double fps = mlt_profile_fps( NULL ); + int samples = mlt_sample_calculator( fps, frequency, mlt_frame_get_position( this ) ); + + // Get the pcm data + mlt_frame_get_audio( this, &pcm, &format, &frequency, &channels, &samples ); + + // Make an 8-bit buffer large enough to hold rendering + int size = w * h; + unsigned char *bitmap = ( unsigned char* )mlt_pool_alloc( size ); + if ( bitmap != NULL ) + memset( bitmap, 0, size ); + mlt_properties_set_data( properties, "waveform", bitmap, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Render vertical lines + int16_t *ubound = pcm + samples * channels; + int skip = samples / w - 1; + int i, j, k; + + // Iterate sample stream and along x coordinate + for ( i = 0; i < w && pcm < ubound; i++ ) + { + // pcm data has channels interleaved + for ( j = 0; j < channels; j++ ) + { + // Determine sample's magnitude from 2s complement; + int pcm_magnitude = *pcm < 0 ? ~(*pcm) + 1 : *pcm; + // The height of a line is the ratio of the magnitude multiplied by + // half the vertical resolution + int height = ( int )( ( double )( pcm_magnitude ) / 32768 * h / 2 ); + // Determine the starting y coordinate - left channel above center, + // right channel below - currently assumes 2 channels + int displacement = ( h / 2 ) - ( 1 - j ) * height; + // Position buffer pointer using y coordinate, stride, and x coordinate + unsigned char *p = &bitmap[ i + displacement * w ]; + + // Draw vertical line + for ( k = 0; k < height; k++ ) + p[ w * k ] = 0xFF; + + pcm++; + } + pcm += skip * channels; + } + + return bitmap; +} + +mlt_producer mlt_frame_get_original_producer( mlt_frame this ) +{ + if ( this != NULL ) + return mlt_properties_get_data( MLT_FRAME_PROPERTIES( this ), "_producer", NULL ); + return NULL; +} + +void mlt_frame_close( mlt_frame this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_FRAME_PROPERTIES( this ) ) <= 0 ) + { + mlt_deque_close( this->stack_image ); + mlt_deque_close( this->stack_audio ); + while( mlt_deque_peek_back( this->stack_service ) ) + mlt_service_close( mlt_deque_pop_back( this->stack_service ) ); + mlt_deque_close( this->stack_service ); + mlt_properties_close( &this->parent ); + free( this ); + } +} + +/***** convenience functions *****/ + +int mlt_convert_yuv422_to_rgb24a( uint8_t *yuv, uint8_t *rgba, unsigned int total ) +{ + int ret = 0; + int yy, uu, vv; + int r,g,b; + total /= 2; + while (total--) + { + yy = yuv[0]; + uu = yuv[1]; + vv = yuv[3]; + YUV2RGB(yy, uu, vv, r, g, b); + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = 255; + yy = yuv[2]; + YUV2RGB(yy, uu, vv, r, g, b); + rgba[4] = r; + rgba[5] = g; + rgba[6] = b; + rgba[7] = 255; + yuv += 4; + rgba += 8; + } + return ret; +} + +int mlt_convert_rgb24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + + return ret; +} + +int mlt_convert_rgb24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgb + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_bgr24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_bgr24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgb + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_argb_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_yuv420p_to_yuv422( uint8_t *yuv420p, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int i, j; + + int half = width >> 1; + + uint8_t *Y = yuv420p; + uint8_t *U = Y + width * height; + uint8_t *V = U + width * height / 4; + + register uint8_t *d = yuv; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *u = U + ( i / 2 ) * ( half ); + register uint8_t *v = V + ( i / 2 ) * ( half ); + + for ( j = 0; j < half; j++ ) + { + *d ++ = *Y ++; + *d ++ = *u ++; + *d ++ = *Y ++; + *d ++ = *v ++; + } + } + return ret; +} + +uint8_t *mlt_resize_alpha( uint8_t *input, int owidth, int oheight, int iwidth, int iheight, uint8_t alpha_value ) +{ + uint8_t *output = NULL; + + if ( input != NULL && ( iwidth != owidth || iheight != oheight ) && ( owidth > 6 && oheight > 6 ) ) + { + uint8_t *out_line; + int offset_x = ( owidth - iwidth ) / 2; + int offset_y = ( oheight - iheight ) / 2; + int iused = iwidth; + + output = mlt_pool_alloc( owidth * oheight ); + memset( output, alpha_value, owidth * oheight ); + + offset_x -= offset_x % 2; + + out_line = output + offset_y * owidth; + out_line += offset_x; + + // Loop for the entirety of our output height. + while ( iheight -- ) + { + // We're in the input range for this row. + memcpy( out_line, input, iused ); + + // Move to next input line + input += iwidth; + + // Move to next output line + out_line += owidth; + } + } + + return output; +} + +void mlt_resize_yuv422( uint8_t *output, int owidth, int oheight, uint8_t *input, int iwidth, int iheight ) +{ + // Calculate strides + int istride = iwidth * 2; + int ostride = owidth * 2; + int offset_x = ( owidth - iwidth ); + int offset_y = ( oheight - iheight ) / 2; + uint8_t *in_line = input; + uint8_t *out_line; + int size = owidth * oheight; + uint8_t *p = output; + + // Optimisation point + if ( output == NULL || input == NULL || ( owidth <= 6 || oheight <= 6 || iwidth <= 6 || oheight <= 6 ) ) + { + return; + } + else if ( iwidth == owidth && iheight == oheight ) + { + memcpy( output, input, iheight * istride ); + return; + } + + while( size -- ) + { + *p ++ = 16; + *p ++ = 128; + } + + offset_x -= offset_x % 4; + + out_line = output + offset_y * ostride; + out_line += offset_x; + + // Loop for the entirety of our output height. + while ( iheight -- ) + { + // We're in the input range for this row. + memcpy( out_line, in_line, iwidth * 2 ); + + // Move to next input line + in_line += istride; + + // Move to next output line + out_line += ostride; + } +} + +/** A resizing function for yuv422 frames - this does not rescale, but simply + resizes. It assumes yuv422 images available on the frame so use with care. +*/ + +uint8_t *mlt_frame_resize_yuv422( mlt_frame this, int owidth, int oheight ) +{ + // Get properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the input image, width and height + uint8_t *input = mlt_properties_get_data( properties, "image", NULL ); + uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + + int iwidth = mlt_properties_get_int( properties, "width" ); + int iheight = mlt_properties_get_int( properties, "height" ); + + // If width and height are correct, don't do anything + if ( iwidth != owidth || iheight != oheight ) + { + uint8_t alpha_value = mlt_properties_get_int( properties, "resize_alpha" ); + + // Create the output image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + // Call the generic resize + mlt_resize_yuv422( output, owidth, oheight, input, iwidth, iheight ); + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + // We should resize the alpha too + alpha = mlt_resize_alpha( alpha, owidth, oheight, iwidth, iheight, alpha_value ); + if ( alpha != NULL ) + { + mlt_properties_set_data( properties, "alpha", alpha, owidth * oheight, ( mlt_destructor )mlt_pool_release, NULL ); + this->get_alpha_mask = NULL; + } + + // Return the output + return output; + } + // No change, return input + return input; +} + +/** A rescaling function for yuv422 frames - low quality, and provided for testing + only. It assumes yuv422 images available on the frame so use with care. +*/ + +uint8_t *mlt_frame_rescale_yuv422( mlt_frame this, int owidth, int oheight ) +{ + // Get properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the input image, width and height + uint8_t *input = mlt_properties_get_data( properties, "image", NULL ); + int iwidth = mlt_properties_get_int( properties, "width" ); + int iheight = mlt_properties_get_int( properties, "height" ); + + // If width and height are correct, don't do anything + if ( iwidth != owidth || iheight != oheight ) + { + // Create the output image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + // Calculate strides + int istride = iwidth * 2; + int ostride = owidth * 2; + + iwidth = iwidth - ( iwidth % 4 ); + + // Derived coordinates + int dy, dx; + + // Calculate ranges + int out_x_range = owidth / 2; + int out_y_range = oheight / 2; + int in_x_range = iwidth / 2; + int in_y_range = iheight / 2; + + // Output pointers + register uint8_t *out_line = output; + register uint8_t *out_ptr; + + // Calculate a middle pointer + uint8_t *in_middle = input + istride * in_y_range + in_x_range * 2; + uint8_t *in_line; + + // Generate the affine transform scaling values + register int scale_width = ( iwidth << 16 ) / owidth; + register int scale_height = ( iheight << 16 ) / oheight; + register int base = 0; + + int outer = out_x_range * scale_width; + int bottom = out_y_range * scale_height; + + // Loop for the entirety of our output height. + for ( dy = - bottom; dy < bottom; dy += scale_height ) + { + // Start at the beginning of the line + out_ptr = out_line; + + // Pointer to the middle of the input line + in_line = in_middle + ( dy >> 16 ) * istride; + + // Loop for the entirety of our output row. + for ( dx = - outer; dx < outer; dx += scale_width ) + { + base = dx >> 15; + base &= 0xfffffffe; + *out_ptr ++ = *( in_line + base ); + base &= 0xfffffffc; + *out_ptr ++ = *( in_line + base + 1 ); + dx += scale_width; + base = dx >> 15; + base &= 0xfffffffe; + *out_ptr ++ = *( in_line + base ); + base &= 0xfffffffc; + *out_ptr ++ = *( in_line + base + 3 ); + } + + // Move to next output line + out_line += ostride; + } + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + // Return the output + return output; + } + + // No change, return input + return input; +} + +int mlt_frame_mix_audio( mlt_frame this, mlt_frame that, float weight_start, float weight_end, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + int ret = 0; + int16_t *src, *dest; + int frequency_src = *frequency, frequency_dest = *frequency; + int channels_src = *channels, channels_dest = *channels; + int samples_src = *samples, samples_dest = *samples; + int i, j; + double d = 0, s = 0; + + mlt_frame_get_audio( that, &src, format, &frequency_src, &channels_src, &samples_src ); + mlt_frame_get_audio( this, &dest, format, &frequency_dest, &channels_dest, &samples_dest ); + + int silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "silent_audio", 0 ); + if ( silent ) + memset( dest, 0, samples_dest * channels_dest * sizeof( int16_t ) ); + + silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( that ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( that ), "silent_audio", 0 ); + if ( silent ) + memset( src, 0, samples_src * channels_src * sizeof( int16_t ) ); + + if ( channels_src > 6 ) + channels_src = 0; + if ( channels_dest > 6 ) + channels_dest = 0; + if ( samples_src > 4000 ) + samples_src = 0; + if ( samples_dest > 4000 ) + samples_dest = 0; + + // determine number of samples to process + *samples = samples_src < samples_dest ? samples_src : samples_dest; + *channels = channels_src < channels_dest ? channels_src : channels_dest; + *buffer = dest; + *frequency = frequency_dest; + + // Compute a smooth ramp over start to end + float weight = weight_start; + float weight_step = ( weight_end - weight_start ) / *samples; + + if ( src == dest ) + { + *samples = samples_src; + *channels = channels_src; + *buffer = src; + *frequency = frequency_src; + return ret; + } + + // Mixdown + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + if ( j < channels_dest ) + d = (double) dest[ i * channels_dest + j ]; + if ( j < channels_src ) + s = (double) src[ i * channels_src + j ]; + dest[ i * channels_dest + j ] = s * weight + d * ( 1.0 - weight ); + } + weight += weight_step; + } + + return ret; +} + +// Replacement for broken mlt_frame_audio_mix - this filter uses an inline low pass filter +// to allow mixing without volume hacking +int mlt_frame_combine_audio( mlt_frame this, mlt_frame that, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + int ret = 0; + int16_t *src, *dest; + int frequency_src = *frequency, frequency_dest = *frequency; + int channels_src = *channels, channels_dest = *channels; + int samples_src = *samples, samples_dest = *samples; + int i, j; + double vp[ 6 ]; + double b_weight = 1.0; + + if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "meta.mixdown" ) ) + b_weight = 1.0 - mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "meta.volume" ); + + mlt_frame_get_audio( that, &src, format, &frequency_src, &channels_src, &samples_src ); + mlt_frame_get_audio( this, &dest, format, &frequency_dest, &channels_dest, &samples_dest ); + + int silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "silent_audio", 0 ); + if ( silent ) + memset( dest, 0, samples_dest * channels_dest * sizeof( int16_t ) ); + + silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( that ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( that ), "silent_audio", 0 ); + if ( silent ) + memset( src, 0, samples_src * channels_src * sizeof( int16_t ) ); + + if ( src == dest ) + { + *samples = samples_src; + *channels = channels_src; + *buffer = src; + *frequency = frequency_src; + return ret; + } + + // determine number of samples to process + *samples = samples_src < samples_dest ? samples_src : samples_dest; + *channels = channels_src < channels_dest ? channels_src : channels_dest; + *buffer = dest; + *frequency = frequency_dest; + + for ( j = 0; j < *channels; j++ ) + vp[ j ] = ( double )dest[ j ]; + + double Fc = 0.5; + double B = exp(-2.0 * M_PI * Fc); + double A = 1.0 - B; + double v; + + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + v = ( double )( b_weight * dest[ i * channels_dest + j ] + src[ i * channels_src + j ] ); + v = v < -32767 ? -32767 : v > 32768 ? 32768 : v; + vp[ j ] = dest[ i * channels_dest + j ] = ( int16_t )( v * A + vp[ j ] * B ); + } + } + + return ret; +} + +/* Will this break when mlt_position is converted to double? -Zach */ +int mlt_sample_calculator( float fps, int frequency, int64_t position ) +{ + int samples = 0; + + if ( ( int )( fps * 100 ) == 2997 ) + { + samples = frequency / 30; + + switch ( frequency ) + { + case 48000: + if ( position % 5 != 0 ) + samples += 2; + break; + case 44100: + if ( position % 300 == 0 ) + samples = 1471; + else if ( position % 30 == 0 ) + samples = 1470; + else if ( position % 2 == 0 ) + samples = 1472; + else + samples = 1471; + break; + case 32000: + if ( position % 30 == 0 ) + samples = 1068; + else if ( position % 29 == 0 ) + samples = 1067; + else if ( position % 4 == 2 ) + samples = 1067; + else + samples = 1068; + break; + default: + samples = 0; + } + } + else if ( fps != 0 ) + { + samples = frequency / fps; + } + + return samples; +} + +int64_t mlt_sample_calculator_to_now( float fps, int frequency, int64_t frame ) +{ + int64_t samples = 0; + + // TODO: Correct rules for NTSC and drop the * 100 hack + if ( ( int )( fps * 100 ) == 2997 ) + { + samples = ( ( double )( frame * frequency ) / 30 ); + switch( frequency ) + { + case 48000: + samples += 2 * ( frame / 5 ); + break; + case 44100: + samples += frame + ( frame / 2 ) - ( frame / 30 ) + ( frame / 300 ); + break; + case 32000: + samples += ( 2 * frame ) - ( frame / 4 ) - ( frame / 29 ); + break; + } + } + else if ( fps != 0 ) + { + samples = ( ( frame * frequency ) / ( int )fps ); + } + + return samples; +} diff --git a/src/framework/mlt_frame.h b/src/framework/mlt_frame.h new file mode 100644 index 0000000..2cb5ab7 --- /dev/null +++ b/src/framework/mlt_frame.h @@ -0,0 +1,118 @@ +/* + * mlt_frame.h -- interface for all frame classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FRAME_H_ +#define _MLT_FRAME_H_ + +#include "mlt_properties.h" +#include "mlt_deque.h" + +typedef int ( *mlt_get_image )( mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); +typedef int ( *mlt_get_audio )( mlt_frame self, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); + +struct mlt_frame_s +{ + /* We're extending properties here */ + struct mlt_properties_s parent; + + /* Virtual methods */ + uint8_t * ( *get_alpha_mask )( mlt_frame self ); + + /* Private properties */ + mlt_deque stack_image; + mlt_deque stack_audio; + mlt_deque stack_service; +}; + +#define MLT_FRAME_PROPERTIES( frame ) ( &( frame )->parent ) +#define MLT_FRAME_SERVICE_STACK( frame ) ( ( frame )->stack_service ) +#define MLT_FRAME_IMAGE_STACK( frame ) ( ( frame )->stack_image ) +#define MLT_FRAME_AUDIO_STACK( frame ) ( ( frame )->stack_audio ) + +extern mlt_frame mlt_frame_init( ); +extern mlt_properties mlt_frame_properties( mlt_frame self ); +extern int mlt_frame_is_test_card( mlt_frame self ); +extern int mlt_frame_is_test_audio( mlt_frame self ); +extern double mlt_frame_get_aspect_ratio( mlt_frame self ); +extern int mlt_frame_set_aspect_ratio( mlt_frame self, double value ); +extern mlt_position mlt_frame_get_position( mlt_frame self ); +extern int mlt_frame_set_position( mlt_frame self, mlt_position value ); +extern void mlt_frame_replace_image( mlt_frame self, uint8_t *image, mlt_image_format format, int width, int height ); +extern int mlt_frame_get_image( mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); +extern uint8_t *mlt_frame_get_alpha_mask( mlt_frame self ); +extern int mlt_frame_get_audio( mlt_frame self, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern unsigned char *mlt_frame_get_waveform( mlt_frame self, int w, int h ); +extern int mlt_frame_push_get_image( mlt_frame self, mlt_get_image get_image ); +extern mlt_get_image mlt_frame_pop_get_image( mlt_frame self ); +extern int mlt_frame_push_frame( mlt_frame self, mlt_frame that ); +extern mlt_frame mlt_frame_pop_frame( mlt_frame self ); +extern int mlt_frame_push_service( mlt_frame self, void *that ); +extern void *mlt_frame_pop_service( mlt_frame self ); +extern int mlt_frame_push_service_int( mlt_frame self, int that ); +extern int mlt_frame_pop_service_int( mlt_frame self ); +extern int mlt_frame_push_audio( mlt_frame self, void *that ); +extern void *mlt_frame_pop_audio( mlt_frame self ); +extern mlt_deque mlt_frame_service_stack( mlt_frame self ); +extern mlt_producer mlt_frame_get_original_producer( mlt_frame self ); +extern void mlt_frame_close( mlt_frame self ); + +/* convenience functions */ +extern int mlt_convert_yuv422_to_rgb24a( uint8_t *yuv, uint8_t *rgba, unsigned int total ); +extern int mlt_convert_rgb24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_rgb24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ); +extern int mlt_convert_bgr24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_argb_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_bgr24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ); +extern int mlt_convert_yuv420p_to_yuv422( uint8_t *yuv420p, int width, int height, int stride, uint8_t *yuv ); +extern uint8_t *mlt_frame_resize_yuv422( mlt_frame self, int owidth, int oheight ); +extern uint8_t *mlt_frame_rescale_yuv422( mlt_frame self, int owidth, int oheight ); +extern void mlt_resize_yuv422( uint8_t *output, int owidth, int oheight, uint8_t *input, int iwidth, int iheight ); +extern int mlt_frame_mix_audio( mlt_frame self, mlt_frame that, float weight_start, float weight_end, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern int mlt_frame_combine_audio( mlt_frame self, mlt_frame that, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern int mlt_sample_calculator( float fps, int frequency, int64_t position ); +extern int64_t mlt_sample_calculator_to_now( float fps, int frequency, int64_t position ); + +/* this macro scales rgb into the yuv gamut, y is scaled by 219/255 and uv by 224/255 */ +#define RGB2YUV(r, g, b, y, u, v)\ + y = ((263*r + 516*g + 100*b) >> 10) + 16;\ + u = ((-152*r - 298*g + 450*b) >> 10) + 128;\ + v = ((450*r - 377*g - 73*b) >> 10) + 128; + +/* this macro assumes the user has already scaled their rgb down into the broadcast limits */ +#define RGB2YUV_UNSCALED(r, g, b, y, u, v)\ + y = (299*r + 587*g + 114*b) >> 10;\ + u = ((-169*r - 331*g + 500*b) >> 10) + 128;\ + v = ((500*r - 419*g - 81*b) >> 10) + 128;\ + y = y < 16 ? 16 : y;\ + u = u < 16 ? 16 : u;\ + v = v < 16 ? 16 : v;\ + y = y > 235 ? 235 : y;\ + u = u > 240 ? 240 : u;\ + v = v > 240 ? 240 : v + +#define YUV2RGB( y, u, v, r, g, b ) \ + r = ((1192 * ( y - 16 ) + 1634 * ( v - 128 ) ) >> 10 ); \ + g = ((1192 * ( y - 16 ) - 832 * ( v - 128 ) - 400 * ( u - 128 ) ) >> 10 ); \ + b = ((1192 * ( y - 16 ) + 2066 * ( u - 128 ) ) >> 10 ); \ + r = r < 0 ? 0 : r > 255 ? 255 : r; \ + g = g < 0 ? 0 : g > 255 ? 255 : g; \ + b = b < 0 ? 0 : b > 255 ? 255 : b; + +#endif diff --git a/src/framework/mlt_geometry.c b/src/framework/mlt_geometry.c new file mode 100644 index 0000000..e7819bf --- /dev/null +++ b/src/framework/mlt_geometry.c @@ -0,0 +1,700 @@ +/* + * mlt_geometry.c -- provides the geometry API + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_geometry.h" +#include "mlt_tokeniser.h" +#include "mlt_factory.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +typedef struct geometry_item_s +{ + struct mlt_geometry_item_s data; + struct geometry_item_s *next, *prev; +} +*geometry_item; + +typedef struct +{ + char *data; + int length; + int nw; + int nh; + geometry_item item; +} +geometry_s, *geometry; + +// Create a new geometry structure +mlt_geometry mlt_geometry_init( ) +{ + mlt_geometry this = calloc( 1, sizeof( struct mlt_geometry_s ) ); + if ( this != NULL ) + { + this->local = calloc( 1, sizeof( geometry_s ) ); + if ( this->local != NULL ) + { + geometry self = this->local; + self->nw = mlt_profile_get()->width; + self->nh = mlt_profile_get()->height; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +/** A linear step +*/ + +static inline double linearstep( double start, double end, double position, int length ) +{ + double o = ( end - start ) / length; + return start + position * o; +} + +static void mlt_geometry_virtual_refresh( mlt_geometry this ) +{ + geometry self = this->local; + + // Parse of all items to ensure unspecified keys are calculated correctly + if ( self->item != NULL ) + { + int i = 0; + for ( i = 0; i < 5; i ++ ) + { + geometry_item current = self->item; + while( current != NULL ) + { + int fixed = current->data.f[ i ]; + if ( !fixed ) + { + geometry_item prev = current->prev; + geometry_item next = current->next; + + double prev_value = 0; + double next_value = 0; + double value = 0; + + while( prev != NULL && !prev->data.f[ i ] ) prev = prev->prev; + while( next != NULL && !next->data.f[ i ] ) next = next->next; + + switch( i ) + { + case 0: + if ( prev ) prev_value = prev->data.x; + if ( next ) next_value = next->data.x; + break; + case 1: + if ( prev ) prev_value = prev->data.y; + if ( next ) next_value = next->data.y; + break; + case 2: + if ( prev ) prev_value = prev->data.w; + if ( next ) next_value = next->data.w; + break; + case 3: + if ( prev ) prev_value = prev->data.h; + if ( next ) next_value = next->data.h; + break; + case 4: + if ( prev ) prev_value = prev->data.mix; + if ( next ) next_value = next->data.mix; + break; + } + + // This should never happen + if ( prev == NULL ) + current->data.f[ i ] = 1; + else if ( next == NULL ) + value = prev_value; + else + value = linearstep( prev_value, next_value, current->data.frame - prev->data.frame, next->data.frame - prev->data.frame ); + + switch( i ) + { + case 0: current->data.x = value; break; + case 1: current->data.y = value; break; + case 2: current->data.w = value; break; + case 3: current->data.h = value; break; + case 4: current->data.mix = value; break; + } + } + + // Move to the next item + current = current->next; + } + } + } +} + +static int mlt_geometry_drop( mlt_geometry this, geometry_item item ) +{ + geometry self = this->local; + + if ( item == self->item ) + { + self->item = item->next; + if ( self->item != NULL ) + self->item->prev = NULL; + // To ensure correct seeding, ensure all values are fixed + if ( self->item != NULL ) + { + self->item->data.f[0] = 1; + self->item->data.f[1] = 1; + self->item->data.f[2] = 1; + self->item->data.f[3] = 1; + self->item->data.f[4] = 1; + } + } + else if ( item->next != NULL && item->prev != NULL ) + { + item->prev->next = item->next; + item->next->prev = item->prev; + } + else if ( item->next != NULL ) + { + item->next->prev = item->prev; + } + else if ( item->prev != NULL ) + { + item->prev->next = item->next; + } + + free( item ); + + return 0; +} + +static void mlt_geometry_clean( mlt_geometry this ) +{ + geometry self = this->local; + free( self->data ); + self->data = NULL; + while( self->item ) + mlt_geometry_drop( this, self->item ); +} + +// Parse the geometry specification for a given length and normalised width/height (-1 for default) +// data is constructed as: [frame=]X,Y:WxH[:mix][;[frame=]X,Y:WxH[:mix]]* +// and X, Y, W and H can have trailing % chars to indicate percentage of normalised size +int mlt_geometry_parse( mlt_geometry this, char *data, int length, int nw, int nh ) +{ + int i = 0; + + // Create a tokeniser + mlt_tokeniser tokens = mlt_tokeniser_init( ); + + // Get the local/private structure + geometry self = this->local; + + // Clean the existing geometry + mlt_geometry_clean( this ); + + // Update the info on the data + if ( length != -1 ) + self->length = length; + if ( nw != -1 ) + self->nw = nw; + if ( nh != -1 ) + self->nh = nh; + if ( data != NULL ) + self->data = strdup( data ); + + // Tokenise + if ( data != NULL ) + mlt_tokeniser_parse_new( tokens, data, ";" ); + + // Iterate through each token + for ( i = 0; i < mlt_tokeniser_count( tokens ); i ++ ) + { + struct mlt_geometry_item_s item; + char *value = mlt_tokeniser_get_string( tokens, i ); + + // Set item to 0 + memset( &item, 0, sizeof( struct mlt_geometry_item_s ) ); + + // Now parse the item + mlt_geometry_parse_item( this, &item, value ); + + // Now insert into place + mlt_geometry_insert( this, &item ); + } + + // Remove the tokeniser + mlt_tokeniser_close( tokens ); + + // ??? + return 0; +} + +// Conditionally refresh in case of a change +int mlt_geometry_refresh( mlt_geometry this, char *data, int length, int nw, int nh ) +{ + geometry self = this->local; + int changed = ( length != -1 && length != self->length ); + changed = changed || ( nw != -1 && nw != self->nw ); + changed = changed || ( nh != -1 && nh != self->nh ); + changed = changed || ( data != NULL && ( self->data == NULL || strcmp( data, self->data ) ) ); + if ( changed ) + return mlt_geometry_parse( this, data, length, nw, nh ); + return -1; +} + +int mlt_geometry_get_length( mlt_geometry this ) +{ + // Get the local/private structure + geometry self = this->local; + + // return the length + return self->length; +} + +void mlt_geometry_set_length( mlt_geometry this, int length ) +{ + // Get the local/private structure + geometry self = this->local; + + // set the length + self->length = length; +} + +int mlt_geometry_parse_item( mlt_geometry this, mlt_geometry_item item, char *value ) +{ + int ret = 0; + + // Get the local/private structure + geometry self = this->local; + + if ( value != NULL && strcmp( value, "" ) ) + { + char *p = strchr( value, '=' ); + int count = 0; + double temp; + + // Determine if a position has been specified + if ( p != NULL ) + { + temp = atof( value ); + if ( temp > -1 && temp < 1 ) + item->frame = temp * self->length; + else + item->frame = temp; + value = p + 1; + } + + // Special case - frame < 0 + if ( item->frame < 0 ) + item->frame += self->length; + + // Obtain the current value at this position - this allows new + // frames to be created which don't specify all values + mlt_geometry_fetch( this, item, item->frame ); + + // Special case - when an empty string is specified, all values are fixed + // TODO: Check if this is logical - it's convenient, but it's also odd... + if ( !*value ) + { + item->f[0] = 1; + item->f[1] = 1; + item->f[2] = 1; + item->f[3] = 1; + item->f[4] = 1; + } + + // Iterate through the remainder of value + while( *value ) + { + // Get the value + temp = strtod( value, &p ); + + // Check if a value was specified + if ( p != value ) + { + // Handle the % case + if ( *p == '%' ) + { + if ( count == 0 || count == 2 ) + temp *= self->nw / 100.0; + else if ( count == 1 || count == 3 ) + temp *= self->nh / 100.0; + p ++; + } + + // Special case - distort token + if ( *p == '!' || *p == '*' ) + { + p ++; + item->distort = 1; + } + + // Actually, we don't care about the delimiter at all.. + if ( *p ) p ++; + + // Assign to the item + switch( count ) + { + case 0: item->x = temp; item->f[0] = 1; break; + case 1: item->y = temp; item->f[1] = 1; break; + case 2: item->w = temp; item->f[2] = 1; break; + case 3: item->h = temp; item->f[3] = 1; break; + case 4: item->mix = temp; item->f[4] = 1; break; + } + } + else + { + p ++; + } + + // Update the value pointer + value = p; + count ++; + } + } + else + { + ret = 1; + } + + return ret; +} + +// Fetch a geometry item for an absolute position +int mlt_geometry_fetch( mlt_geometry this, mlt_geometry_item item, float position ) +{ + // Get the local geometry + geometry self = this->local; + + // Need to find the nearest key to the position specifed + geometry_item key = self->item; + + // Iterate through the keys until we reach last or have + while( key != NULL && key->next != NULL && position >= key->next->data.frame ) + key = key->next; + + if ( key != NULL ) + { + // Position is situated before the first key - all zeroes + if ( position < key->data.frame ) + { + memset( item, 0, sizeof( struct mlt_geometry_item_s ) ); + item->mix = 100; + } + // Position is a key itself - no iterpolation need + else if ( position == key->data.frame ) + { + memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) ); + } + // Position is after the last key - no interpolation, but not a key frame + else if ( key->next == NULL ) + { + memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) ); + item->key = 0; + item->f[ 0 ] = 0; + item->f[ 1 ] = 0; + item->f[ 2 ] = 0; + item->f[ 3 ] = 0; + item->f[ 4 ] = 0; + } + // Interpolation is needed - position > key and there is a following key + else + { + item->key = 0; + item->frame = position; + position -= key->data.frame; + item->x = linearstep( key->data.x, key->next->data.x, position, key->next->data.frame - key->data.frame ); + item->y = linearstep( key->data.y, key->next->data.y, position, key->next->data.frame - key->data.frame ); + item->w = linearstep( key->data.w, key->next->data.w, position, key->next->data.frame - key->data.frame ); + item->h = linearstep( key->data.h, key->next->data.h, position, key->next->data.frame - key->data.frame ); + item->mix = linearstep( key->data.mix, key->next->data.mix, position, key->next->data.frame - key->data.frame ); + item->distort = key->data.distort; + position += key->data.frame; + } + + item->frame = position; + } + else + { + memset( item, 0, sizeof( struct mlt_geometry_item_s ) ); + item->frame = position; + item->mix = 100; + } + + return key == NULL; +} + +// Specify a geometry item at an absolute position +int mlt_geometry_insert( mlt_geometry this, mlt_geometry_item item ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Create a new local item (this may be removed if a key already exists at this position) + geometry_item new = calloc( 1, sizeof( struct geometry_item_s ) ); + memcpy( &new->data, item, sizeof( struct mlt_geometry_item_s ) ); + new->data.key = 1; + + // Determine if we need to insert or append to the list, or if it's a new list + if ( self->item != NULL ) + { + // Get the first item + geometry_item place = self->item; + + // Locate an existing nearby item + while ( place->next != NULL && item->frame > place->data.frame ) + place = place->next; + + if ( item->frame < place->data.frame ) + { + if ( place == self->item ) + self->item = new; + if ( place->prev ) + place->prev->next = new; + new->next = place; + new->prev = place->prev; + place->prev = new; + } + else if ( item->frame > place->data.frame ) + { + if ( place->next ) + place->next->prev = new; + new->next = place->next; + new->prev = place; + place->next = new; + } + else + { + memcpy( &place->data, &new->data, sizeof( struct mlt_geometry_item_s ) ); + free( new ); + } + } + else + { + // Set the first item + self->item = new; + + // To ensure correct seeding, ensure all values are fixed + self->item->data.f[0] = 1; + self->item->data.f[1] = 1; + self->item->data.f[2] = 1; + self->item->data.f[3] = 1; + self->item->data.f[4] = 1; + } + + // Refresh all geometries + mlt_geometry_virtual_refresh( this ); + + // TODO: Error checking + return 0; +} + +// Remove the key at the specified position +int mlt_geometry_remove( mlt_geometry this, int position ) +{ + int ret = 1; + + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && position != place->data.frame ) + place = place->next; + + if ( place != NULL && position == place->data.frame ) + ret = mlt_geometry_drop( this, place ); + + // Refresh all geometries + mlt_geometry_virtual_refresh( this ); + + return ret; +} + +// Get the key at the position or the next following +int mlt_geometry_next_key( mlt_geometry this, mlt_geometry_item item, int position ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && position > place->data.frame ) + place = place->next; + + if ( place != NULL ) + memcpy( item, &place->data, sizeof( struct mlt_geometry_item_s ) ); + + return place == NULL; +} + +// Get the key at the position or the previous key +int mlt_geometry_prev_key( mlt_geometry this, mlt_geometry_item item, int position ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && place->next != NULL && position >= place->next->data.frame ) + place = place->next; + + if ( place != NULL ) + memcpy( item, &place->data, sizeof( struct mlt_geometry_item_s ) ); + + return place == NULL; +} + +char *mlt_geometry_serialise_cut( mlt_geometry this, int in, int out ) +{ + geometry self = this->local; + struct mlt_geometry_item_s item; + char *ret = malloc( 1000 ); + int used = 0; + int size = 1000; + + if ( in == -1 ) + in = 0; + if ( out == -1 ) + out = mlt_geometry_get_length( this ); + + if ( ret != NULL ) + { + char temp[ 100 ]; + + strcpy( ret, "" ); + + item.frame = in; + + while( 1 ) + { + strcpy( temp, "" ); + + // If it's the first frame, then it's not necessarily a key + if ( item.frame == in ) + { + if ( mlt_geometry_fetch( this, &item, item.frame ) ) + break; + + // If the first key is larger than the current position + // then do nothing here + if ( self->item->data.frame > item.frame ) + { + item.frame ++; + continue; + } + + // To ensure correct seeding, ensure all values are fixed + item.f[0] = 1; + item.f[1] = 1; + item.f[2] = 1; + item.f[3] = 1; + item.f[4] = 1; + } + // Typically, we move from key to key + else if ( item.frame < out ) + { + if ( mlt_geometry_next_key( this, &item, item.frame ) ) + break; + + // Special case - crop at the out point + if ( item.frame > out ) + mlt_geometry_fetch( this, &item, out ); + } + // We've handled the last key + else + { + break; + } + + if ( item.frame - in != 0 ) + sprintf( temp, "%d=", item.frame - in ); + + if ( item.f[0] ) + sprintf( temp + strlen( temp ), "%.0f", item.x ); + strcat( temp, "," ); + if ( item.f[1] ) + sprintf( temp + strlen( temp ), "%.0f", item.y ); + strcat( temp, ":" ); + if ( item.f[2] ) + sprintf( temp + strlen( temp ), "%.0f", item.w ); + strcat( temp, "x" ); + if ( item.f[3] ) + sprintf( temp + strlen( temp ), "%.0f", item.h ); + if ( item.f[4] ) + sprintf( temp + strlen( temp ), ":%.0f", item.mix ); + + if ( used + strlen( temp ) > size ) + { + size += 1000; + ret = realloc( ret, size ); + } + + if ( ret != NULL && used != 0 ) + { + used ++; + strcat( ret, ";" ); + } + if ( ret != NULL ) + { + used += strlen( temp ); + strcat( ret, temp ); + } + + item.frame ++; + } + } + + return ret; +} + +// Serialise the current geometry +char *mlt_geometry_serialise( mlt_geometry this ) +{ + geometry self = this->local; + char *ret = mlt_geometry_serialise_cut( this, 0, self->length ); + if ( ret ) + { + free( self->data ); + self->data = ret; + } + return ret; +} + +// Close the geometry +void mlt_geometry_close( mlt_geometry this ) +{ + if ( this != NULL ) + { + mlt_geometry_clean( this ); + free( this->local ); + free( this ); + } +} + + diff --git a/src/framework/mlt_geometry.h b/src/framework/mlt_geometry.h new file mode 100644 index 0000000..797f6a0 --- /dev/null +++ b/src/framework/mlt_geometry.h @@ -0,0 +1,73 @@ +/* + * mlt_geometry.h -- provides the geometry API + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_GEOMETRY_H +#define _MLT_GEOMETRY_H + +#include "mlt_types.h" + +struct mlt_geometry_item_s +{ + /* Will be 1 when this is a key frame */ + int key; + /* The actual frame this corresponds to */ + int frame; + /* Distort */ + int distort; + /* x,y are upper left */ + float x, y, w, h, mix; + /* Indicates which values are fixed */ + int f[ 5 ]; +}; + +struct mlt_geometry_s +{ + void *local; +}; + +/* Create a new geometry structure */ +extern mlt_geometry mlt_geometry_init( ); +/* Parse the geometry specification for a given length and normalised width/height (-1 for default) */ +extern int mlt_geometry_parse( mlt_geometry self, char *data, int length, int nw, int nh ); +/* Conditionally refresh the geometry if it's modified */ +extern int mlt_geometry_refresh( mlt_geometry self, char *data, int length, int nw, int nh ); +/* Get and set the length */ +extern int mlt_geometry_get_length( mlt_geometry self ); +extern void mlt_geometry_set_length( mlt_geometry self, int length ); +/* Parse an item - doesn't affect the geometry itself but uses current information for evaluation */ +/* (item->frame should be specified if not included in the data itself) */ +extern int mlt_geometry_parse_item( mlt_geometry self, mlt_geometry_item item, char *data ); +/* Fetch a geometry item for an absolute position */ +extern int mlt_geometry_fetch( mlt_geometry self, mlt_geometry_item item, float position ); +/* Specify a geometry item at an absolute position */ +extern int mlt_geometry_insert( mlt_geometry self, mlt_geometry_item item ); +/* Remove the key at the specified position */ +extern int mlt_geometry_remove( mlt_geometry self, int position ); +/* Get the key at the position or the next following */ +extern int mlt_geometry_next_key( mlt_geometry self, mlt_geometry_item item, int position ); +extern int mlt_geometry_prev_key( mlt_geometry self, mlt_geometry_item item, int position ); +/* Serialise the current geometry */ +extern char *mlt_geometry_serialise_cut( mlt_geometry self, int in, int out ); +extern char *mlt_geometry_serialise( mlt_geometry self ); +/* Close the geometry */ +extern void mlt_geometry_close( mlt_geometry self ); + +#endif + diff --git a/src/framework/mlt_multitrack.c b/src/framework/mlt_multitrack.c new file mode 100644 index 0000000..1a7bcd2 --- /dev/null +++ b/src/framework/mlt_multitrack.c @@ -0,0 +1,436 @@ +/* + * mlt_multitrack.c -- multitrack service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_multitrack.h" +#include "mlt_playlist.h" +#include "mlt_frame.h" + +#include +#include + +/** Forward reference. +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); + +/** Constructor. +*/ + +mlt_multitrack mlt_multitrack_init( ) +{ + // Allocate the multitrack object + mlt_multitrack this = calloc( sizeof( struct mlt_multitrack_s ), 1 ); + + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( this ); + producer->get_frame = producer_get_frame; + mlt_properties_set_data( properties, "multitrack", this, 0, NULL, NULL ); + mlt_properties_set( properties, "log_id", "multitrack" ); + mlt_properties_set( properties, "resource", "" ); + mlt_properties_set_int( properties, "in", 0 ); + mlt_properties_set_int( properties, "out", -1 ); + mlt_properties_set_int( properties, "length", 0 ); + producer->close = ( mlt_destructor )mlt_multitrack_close; + } + else + { + free( this ); + this = NULL; + } + } + + return this; +} + +/** Get the producer associated to this multitrack. +*/ + +mlt_producer mlt_multitrack_producer( mlt_multitrack this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the service associated this multitrack. +*/ + +mlt_service mlt_multitrack_service( mlt_multitrack this ) +{ + return MLT_MULTITRACK_SERVICE( this ); +} + +/** Get the properties associated this multitrack. +*/ + +mlt_properties mlt_multitrack_properties( mlt_multitrack this ) +{ + return MLT_MULTITRACK_PROPERTIES( this ); +} + +/** Initialise position related information. +*/ + +void mlt_multitrack_refresh( mlt_multitrack this ) +{ + int i = 0; + + // Obtain the properties of this multitrack + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( this ); + + // We need to ensure that the multitrack reports the longest track as its length + mlt_position length = 0; + + // Obtain stats on all connected services + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer from this index + mlt_track track = this->list[ i ]; + mlt_producer producer = track->producer; + + // If it's allocated then, update our stats + if ( producer != NULL ) + { + // If we have more than 1 track, we must be in continue mode + if ( this->count > 1 ) + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "continue" ); + + // Determine the longest length + //if ( !mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( producer ), "hide" ) ) + length = mlt_producer_get_playtime( producer ) > length ? mlt_producer_get_playtime( producer ) : length; + } + } + + // Update multitrack properties now - we'll not destroy the in point here + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "length", length ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", length - 1 ); +} + +/** Listener for producers on the playlist. +*/ + +static void mlt_multitrack_listener( mlt_producer producer, mlt_multitrack this ) +{ + mlt_multitrack_refresh( this ); +} + +/** Connect a producer to a given track. + + Note that any producer can be connected here, but see special case treatment + of playlist in clip point determination below. +*/ + +int mlt_multitrack_connect( mlt_multitrack this, mlt_producer producer, int track ) +{ + // Connect to the producer to ourselves at the specified track + int result = mlt_service_connect_producer( MLT_MULTITRACK_SERVICE( this ), MLT_PRODUCER_SERVICE( producer ), track ); + + if ( result == 0 ) + { + // Resize the producer list if need be + if ( track >= this->size ) + { + int i; + this->list = realloc( this->list, ( track + 10 ) * sizeof( mlt_track ) ); + for ( i = this->size; i < track + 10; i ++ ) + this->list[ i ] = NULL; + this->size = track + 10; + } + + if ( this->list[ track ] != NULL ) + { + mlt_event_close( this->list[ track ]->event ); + mlt_producer_close( this->list[ track ]->producer ); + } + else + { + this->list[ track ] = malloc( sizeof( struct mlt_track_s ) ); + } + + // Assign the track in our list here + this->list[ track ]->producer = producer; + this->list[ track ]->event = mlt_events_listen( MLT_PRODUCER_PROPERTIES( producer ), this, + "producer-changed", ( mlt_listener )mlt_multitrack_listener ); + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + mlt_event_inc_ref( this->list[ track ]->event ); + + // Increment the track count if need be + if ( track >= this->count ) + this->count = track + 1; + + // Refresh our stats + mlt_multitrack_refresh( this ); + } + + return result; +} + +/** Get the number of tracks. +*/ + +int mlt_multitrack_count( mlt_multitrack this ) +{ + return this->count; +} + +/** Get an individual track as a producer. +*/ + +mlt_producer mlt_multitrack_track( mlt_multitrack this, int track ) +{ + mlt_producer producer = NULL; + + if ( this->list != NULL && track < this->count ) + producer = this->list[ track ]->producer; + + return producer; +} + +static int position_compare( const void *p1, const void *p2 ) +{ + return *( mlt_position * )p1 - *( mlt_position * )p2; +} + +static int add_unique( mlt_position *array, int size, mlt_position position ) +{ + int i = 0; + for ( i = 0; i < size; i ++ ) + if ( array[ i ] == position ) + break; + if ( i == size ) + array[ size ++ ] = position; + return size; +} + +/** Determine the clip point. + + Special case here: a 'producer' has no concept of multiple clips - only the + playlist and multitrack producers have clip functionality. Further to that a + multitrack determines clip information from any connected tracks that happen + to be playlists. + + Additionally, it must locate clips in the correct order, for example, consider + the following track arrangement: + + playlist1 |0.0 |b0.0 |0.1 |0.1 |0.2 | + playlist2 |b1.0 |1.0 |b1.1 |1.1 | + + Note - b clips represent blanks. They are also reported as clip positions. + + When extracting clip positions from these playlists, we should get a sequence of: + + 0.0, 1.0, b0.0, 0.1, b1.1, 1.1, 0.1, 0.2, [out of playlist2], [out of playlist1] +*/ + +mlt_position mlt_multitrack_clip( mlt_multitrack this, mlt_whence whence, int index ) +{ + mlt_position position = 0; + int i = 0; + int j = 0; + mlt_position *map = malloc( 1000 * sizeof( mlt_position ) ); + int count = 0; + + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer for this track + mlt_producer producer = this->list[ i ]->producer; + + // If it's assigned and not a hidden track + if ( producer != NULL ) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Determine if it's a playlist + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + + // Special case consideration of playlists + if ( playlist != NULL ) + { + for ( j = 0; j < mlt_playlist_count( playlist ); j ++ ) + count = add_unique( map, count, mlt_playlist_clip( playlist, mlt_whence_relative_start, j ) ); + count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 ); + } + else + { + count = add_unique( map, count, 0 ); + count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 ); + } + } + } + + // Now sort the map + qsort( map, count, sizeof( mlt_position ), position_compare ); + + // Now locate the requested index + switch( whence ) + { + case mlt_whence_relative_start: + if ( index < count ) + position = map[ index ]; + else + position = map[ count - 1 ]; + break; + + case mlt_whence_relative_current: + position = mlt_producer_position( MLT_MULTITRACK_PRODUCER( this ) ); + for ( i = 0; i < count - 2; i ++ ) + if ( position >= map[ i ] && position < map[ i + 1 ] ) + break; + index += i; + if ( index >= 0 && index < count ) + position = map[ index ]; + else if ( index < 0 ) + position = map[ 0 ]; + else + position = map[ count - 1 ]; + break; + + case mlt_whence_relative_end: + if ( index < count ) + position = map[ count - index - 1 ]; + else + position = map[ 0 ]; + break; + } + + // Free the map + free( map ); + + return position; +} + +/** Get frame method. + + Special case here: The multitrack must be used in a conjunction with a downstream + tractor-type service, ie: + + Producer1 \ + Producer2 - multitrack - { filters/transitions } - tractor - consumer + Producer3 / + + The get_frame of a tractor pulls frames from it's connected service on all tracks and + will terminate as soon as it receives a test card with a last_track property. The + important case here is that the mulitrack does not move to the next frame until all + tracks have been pulled. + + Reasoning: In order to seek on a network such as above, the multitrack needs to ensure + that all producers are positioned on the same frame. It uses the 'last track' logic + to determine when to move to the next frame. + + Flaw: if a transition is configured to read from a b-track which happens to trigger + the last frame logic (ie: it's configured incorrectly), then things are going to go + out of sync. + + See playlist logic too. +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ) +{ + // Get the mutiltrack object + mlt_multitrack this = parent->child; + + // Check if we have a track for this index + if ( index < this->count && this->list[ index ] != NULL ) + { + // Get the producer for this track + mlt_producer producer = this->list[ index ]->producer; + + // Get the track hide property + int hide = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( producer ) ), "hide" ); + + // Obtain the current position + mlt_position position = mlt_producer_frame( parent ); + + // Get the parent properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Get the speed + double speed = mlt_properties_get_double( producer_properties, "_speed" ); + + // Make sure we're at the same point + mlt_producer_seek( producer, position ); + + // Get the frame from the producer + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), frame, 0 ); + + // Indicate speed of this producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + mlt_properties_set_double( properties, "_speed", speed ); + mlt_properties_set_position( properties, "_position", position ); + mlt_properties_set_int( properties, "hide", hide ); + } + else + { + // Generate a test frame + *frame = mlt_frame_init( ); + + // Update position on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( parent ) ); + + // Move on to the next frame + if ( index >= this->count ) + { + // Let tractor know if we've reached the end + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "last_track", 1 ); + + // Move to the next frame + mlt_producer_prepare_next( parent ); + } + } + + return 0; +} + +/** Close this instance. +*/ + +void mlt_multitrack_close( mlt_multitrack this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_MULTITRACK_PROPERTIES( this ) ) <= 0 ) + { + int i = 0; + for ( i = 0; i < this->count; i ++ ) + { + if ( this->list[ i ] != NULL ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + free( this->list[ i ] ); + } + } + + // Close the producer + this->parent.close = NULL; + mlt_producer_close( &this->parent ); + + // Free the list + free( this->list ); + + // Free the object + free( this ); + } +} diff --git a/src/framework/mlt_multitrack.h b/src/framework/mlt_multitrack.h new file mode 100644 index 0000000..95a5e04 --- /dev/null +++ b/src/framework/mlt_multitrack.h @@ -0,0 +1,65 @@ +/* + * mlt_multitrack.h -- multitrack service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_MULITRACK_H_ +#define _MLT_MULITRACK_H_ + +#include "mlt_producer.h" + +/** Private definition. +*/ + +struct mlt_track_s +{ + mlt_producer producer; + mlt_event event; +}; + +typedef struct mlt_track_s *mlt_track; + +struct mlt_multitrack_s +{ + /* We're extending producer here */ + struct mlt_producer_s parent; + mlt_track *list; + int size; + int count; +}; + +/** Public final methods +*/ + +#define MLT_MULTITRACK_PRODUCER( multitrack ) ( &( multitrack )->parent ) +#define MLT_MULTITRACK_SERVICE( multitrack ) MLT_PRODUCER_SERVICE( MLT_MULTITRACK_PRODUCER( multitrack ) ) +#define MLT_MULTITRACK_PROPERTIES( multitrack ) MLT_SERVICE_PROPERTIES( MLT_MULTITRACK_SERVICE( multitrack ) ) + +extern mlt_multitrack mlt_multitrack_init( ); +extern mlt_producer mlt_multitrack_producer( mlt_multitrack self ); +extern mlt_service mlt_multitrack_service( mlt_multitrack self ); +extern mlt_properties mlt_multitrack_properties( mlt_multitrack self ); +extern int mlt_multitrack_connect( mlt_multitrack self, mlt_producer producer, int track ); +extern mlt_position mlt_multitrack_clip( mlt_multitrack self, mlt_whence whence, int index ); +extern void mlt_multitrack_close( mlt_multitrack self ); +extern int mlt_multitrack_count( mlt_multitrack self ); +extern void mlt_multitrack_refresh( mlt_multitrack self ); +extern mlt_producer mlt_multitrack_track( mlt_multitrack self, int track ); + +#endif + diff --git a/src/framework/mlt_parser.c b/src/framework/mlt_parser.c new file mode 100644 index 0000000..d269f0a --- /dev/null +++ b/src/framework/mlt_parser.c @@ -0,0 +1,243 @@ +/* + * mlt_parser.c -- service parsing functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt.h" +#include + +static int on_invalid( mlt_parser this, mlt_service object ) +{ + return 0; +} + +static int on_unknown( mlt_parser this, mlt_service object ) +{ + return 0; +} + +static int on_start_producer( mlt_parser this, mlt_producer object ) +{ + return 0; +} + +static int on_end_producer( mlt_parser this, mlt_producer object ) +{ + return 0; +} + +static int on_start_playlist( mlt_parser this, mlt_playlist object ) +{ + return 0; +} + +static int on_end_playlist( mlt_parser this, mlt_playlist object ) +{ + return 0; +} + +static int on_start_tractor( mlt_parser this, mlt_tractor object ) +{ + return 0; +} + +static int on_end_tractor( mlt_parser this, mlt_tractor object ) +{ + return 0; +} + +static int on_start_multitrack( mlt_parser this, mlt_multitrack object ) +{ + return 0; +} + +static int on_end_multitrack( mlt_parser this, mlt_multitrack object ) +{ + return 0; +} + +static int on_start_track( mlt_parser this ) +{ + return 0; +} + +static int on_end_track( mlt_parser this ) +{ + return 0; +} + +static int on_start_filter( mlt_parser this, mlt_filter object ) +{ + return 0; +} + +static int on_end_filter( mlt_parser this, mlt_filter object ) +{ + return 0; +} + +static int on_start_transition( mlt_parser this, mlt_transition object ) +{ + return 0; +} + +static int on_end_transition( mlt_parser this, mlt_transition object ) +{ + return 0; +} + +mlt_parser mlt_parser_new( ) +{ + mlt_parser this = calloc( 1, sizeof( struct mlt_parser_s ) ); + if ( this != NULL && mlt_properties_init( &this->parent, this ) == 0 ) + { + this->on_invalid = on_invalid; + this->on_unknown = on_unknown; + this->on_start_producer = on_start_producer; + this->on_end_producer = on_end_producer; + this->on_start_playlist = on_start_playlist; + this->on_end_playlist = on_end_playlist; + this->on_start_tractor = on_start_tractor; + this->on_end_tractor = on_end_tractor; + this->on_start_multitrack = on_start_multitrack; + this->on_end_multitrack = on_end_multitrack; + this->on_start_track = on_start_track; + this->on_end_track = on_end_track; + this->on_start_filter = on_start_filter; + this->on_end_filter = on_end_filter; + this->on_start_transition = on_start_transition; + this->on_end_transition = on_end_transition; + } + return this; +} + +mlt_properties mlt_parser_properties( mlt_parser this ) +{ + return &this->parent; +} + +int mlt_parser_start( mlt_parser this, mlt_service object ) +{ + int error = 0; + mlt_service_type type = mlt_service_identify( object ); + switch( type ) + { + case invalid_type: + error = this->on_invalid( this, object ); + break; + case unknown_type: + error = this->on_unknown( this, object ); + break; + case producer_type: + if ( mlt_producer_is_cut( ( mlt_producer )object ) ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_cut_parent( ( mlt_producer )object ) ); + error = this->on_start_producer( this, ( mlt_producer )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_producer( this, ( mlt_producer )object ); + break; + case playlist_type: + error = this->on_start_playlist( this, ( mlt_playlist )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && i < mlt_playlist_count( ( mlt_playlist )object ) ) + mlt_parser_start( this, ( mlt_service )mlt_playlist_get_clip( ( mlt_playlist )object, i ++ ) ); + i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_playlist( this, ( mlt_playlist )object ); + break; + case tractor_type: + error = this->on_start_tractor( this, ( mlt_tractor )object ); + if ( error == 0 ) + { + int i = 0; + mlt_service next = mlt_service_producer( object ); + mlt_parser_start( this, ( mlt_service )mlt_tractor_multitrack( ( mlt_tractor )object ) ); + while ( next != ( mlt_service )mlt_tractor_multitrack( ( mlt_tractor )object ) ) + { + mlt_parser_start( this, next ); + next = mlt_service_producer( next ); + } + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_tractor( this, ( mlt_tractor )object ); + break; + case multitrack_type: + error = this->on_start_multitrack( this, ( mlt_multitrack )object ); + if ( error == 0 ) + { + int i = 0; + while ( i < mlt_multitrack_count( ( mlt_multitrack )object ) ) + { + this->on_start_track( this ); + mlt_parser_start( this, ( mlt_service )mlt_multitrack_track( ( mlt_multitrack )object , i ++ ) ); + this->on_end_track( this ); + } + i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_multitrack( this, ( mlt_multitrack )object ); + break; + case filter_type: + error = this->on_start_filter( this, ( mlt_filter )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_filter( this, ( mlt_filter )object ); + break; + case transition_type: + error = this->on_start_transition( this, ( mlt_transition )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_transition( this, ( mlt_transition )object ); + break; + case field_type: + break; + case consumer_type: + break; + } + return error; +} + +void mlt_parser_close( mlt_parser this ) +{ + if ( this != NULL ) + { + mlt_properties_close( &this->parent ); + free( this ); + } +} + + diff --git a/src/framework/mlt_parser.h b/src/framework/mlt_parser.h new file mode 100644 index 0000000..bdedda5 --- /dev/null +++ b/src/framework/mlt_parser.h @@ -0,0 +1,52 @@ +/* + * mlt_parser.h -- service parsing functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PARSER_H_ +#define _MLT_PARSER_H_ + +#include "mlt_types.h" + +struct mlt_parser_s +{ + struct mlt_properties_s parent; + int ( *on_invalid )( mlt_parser self, mlt_service object ); + int ( *on_unknown )( mlt_parser self, mlt_service object ); + int ( *on_start_producer )( mlt_parser self, mlt_producer object ); + int ( *on_end_producer )( mlt_parser self, mlt_producer object ); + int ( *on_start_playlist )( mlt_parser self, mlt_playlist object ); + int ( *on_end_playlist )( mlt_parser self, mlt_playlist object ); + int ( *on_start_tractor )( mlt_parser self, mlt_tractor object ); + int ( *on_end_tractor )( mlt_parser self, mlt_tractor object ); + int ( *on_start_multitrack )( mlt_parser self, mlt_multitrack object ); + int ( *on_end_multitrack )( mlt_parser self, mlt_multitrack object ); + int ( *on_start_track )( mlt_parser self ); + int ( *on_end_track )( mlt_parser self ); + int ( *on_start_filter )( mlt_parser self, mlt_filter object ); + int ( *on_end_filter )( mlt_parser self, mlt_filter object ); + int ( *on_start_transition )( mlt_parser self, mlt_transition object ); + int ( *on_end_transition )( mlt_parser self, mlt_transition object ); +}; + +extern mlt_parser mlt_parser_new( ); +extern mlt_properties mlt_parser_properties( mlt_parser self ); +extern int mlt_parser_start( mlt_parser self, mlt_service object ); +extern void mlt_parser_close( mlt_parser self ); + +#endif diff --git a/src/framework/mlt_playlist.c b/src/framework/mlt_playlist.c new file mode 100644 index 0000000..20aee7a --- /dev/null +++ b/src/framework/mlt_playlist.c @@ -0,0 +1,1500 @@ +/* + * mlt_playlist.c -- playlist service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_playlist.h" +#include "mlt_tractor.h" +#include "mlt_multitrack.h" +#include "mlt_field.h" +#include "mlt_frame.h" +#include "mlt_transition.h" + +#include +#include +#include + +/** Virtual playlist entry. +*/ + +struct playlist_entry_s +{ + mlt_producer producer; + mlt_position frame_in; + mlt_position frame_out; + mlt_position frame_count; + int repeat; + mlt_position producer_length; + mlt_event event; + int preservation_hack; +}; + +/** Forward declarations +*/ + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ); +static int mlt_playlist_unmix( mlt_playlist this, int clip ); +static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out ); + +/** Constructor. +*/ + +mlt_playlist mlt_playlist_init( ) +{ + mlt_playlist this = calloc( sizeof( struct mlt_playlist_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + + // Construct the producer + mlt_producer_init( producer, this ); + + // Override the producer get_frame + producer->get_frame = producer_get_frame; + + // Define the destructor + producer->close = ( mlt_destructor )mlt_playlist_close; + producer->close_object = this; + + // Initialise blank + mlt_producer_init( &this->blank, NULL ); + mlt_properties_set( MLT_PRODUCER_PROPERTIES( &this->blank ), "mlt_service", "blank" ); + mlt_properties_set( MLT_PRODUCER_PROPERTIES( &this->blank ), "resource", "blank" ); + + // Indicate that this producer is a playlist + mlt_properties_set_data( MLT_PLAYLIST_PROPERTIES( this ), "playlist", this, 0, NULL, NULL ); + + // Specify the eof condition + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "eof", "pause" ); + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "resource", "" ); + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "mlt_type", "mlt_producer" ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "in", 0 ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "out", -1 ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "length", 0 ); + + this->size = 10; + this->list = malloc( this->size * sizeof( playlist_entry * ) ); + } + + return this; +} + +/** Get the producer associated to this playlist. +*/ + +mlt_producer mlt_playlist_producer( mlt_playlist this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the service associated to this playlist. +*/ + +mlt_service mlt_playlist_service( mlt_playlist this ) +{ + return MLT_PRODUCER_SERVICE( &this->parent ); +} + +/** Get the propertues associated to this playlist. +*/ + +mlt_properties mlt_playlist_properties( mlt_playlist this ) +{ + return MLT_PRODUCER_PROPERTIES( &this->parent ); +} + +/** Refresh the playlist after a clip has been changed. +*/ + +static int mlt_playlist_virtual_refresh( mlt_playlist this ) +{ + // Obtain the properties + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int i = 0; + mlt_position frame_count = 0; + + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer + mlt_producer producer = this->list[ i ]->producer; + int current_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1; + + // Check if the length of the producer has changed + if ( this->list[ i ]->frame_in != mlt_producer_get_in( producer ) || + this->list[ i ]->frame_out != mlt_producer_get_out( producer ) ) + { + // This clip should be removed... + if ( current_length < 1 ) + { + this->list[ i ]->frame_in = 0; + this->list[ i ]->frame_out = -1; + this->list[ i ]->frame_count = 0; + } + else + { + this->list[ i ]->frame_in = mlt_producer_get_in( producer ); + this->list[ i ]->frame_out = mlt_producer_get_out( producer ); + this->list[ i ]->frame_count = current_length; + } + + // Update the producer_length + this->list[ i ]->producer_length = current_length; + } + + // Calculate the frame_count + this->list[ i ]->frame_count = ( this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1 ) * this->list[ i ]->repeat; + + // Update the frame_count for this clip + frame_count += this->list[ i ]->frame_count; + } + + // Refresh all properties + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "length", frame_count ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", frame_count - 1 ); + + return 0; +} + +/** Listener for producers on the playlist. +*/ + +static void mlt_playlist_listener( mlt_producer producer, mlt_playlist this ) +{ + mlt_playlist_virtual_refresh( this ); +} + +/** Append to the virtual playlist. +*/ + +static int mlt_playlist_virtual_append( mlt_playlist this, mlt_producer source, mlt_position in, mlt_position out ) +{ + mlt_producer producer = NULL; + mlt_properties properties = NULL; + mlt_properties parent = NULL; + + // If we have a cut, then use the in/out points from the cut + if ( mlt_producer_is_blank( source ) ) + { + // Make sure the blank is long enough to accomodate the length specified + if ( out - in + 1 > mlt_producer_get_length( &this->blank ) ) + { + mlt_properties blank_props = MLT_PRODUCER_PROPERTIES( &this->blank ); + mlt_events_block( blank_props, blank_props ); + mlt_producer_set_in_and_out( &this->blank, in, out ); + mlt_events_unblock( blank_props, blank_props ); + } + + // Now make sure the cut comes from this this->blank + if ( source == NULL ) + { + producer = mlt_producer_cut( &this->blank, in, out ); + } + else if ( !mlt_producer_is_cut( source ) || mlt_producer_cut_parent( source ) != &this->blank ) + { + producer = mlt_producer_cut( &this->blank, in, out ); + } + else + { + producer = source; + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + } + + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + else if ( mlt_producer_is_cut( source ) ) + { + producer = source; + if ( in == -1 ) + in = mlt_producer_get_in( producer ); + if ( out == -1 || out > mlt_producer_get_out( producer ) ) + out = mlt_producer_get_out( producer ); + properties = MLT_PRODUCER_PROPERTIES( producer ); + mlt_properties_inc_ref( properties ); + } + else + { + producer = mlt_producer_cut( source, in, out ); + if ( in == -1 || in < mlt_producer_get_in( producer ) ) + in = mlt_producer_get_in( producer ); + if ( out == -1 || out > mlt_producer_get_out( producer ) ) + out = mlt_producer_get_out( producer ); + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + + // Fetch the cuts parent properties + parent = MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( producer ) ); + + // Remove fezzik normalisers for fx cuts + if ( mlt_properties_get_int( parent, "meta.fx_cut" ) ) + { + mlt_service service = MLT_PRODUCER_SERVICE( mlt_producer_cut_parent( producer ) ); + mlt_filter filter = mlt_service_filter( service, 0 ); + while ( filter != NULL && mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "_fezzik" ) ) + { + mlt_service_detach( service, filter ); + filter = mlt_service_filter( service, 0 ); + } + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "meta.fx_cut", 1 ); + } + + // Check that we have room + if ( this->count >= this->size ) + { + int i; + this->list = realloc( this->list, ( this->size + 10 ) * sizeof( playlist_entry * ) ); + for ( i = this->size; i < this->size + 10; i ++ ) this->list[ i ] = NULL; + this->size += 10; + } + + // Create the entry + this->list[ this->count ] = calloc( sizeof( playlist_entry ), 1 ); + if ( this->list[ this->count ] != NULL ) + { + this->list[ this->count ]->producer = producer; + this->list[ this->count ]->frame_in = in; + this->list[ this->count ]->frame_out = out; + this->list[ this->count ]->frame_count = out - in + 1; + this->list[ this->count ]->repeat = 1; + this->list[ this->count ]->producer_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1; + this->list[ this->count ]->event = mlt_events_listen( parent, this, "producer-changed", ( mlt_listener )mlt_playlist_listener ); + mlt_event_inc_ref( this->list[ this->count ]->event ); + mlt_properties_set( properties, "eof", "pause" ); + mlt_producer_set_speed( producer, 0 ); + this->count ++; + } + + return mlt_playlist_virtual_refresh( this ); +} + +static mlt_producer mlt_playlist_locate( mlt_playlist this, mlt_position *position, int *clip, int *total ) +{ + // Default producer to NULL + mlt_producer producer = NULL; + + // Loop for each producer until found + for ( *clip = 0; *clip < this->count; *clip += 1 ) + { + // Increment the total + *total += this->list[ *clip ]->frame_count; + + // Check if the position indicates that we have found the clip + // Note that 0 length clips get skipped automatically + if ( *position < this->list[ *clip ]->frame_count ) + { + // Found it, now break + producer = this->list[ *clip ]->producer; + break; + } + else + { + // Decrement position by length of this entry + *position -= this->list[ *clip ]->frame_count; + } + } + + return producer; +} + +/** Seek in the virtual playlist. +*/ + +static mlt_service mlt_playlist_virtual_seek( mlt_playlist this, int *progressive ) +{ + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Keep the original position since we change it while iterating through the list + mlt_position original = position; + + // Clip index and total + int i = 0; + int total = 0; + + // Locate the producer for the position + mlt_producer producer = mlt_playlist_locate( this, &position, &i, &total ); + + // Get the properties + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + // Get the eof handling + char *eof = mlt_properties_get( properties, "eof" ); + + // Seek in real producer to relative position + if ( producer != NULL ) + { + int count = this->list[ i ]->frame_count / this->list[ i ]->repeat; + *progressive = count == 1; + mlt_producer_seek( producer, (int)position % count ); + } + else if ( !strcmp( eof, "pause" ) && total > 0 ) + { + playlist_entry *entry = this->list[ this->count - 1 ]; + int count = entry->frame_count / entry->repeat; + mlt_producer this_producer = MLT_PLAYLIST_PRODUCER( this ); + mlt_producer_seek( this_producer, original - 1 ); + producer = entry->producer; + mlt_producer_seek( producer, (int)entry->frame_out % count ); + mlt_producer_set_speed( this_producer, 0 ); + mlt_producer_set_speed( producer, 0 ); + *progressive = count == 1; + } + else if ( !strcmp( eof, "loop" ) && total > 0 ) + { + playlist_entry *entry = this->list[ 0 ]; + mlt_producer this_producer = MLT_PLAYLIST_PRODUCER( this ); + mlt_producer_seek( this_producer, 0 ); + producer = entry->producer; + mlt_producer_seek( producer, 0 ); + } + else + { + producer = &this->blank; + } + + return MLT_PRODUCER_SERVICE( producer ); +} + +/** Invoked when a producer indicates that it has prematurely reached its end. +*/ + +static mlt_producer mlt_playlist_virtual_set_out( mlt_playlist this ) +{ + // Default producer to blank + mlt_producer producer = &this->blank; + + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Loop through the virtual playlist + int i = 0; + + for ( i = 0; i < this->count; i ++ ) + { + if ( position < this->list[ i ]->frame_count ) + { + // Found it, now break + producer = this->list[ i ]->producer; + break; + } + else + { + // Decrement position by length of this entry + position -= this->list[ i ]->frame_count; + } + } + + // Seek in real producer to relative position + if ( i < this->count && this->list[ i ]->frame_out != position ) + { + // Update the frame_count for the changed clip (hmmm) + this->list[ i ]->frame_out = position; + this->list[ i ]->frame_count = this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1; + + // Refresh the playlist + mlt_playlist_virtual_refresh( this ); + } + + return producer; +} + +/** Obtain the current clips index. +*/ + +int mlt_playlist_current_clip( mlt_playlist this ) +{ + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Loop through the virtual playlist + int i = 0; + + for ( i = 0; i < this->count; i ++ ) + { + if ( position < this->list[ i ]->frame_count ) + { + // Found it, now break + break; + } + else + { + // Decrement position by length of this entry + position -= this->list[ i ]->frame_count; + } + } + + return i; +} + +/** Obtain the current clips producer. +*/ + +mlt_producer mlt_playlist_current( mlt_playlist this ) +{ + int i = mlt_playlist_current_clip( this ); + if ( i < this->count ) + return this->list[ i ]->producer; + else + return &this->blank; +} + +/** Get the position which corresponds to the start of the next clip. +*/ + +mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index ) +{ + mlt_position position = 0; + int absolute_clip = index; + int i = 0; + + // Determine the absolute clip + switch ( whence ) + { + case mlt_whence_relative_start: + absolute_clip = index; + break; + + case mlt_whence_relative_current: + absolute_clip = mlt_playlist_current_clip( this ) + index; + break; + + case mlt_whence_relative_end: + absolute_clip = this->count - index; + break; + } + + // Check that we're in a valid range + if ( absolute_clip < 0 ) + absolute_clip = 0; + else if ( absolute_clip > this->count ) + absolute_clip = this->count; + + // Now determine the position + for ( i = 0; i < absolute_clip; i ++ ) + position += this->list[ i ]->frame_count; + + return position; +} + +/** Get all the info about the clip specified. +*/ + +int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index ) +{ + int error = index < 0 || index >= this->count; + memset( info, 0, sizeof( mlt_playlist_clip_info ) ); + if ( !error ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ index ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + info->clip = index; + info->producer = producer; + info->cut = this->list[ index ]->producer; + info->start = mlt_playlist_clip( this, mlt_whence_relative_start, index ); + info->resource = mlt_properties_get( properties, "resource" ); + info->frame_in = this->list[ index ]->frame_in; + info->frame_out = this->list[ index ]->frame_out; + info->frame_count = this->list[ index ]->frame_count; + info->repeat = this->list[ index ]->repeat; + info->length = mlt_producer_get_length( producer ); + info->fps = mlt_producer_get_fps( producer ); + } + + return error; +} + +/** Get number of clips in the playlist. +*/ + +int mlt_playlist_count( mlt_playlist this ) +{ + return this->count; +} + +/** Clear the playlist. +*/ + +int mlt_playlist_clear( mlt_playlist this ) +{ + int i; + for ( i = 0; i < this->count; i ++ ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + } + this->count = 0; + return mlt_playlist_virtual_refresh( this ); +} + +/** Append a producer to the playlist. +*/ + +int mlt_playlist_append( mlt_playlist this, mlt_producer producer ) +{ + // Append to virtual list + return mlt_playlist_virtual_append( this, producer, 0, mlt_producer_get_playtime( producer ) - 1 ); +} + +/** Append a producer to the playlist with in/out points. +*/ + +int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out ) +{ + // Append to virtual list + if ( in != -1 && out != -1 ) + return mlt_playlist_virtual_append( this, producer, in, out ); + else + return mlt_playlist_append( this, producer ); +} + +/** Append a blank to the playlist of a given length. +*/ + +int mlt_playlist_blank( mlt_playlist this, mlt_position length ) +{ + // Append to the virtual list + return mlt_playlist_virtual_append( this, &this->blank, 0, length ); +} + +/** Insert a producer into the playlist. +*/ + +int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out ) +{ + // Append to end + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_append_io( this, producer, in, out ); + + // Move to the position specified + mlt_playlist_move( this, this->count - 1, where ); + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + + return mlt_playlist_virtual_refresh( this ); +} + +/** Remove an entry in the playlist. +*/ + +int mlt_playlist_remove( mlt_playlist this, int where ) +{ + int error = where < 0 || where >= this->count; + if ( error == 0 && mlt_playlist_unmix( this, where ) != 0 ) + { + // We need to know the current clip and the position within the playlist + int current = mlt_playlist_current_clip( this ); + mlt_position position = mlt_producer_position( MLT_PLAYLIST_PRODUCER( this ) ); + + // We need all the details about the clip we're removing + mlt_playlist_clip_info where_info; + playlist_entry *entry = this->list[ where ]; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( entry->producer ); + + // Loop variable + int i = 0; + + // Get the clip info + mlt_playlist_get_clip_info( this, &where_info, where ); + + // Make sure the clip to be removed is valid and correct if necessary + if ( where < 0 ) + where = 0; + if ( where >= this->count ) + where = this->count - 1; + + // Reorganise the list + for ( i = where + 1; i < this->count; i ++ ) + this->list[ i - 1 ] = this->list[ i ]; + this->count --; + + if ( entry->preservation_hack == 0 ) + { + // Decouple from mix_in/out if necessary + if ( mlt_properties_get_data( properties, "mix_in", NULL ) != NULL ) + { + mlt_properties mix = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_properties_set_data( mix, "mix_out", NULL, 0, NULL, NULL ); + } + if ( mlt_properties_get_data( properties, "mix_out", NULL ) != NULL ) + { + mlt_properties mix = mlt_properties_get_data( properties, "mix_out", NULL ); + mlt_properties_set_data( mix, "mix_in", NULL, 0, NULL, NULL ); + } + + if ( mlt_properties_ref_count( MLT_PRODUCER_PROPERTIES( entry->producer ) ) == 1 ) + mlt_producer_clear( entry->producer ); + } + + // Close the producer associated to the clip info + mlt_event_close( entry->event ); + mlt_producer_close( entry->producer ); + + // Correct position + if ( where == current ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), where_info.start ); + else if ( where < current && this->count > 0 ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), position - where_info.frame_count ); + else if ( this->count == 0 ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), 0 ); + + // Free the entry + free( entry ); + + // Refresh the playlist + mlt_playlist_virtual_refresh( this ); + } + + return error; +} + +/** Move an entry in the playlist. +*/ + +int mlt_playlist_move( mlt_playlist this, int src, int dest ) +{ + int i; + + /* We need to ensure that the requested indexes are valid and correct it as necessary */ + if ( src < 0 ) + src = 0; + if ( src >= this->count ) + src = this->count - 1; + + if ( dest < 0 ) + dest = 0; + if ( dest >= this->count ) + dest = this->count - 1; + + if ( src != dest && this->count > 1 ) + { + int current = mlt_playlist_current_clip( this ); + mlt_position position = mlt_producer_position( MLT_PLAYLIST_PRODUCER( this ) ); + playlist_entry *src_entry = NULL; + + // We need all the details about the current clip + mlt_playlist_clip_info current_info; + + mlt_playlist_get_clip_info( this, ¤t_info, current ); + position -= current_info.start; + + if ( current == src ) + current = dest; + else if ( current > src && current < dest ) + current ++; + else if ( current == dest ) + current = src; + + src_entry = this->list[ src ]; + if ( src > dest ) + { + for ( i = src; i > dest; i -- ) + this->list[ i ] = this->list[ i - 1 ]; + } + else + { + for ( i = src; i < dest; i ++ ) + this->list[ i ] = this->list[ i + 1 ]; + } + this->list[ dest ] = src_entry; + + mlt_playlist_get_clip_info( this, ¤t_info, current ); + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), current_info.start + position ); + mlt_playlist_virtual_refresh( this ); + } + + return 0; +} + +/** Repeat the specified clip n times. +*/ + +int mlt_playlist_repeat_clip( mlt_playlist this, int clip, int repeat ) +{ + int error = repeat < 1 || clip < 0 || clip >= this->count; + if ( error == 0 ) + { + playlist_entry *entry = this->list[ clip ]; + entry->repeat = repeat; + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Resize the specified clip. +*/ + +int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 && mlt_playlist_resize_mix( this, clip, in, out ) != 0 ) + { + playlist_entry *entry = this->list[ clip ]; + mlt_producer producer = entry->producer; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + mlt_events_block( properties, properties ); + + if ( mlt_producer_is_blank( producer ) ) + { + // Make sure the blank is long enough to accomodate the length specified + if ( out - in + 1 > mlt_producer_get_length( &this->blank ) ) + { + mlt_properties blank_props = MLT_PRODUCER_PROPERTIES( &this->blank ); + mlt_properties_set_int( blank_props, "length", out - in + 1 ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "length", out - in + 1 ); + mlt_producer_set_in_and_out( &this->blank, 0, out - in ); + } + } + + if ( in <= -1 ) + in = 0; + if ( out <= -1 || out >= mlt_producer_get_length( producer ) ) + out = mlt_producer_get_length( producer ) - 1; + + if ( out < in ) + { + mlt_position t = in; + in = out; + out = t; + } + + mlt_producer_set_in_and_out( producer, in, out ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Split a clip on the playlist at the given position. +*/ + +int mlt_playlist_split( mlt_playlist this, int clip, mlt_position position ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 ) + { + playlist_entry *entry = this->list[ clip ]; + position = position < 0 ? entry->frame_count + position - 1 : position; + if ( position >= 0 && position < entry->frame_count - 1 ) + { + int in = entry->frame_in; + int out = entry->frame_out; + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_resize_clip( this, clip, in, in + position ); + if ( !mlt_producer_is_blank( entry->producer ) ) + { + int i = 0; + mlt_properties entry_properties = MLT_PRODUCER_PROPERTIES( entry->producer ); + mlt_producer split = mlt_producer_cut( entry->producer, in + position + 1, out ); + mlt_properties split_properties = MLT_PRODUCER_PROPERTIES( split ); + mlt_playlist_insert( this, split, clip + 1, 0, -1 ); + for ( i = 0; i < mlt_properties_count( entry_properties ); i ++ ) + { + char *name = mlt_properties_get_name( entry_properties, i ); + if ( name != NULL && !strncmp( name, "meta.", 5 ) ) + mlt_properties_set( split_properties, name, mlt_properties_get_value( entry_properties, i ) ); + } + mlt_producer_close( split ); + } + else + { + mlt_playlist_insert( this, &this->blank, clip + 1, 0, out - position - 1 ); + } + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + else + { + error = 1; + } + } + return error; +} + +/** Split the playlist at the absolute position. +*/ + +int mlt_playlist_split_at( mlt_playlist this, mlt_position position, int left ) +{ + int result = this == NULL ? -1 : 0; + if ( !result ) + { + if ( position >= 0 && position < mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ) ) + { + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + if ( left && position != info.start ) + mlt_playlist_split( this, clip, position - info.start - 1 ); + else if ( !left ) + mlt_playlist_split( this, clip, position - info.start ); + result = position; + } + else if ( position <= 0 ) + { + result = 0; + } + else + { + result = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); + } + } + return result; +} + +/** Join 1 or more consecutive clips. +*/ + +int mlt_playlist_join( mlt_playlist this, int clip, int count, int merge ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 ) + { + int i = clip; + mlt_playlist new_clip = mlt_playlist_init( ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + if ( clip + count >= this->count ) + count = this->count - clip - 1; + for ( i = 0; i <= count; i ++ ) + { + playlist_entry *entry = this->list[ clip ]; + mlt_playlist_append( new_clip, entry->producer ); + mlt_playlist_repeat_clip( new_clip, i, entry->repeat ); + entry->preservation_hack = 1; + mlt_playlist_remove( this, clip ); + } + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_insert( this, MLT_PLAYLIST_PRODUCER( new_clip ), clip, 0, -1 ); + mlt_playlist_close( new_clip ); + } + return error; +} + +/** Mix consecutive clips for a specified length and apply transition if specified. +*/ + +int mlt_playlist_mix( mlt_playlist this, int clip, int length, mlt_transition transition ) +{ + int error = ( clip < 0 || clip + 1 >= this->count ); + if ( error == 0 ) + { + playlist_entry *clip_a = this->list[ clip ]; + playlist_entry *clip_b = this->list[ clip + 1 ]; + mlt_producer track_a = NULL; + mlt_producer track_b = NULL; + mlt_tractor tractor = mlt_tractor_new( ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + // Check length is valid for both clips and resize if necessary. + int max_size = clip_a->frame_count > clip_b->frame_count ? clip_a->frame_count : clip_b->frame_count; + length = length > max_size ? max_size : length; + + // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches + if ( length != clip_a->frame_count ) + track_a = mlt_producer_cut( clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out ); + else + track_a = clip_a->producer; + + if ( length != clip_b->frame_count ) + track_b = mlt_producer_cut( clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1 ); + else + track_b = clip_b->producer; + + // Set the tracks on the tractor + mlt_tractor_set_track( tractor, track_a, 0 ); + mlt_tractor_set_track( tractor, track_b, 1 ); + + // Insert the mix object into the playlist + mlt_playlist_insert( this, MLT_TRACTOR_PRODUCER( tractor ), clip + 1, -1, -1 ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mlt_mix", tractor, 0, NULL, NULL ); + + // Attach the transition + if ( transition != NULL ) + { + mlt_field field = mlt_tractor_field( tractor ); + mlt_field_plant_transition( field, transition, 0, 1 ); + mlt_transition_set_in_and_out( transition, 0, length - 1 ); + } + + // Close our references to the tracks if we created new cuts above (the tracks can still be used here) + if ( track_a != clip_a->producer ) + mlt_producer_close( track_a ); + if ( track_b != clip_b->producer ) + mlt_producer_close( track_b ); + + // Check if we have anything left on the right hand clip + if ( track_b == clip_b->producer ) + { + clip_b->preservation_hack = 1; + mlt_playlist_remove( this, clip + 2 ); + } + else if ( clip_b->frame_out - clip_b->frame_in > length ) + { + mlt_playlist_resize_clip( this, clip + 2, clip_b->frame_in + length, clip_b->frame_out ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_b->producer ), "mix_in", tractor, 0, NULL, NULL ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_out", clip_b->producer, 0, NULL, NULL ); + } + else + { + mlt_producer_clear( clip_b->producer ); + mlt_playlist_remove( this, clip + 2 ); + } + + // Check if we have anything left on the left hand clip + if ( track_a == clip_a->producer ) + { + clip_a->preservation_hack = 1; + mlt_playlist_remove( this, clip ); + } + else if ( clip_a->frame_out - clip_a->frame_in > length ) + { + mlt_playlist_resize_clip( this, clip, clip_a->frame_in, clip_a->frame_out - length ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_a->producer ), "mix_out", tractor, 0, NULL, NULL ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_in", clip_a->producer, 0, NULL, NULL ); + } + else + { + mlt_producer_clear( clip_a->producer ); + mlt_playlist_remove( this, clip ); + } + + // Unblock and force a fire off of change events to listeners + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + mlt_tractor_close( tractor ); + } + return error; +} + +/** Add a transition to an existing mix. +*/ + +int mlt_playlist_mix_add( mlt_playlist this, int clip, mlt_transition transition ) +{ + mlt_producer producer = mlt_producer_cut_parent( mlt_playlist_get_clip( this, clip ) ); + mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES( producer ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + int error = transition == NULL || tractor == NULL; + if ( error == 0 ) + { + mlt_field field = mlt_tractor_field( tractor ); + mlt_field_plant_transition( field, transition, 0, 1 ); + mlt_transition_set_in_and_out( transition, 0, this->list[ clip ]->frame_count - 1 ); + } + return error; +} + +/** Return the clip at the clip index. +*/ + +mlt_producer mlt_playlist_get_clip( mlt_playlist this, int clip ) +{ + if ( clip >= 0 && clip < this->count ) + return this->list[ clip ]->producer; + return NULL; +} + +/** Return the clip at the specified position. +*/ + +mlt_producer mlt_playlist_get_clip_at( mlt_playlist this, mlt_position position ) +{ + int index = 0, total = 0; + return mlt_playlist_locate( this, &position, &index, &total ); +} + +/** Return the clip index of the specified position. +*/ + +int mlt_playlist_get_clip_index_at( mlt_playlist this, mlt_position position ) +{ + int index = 0, total = 0; + mlt_playlist_locate( this, &position, &index, &total ); + return index; +} + +/** Determine if the clip is a mix. +*/ + +int mlt_playlist_clip_is_mix( mlt_playlist this, int clip ) +{ + mlt_producer producer = mlt_producer_cut_parent( mlt_playlist_get_clip( this, clip ) ); + mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES( producer ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + return tractor != NULL; +} + +/** Remove a mixed clip - ensure that the cuts included in the mix find their way + back correctly on to the playlist. +*/ + +static int mlt_playlist_unmix( mlt_playlist this, int clip ) +{ + int error = ( clip < 0 || clip >= this->count ); + + // Ensure that the clip request is actually a mix + if ( error == 0 ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL || + this->list[ clip ]->preservation_hack; + } + + if ( error == 0 ) + { + playlist_entry *mix = this->list[ clip ]; + mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer ); + mlt_properties properties = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL ); + int length = mlt_producer_get_playtime( MLT_TRACTOR_PRODUCER( tractor ) ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + if ( clip_a != NULL ) + { + mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) + length ); + } + else + { + mlt_producer cut = mlt_tractor_get_track( tractor, 0 ); + mlt_playlist_insert( this, cut, clip, -1, -1 ); + clip ++; + } + + if ( clip_b != NULL ) + { + mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) - length, mlt_producer_get_out( clip_b ) ); + } + else + { + mlt_producer cut = mlt_tractor_get_track( tractor, 1 ); + mlt_playlist_insert( this, cut, clip + 1, -1, -1 ); + } + + mlt_properties_set_data( properties, "mlt_mix", NULL, 0, NULL, NULL ); + mlt_playlist_remove( this, clip ); + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out ) +{ + int error = ( clip < 0 || clip >= this->count ); + + // Ensure that the clip request is actually a mix + if ( error == 0 ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL; + } + + if ( error == 0 ) + { + playlist_entry *mix = this->list[ clip ]; + mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer ); + mlt_properties properties = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL ); + mlt_producer track_a = mlt_tractor_get_track( tractor, 0 ); + mlt_producer track_b = mlt_tractor_get_track( tractor, 1 ); + int length = out - in + 1; + int length_diff = length - mlt_producer_get_playtime( MLT_TRACTOR_PRODUCER( tractor ) ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + if ( clip_a != NULL ) + mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) - length_diff ); + + if ( clip_b != NULL ) + mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) + length_diff, mlt_producer_get_out( clip_b ) ); + + mlt_producer_set_in_and_out( track_a, mlt_producer_get_in( track_a ) - length_diff, mlt_producer_get_out( track_a ) ); + mlt_producer_set_in_and_out( track_b, mlt_producer_get_in( track_b ), mlt_producer_get_out( track_b ) + length_diff ); + mlt_producer_set_in_and_out( MLT_MULTITRACK_PRODUCER( mlt_tractor_multitrack( tractor ) ), in, out ); + mlt_producer_set_in_and_out( MLT_TRACTOR_PRODUCER( tractor ), in, out ); + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( mix->producer ), "length", out - in + 1 ); + mlt_producer_set_in_and_out( mix->producer, in, out ); + + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Consolodate adjacent blank producers. +*/ + +void mlt_playlist_consolidate_blanks( mlt_playlist this, int keep_length ) +{ + if ( this != NULL ) + { + int i = 0; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + mlt_events_block( properties, properties ); + for ( i = 1; i < this->count; i ++ ) + { + playlist_entry *left = this->list[ i - 1 ]; + playlist_entry *right = this->list[ i ]; + + if ( mlt_producer_is_blank( left->producer ) && mlt_producer_is_blank( right->producer ) ) + { + mlt_playlist_resize_clip( this, i - 1, 0, left->frame_count + right->frame_count - 1 ); + mlt_playlist_remove( this, i -- ); + } + } + + if ( !keep_length && this->count > 0 ) + { + playlist_entry *last = this->list[ this->count - 1 ]; + if ( mlt_producer_is_blank( last->producer ) ) + mlt_playlist_remove( this, this->count - 1 ); + } + + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +/** Determine if the specified clip index is a blank. +*/ + +int mlt_playlist_is_blank( mlt_playlist this, int clip ) +{ + return this == NULL || mlt_producer_is_blank( mlt_playlist_get_clip( this, clip ) ); +} + +/** Determine if the specified position is a blank. +*/ + +int mlt_playlist_is_blank_at( mlt_playlist this, mlt_position position ) +{ + return this == NULL || mlt_producer_is_blank( mlt_playlist_get_clip_at( this, position ) ); +} + +/** Replace the specified clip with a blank and return the clip. +*/ + +mlt_producer mlt_playlist_replace_with_blank( mlt_playlist this, int clip ) +{ + mlt_producer producer = NULL; + if ( !mlt_playlist_is_blank( this, clip ) ) + { + playlist_entry *entry = this->list[ clip ]; + int in = entry->frame_in; + int out = entry->frame_out; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + producer = entry->producer; + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + mlt_events_block( properties, properties ); + mlt_playlist_remove( this, clip ); + mlt_playlist_blank( this, out - in ); + mlt_playlist_move( this, this->count - 1, clip ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + mlt_producer_set_in_and_out( producer, in, out ); + } + return producer; +} + +void mlt_playlist_insert_blank( mlt_playlist this, int clip, int length ) +{ + if ( this != NULL && length >= 0 ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + mlt_events_block( properties, properties ); + mlt_playlist_blank( this, length ); + mlt_playlist_move( this, this->count - 1, clip ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +void mlt_playlist_pad_blanks( mlt_playlist this, mlt_position position, int length, int find ) +{ + if ( this != NULL && length != 0 ) + { + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + mlt_events_block( properties, properties ); + if ( find && clip < this->count && !mlt_playlist_is_blank( this, clip ) ) + clip ++; + if ( clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + if ( info.frame_out + length > info.frame_in ) + mlt_playlist_resize_clip( this, clip, info.frame_in, info.frame_out + length ); + else + mlt_playlist_remove( this, clip ); + } + else if ( find && clip < this->count && length > 0 ) + { + mlt_playlist_insert_blank( this, clip, length ); + } + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +int mlt_playlist_insert_at( mlt_playlist this, mlt_position position, mlt_producer producer, int mode ) +{ + int ret = this == NULL || position < 0 || producer == NULL; + if ( ret == 0 ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int length = mlt_producer_get_playtime( producer ); + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + mlt_events_block( properties, this ); + if ( clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + // Split and move to new clip if need be + if ( position != info.start && mlt_playlist_split( this, clip, position - info.start ) == 0 ) + mlt_playlist_get_clip_info( this, &info, ++ clip ); + + // Split again if need be + if ( length < info.frame_count ) + mlt_playlist_split( this, clip, length - 1 ); + + // Remove + mlt_playlist_remove( this, clip ); + + // Insert + mlt_playlist_insert( this, producer, clip, -1, -1 ); + ret = clip; + } + else if ( clip < this->count ) + { + if ( position > info.start + info.frame_count / 2 ) + clip ++; + if ( mode == 1 && clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( length < info.frame_count ) + mlt_playlist_split( this, clip, length ); + mlt_playlist_remove( this, clip ); + } + mlt_playlist_insert( this, producer, clip, -1, -1 ); + ret = clip; + } + else + { + if ( mode == 1 ) + mlt_playlist_blank( this, position - mlt_properties_get_int( properties, "length" ) ); + mlt_playlist_append( this, producer ); + ret = this->count - 1; + } + mlt_events_unblock( properties, this ); + mlt_playlist_virtual_refresh( this ); + } + else + { + ret = -1; + } + return ret; +} + +int mlt_playlist_clip_start( mlt_playlist this, int clip ) +{ + mlt_playlist_clip_info info; + if ( mlt_playlist_get_clip_info( this, &info, clip ) == 0 ) + return info.start; + return clip < 0 ? 0 : mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); +} + +int mlt_playlist_clip_length( mlt_playlist this, int clip ) +{ + mlt_playlist_clip_info info; + if ( mlt_playlist_get_clip_info( this, &info, clip ) == 0 ) + return info.frame_count; + return 0; +} + +int mlt_playlist_blanks_from( mlt_playlist this, int clip, int bounded ) +{ + int count = 0; + mlt_playlist_clip_info info; + if ( this != NULL && clip < this->count ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( mlt_playlist_is_blank( this, clip ) ) + count += info.frame_count; + if ( bounded == 0 ) + bounded = this->count; + for ( clip ++; clip < this->count && bounded >= 0; clip ++ ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( mlt_playlist_is_blank( this, clip ) ) + count += info.frame_count; + else + bounded --; + } + } + return count; +} + +int mlt_playlist_remove_region( mlt_playlist this, mlt_position position, int length ) +{ + int index = mlt_playlist_get_clip_index_at( this, position ); + if ( index >= 0 && index < this->count ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int clip_start = mlt_playlist_clip_start( this, index ); + int clip_length = mlt_playlist_clip_length( this, index ); + int list_length = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); + mlt_events_block( properties, this ); + + if ( position + length > list_length ) + length -= ( position + length - list_length ); + + if ( clip_start < position ) + { + mlt_playlist_split( this, index ++, position - clip_start ); + clip_length -= position - clip_start; + } + + while( length > 0 ) + { + if ( mlt_playlist_clip_length( this, index ) > length ) + mlt_playlist_split( this, index, length ); + length -= mlt_playlist_clip_length( this, index ); + mlt_playlist_remove( this, index ); + } + + mlt_playlist_consolidate_blanks( this, 0 ); + mlt_events_unblock( properties, this ); + mlt_playlist_virtual_refresh( this ); + + // Just to be sure, we'll get the clip index again... + index = mlt_playlist_get_clip_index_at( this, position ); + } + return index; +} + +int mlt_playlist_move_region( mlt_playlist this, mlt_position position, int length, int new_position ) +{ + if ( this != NULL ) + { + } + return 0; +} + +/** Get the current frame. +*/ + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Check that we have a producer + if ( producer == NULL ) + { + *frame = mlt_frame_init( ); + return 0; + } + + // Get this mlt_playlist + mlt_playlist this = producer->child; + + // Need to ensure the frame is deinterlaced when repeating 1 frame + int progressive = 0; + + // Get the real producer + mlt_service real = mlt_playlist_virtual_seek( this, &progressive ); + + // Check that we have a producer + if ( real == NULL ) + { + *frame = mlt_frame_init( ); + return 0; + } + + // Get the frame + if ( !mlt_properties_get_int( MLT_SERVICE_PROPERTIES( real ), "meta.fx_cut" ) ) + { + mlt_service_get_frame( real, frame, index ); + } + else + { + mlt_producer parent = mlt_producer_cut_parent( ( mlt_producer )real ); + *frame = mlt_frame_init( ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "fx_cut", 1 ); + mlt_frame_push_service( *frame, NULL ); + mlt_frame_push_audio( *frame, NULL ); + mlt_service_apply_filters( MLT_PRODUCER_SERVICE( parent ), *frame, 0 ); + mlt_service_apply_filters( real, *frame, 0 ); + mlt_deque_pop_front( MLT_FRAME_IMAGE_STACK( *frame ) ); + mlt_deque_pop_front( MLT_FRAME_AUDIO_STACK( *frame ) ); + } + + // Check if we're at the end of the clip + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + if ( mlt_properties_get_int( properties, "end_of_clip" ) ) + mlt_playlist_virtual_set_out( this ); + + // Set the consumer progressive property + if ( progressive ) + { + mlt_properties_set_int( properties, "consumer_deinterlace", progressive ); + mlt_properties_set_int( properties, "test_audio", 1 ); + } + + // Check for notifier and call with appropriate argument + mlt_properties playlist_properties = MLT_PRODUCER_PROPERTIES( producer ); + void ( *notifier )( void * ) = mlt_properties_get_data( playlist_properties, "notifier", NULL ); + if ( notifier != NULL ) + { + void *argument = mlt_properties_get_data( playlist_properties, "notifier_arg", NULL ); + notifier( argument ); + } + + // Update position on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_frame( producer ) ); + + // Position ourselves on the next frame + mlt_producer_prepare_next( producer ); + + return 0; +} + +/** Close the playlist. +*/ + +void mlt_playlist_close( mlt_playlist this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_PLAYLIST_PROPERTIES( this ) ) <= 0 ) + { + int i = 0; + this->parent.close = NULL; + for ( i = 0; i < this->count; i ++ ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + free( this->list[ i ] ); + } + mlt_producer_close( &this->blank ); + mlt_producer_close( &this->parent ); + free( this->list ); + free( this ); + } +} diff --git a/src/framework/mlt_playlist.h b/src/framework/mlt_playlist.h new file mode 100644 index 0000000..a19f45e --- /dev/null +++ b/src/framework/mlt_playlist.h @@ -0,0 +1,109 @@ +/* + * mlt_playlist.h -- playlist service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PLAYLIST_H_ +#define _MLT_PLAYLIST_H_ + +#include "mlt_producer.h" + +/** Structure for returning clip information. +*/ + +typedef struct +{ + int clip; + mlt_producer producer; + mlt_producer cut; + mlt_position start; + char *resource; + mlt_position frame_in; + mlt_position frame_out; + mlt_position frame_count; + mlt_position length; + float fps; + int repeat; +} +mlt_playlist_clip_info; + +/** Private definition. +*/ + +typedef struct playlist_entry_s playlist_entry; + +struct mlt_playlist_s +{ + struct mlt_producer_s parent; + struct mlt_producer_s blank; + + int size; + int count; + playlist_entry **list; +}; + +/** Public final methods +*/ + +#define MLT_PLAYLIST_PRODUCER( playlist ) ( &( playlist )->parent ) +#define MLT_PLAYLIST_SERVICE( playlist ) MLT_PRODUCER_SERVICE( MLT_PLAYLIST_PRODUCER( playlist ) ) +#define MLT_PLAYLIST_PROPERTIES( playlist ) MLT_SERVICE_PROPERTIES( MLT_PLAYLIST_SERVICE( playlist ) ) + +extern mlt_playlist mlt_playlist_init( ); +extern mlt_producer mlt_playlist_producer( mlt_playlist self ); +extern mlt_service mlt_playlist_service( mlt_playlist self ); +extern mlt_properties mlt_playlist_properties( mlt_playlist self ); +extern int mlt_playlist_count( mlt_playlist self ); +extern int mlt_playlist_clear( mlt_playlist self ); +extern int mlt_playlist_append( mlt_playlist self, mlt_producer producer ); +extern int mlt_playlist_append_io( mlt_playlist self, mlt_producer producer, mlt_position in, mlt_position out ); +extern int mlt_playlist_blank( mlt_playlist self, mlt_position length ); +extern mlt_position mlt_playlist_clip( mlt_playlist self, mlt_whence whence, int index ); +extern int mlt_playlist_current_clip( mlt_playlist self ); +extern mlt_producer mlt_playlist_current( mlt_playlist self ); +extern int mlt_playlist_get_clip_info( mlt_playlist self, mlt_playlist_clip_info *info, int index ); +extern int mlt_playlist_insert( mlt_playlist self, mlt_producer producer, int where, mlt_position in, mlt_position out ); +extern int mlt_playlist_remove( mlt_playlist self, int where ); +extern int mlt_playlist_move( mlt_playlist self, int from, int to ); +extern int mlt_playlist_resize_clip( mlt_playlist self, int clip, mlt_position in, mlt_position out ); +extern int mlt_playlist_repeat_clip( mlt_playlist self, int clip, int repeat ); +extern int mlt_playlist_split( mlt_playlist self, int clip, mlt_position position ); +extern int mlt_playlist_split_at( mlt_playlist self, mlt_position position, int left ); +extern int mlt_playlist_join( mlt_playlist self, int clip, int count, int merge ); +extern int mlt_playlist_mix( mlt_playlist self, int clip, int length, mlt_transition transition ); +extern int mlt_playlist_mix_add( mlt_playlist self, int clip, mlt_transition transition ); +extern mlt_producer mlt_playlist_get_clip( mlt_playlist self, int clip ); +extern mlt_producer mlt_playlist_get_clip_at( mlt_playlist self, mlt_position position ); +extern int mlt_playlist_get_clip_index_at( mlt_playlist self, mlt_position position ); +extern int mlt_playlist_clip_is_mix( mlt_playlist self, int clip ); +extern void mlt_playlist_consolidate_blanks( mlt_playlist self, int keep_length ); +extern int mlt_playlist_is_blank( mlt_playlist self, int clip ); +extern int mlt_playlist_is_blank_at( mlt_playlist self, mlt_position position ); +extern void mlt_playlist_insert_blank( mlt_playlist self, int clip, int length ); +extern void mlt_playlist_pad_blanks( mlt_playlist self, mlt_position position, int length, int find ); +extern mlt_producer mlt_playlist_replace_with_blank( mlt_playlist self, int clip ); +extern int mlt_playlist_insert_at( mlt_playlist self, mlt_position position, mlt_producer producer, int mode ); +extern int mlt_playlist_clip_start( mlt_playlist self, int clip ); +extern int mlt_playlist_clip_length( mlt_playlist self, int clip ); +extern int mlt_playlist_blanks_from( mlt_playlist self, int clip, int bounded ); +extern int mlt_playlist_remove_region( mlt_playlist self, mlt_position position, int length ); +extern int mlt_playlist_move_region( mlt_playlist self, mlt_position position, int length, int new_position ); +extern void mlt_playlist_close( mlt_playlist self ); + +#endif + diff --git a/src/framework/mlt_pool.c b/src/framework/mlt_pool.c new file mode 100644 index 0000000..099bbd0 --- /dev/null +++ b/src/framework/mlt_pool.c @@ -0,0 +1,355 @@ +/* + * mlt_pool.c -- memory pooling functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_properties.h" +#include "mlt_deque.h" + +#include +#include +#include + +// Not nice - memalign is defined here apparently? +#ifdef linux +#include +#endif + +/** Singleton repositories +*/ + +static mlt_properties pools = NULL; + +/** Private pooling structure. +*/ + +typedef struct mlt_pool_s +{ + pthread_mutex_t lock; + mlt_deque stack; + int size; + int count; +} +*mlt_pool; + +typedef struct mlt_release_s +{ + mlt_pool pool; + int references; +} +*mlt_release; + +/** Create a pool. +*/ + +static mlt_pool pool_init( int size ) +{ + // Create the pool + mlt_pool this = calloc( 1, sizeof( struct mlt_pool_s ) ); + + // Initialise it + if ( this != NULL ) + { + // Initialise the mutex + pthread_mutex_init( &this->lock, NULL ); + + // Create the stack + this->stack = mlt_deque_init( ); + + // Assign the size + this->size = size; + } + + // Return it + return this; +} + +/** Get an item from the pool. +*/ + +static void *pool_fetch( mlt_pool this ) +{ + // We will generate a release object + void *ptr = NULL; + + // Sanity check + if ( this != NULL ) + { + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // Check if the stack is empty + if ( mlt_deque_count( this->stack ) != 0 ) + { + // Pop the top of the stack + ptr = mlt_deque_pop_back( this->stack ); + + // Assign the reference + ( ( mlt_release )ptr )->references = 1; + } + else + { + // We need to generate a release item +#ifdef linux + mlt_release release = memalign( 16, this->size ); +#else + mlt_release release = malloc( this->size ); +#endif + + // Initialise it + if ( release != NULL ) + { + // Increment the number of items allocated to this pool + this->count ++; + + // Assign the pool + release->pool = this; + + // Assign the reference + release->references = 1; + + // Determine the ptr + ptr = ( void * )release + sizeof( struct mlt_release_s ); + } + } + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + } + + // Return the generated release object + return ptr; +} + +/** Return an item to the pool. +*/ + +static void pool_return( void *ptr ) +{ + // Sanity checks + if ( ptr != NULL ) + { + // Get the release pointer + mlt_release that = ptr - sizeof( struct mlt_release_s ); + + // Get the pool + mlt_pool this = that->pool; + + if ( this != NULL ) + { + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // Push the that back back on to the stack + mlt_deque_push_back( this->stack, ptr ); + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + + // Ensure that we don't clean up + ptr = NULL; + } + } + + // Tidy up - this will only occur if the returned item is incorrect + if ( ptr != NULL ) + { + // Free the release itself + free( ptr - sizeof( struct mlt_release_s ) ); + } +} + +/** Destroy a pool. +*/ + +static void pool_close( mlt_pool this ) +{ + if ( this != NULL ) + { + // We need to free up all items in the pool + void *release = NULL; + + // Iterate through the stack until depleted + while ( ( release = mlt_deque_pop_back( this->stack ) ) != NULL ) + { + // We'll free this item now + free( release - sizeof( struct mlt_release_s ) ); + } + + // We can now close the stack + mlt_deque_close( this->stack ); + + // Destroy the mutex + pthread_mutex_destroy( &this->lock ); + + // Close the pool + free( this ); + } +} + +/** Initialise the pool. +*/ + +void mlt_pool_init( ) +{ + // Loop variable used to create the pools + int i = 0; + + // Create the pools + pools = mlt_properties_new( ); + + // Create the pools + for ( i = 8; i < 31; i ++ ) + { + // Each properties item needs a name + char name[ 32 ]; + + // Construct a pool + mlt_pool pool = pool_init( 1 << i ); + + // Generate a name + sprintf( name, "%d", i ); + + // Register with properties + mlt_properties_set_data( pools, name, pool, 0, ( mlt_destructor )pool_close, NULL ); + } +} + +/** Allocate size bytes from the pool. +*/ + +void *mlt_pool_alloc( int size ) +{ + // This will be used to obtain the pool to use + mlt_pool pool = NULL; + + // Determines the index of the pool to use + int index = 8; + + // Minimum size pooled is 256 bytes + size = size + sizeof( mlt_release ); + while ( ( 1 << index ) < size ) + index ++; + + // Now get the pool at the index + pool = mlt_properties_get_data_at( pools, index - 8, NULL ); + + // Now get the real item + return pool_fetch( pool ); +} + +/** Allocate size bytes from the pool. +*/ + +void *mlt_pool_realloc( void *ptr, int size ) +{ + // Result to return + void *result = NULL; + + // Check if we actually have an address + if ( ptr != NULL ) + { + // Get the release pointer + mlt_release that = ptr - sizeof( struct mlt_release_s ); + + // If the current pool this ptr belongs to is big enough + if ( size > that->pool->size - sizeof( struct mlt_release_s ) ) + { + // Allocate + result = mlt_pool_alloc( size ); + + // Copy + memcpy( result, ptr, that->pool->size - sizeof( struct mlt_release_s ) ); + + // Release + mlt_pool_release( ptr ); + } + else + { + // Nothing to do + result = ptr; + } + } + else + { + // Simply allocate + result = mlt_pool_alloc( size ); + } + + return result; +} + +/** Purge unused items in the pool. +*/ + +void mlt_pool_purge( ) +{ + int i = 0; + + // For each pool + for ( i = 0; i < mlt_properties_count( pools ); i ++ ) + { + // Get the pool + mlt_pool this = mlt_properties_get_data_at( pools, i, NULL ); + + // Pointer to unused memory + void *release = NULL; + + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // We'll free all unused items now + while ( ( release = mlt_deque_pop_back( this->stack ) ) != NULL ) + free( release - sizeof( struct mlt_release_s ) ); + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + } +} + +/** Release the allocated memory. +*/ + +void mlt_pool_release( void *release ) +{ + // Return to the pool + pool_return( release ); +} + +/** Close the pool. +*/ + +void mlt_pool_close( ) +{ +#ifdef _MLT_POOL_CHECKS_ + // Stats dump on close + int i = 0; + fprintf( stderr, "Usage:\n\n" ); + for ( i = 0; i < mlt_properties_count( pools ); i ++ ) + { + mlt_pool pool = mlt_properties_get_data_at( pools, i, NULL ); + if ( pool->count ) + fprintf( stderr, "%d: allocated %d returned %d %c\n", pool->size, pool->count, mlt_deque_count( pool->stack ), + pool->count != mlt_deque_count( pool->stack ) ? '*' : ' ' ); + } +#endif + + // Close the properties + mlt_properties_close( pools ); +} + diff --git a/src/framework/mlt_pool.h b/src/framework/mlt_pool.h new file mode 100644 index 0000000..48bb811 --- /dev/null +++ b/src/framework/mlt_pool.h @@ -0,0 +1,31 @@ +/* + * mlt_pool.h -- memory pooling functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_POOL_H +#define _MLT_POOL_H + +extern void mlt_pool_init( ); +extern void *mlt_pool_alloc( int size ); +extern void *mlt_pool_realloc( void *ptr, int size ); +extern void mlt_pool_release( void *release ); +extern void mlt_pool_purge( ); +extern void mlt_pool_close( ); + +#endif diff --git a/src/framework/mlt_producer.c b/src/framework/mlt_producer.c new file mode 100644 index 0000000..0628166 --- /dev/null +++ b/src/framework/mlt_producer.c @@ -0,0 +1,846 @@ +/* + * mlt_producer.c -- abstraction for all producer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_producer.h" +#include "mlt_factory.h" +#include "mlt_frame.h" +#include "mlt_parser.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +/** Forward references. +*/ + +static int producer_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); +static void mlt_producer_property_changed( mlt_service owner, mlt_producer this, char *name ); +static void mlt_producer_service_changed( mlt_service owner, mlt_producer this ); + +//#define _MLT_PRODUCER_CHECKS_ 1 + +#ifdef _MLT_PRODUCER_CHECKS_ +static int producers_created = 0; +static int producers_destroyed = 0; +#endif + +/** Constructor +*/ + +int mlt_producer_init( mlt_producer this, void *child ) +{ + // Check that we haven't received NULL + int error = this == NULL; + + // Continue if no error + if ( error == 0 ) + { +#ifdef _MLT_PRODUCER_CHECKS_ + producers_created ++; +#endif + + // Initialise the producer + memset( this, 0, sizeof( struct mlt_producer_s ) ); + + // Associate with the child + this->child = child; + + // Initialise the service + if ( mlt_service_init( &this->parent, this ) == 0 ) + { + // The parent is the service + mlt_service parent = &this->parent; + + // Define the parent close + parent->close = ( mlt_destructor )mlt_producer_close; + parent->close_object = this; + + // For convenience, we'll assume the close_object is this + this->close_object = this; + + // Get the properties of the parent + mlt_properties properties = MLT_SERVICE_PROPERTIES( parent ); + + // Set the default properties + mlt_properties_set( properties, "mlt_type", "mlt_producer" ); + mlt_properties_set_position( properties, "_position", 0.0 ); + mlt_properties_set_double( properties, "_frame", 0 ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + mlt_properties_set_double( properties, "_speed", 1.0 ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 14999 ); + mlt_properties_set_position( properties, "length", 15000 ); + mlt_properties_set( properties, "eof", "pause" ); + mlt_properties_set( properties, "resource", "" ); + + // Override service get_frame + parent->get_frame = producer_get_frame; + + mlt_events_listen( properties, this, "service-changed", ( mlt_listener )mlt_producer_service_changed ); + mlt_events_listen( properties, this, "property-changed", ( mlt_listener )mlt_producer_property_changed ); + mlt_events_register( properties, "producer-changed", NULL ); + } + } + + return error; +} + +/** Listener for property changes. +*/ + +static void mlt_producer_property_changed( mlt_service owner, mlt_producer this, char *name ) +{ + if ( !strcmp( name, "in" ) || !strcmp( name, "out" ) || !strcmp( name, "length" ) ) + mlt_events_fire( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "producer-changed", NULL ); +} + +/** Listener for service changes. +*/ + +static void mlt_producer_service_changed( mlt_service owner, mlt_producer this ) +{ + mlt_events_fire( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "producer-changed", NULL ); +} + +/** Create a new producer. +*/ + +mlt_producer mlt_producer_new( ) +{ + mlt_producer this = malloc( sizeof( struct mlt_producer_s ) ); + mlt_producer_init( this, NULL ); + return this; +} + +/** Determine if producer is a cut. +*/ + +int mlt_producer_is_cut( mlt_producer this ) +{ + return mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( this ), "_cut" ); +} + +/** Determine if producer is a mix. +*/ + +int mlt_producer_is_mix( mlt_producer this ) +{ + mlt_properties properties = this != NULL ? MLT_PRODUCER_PROPERTIES( this ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + return tractor != NULL; +} + +/** Determine if the producer is a blank [from a playlist]. +*/ + +int mlt_producer_is_blank( mlt_producer this ) +{ + return this == NULL || !strcmp( mlt_properties_get( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "resource" ), "blank" ); +} + +/** Obtain the parent producer. +*/ + +mlt_producer mlt_producer_cut_parent( mlt_producer this ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + if ( mlt_producer_is_cut( this ) ) + return mlt_properties_get_data( properties, "_cut_parent", NULL ); + else + return this; +} + +/** Create a cut of this producer +*/ + +mlt_producer mlt_producer_cut( mlt_producer this, int in, int out ) +{ + mlt_producer result = mlt_producer_new( ); + mlt_producer parent = mlt_producer_cut_parent( this ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( result ); + mlt_properties parent_props = MLT_PRODUCER_PROPERTIES( parent ); + + mlt_events_block( MLT_PRODUCER_PROPERTIES( result ), MLT_PRODUCER_PROPERTIES( result ) ); + // Special case - allow for a cut of the entire producer (this will squeeze all other cuts to 0) + if ( in <= 0 ) + in = 0; + if ( ( out < 0 || out >= mlt_producer_get_length( parent ) ) && !mlt_producer_is_blank( this ) ) + out = mlt_producer_get_length( parent ) - 1; + + mlt_properties_inc_ref( parent_props ); + mlt_properties_set_int( properties, "_cut", 1 ); + mlt_properties_set_data( properties, "_cut_parent", parent, 0, ( mlt_destructor )mlt_producer_close, NULL ); + mlt_properties_set_position( properties, "length", mlt_properties_get_position( parent_props, "length" ) ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( parent_props, "aspect_ratio" ) ); + mlt_producer_set_in_and_out( result, in, out ); + + return result; +} + +/** Get the parent service object. +*/ + +mlt_service mlt_producer_service( mlt_producer this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the producer properties. +*/ + +mlt_properties mlt_producer_properties( mlt_producer this ) +{ + return MLT_SERVICE_PROPERTIES( &this->parent ); +} + +/** Seek to a specified position. +*/ + +int mlt_producer_seek( mlt_producer this, mlt_position position ) +{ + // Determine eof handling + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + char *eof = mlt_properties_get( properties, "eof" ); + int use_points = 1 - mlt_properties_get_int( properties, "ignore_points" ); + + // Recursive behaviour for cuts - repositions parent and then repositions cut + // hence no return on this condition + if ( mlt_producer_is_cut( this ) ) + mlt_producer_seek( mlt_producer_cut_parent( this ), position + mlt_producer_get_in( this ) ); + + // Check bounds + if ( position < 0 || mlt_producer_get_playtime( this ) == 0 ) + { + position = 0; + } + else if ( use_points && ( eof == NULL || !strcmp( eof, "pause" ) ) && position >= mlt_producer_get_playtime( this ) ) + { + mlt_producer_set_speed( this, 0 ); + position = mlt_producer_get_playtime( this ) - 1; + } + else if ( use_points && !strcmp( eof, "loop" ) && position >= mlt_producer_get_playtime( this ) ) + { + position = (int)position % (int)mlt_producer_get_playtime( this ); + } + + // Set the position + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "_position", position ); + + // Calculate the absolute frame + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "_frame", use_points * mlt_producer_get_in( this ) + position ); + + return 0; +} + +/** Get the current position (relative to in point). +*/ + +mlt_position mlt_producer_position( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "_position" ); +} + +/** Get the current position (relative to start of producer). +*/ + +mlt_position mlt_producer_frame( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "_frame" ); +} + +/** Set the playing speed. +*/ + +int mlt_producer_set_speed( mlt_producer this, double speed ) +{ + return mlt_properties_set_double( MLT_PRODUCER_PROPERTIES( this ), "_speed", speed ); +} + +/** Get the playing speed. +*/ + +double mlt_producer_get_speed( mlt_producer this ) +{ + return mlt_properties_get_double( MLT_PRODUCER_PROPERTIES( this ), "_speed" ); +} + +/** Get the frames per second. +*/ + +double mlt_producer_get_fps( mlt_producer this ) +{ + return mlt_profile_fps( NULL ); +} + +/** Set the in and out points. +*/ + +int mlt_producer_set_in_and_out( mlt_producer this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Correct ins and outs if necessary + if ( in < 0 ) + in = 0; + else if ( in >= mlt_producer_get_length( this ) ) + in = mlt_producer_get_length( this ) - 1; + + if ( out < 0 ) + out = 0; + else if ( out >= mlt_producer_get_length( this ) && !mlt_producer_is_blank( this ) ) + out = mlt_producer_get_length( this ) - 1; + else if ( out >= mlt_producer_get_length( this ) && mlt_producer_is_blank( this ) ) + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "length", out + 1 ); + + // Swap ins and outs if wrong + if ( out < in ) + { + mlt_position t = in; + in = out; + out = t; + } + + // Set the values + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "in", in ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", out ); + + return 0; +} + +/** Physically reduce the producer (typically a cut) to a 0 length. + Essentially, all 0 length cuts should be immediately removed by containers. +*/ + +int mlt_producer_clear( mlt_producer this ) +{ + if ( this != NULL ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", -1 ); + } + return 0; +} + +/** Get the in point. +*/ + +mlt_position mlt_producer_get_in( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_producer_get_out( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "out" ); +} + +/** Get the total play time. +*/ + +mlt_position mlt_producer_get_playtime( mlt_producer this ) +{ + return mlt_producer_get_out( this ) - mlt_producer_get_in( this ) + 1; +} + +/** Get the total length of the producer. +*/ + +mlt_position mlt_producer_get_length( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "length" ); +} + +/** Prepare for next frame. +*/ + +void mlt_producer_prepare_next( mlt_producer this ) +{ + if ( mlt_producer_get_speed( this ) != 0 ) + mlt_producer_seek( this, mlt_producer_position( this ) + mlt_producer_get_speed( this ) ); +} + +/** Get a frame. +*/ + +static int producer_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + int result = 1; + mlt_producer this = service != NULL ? service->child : NULL; + + if ( this != NULL && !mlt_producer_is_cut( this ) ) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Determine eof handling + char *eof = mlt_properties_get( MLT_PRODUCER_PROPERTIES( this ), "eof" ); + + // Get the speed of the producer + double speed = mlt_producer_get_speed( this ); + + // We need to use the clone if it's specified + mlt_producer clone = mlt_properties_get_data( properties, "use_clone", NULL ); + + // If no clone is specified, use this + clone = clone == NULL ? this : clone; + + // A properly instatiated producer will have a get_frame method... + if ( this->get_frame == NULL || ( !strcmp( eof, "continue" ) && mlt_producer_position( this ) > mlt_producer_get_out( this ) ) ) + { + // Generate a test frame + *frame = mlt_frame_init( ); + + // Set the position + result = mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Mark as a test card + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", 1 ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_audio", 1 ); + + // Calculate the next position + mlt_producer_prepare_next( this ); + } + else + { + // Get the frame from the implementation + result = this->get_frame( clone, frame, index ); + } + + // Copy the fps and speed of the producer onto the frame + properties = MLT_FRAME_PROPERTIES( *frame ); + mlt_properties_set_double( properties, "_speed", speed ); + mlt_properties_set_int( properties, "test_audio", mlt_frame_is_test_audio( *frame ) ); + mlt_properties_set_int( properties, "test_image", mlt_frame_is_test_card( *frame ) ); + if ( mlt_properties_get_data( properties, "_producer", NULL ) == NULL ) + mlt_properties_set_data( properties, "_producer", service, 0, NULL, NULL ); + } + else if ( this != NULL ) + { + // Get the speed of the cut + double speed = mlt_producer_get_speed( this ); + + // Get the parent of this cut + mlt_producer parent = mlt_producer_cut_parent( this ); + + // Get the properties of the parent + mlt_properties parent_properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Get the properties of the cut + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Determine the clone index + int clone_index = mlt_properties_get_int( properties, "_clone" ); + + // Determine the clone to use + mlt_producer clone = this; + + if ( clone_index > 0 ) + { + char key[ 25 ]; + sprintf( key, "_clone.%d", clone_index - 1 ); + clone = mlt_properties_get_data( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), key, NULL ); + if ( clone == NULL ) fprintf( stderr, "requested clone doesn't exist %d\n", clone_index ); + clone = clone == NULL ? this : clone; + } + else + { + clone = parent; + } + + // We need to seek to the correct position in the clone + mlt_producer_seek( clone, mlt_producer_get_in( this ) + mlt_properties_get_int( properties, "_position" ) ); + + // Assign the clone property to the parent + mlt_properties_set_data( parent_properties, "use_clone", clone, 0, NULL, NULL ); + + // Now get the frame from the parents service + result = mlt_service_get_frame( MLT_PRODUCER_SERVICE( parent ), frame, index ); + + // We're done with the clone now + mlt_properties_set_data( parent_properties, "use_clone", NULL, 0, NULL, NULL ); + + // This is useful and required by always_active transitions to determine in/out points of the cut + if ( mlt_properties_get_data( MLT_FRAME_PROPERTIES( *frame ), "_producer", NULL ) == MLT_PRODUCER_SERVICE( parent ) ) + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "_producer", this, 0, NULL, NULL ); + + mlt_properties_set_double( MLT_FRAME_PROPERTIES( *frame ), "_speed", speed ); + mlt_producer_prepare_next( this ); + } + else + { + *frame = mlt_frame_init( ); + result = 0; + } + + // Pass on all meta properties from the producer/cut on to the frame + if ( *frame != NULL && this != NULL ) + { + int i = 0; + mlt_properties p_props = MLT_PRODUCER_PROPERTIES( this ); + mlt_properties f_props = MLT_FRAME_PROPERTIES( *frame ); + int count = mlt_properties_count( p_props ); + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( p_props, i ); + if ( !strncmp( name, "meta.", 5 ) ) + mlt_properties_set( f_props, name, mlt_properties_get( p_props, name ) ); + else if ( !strncmp( name, "set.", 4 ) ) + mlt_properties_set( f_props, name + 4, mlt_properties_get( p_props, name ) ); + } + } + + return result; +} + +/** Attach a filter. +*/ + +int mlt_producer_attach( mlt_producer this, mlt_filter filter ) +{ + return mlt_service_attach( MLT_PRODUCER_SERVICE( this ), filter ); +} + +/** Detach a filter. +*/ + +int mlt_producer_detach( mlt_producer this, mlt_filter filter ) +{ + return mlt_service_detach( MLT_PRODUCER_SERVICE( this ), filter ); +} + +/** Retrieve a filter. +*/ + +mlt_filter mlt_producer_filter( mlt_producer this, int index ) +{ + return mlt_service_filter( MLT_PRODUCER_SERVICE( this ), index ); +} + +/** Clone this producer. +*/ + +static mlt_producer mlt_producer_clone( mlt_producer this ) +{ + mlt_producer clone = NULL; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + char *resource = mlt_properties_get( properties, "resource" ); + char *service = mlt_properties_get( properties, "mlt_service" ); + + mlt_events_block( mlt_factory_event_object( ), mlt_factory_event_object( ) ); + + if ( service != NULL ) + clone = mlt_factory_producer( service, resource ); + + if ( clone == NULL && resource != NULL ) + clone = mlt_factory_producer( "fezzik", resource ); + + if ( clone != NULL ) + mlt_properties_inherit( MLT_PRODUCER_PROPERTIES( clone ), properties ); + + mlt_events_unblock( mlt_factory_event_object( ), mlt_factory_event_object( ) ); + + return clone; +} + +/** Create clones. +*/ + +static void mlt_producer_set_clones( mlt_producer this, int clones ) +{ + mlt_producer parent = mlt_producer_cut_parent( this ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( parent ); + int existing = mlt_properties_get_int( properties, "_clones" ); + int i = 0; + char key[ 25 ]; + + // If the number of existing clones is different, then create/remove as necessary + if ( existing != clones ) + { + if ( existing < clones ) + { + for ( i = existing; i < clones; i ++ ) + { + mlt_producer clone = mlt_producer_clone( parent ); + sprintf( key, "_clone.%d", i ); + mlt_properties_set_data( properties, key, clone, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + } + else + { + for ( i = clones; i < existing; i ++ ) + { + sprintf( key, "_clone.%d", i ); + mlt_properties_set_data( properties, key, NULL, 0, NULL, NULL ); + } + } + } + + // Ensure all properties on the parent are passed to the clones + for ( i = 0; i < clones; i ++ ) + { + mlt_producer clone = NULL; + sprintf( key, "_clone.%d", i ); + clone = mlt_properties_get_data( properties, key, NULL ); + if ( clone != NULL ) + mlt_properties_pass( MLT_PRODUCER_PROPERTIES( clone ), properties, "" ); + } + + // Update the number of clones on the properties + mlt_properties_set_int( properties, "_clones", clones ); +} + +/** Optimise for overlapping cuts from the same clip. +*/ + +typedef struct +{ + int multitrack; + int track; + int position; + int length; + int offset; +} +track_info; + +typedef struct +{ + mlt_producer cut; + int start; + int end; +} +clip_references; + +static int intersect( clip_references *a, clip_references *b ) +{ + int diff = ( a->start - b->start ) + ( a->end - b->end ); + return diff >= 0 && diff < ( a->end - a->start + 1 ); +} + +static int push( mlt_parser this, int multitrack, int track, int position ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + track_info *info = malloc( sizeof( track_info ) ); + info->multitrack = multitrack; + info->track = track; + info->position = position; + info->length = 0; + info->offset = 0; + return mlt_deque_push_back( stack, info ); +} + +static track_info *pop( mlt_parser this ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + return mlt_deque_pop_back( stack ); +} + +static track_info *peek( mlt_parser this ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + return mlt_deque_peek_back( stack ); +} + +static int on_start_multitrack( mlt_parser this, mlt_multitrack object ) +{ + track_info *info = peek( this ); + return push( this, info->multitrack ++, info->track, info->position ); +} + +static int on_start_track( mlt_parser this ) +{ + track_info *info = peek( this ); + info->position -= info->offset; + info->length -= info->offset; + return push( this, info->multitrack, info->track ++, info->position ); +} + +static int on_start_producer( mlt_parser this, mlt_producer object ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_properties producers = mlt_properties_get_data( properties, "producers", NULL ); + mlt_producer parent = mlt_producer_cut_parent( object ); + if ( mlt_service_identify( ( mlt_service )mlt_producer_cut_parent( object ) ) == producer_type && mlt_producer_is_cut( object ) ) + { + int ref_count = 0; + clip_references *old_refs = NULL; + clip_references *refs = NULL; + char key[ 50 ]; + int count = 0; + track_info *info = peek( this ); + sprintf( key, "%p", parent ); + mlt_properties_get_data( producers, key, &count ); + mlt_properties_set_data( producers, key, parent, ++ count, NULL, NULL ); + old_refs = mlt_properties_get_data( properties, key, &ref_count ); + refs = malloc( ( ref_count + 1 ) * sizeof( clip_references ) ); + if ( old_refs != NULL ) + memcpy( refs, old_refs, ref_count * sizeof( clip_references ) ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( object ), "_clone", -1 ); + refs[ ref_count ].cut = object; + refs[ ref_count ].start = info->position; + refs[ ref_count ].end = info->position + mlt_producer_get_playtime( object ) - 1; + mlt_properties_set_data( properties, key, refs, ++ ref_count, free, NULL ); + info->position += mlt_producer_get_playtime( object ); + info->length += mlt_producer_get_playtime( object ); + } + return 0; +} + +static int on_end_track( mlt_parser this ) +{ + track_info *track = pop( this ); + track_info *multi = peek( this ); + multi->length += track->length; + multi->position += track->length; + multi->offset = track->length; + free( track ); + return 0; +} + +static int on_end_multitrack( mlt_parser this, mlt_multitrack object ) +{ + track_info *multi = pop( this ); + track_info *track = peek( this ); + track->position += multi->length; + track->length += multi->length; + free( multi ); + return 0; +} + +int mlt_producer_optimise( mlt_producer this ) +{ + int error = 1; + mlt_parser parser = mlt_parser_new( ); + if ( parser != NULL ) + { + int i = 0, j = 0, k = 0; + mlt_properties properties = mlt_parser_properties( parser ); + mlt_properties producers = mlt_properties_new( ); + mlt_deque stack = mlt_deque_init( ); + mlt_properties_set_data( properties, "producers", producers, 0, ( mlt_destructor )mlt_properties_close, NULL ); + mlt_properties_set_data( properties, "stack", stack, 0, ( mlt_destructor )mlt_deque_close, NULL ); + parser->on_start_producer = on_start_producer; + parser->on_start_track = on_start_track; + parser->on_end_track = on_end_track; + parser->on_start_multitrack = on_start_multitrack; + parser->on_end_multitrack = on_end_multitrack; + push( parser, 0, 0, 0 ); + mlt_parser_start( parser, MLT_PRODUCER_SERVICE( this ) ); + free( pop( parser ) ); + for ( k = 0; k < mlt_properties_count( producers ); k ++ ) + { + char *name = mlt_properties_get_name( producers, k ); + int count = 0; + int clones = 0; + int max_clones = 0; + mlt_producer producer = mlt_properties_get_data( producers, name, &count ); + if ( producer != NULL && count > 1 ) + { + clip_references *refs = mlt_properties_get_data( properties, name, &count ); + for ( i = 0; i < count; i ++ ) + { + clones = 0; + for ( j = i + 1; j < count; j ++ ) + { + if ( intersect( &refs[ i ], &refs[ j ] ) ) + { + clones ++; + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( refs[ j ].cut ), "_clone", clones ); + } + } + if ( clones > max_clones ) + max_clones = clones; + } + + for ( i = 0; i < count; i ++ ) + { + mlt_producer cut = refs[ i ].cut; + if ( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone" ) == -1 ) + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone", 0 ); + } + + mlt_producer_set_clones( producer, max_clones ); + } + else if ( producer != NULL ) + { + clip_references *refs = mlt_properties_get_data( properties, name, &count ); + for ( i = 0; i < count; i ++ ) + { + mlt_producer cut = refs[ i ].cut; + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone", 0 ); + } + mlt_producer_set_clones( producer, 0 ); + } + } + mlt_parser_close( parser ); + } + return error; +} + +/** Close the producer. +*/ + +void mlt_producer_close( mlt_producer this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_PRODUCER_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + int destroy = mlt_producer_is_cut( this ); + +#if _MLT_PRODUCER_CHECKS_ == 1 + // Show debug info + mlt_properties_debug( MLT_PRODUCER_PROPERTIES( this ), "Producer closing", stderr ); +#endif + +#ifdef _MLT_PRODUCER_CHECKS_ + // Increment destroyed count + producers_destroyed ++; + + // Show current stats - these should match when the app is closed + fprintf( stderr, "Producers created %d, destroyed %d\n", producers_created, producers_destroyed ); +#endif + + mlt_service_close( &this->parent ); + + if ( destroy ) + free( this ); + } + } +} diff --git a/src/framework/mlt_producer.h b/src/framework/mlt_producer.h new file mode 100644 index 0000000..c1a0531 --- /dev/null +++ b/src/framework/mlt_producer.h @@ -0,0 +1,79 @@ +/* + * mlt_producer.h -- abstraction for all producer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PRODUCER_H_ +#define _MLT_PRODUCER_H_ + +#include "mlt_service.h" +#include "mlt_filter.h" + +/** The interface definition for all producers. +*/ + +struct mlt_producer_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* Public virtual methods */ + int ( *get_frame )( mlt_producer, mlt_frame_ptr, int ); + mlt_destructor close; + void *close_object; + + /* Private data */ + void *local; + void *child; +}; + +/** Public final methods +*/ + +#define MLT_PRODUCER_SERVICE( producer ) ( &( producer )->parent ) +#define MLT_PRODUCER_PROPERTIES( producer ) MLT_SERVICE_PROPERTIES( MLT_PRODUCER_SERVICE( producer ) ) + +extern int mlt_producer_init( mlt_producer self, void *child ); +extern mlt_producer mlt_producer_new( ); +extern mlt_service mlt_producer_service( mlt_producer self ); +extern mlt_properties mlt_producer_properties( mlt_producer self ); +extern int mlt_producer_seek( mlt_producer self, mlt_position position ); +extern mlt_position mlt_producer_position( mlt_producer self ); +extern mlt_position mlt_producer_frame( mlt_producer self ); +extern int mlt_producer_set_speed( mlt_producer self, double speed ); +extern double mlt_producer_get_speed( mlt_producer self ); +extern double mlt_producer_get_fps( mlt_producer self ); +extern int mlt_producer_set_in_and_out( mlt_producer self, mlt_position in, mlt_position out ); +extern int mlt_producer_clear( mlt_producer self ); +extern mlt_position mlt_producer_get_in( mlt_producer self ); +extern mlt_position mlt_producer_get_out( mlt_producer self ); +extern mlt_position mlt_producer_get_playtime( mlt_producer self ); +extern mlt_position mlt_producer_get_length( mlt_producer self ); +extern void mlt_producer_prepare_next( mlt_producer self ); +extern int mlt_producer_attach( mlt_producer self, mlt_filter filter ); +extern int mlt_producer_detach( mlt_producer self, mlt_filter filter ); +extern mlt_filter mlt_producer_filter( mlt_producer self, int index ); +extern mlt_producer mlt_producer_cut( mlt_producer self, int in, int out ); +extern int mlt_producer_is_cut( mlt_producer self ); +extern int mlt_producer_is_mix( mlt_producer self ); +extern int mlt_producer_is_blank( mlt_producer self ); +extern mlt_producer mlt_producer_cut_parent( mlt_producer self ); +extern int mlt_producer_optimise( mlt_producer self ); +extern void mlt_producer_close( mlt_producer self ); + +#endif diff --git a/src/framework/mlt_profile.c b/src/framework/mlt_profile.c new file mode 100644 index 0000000..5a627b5 --- /dev/null +++ b/src/framework/mlt_profile.c @@ -0,0 +1,241 @@ +/* + * mlt_profile.c -- video output definition + * Copyright (C) 2007 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_profile.h" +#include "mlt_factory.h" +#include "mlt_properties.h" + +#include +#include +#include + +#define PROFILES_DIR "/share/mlt/profiles/" + +static mlt_profile profile = NULL; + +/** Get the current profile +* Builds one for PAL DV if non-existing +*/ + +mlt_profile mlt_profile_get( ) +{ + if ( !profile ) + { + profile = calloc( 1, sizeof( struct mlt_profile_s ) ); + if ( profile ) + { + mlt_environment_set( "MLT_PROFILE", "dv_pal" ); + profile->description = strdup( "PAL 4:3 DV or DVD" ); + profile->frame_rate_num = 25; + profile->frame_rate_den = 1; + profile->width = 720; + profile->height = 576; + profile->progressive = 0; + profile->sample_aspect_num = 59; + profile->sample_aspect_den = 54; + profile->display_aspect_num = 4; + profile->display_aspect_den = 3; + } + } + return profile; +} + + +/** Load a profile from the system folder +*/ + +mlt_profile mlt_profile_select( const char *name ) +{ + char *filename = NULL; + const char *prefix = getenv( "MLT_PROFILES_PATH" ); + mlt_properties properties = mlt_properties_load( name ); + + // Try to load from file specification + if ( properties && mlt_properties_get_int( properties, "width" ) ) + { + filename = calloc( 1, strlen( name ) + 1 ); + } + // Load from $prefix/share/mlt/profiles + else if ( prefix == NULL ) + { + prefix = PREFIX; + filename = calloc( 1, strlen( prefix ) + strlen( PROFILES_DIR ) + strlen( name ) + 2 ); + strcpy( filename, prefix ); + if ( filename[ strlen( filename ) - 1 ] != '/' ) + filename[ strlen( filename ) ] = '/'; + strcat( filename, PROFILES_DIR ); + } + // Use environment variable instead + else + { + filename = calloc( 1, strlen( prefix ) + strlen( name ) + 2 ); + strcpy( filename, prefix ); + if ( filename[ strlen( filename ) - 1 ] != '/' ) + filename[ strlen( filename ) ] = '/'; + } + + // Finish loading + strcat( filename, name ); + mlt_profile_load_file( filename ); + + // Cleanup + mlt_properties_close( properties ); + free( filename ); + + return profile; +} + +/** Load a profile from specific file +*/ + +mlt_profile mlt_profile_load_file( const char *file ) +{ + // Load the profile as properties + mlt_properties properties = mlt_properties_load( file ); + if ( properties && mlt_properties_get_int( properties, "width" ) ) + { + mlt_profile_load_properties( properties ); + mlt_properties_close( properties ); + + // Set MLT_PROFILE to basename + char *filename = strdup( file ); + mlt_environment_set( "MLT_PROFILE", basename( filename ) ); + free( filename ); + } + else + { + // Cleanup + mlt_properties_close( properties ); + mlt_profile_close(); + // Failover + mlt_profile_get(); + } + + // Set MLT_NORMALISATION to appease legacy modules + char *profile_name = mlt_environment( "MLT_PROFILE" ); + if ( strstr( profile_name, "_ntsc" ) || + strstr( profile_name, "_60" ) || + strstr( profile_name, "_30" ) ) + { + mlt_environment_set( "MLT_NORMALISATION", "NTSC" ); + } + else if ( strstr( profile_name, "_pal" ) || + strstr( profile_name, "_50" ) || + strstr( profile_name, "_25" ) ) + { + mlt_environment_set( "MLT_NORMALISATION", "PAL" ); + } + + return profile; +} + +/** Load a profile from a properties object +*/ + +mlt_profile mlt_profile_load_properties( mlt_properties properties ) +{ + mlt_profile_close(); + profile = calloc( 1, sizeof( struct mlt_profile_s ) ); + if ( profile ) + { + if ( mlt_properties_get( properties, "name" ) ) + mlt_environment_set( "MLT_PROFILE", mlt_properties_get( properties, "name" ) ); + if ( mlt_properties_get( properties, "description" ) ) + profile->description = strdup( mlt_properties_get( properties, "description" ) ); + profile->frame_rate_num = mlt_properties_get_int( properties, "frame_rate_num" ); + profile->frame_rate_den = mlt_properties_get_int( properties, "frame_rate_den" ); + profile->width = mlt_properties_get_int( properties, "width" ); + profile->height = mlt_properties_get_int( properties, "height" ); + profile->progressive = mlt_properties_get_int( properties, "progressive" ); + profile->sample_aspect_num = mlt_properties_get_int( properties, "sample_aspect_num" ); + profile->sample_aspect_den = mlt_properties_get_int( properties, "sample_aspect_den" ); + profile->display_aspect_num = mlt_properties_get_int( properties, "display_aspect_num" ); + profile->display_aspect_den = mlt_properties_get_int( properties, "display_aspect_den" ); + } + return profile; +} + +/** Load an anonymous profile from string +*/ + +mlt_profile mlt_profile_load_string( const char *string ) +{ + mlt_properties properties = mlt_properties_new(); + if ( properties ) + { + const char *p = string; + while ( p ) + { + if ( strcmp( p, "" ) && p[ 0 ] != '#' ) + mlt_properties_parse( properties, p ); + p = strchr( p, '\n' ); + if ( p ) p++; + } + } + return mlt_profile_load_properties( properties ); +} + +/** Get the framerate as float +*/ + +double mlt_profile_fps( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->frame_rate_num / aprofile->frame_rate_den; + else + return ( double ) mlt_profile_get()->frame_rate_num / mlt_profile_get()->frame_rate_den; +} + +/** Get the sample aspect ratio as float +*/ + +double mlt_profile_sar( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->sample_aspect_num / aprofile->sample_aspect_den; + else + return ( double ) mlt_profile_get()->sample_aspect_num / mlt_profile_get()->sample_aspect_den; +} + +/** Get the display aspect ratio as float +*/ + +double mlt_profile_dar( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->display_aspect_num / aprofile->display_aspect_den; + else + return ( double ) mlt_profile_get()->display_aspect_num / mlt_profile_get()->display_aspect_den; +} + +/** Free up the global profile resources +*/ + +void mlt_profile_close( ) +{ + if ( profile ) + { + if ( profile->description ) + free( profile->description ); + profile->description = NULL; + free( profile ); + profile = NULL; + } +} diff --git a/src/framework/mlt_profile.h b/src/framework/mlt_profile.h new file mode 100644 index 0000000..29f97ae --- /dev/null +++ b/src/framework/mlt_profile.h @@ -0,0 +1,49 @@ +/* + * mlt_profile.h -- video output definition + * Copyright (C) 2007 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROFILE_H +#define _MLT_PROFILE_H + +#include "mlt_types.h" + +struct mlt_profile_s +{ + char* description; + int frame_rate_num; + int frame_rate_den; + int width; + int height; + int progressive; + int sample_aspect_num; + int sample_aspect_den; + int display_aspect_num; + int display_aspect_den; +}; + +extern mlt_profile mlt_profile_get( ); +extern mlt_profile mlt_profile_select( const char *name ); +extern mlt_profile mlt_profile_load_file( const char *file ); +extern mlt_profile mlt_profile_load_properties( mlt_properties properties ); +extern mlt_profile mlt_profile_load_string( const char *string ); +extern double mlt_profile_fps( mlt_profile profile ); +extern double mlt_profile_sar( mlt_profile profile ); +extern double mlt_profile_dar( mlt_profile profile ); +extern void mlt_profile_close( ); +#endif diff --git a/src/framework/mlt_properties.c b/src/framework/mlt_properties.c new file mode 100644 index 0000000..d2b5a8e --- /dev/null +++ b/src/framework/mlt_properties.c @@ -0,0 +1,912 @@ +/* + * mlt_properties.c -- base properties class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_properties.h" +#include "mlt_property.h" + +#include +#include +#include +#include + +#include +#include + +/* ---------------- // Private Implementation // ---------------- */ + +/** Private implementation of the property list. +*/ + +typedef struct +{ + int hash[ 199 ]; + char **name; + mlt_property *value; + int count; + int size; + mlt_properties mirror; + int ref_count; +} +property_list; + +/** Memory leak checks. +*/ + +//#define _MLT_PROPERTY_CHECKS_ 2 + +#ifdef _MLT_PROPERTY_CHECKS_ +static int properties_created = 0; +static int properties_destroyed = 0; +#endif + +/** Basic implementation. +*/ + +int mlt_properties_init( mlt_properties this, void *child ) +{ + if ( this != NULL ) + { +#ifdef _MLT_PROPERTY_CHECKS_ + // Increment number of properties created + properties_created ++; +#endif + + // NULL all methods + memset( this, 0, sizeof( struct mlt_properties_s ) ); + + // Assign the child of the object + this->child = child; + + // Allocate the local structure + this->local = calloc( sizeof( property_list ), 1 ); + + // Increment the ref count + ( ( property_list * )this->local )->ref_count = 1; + } + + // Check that initialisation was successful + return this != NULL && this->local == NULL; +} + +/** Constructor for stand alone object. +*/ + +mlt_properties mlt_properties_new( ) +{ + // Construct a standalone properties object + mlt_properties this = calloc( sizeof( struct mlt_properties_s ), 1 ); + + // Initialise this + mlt_properties_init( this, NULL ); + + // Return the pointer + return this; +} + +/** Load properties from a file. +*/ + +mlt_properties mlt_properties_load( const char *filename ) +{ + // Construct a standalone properties object + mlt_properties this = mlt_properties_new( ); + + if ( this != NULL ) + { + // Open the file + FILE *file = fopen( filename, "r" ); + + // Load contents of file + if ( file != NULL ) + { + // Temp string + char temp[ 1024 ]; + char last[ 1024 ] = ""; + + // Read each string from the file + while( fgets( temp, 1024, file ) ) + { + // Chomp the string + temp[ strlen( temp ) - 1 ] = '\0'; + + // Check if the line starts with a . + if ( temp[ 0 ] == '.' ) + { + char temp2[ 1024 ]; + sprintf( temp2, "%s%s", last, temp ); + strcpy( temp, temp2 ); + } + else if ( strchr( temp, '=' ) ) + { + strcpy( last, temp ); + *( strchr( last, '=' ) ) = '\0'; + } + + // Parse and set the property + if ( strcmp( temp, "" ) && temp[ 0 ] != '#' ) + mlt_properties_parse( this, temp ); + } + + // Close the file + fclose( file ); + } + } + + // Return the pointer + return this; +} + +static inline int generate_hash( const char *name ) +{ + int hash = 0; + int i = 1; + while ( *name ) + hash = ( hash + ( i ++ * ( *name ++ & 31 ) ) ) % 199; + return hash; +} + +/** Special case - when a container (such as fezzik) is protecting another + producer, we need to ensure that properties are passed through to the + real producer. +*/ + +static inline void mlt_properties_do_mirror( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + if ( list->mirror != NULL ) + { + char *value = mlt_properties_get( this, name ); + if ( value != NULL ) + mlt_properties_set( list->mirror, name, value ); + } +} + +/** Maintain ref count to allow multiple uses of an mlt object. +*/ + +int mlt_properties_inc_ref( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return ++ list->ref_count; + } + return 0; +} + +/** Maintain ref count to allow multiple uses of an mlt object. +*/ + +int mlt_properties_dec_ref( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return -- list->ref_count; + } + return 0; +} + +/** Return the ref count of this object. +*/ + +int mlt_properties_ref_count( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return list->ref_count; + } + return 0; +} + +/** Mirror properties set on 'this' to 'that'. +*/ + +void mlt_properties_mirror( mlt_properties this, mlt_properties that ) +{ + property_list *list = this->local; + list->mirror = that; +} + +/** Inherit all serialisable properties from that into this. +*/ + +int mlt_properties_inherit( mlt_properties this, mlt_properties that ) +{ + int count = mlt_properties_count( that ); + int i = 0; + for ( i = 0; i < count; i ++ ) + { + char *value = mlt_properties_get_value( that, i ); + if ( value != NULL ) + { + char *name = mlt_properties_get_name( that, i ); + mlt_properties_set( this, name, value ); + } + } + return 0; +} + +/** Pass all properties from 'that' that match the prefix to 'this' (excluding the prefix). +*/ + +int mlt_properties_pass( mlt_properties this, mlt_properties that, const char *prefix ) +{ + int count = mlt_properties_count( that ); + int length = strlen( prefix ); + int i = 0; + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( that, i ); + if ( !strncmp( name, prefix, length ) ) + { + char *value = mlt_properties_get_value( that, i ); + if ( value != NULL ) + mlt_properties_set( this, name + length, value ); + } + } + return 0; +} + +/** Locate a property by name +*/ + +static inline mlt_property mlt_properties_find( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + mlt_property value = NULL; + int key = generate_hash( name ); + int i = list->hash[ key ] - 1; + + if ( i >= 0 ) + { + // Check if we're hashed + if ( list->count > 0 && + name[ 0 ] == list->name[ i ][ 0 ] && + !strcmp( list->name[ i ], name ) ) + value = list->value[ i ]; + + // Locate the item + for ( i = list->count - 1; value == NULL && i >= 0; i -- ) + if ( name[ 0 ] == list->name[ i ][ 0 ] && !strcmp( list->name[ i ], name ) ) + value = list->value[ i ]; + } + + return value; +} + +/** Add a new property. +*/ + +static mlt_property mlt_properties_add( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + int key = generate_hash( name ); + + // Check that we have space and resize if necessary + if ( list->count == list->size ) + { + list->size += 50; + list->name = realloc( list->name, list->size * sizeof( const char * ) ); + list->value = realloc( list->value, list->size * sizeof( mlt_property ) ); + } + + // Assign name/value pair + list->name[ list->count ] = strdup( name ); + list->value[ list->count ] = mlt_property_init( ); + + // Assign to hash table + if ( list->hash[ key ] == 0 ) + list->hash[ key ] = list->count + 1; + + // Return and increment count accordingly + return list->value[ list->count ++ ]; +} + +/** Fetch a property by name - this includes add if not found. +*/ + +static mlt_property mlt_properties_fetch( mlt_properties this, const char *name ) +{ + // Try to find an existing property first + mlt_property property = mlt_properties_find( this, name ); + + // If it wasn't found, create one + if ( property == NULL ) + property = mlt_properties_add( this, name ); + + // Return the property + return property; +} + +/** Pass property 'name' from 'that' to 'this' +* Who to blame: Zach +*/ + +void mlt_properties_pass_property( mlt_properties this, mlt_properties that, const char *name ) +{ + // Make sure the source property isn't null. + mlt_property that_prop = mlt_properties_find( that, name ); + if( that_prop == NULL ) + return; + + mlt_property_pass( mlt_properties_fetch( this, name ), that_prop ); +} + +/** Pass all properties from 'that' to 'this' as found in comma seperated 'list'. +* Who to blame: Zach +*/ + +int mlt_properties_pass_list( mlt_properties this, mlt_properties that, const char *list ) +{ + char *props = strdup( list ); + char *ptr = props; + char *delim = " ,\t\n"; // Any combination of spaces, commas, tabs, and newlines + int count, done = 0; + + while( !done ) + { + count = strcspn( ptr, delim ); + + if( ptr[count] == '\0' ) + done = 1; + else + ptr[count] = '\0'; // Make it a real string + + mlt_properties_pass_property( this, that, ptr ); + + ptr += count + 1; + ptr += strspn( ptr, delim ); + } + + free( props ); + + return 0; +} + + +/** Set the property. +*/ + +int mlt_properties_set( mlt_properties this, const char *name, const char *value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property == NULL ) + { + fprintf( stderr, "Whoops - %s not found (should never occur)\n", name ); + } + else if ( value == NULL ) + { + error = mlt_property_set_string( property, value ); + mlt_properties_do_mirror( this, name ); + } + else if ( *value != '@' ) + { + error = mlt_property_set_string( property, value ); + mlt_properties_do_mirror( this, name ); + } + else if ( value[ 0 ] == '@' ) + { + int total = 0; + int current = 0; + char id[ 255 ]; + char op = '+'; + + value ++; + + while ( *value != '\0' ) + { + int length = strcspn( value, "+-*/" ); + + // Get the identifier + strncpy( id, value, length ); + id[ length ] = '\0'; + value += length; + + // Determine the value + if ( isdigit( id[ 0 ] ) ) + current = atof( id ); + else + current = mlt_properties_get_int( this, id ); + + // Apply the operation + switch( op ) + { + case '+': + total += current; + break; + case '-': + total -= current; + break; + case '*': + total *= current; + break; + case '/': + total /= current; + break; + } + + // Get the next op + op = *value != '\0' ? *value ++ : ' '; + } + + error = mlt_property_set_int( property, total ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Set or default the property. +*/ + +int mlt_properties_set_or_default( mlt_properties this, const char *name, const char *value, const char *def ) +{ + return mlt_properties_set( this, name, value == NULL ? def : value ); +} + +/** Get a string value by name. +*/ + +char *mlt_properties_get( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? NULL : mlt_property_get_string( value ); +} + +/** Get a name by index. +*/ + +char *mlt_properties_get_name( mlt_properties this, int index ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return list->name[ index ]; + return NULL; +} + +/** Get a string value by index. +*/ + +char *mlt_properties_get_value( mlt_properties this, int index ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return mlt_property_get_string( list->value[ index ] ); + return NULL; +} + +/** Get a data value by index. +*/ + +void *mlt_properties_get_data_at( mlt_properties this, int index, int *size ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return mlt_property_get_data( list->value[ index ], size ); + return NULL; +} + +/** Return the number of items in the list. +*/ + +int mlt_properties_count( mlt_properties this ) +{ + property_list *list = this->local; + return list->count; +} + +/** Set a value by parsing a name=value string +*/ + +int mlt_properties_parse( mlt_properties this, const char *namevalue ) +{ + char *name = strdup( namevalue ); + char *value = NULL; + int error = 0; + char *ptr = strchr( name, '=' ); + + if ( ptr ) + { + *( ptr ++ ) = '\0'; + + if ( *ptr != '\"' ) + { + value = strdup( ptr ); + } + else + { + ptr ++; + value = strdup( ptr ); + if ( value != NULL && value[ strlen( value ) - 1 ] == '\"' ) + value[ strlen( value ) - 1 ] = '\0'; + } + } + else + { + value = strdup( "" ); + } + + error = mlt_properties_set( this, name, value ); + + free( name ); + free( value ); + + return error; +} + +/** Get a value associated to the name. +*/ + +int mlt_properties_get_int( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_int( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_int( mlt_properties this, const char *name, int value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_int( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +int64_t mlt_properties_get_int64( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_int64( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_int64( mlt_properties this, const char *name, int64_t value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_int64( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +double mlt_properties_get_double( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_double( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_double( mlt_properties this, const char *name, double value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_double( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +mlt_position mlt_properties_get_position( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_position( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_position( mlt_properties this, const char *name, mlt_position value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_position( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +void *mlt_properties_get_data( mlt_properties this, const char *name, int *length ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? NULL : mlt_property_get_data( value, length ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_data( mlt_properties this, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + error = mlt_property_set_data( property, value, length, destroy, serialise ); + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Rename a property. +*/ + +int mlt_properties_rename( mlt_properties this, const char *source, const char *dest ) +{ + mlt_property value = mlt_properties_find( this, dest ); + + if ( value == NULL ) + { + property_list *list = this->local; + int i = 0; + + // Locate the item + for ( i = 0; i < list->count; i ++ ) + { + if ( !strcmp( list->name[ i ], source ) ) + { + free( list->name[ i ] ); + list->name[ i ] = strdup( dest ); + list->hash[ generate_hash( dest ) ] = i + 1; + break; + } + } + } + + return value != NULL; +} + +/** Dump the properties. +*/ + +void mlt_properties_dump( mlt_properties this, FILE *output ) +{ + property_list *list = this->local; + int i = 0; + for ( i = 0; i < list->count; i ++ ) + if ( mlt_properties_get( this, list->name[ i ] ) != NULL ) + fprintf( output, "%s=%s\n", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) ); +} + +void mlt_properties_debug( mlt_properties this, const char *title, FILE *output ) +{ + if ( output == NULL ) output = stderr; + fprintf( output, "%s: ", title ); + if ( this != NULL ) + { + property_list *list = this->local; + int i = 0; + fprintf( output, "[ ref=%d", list->ref_count ); + for ( i = 0; i < list->count; i ++ ) + if ( mlt_properties_get( this, list->name[ i ] ) != NULL ) + fprintf( output, ", %s=%s", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) ); + else + fprintf( output, ", %s=%p", list->name[ i ], mlt_properties_get_data( this, list->name[ i ], NULL ) ); + fprintf( output, " ]" ); + } + fprintf( output, "\n" ); +} + +int mlt_properties_save( mlt_properties this, const char *filename ) +{ + int error = 1; + FILE *f = fopen( filename, "w" ); + if ( f != NULL ) + { + mlt_properties_dump( this, f ); + fclose( f ); + error = 0; + } + return error; +} + +/* This is a very basic cross platform fnmatch replacement - it will fail in +** many cases, but for the basic *.XXX and YYY*.XXX, it will work ok. +*/ + +static int mlt_fnmatch( const char *wild, const char *file ) +{ + int f = 0; + int w = 0; + + while( f < strlen( file ) && w < strlen( wild ) ) + { + if ( wild[ w ] == '*' ) + { + w ++; + if ( w == strlen( wild ) ) + f = strlen( file ); + while ( f != strlen( file ) && tolower( file[ f ] ) != tolower( wild[ w ] ) ) + f ++; + } + else if ( wild[ w ] == '?' || tolower( file[ f ] ) == tolower( wild[ w ] ) ) + { + f ++; + w ++; + } + else if ( wild[ 0 ] == '*' ) + { + w = 0; + } + else + { + return 0; + } + } + + return strlen( file ) == f && strlen( wild ) == w; +} + +static int mlt_compare( const void *this, const void *that ) +{ + return strcmp( mlt_property_get_string( *( mlt_property * )this ), mlt_property_get_string( *( mlt_property * )that ) ); +} + +/* Obtains an optionally sorted list of the files found in a directory with a specific wild card. + * Entries in the list have a numeric name (running from 0 to count - 1). Only values change + * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc). + */ + +int mlt_properties_dir_list( mlt_properties this, const char *dirname, const char *pattern, int sort ) +{ + DIR *dir = opendir( dirname ); + + if ( dir ) + { + char key[ 20 ]; + struct dirent *de = readdir( dir ); + char fullname[ 1024 ]; + while( de != NULL ) + { + sprintf( key, "%d", mlt_properties_count( this ) ); + snprintf( fullname, 1024, "%s/%s", dirname, de->d_name ); + if ( de->d_name[ 0 ] != '.' && mlt_fnmatch( pattern, de->d_name ) ) + mlt_properties_set( this, key, fullname ); + de = readdir( dir ); + } + + closedir( dir ); + } + + if ( sort && mlt_properties_count( this ) ) + { + property_list *list = this->local; + qsort( list->value, mlt_properties_count( this ), sizeof( mlt_property ), mlt_compare ); + } + + return mlt_properties_count( this ); +} + +/** Close the list. +*/ + +void mlt_properties_close( mlt_properties this ) +{ + if ( this != NULL && mlt_properties_dec_ref( this ) <= 0 ) + { + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + property_list *list = this->local; + int index = 0; + +#if _MLT_PROPERTY_CHECKS_ == 1 + // Show debug info + mlt_properties_debug( this, "Closing", stderr ); +#endif + +#ifdef _MLT_PROPERTY_CHECKS_ + // Increment destroyed count + properties_destroyed ++; + + // Show current stats - these should match when the app is closed + fprintf( stderr, "Created %d, destroyed %d\n", properties_created, properties_destroyed ); +#endif + + // Clean up names and values + for ( index = list->count - 1; index >= 0; index -- ) + { + free( list->name[ index ] ); + mlt_property_close( list->value[ index ] ); + } + + // Clear up the list + free( list->name ); + free( list->value ); + free( list ); + + // Free this now if this has no child + if ( this->child == NULL ) + free( this ); + } + } +} + diff --git a/src/framework/mlt_properties.h b/src/framework/mlt_properties.h new file mode 100644 index 0000000..33826f3 --- /dev/null +++ b/src/framework/mlt_properties.h @@ -0,0 +1,79 @@ +/* + * mlt_properties.h -- base properties class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROPERTIES_H_ +#define _MLT_PROPERTIES_H_ + +#include "mlt_types.h" +#include "mlt_events.h" +#include + +/** The properties base class defines the basic property propagation and + handling. +*/ + +struct mlt_properties_s +{ + void *child; + void *local; + mlt_destructor close; + void *close_object; +}; + +/** Public interface. +*/ + +extern int mlt_properties_init( mlt_properties, void *child ); +extern mlt_properties mlt_properties_new( ); +extern mlt_properties mlt_properties_load( const char *file ); +extern int mlt_properties_inc_ref( mlt_properties self ); +extern int mlt_properties_dec_ref( mlt_properties self ); +extern int mlt_properties_ref_count( mlt_properties self ); +extern void mlt_properties_mirror( mlt_properties self, mlt_properties that ); +extern int mlt_properties_inherit( mlt_properties self, mlt_properties that ); +extern int mlt_properties_pass( mlt_properties self, mlt_properties that, const char *prefix ); +extern void mlt_properties_pass_property( mlt_properties self, mlt_properties that, const char *name ); +extern int mlt_properties_pass_list( mlt_properties self, mlt_properties that, const char *list ); +extern int mlt_properties_set( mlt_properties self, const char *name, const char *value ); +extern int mlt_properties_set_or_default( mlt_properties self, const char *name, const char *value, const char *def ); +extern int mlt_properties_parse( mlt_properties self, const char *namevalue ); +extern char *mlt_properties_get( mlt_properties self, const char *name ); +extern char *mlt_properties_get_name( mlt_properties self, int index ); +extern char *mlt_properties_get_value( mlt_properties self, int index ); +extern void *mlt_properties_get_data_at( mlt_properties self, int index, int *size ); +extern int mlt_properties_get_int( mlt_properties self, const char *name ); +extern int mlt_properties_set_int( mlt_properties self, const char *name, int value ); +extern int64_t mlt_properties_get_int64( mlt_properties self, const char *name ); +extern int mlt_properties_set_int64( mlt_properties self, const char *name, int64_t value ); +extern double mlt_properties_get_double( mlt_properties self, const char *name ); +extern int mlt_properties_set_double( mlt_properties self, const char *name, double value ); +extern mlt_position mlt_properties_get_position( mlt_properties self, const char *name ); +extern int mlt_properties_set_position( mlt_properties self, const char *name, mlt_position value ); +extern int mlt_properties_set_data( mlt_properties self, const char *name, void *value, int length, mlt_destructor, mlt_serialiser ); +extern void *mlt_properties_get_data( mlt_properties self, const char *name, int *length ); +extern int mlt_properties_rename( mlt_properties self, const char *source, const char *dest ); +extern int mlt_properties_count( mlt_properties self ); +extern void mlt_properties_dump( mlt_properties self, FILE *output ); +extern void mlt_properties_debug( mlt_properties self, const char *title, FILE *output ); +extern int mlt_properties_save( mlt_properties, const char * ); +extern int mlt_properties_dir_list( mlt_properties, const char *, const char *, int ); +extern void mlt_properties_close( mlt_properties self ); + +#endif diff --git a/src/framework/mlt_property.c b/src/framework/mlt_property.c new file mode 100644 index 0000000..516c4ca --- /dev/null +++ b/src/framework/mlt_property.c @@ -0,0 +1,340 @@ +/* + * mlt_property.c -- property class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_property.h" + +#include +#include +#include + +/** Construct and uninitialised property. +*/ + +mlt_property mlt_property_init( ) +{ + mlt_property this = malloc( sizeof( struct mlt_property_s ) ); + if ( this != NULL ) + { + this->types = 0; + this->prop_int = 0; + this->prop_position = 0; + this->prop_double = 0; + this->prop_int64 = 0; + this->prop_string = NULL; + this->data = NULL; + this->length = 0; + this->destructor = NULL; + this->serialiser = NULL; + } + return this; +} + +/** Clear a property. +*/ + +static inline void mlt_property_clear( mlt_property this ) +{ + // Special case data handling + if ( this->types & mlt_prop_data && this->destructor != NULL ) + this->destructor( this->data ); + + // Special case string handling + if ( this->types & mlt_prop_string ) + free( this->prop_string ); + + // Wipe stuff + this->types = 0; + this->prop_int = 0; + this->prop_position = 0; + this->prop_double = 0; + this->prop_int64 = 0; + this->prop_string = NULL; + this->data = NULL; + this->length = 0; + this->destructor = NULL; + this->serialiser = NULL; +} + +/** Set an int on this property. +*/ + +int mlt_property_set_int( mlt_property this, int value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_int; + this->prop_int = value; + return 0; +} + +/** Set a double on this property. +*/ + +int mlt_property_set_double( mlt_property this, double value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_double; + this->prop_double = value; + return 0; +} + +/** Set a position on this property. +*/ + +int mlt_property_set_position( mlt_property this, mlt_position value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_position; + this->prop_position = value; + return 0; +} + +/** Set a string on this property. +*/ + +int mlt_property_set_string( mlt_property this, const char *value ) +{ + if ( value != this->prop_string ) + { + mlt_property_clear( this ); + this->types = mlt_prop_string; + if ( value != NULL ) + this->prop_string = strdup( value ); + } + else + { + this->types = mlt_prop_string; + } + return this->prop_string == NULL; +} + +/** Set an int64 on this property. +*/ + +int mlt_property_set_int64( mlt_property this, int64_t value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_int64; + this->prop_int64 = value; + return 0; +} + +/** Set a data on this property. +*/ + +int mlt_property_set_data( mlt_property this, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser ) +{ + if ( this->data == value ) + this->destructor = NULL; + mlt_property_clear( this ); + this->types = mlt_prop_data; + this->data = value; + this->length = length; + this->destructor = destructor; + this->serialiser = serialiser; + return 0; +} + +static inline int mlt_property_atoi( const char *value ) +{ + if ( value == NULL ) + return 0; + else if ( value[0] == '0' && value[1] == 'x' ) + return strtol( value + 2, NULL, 16 ); + else + return strtol( value, NULL, 10 ); +} + +/** Get an int from this property. +*/ + +int mlt_property_get_int( mlt_property this ) +{ + if ( this->types & mlt_prop_int ) + return this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( int )this->prop_double; + else if ( this->types & mlt_prop_position ) + return ( int )this->prop_position; + else if ( this->types & mlt_prop_int64 ) + return ( int )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return mlt_property_atoi( this->prop_string ); + return 0; +} + +/** Get a double from this property. +*/ + +double mlt_property_get_double( mlt_property this ) +{ + if ( this->types & mlt_prop_double ) + return this->prop_double; + else if ( this->types & mlt_prop_int ) + return ( double )this->prop_int; + else if ( this->types & mlt_prop_position ) + return ( double )this->prop_position; + else if ( this->types & mlt_prop_int64 ) + return ( double )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return atof( this->prop_string ); + return 0; +} + +/** Get a position from this property. +*/ + +mlt_position mlt_property_get_position( mlt_property this ) +{ + if ( this->types & mlt_prop_position ) + return this->prop_position; + else if ( this->types & mlt_prop_int ) + return ( mlt_position )this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( mlt_position )this->prop_double; + else if ( this->types & mlt_prop_int64 ) + return ( mlt_position )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return ( mlt_position )atol( this->prop_string ); + return 0; +} + +static inline int64_t mlt_property_atoll( const char *value ) +{ + if ( value == NULL ) + return 0; + else if ( value[0] == '0' && value[1] == 'x' ) + return strtoll( value + 2, NULL, 16 ); + else + return strtoll( value, NULL, 10 ); +} + +/** Get an int64 from this property. +*/ + +int64_t mlt_property_get_int64( mlt_property this ) +{ + if ( this->types & mlt_prop_int64 ) + return this->prop_int64; + else if ( this->types & mlt_prop_int ) + return ( int64_t )this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( int64_t )this->prop_double; + else if ( this->types & mlt_prop_position ) + return ( int64_t )this->prop_position; + else if ( this->types & mlt_prop_string ) + return mlt_property_atoll( this->prop_string ); + return 0; +} + +/** Get a string from this property. +*/ + +char *mlt_property_get_string( mlt_property this ) +{ + // Construct a string if need be + if ( ! ( this->types & mlt_prop_string ) ) + { + if ( this->types & mlt_prop_int ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%d", this->prop_int ); + } + else if ( this->types & mlt_prop_double ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%f", this->prop_double ); + } + else if ( this->types & mlt_prop_position ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%d", (int)this->prop_position ); /* I don't know if this is wanted. -Zach */ + } + else if ( this->types & mlt_prop_int64 ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%lld", this->prop_int64 ); + } + else if ( this->types & mlt_prop_data && this->serialiser != NULL ) + { + this->types |= mlt_prop_string; + this->prop_string = this->serialiser( this->data, this->length ); + } + } + + // Return the string (may be NULL) + return this->prop_string; +} + +/** Get a data and associated length. +*/ + +void *mlt_property_get_data( mlt_property this, int *length ) +{ + // Assign length if not NULL + if ( length != NULL ) + *length = this->length; + + // Return the data (note: there is no conversion here) + return this->data; +} + +/** Close this property. +*/ + +void mlt_property_close( mlt_property this ) +{ + mlt_property_clear( this ); + free( this ); +} + +/** Pass the property 'that' to 'this'. +* Who to blame: Zach +*/ +void mlt_property_pass( mlt_property this, mlt_property that ) +{ + mlt_property_clear( this ); + + this->types = that->types; + + if ( this->types & mlt_prop_int64 ) + this->prop_int64 = that->prop_int64; + else if ( this->types & mlt_prop_int ) + this->prop_int = that->prop_int; + else if ( this->types & mlt_prop_double ) + this->prop_double = that->prop_double; + else if ( this->types & mlt_prop_position ) + this->prop_position = that->prop_position; + else if ( this->types & mlt_prop_string ) + { + if ( that->prop_string != NULL ) + this->prop_string = strdup( that->prop_string ); + } + else if ( this->types & mlt_prop_data && this->serialiser != NULL ) + { + this->types = mlt_prop_string; + this->prop_string = this->serialiser( this->data, this->length ); + } +} diff --git a/src/framework/mlt_property.h b/src/framework/mlt_property.h new file mode 100644 index 0000000..aefb5f6 --- /dev/null +++ b/src/framework/mlt_property.h @@ -0,0 +1,87 @@ +/* + * mlt_property.h -- property class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROPERTY_H_ +#define _MLT_PROPERTY_H_ + +#include "mlt_types.h" + +/** Bit pattern for properties. +*/ + +typedef enum +{ + mlt_prop_none = 0, + mlt_prop_int = 1, + mlt_prop_string = 2, + mlt_prop_position = 4, + mlt_prop_double = 8, + mlt_prop_data = 16, + mlt_prop_int64 = 32 +} +mlt_property_type; + +/** Property structure. +*/ + +typedef struct mlt_property_s +{ + // Stores a bit pattern of types available for this property + mlt_property_type types; + + // Atomic type handling + int prop_int; + mlt_position prop_position; + double prop_double; + int64_t prop_int64; + + // String handling + char *prop_string; + + // Generic type handling + void *data; + int length; + mlt_destructor destructor; + mlt_serialiser serialiser; +} +*mlt_property; + +/** API +*/ + +extern mlt_property mlt_property_init( ); +extern int mlt_property_set_int( mlt_property self, int value ); +extern int mlt_property_set_double( mlt_property self, double value ); +extern int mlt_property_set_position( mlt_property self, mlt_position value ); +extern int mlt_property_set_int64( mlt_property self, int64_t value ); +extern int mlt_property_set_string( mlt_property self, const char *value ); +extern int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser ); +extern int mlt_property_get_int( mlt_property self ); +extern double mlt_property_get_double( mlt_property self ); +extern mlt_position mlt_property_get_position( mlt_property self ); +extern int64_t mlt_property_get_int64( mlt_property self ); +extern char *mlt_property_get_string( mlt_property self ); +extern void *mlt_property_get_data( mlt_property self, int *length ); +extern void mlt_property_close( mlt_property self ); + +extern void mlt_property_pass( mlt_property this, mlt_property that ); + +#endif + diff --git a/src/framework/mlt_repository.c b/src/framework/mlt_repository.c new file mode 100644 index 0000000..1e07e9d --- /dev/null +++ b/src/framework/mlt_repository.c @@ -0,0 +1,209 @@ +/* + * repository.c -- provides a map between service and shared objects + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_repository.h" +#include "mlt_properties.h" + +#include +#include +#include +#include + +struct mlt_repository_s +{ + struct mlt_properties_s parent; +}; + +static char *construct_full_file( char *output, const char *prefix, const char *file ) +{ + strcpy( output, prefix ); + if ( prefix[ strlen( prefix ) - 1 ] != '/' ) + strcat( output, "/" ); + strcat( output, file ); + return output; +} + +static char *chomp( char *input ) +{ + if ( input[ strlen( input ) - 1 ] == '\n' ) + input[ strlen( input ) - 1 ] = '\0'; + return input; +} + +static mlt_properties construct_object( const char *prefix, const char *id ) +{ + mlt_properties output = mlt_properties_new( ); + mlt_properties_set( output, "prefix", prefix ); + mlt_properties_set( output, "id", id ); + return output; +} + +static mlt_properties construct_service( mlt_properties object, const char *id ) +{ + mlt_properties output = mlt_properties_new( ); + mlt_properties_set_data( output, "object", object, 0, NULL, NULL ); + mlt_properties_set( output, "id", id ); + return output; +} + +static void *construct_instance( mlt_properties service_properties, const char *symbol, void *input ) +{ + // Extract the service + char *service = mlt_properties_get( service_properties, "id" ); + + // Get the object properties + void *object_properties = mlt_properties_get_data( service_properties, "object", NULL ); + + // Get the dlopen'd object + void *object = mlt_properties_get_data( object_properties, "dlopen", NULL ); + + // Get the dlsym'd symbol + void *( *symbol_ptr )( const char *, void * ) = mlt_properties_get_data( object_properties, symbol, NULL ); + + // Check that we have object and open if we don't + if ( object == NULL ) + { + char full_file[ 512 ]; + + // Get the prefix and id of the shared object + char *prefix = mlt_properties_get( object_properties, "prefix" ); + char *file = mlt_properties_get( object_properties, "id" ); + int flags = RTLD_NOW; + + // Very temporary hack to allow the quicktime plugins to work + // TODO: extend repository to allow this to be used on a case by case basis + if ( !strcmp( service, "kino" ) ) + flags |= RTLD_GLOBAL; + + // Construct the full file + construct_full_file( full_file, prefix, file ); + + // Open the shared object + object = dlopen( full_file, flags ); + if ( object != NULL ) + { + // Set it on the properties + mlt_properties_set_data( object_properties, "dlopen", object, 0, ( mlt_destructor )dlclose, NULL ); + } + else + { + fprintf( stderr, "Failed to load plugin: %s\n", dlerror() ); + } + } + + // Now check if we have this symbol pointer + if ( object != NULL && symbol_ptr == NULL ) + { + // Construct it now + symbol_ptr = dlsym( object, symbol ); + + // Set it on the properties + mlt_properties_set_data( object_properties, "dlsym", symbol_ptr, 0, NULL, NULL ); + } + + // Construct the service + return symbol_ptr != NULL ? symbol_ptr( service, input ) : NULL; +} + +mlt_repository mlt_repository_init( mlt_properties object_list, const char *prefix, const char *data, const char *symbol ) +{ + char full_file[ 512 ]; + FILE *file; + + // Construct the repository + mlt_repository this = calloc( sizeof( struct mlt_repository_s ), 1 ); + mlt_properties_init( &this->parent, this ); + + // Add the symbol to THIS repository properties. + mlt_properties_set( &this->parent, "_symbol", symbol ); + + // Construct full file + construct_full_file( full_file, prefix, data ); + strcat( full_file, ".dat" ); + + // Open the file + file = fopen( full_file, "r" ); + + // Parse the contents + if ( file != NULL ) + { + char full[ 512 ]; + char service[ 256 ]; + char object[ 256 ]; + + while( fgets( full, 512, file ) ) + { + chomp( full ); + + if ( full[ 0 ] != '#' && full[ 0 ] != '\0' && sscanf( full, "%s %s", service, object ) == 2 ) + { + // Get the object properties first + mlt_properties object_properties = mlt_properties_get_data( object_list, object, NULL ); + + // If their are no properties, create them now + if ( object_properties == NULL ) + { + // Construct the object + object_properties = construct_object( prefix, object ); + + // Add it to the object list + mlt_properties_set_data( object_list, object, object_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + // Now construct a property for the service + mlt_properties service_properties = construct_service( object_properties, service ); + + // Add it to the repository + mlt_properties_set_data( &this->parent, service, service_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + } + + // Close the file + fclose( file ); + } + + return this; +} + +void *mlt_repository_fetch( mlt_repository this, const char *service, void *input ) +{ + // Get the service properties + mlt_properties service_properties = mlt_properties_get_data( &this->parent, service, NULL ); + + // If the service exists + if ( service_properties != NULL ) + { + // Get the symbol that is used to generate this service + char *symbol = mlt_properties_get( &this->parent, "_symbol" ); + + // Now get an instance of the service + return construct_instance( service_properties, symbol, input ); + } + + return NULL; +} + +void mlt_repository_close( mlt_repository this ) +{ + mlt_properties_close( &this->parent ); + free( this ); +} + + diff --git a/src/framework/mlt_repository.h b/src/framework/mlt_repository.h new file mode 100644 index 0000000..49e269a --- /dev/null +++ b/src/framework/mlt_repository.h @@ -0,0 +1,39 @@ +/* + * repository.h -- provides a map between service and shared objects + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_REPOSITORY_H_ +#define _MLT_REPOSITORY_H_ + +#include "mlt_types.h" + +/** Repository structure forward reference. +*/ + +typedef struct mlt_repository_s *mlt_repository; + +/** Public functions. +*/ + +extern mlt_repository mlt_repository_init( mlt_properties object_list, const char *prefix, const char *file, const char *symbol ); +extern void *mlt_repository_fetch( mlt_repository self, const char *service, void *input ); +extern void mlt_repository_close( mlt_repository self ); + +#endif + diff --git a/src/framework/mlt_service.c b/src/framework/mlt_service.c new file mode 100644 index 0000000..b1cae16 --- /dev/null +++ b/src/framework/mlt_service.c @@ -0,0 +1,529 @@ +/* + * mlt_service.c -- interface for all service classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_service.h" +#include "mlt_filter.h" +#include "mlt_frame.h" +#include +#include +#include +#include + +/** IMPORTANT NOTES + + The base service implements a null frame producing service - as such, + it is functional without extension and will produce test cards frames + and PAL sized audio frames. + + PLEASE DO NOT CHANGE THIS BEHAVIOUR!!! OVERRIDE THE METHODS THAT + CONTROL THIS IN EXTENDING CLASSES. +*/ + +/** Private service definition. +*/ + +typedef struct +{ + int size; + int count; + mlt_service *in; + mlt_service out; + int filter_count; + int filter_size; + mlt_filter *filters; + pthread_mutex_t mutex; +} +mlt_service_base; + +/** Private methods +*/ + +static void mlt_service_disconnect( mlt_service this ); +static void mlt_service_connect( mlt_service this, mlt_service that ); +static int service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); +static void mlt_service_property_changed( mlt_listener, mlt_properties owner, mlt_service this, void **args ); + +/** Constructor +*/ + +int mlt_service_init( mlt_service this, void *child ) +{ + int error = 0; + + // Initialise everything to NULL + memset( this, 0, sizeof( struct mlt_service_s ) ); + + // Assign the child + this->child = child; + + // Generate local space + this->local = calloc( sizeof( mlt_service_base ), 1 ); + + // Associate the methods + this->get_frame = service_get_frame; + + // Initialise the properties + error = mlt_properties_init( &this->parent, this ); + if ( error == 0 ) + { + this->parent.close = ( mlt_destructor )mlt_service_close; + this->parent.close_object = this; + + mlt_events_init( &this->parent ); + mlt_events_register( &this->parent, "service-changed", NULL ); + mlt_events_register( &this->parent, "property-changed", ( mlt_transmitter )mlt_service_property_changed ); + pthread_mutex_init( &( ( mlt_service_base * )this->local )->mutex, NULL ); + } + + return error; +} + +static void mlt_service_property_changed( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ] ); +} + +void mlt_service_lock( mlt_service this ) +{ + if ( this != NULL ) + pthread_mutex_lock( &( ( mlt_service_base * )this->local )->mutex ); +} + +void mlt_service_unlock( mlt_service this ) +{ + if ( this != NULL ) + pthread_mutex_unlock( &( ( mlt_service_base * )this->local )->mutex ); +} + +mlt_service_type mlt_service_identify( mlt_service this ) +{ + mlt_service_type type = invalid_type; + if ( this != NULL ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + char *mlt_type = mlt_properties_get( properties, "mlt_type" ); + char *resource = mlt_properties_get( properties, "resource" ); + if ( mlt_type == NULL ) + type = unknown_type; + else if ( resource == NULL || !strcmp( resource, "" ) ) + type = producer_type; + else if ( !strcmp( resource, "" ) ) + type = playlist_type; + else if ( !strcmp( resource, "" ) ) + type = tractor_type; + else if ( !strcmp( resource, "" ) ) + type = multitrack_type; + else if ( !strcmp( mlt_type, "producer" ) ) + type = producer_type; + else if ( !strcmp( mlt_type, "filter" ) ) + type = filter_type; + else if ( !strcmp( mlt_type, "transition" ) ) + type = transition_type; + else if ( !strcmp( mlt_type, "consumer" ) ) + type = consumer_type; + else + type = unknown_type; + } + return type; +} + +/** Connect a producer service. + Returns: > 0 warning, == 0 success, < 0 serious error + 1 = this service does not accept input + 2 = the producer is invalid + 3 = the producer is already registered with this consumer +*/ + +int mlt_service_connect_producer( mlt_service this, mlt_service producer, int index ) +{ + int i = 0; + + // Get the service base + mlt_service_base *base = this->local; + + // Special case 'track' index - only works for last filter(s) in a particular chain + // but allows a filter to apply to the output frame regardless of which track it comes from + if ( index == -1 ) + index = 0; + + // Check if the producer is already registered with this service + for ( i = 0; i < base->count; i ++ ) + if ( base->in[ i ] == producer ) + return 3; + + // Allocate space + if ( index >= base->size ) + { + int new_size = base->size + index + 10; + base->in = realloc( base->in, new_size * sizeof( mlt_service ) ); + if ( base->in != NULL ) + { + for ( i = base->size; i < new_size; i ++ ) + base->in[ i ] = NULL; + base->size = new_size; + } + } + + // If we have space, assign the input + if ( base->in != NULL && index >= 0 && index < base->size ) + { + // Get the current service + mlt_service current = base->in[ index ]; + + // Increment the reference count on this producer + if ( producer != NULL ) + { + mlt_service_lock( producer ); + mlt_properties_inc_ref( MLT_SERVICE_PROPERTIES( producer ) ); + mlt_service_unlock( producer ); + } + + // Now we disconnect the producer service from its consumer + mlt_service_disconnect( producer ); + + // Add the service to index specified + base->in[ index ] = producer; + + // Determine the number of active tracks + if ( index >= base->count ) + base->count = index + 1; + + // Now we connect the producer to its connected consumer + mlt_service_connect( producer, this ); + + // Close the current service + mlt_service_close( current ); + + // Inform caller that all went well + return 0; + } + else + { + return -1; + } +} + +/** Disconnect this service from its consumer. +*/ + +static void mlt_service_disconnect( mlt_service this ) +{ + if ( this != NULL ) + { + // Get the service base + mlt_service_base *base = this->local; + + // Disconnect + base->out = NULL; + } +} + +/** Obtain the consumer this service is connected to. +*/ + +mlt_service mlt_service_consumer( mlt_service this ) +{ + // Get the service base + mlt_service_base *base = this->local; + + // Return the connected consumer + return base->out; +} + +/** Obtain the producer this service is connected to. +*/ + +mlt_service mlt_service_producer( mlt_service this ) +{ + // Get the service base + mlt_service_base *base = this->local; + + // Return the connected producer + return base->count > 0 ? base->in[ base->count - 1 ] : NULL; +} + +/** Associate this service to the consumer. +*/ + +static void mlt_service_connect( mlt_service this, mlt_service that ) +{ + if ( this != NULL ) + { + // Get the service base + mlt_service_base *base = this->local; + + // There's a bit more required here... + base->out = that; + } +} + +/** Get the first connected producer service. +*/ + +mlt_service mlt_service_get_producer( mlt_service this ) +{ + mlt_service producer = NULL; + + // Get the service base + mlt_service_base *base = this->local; + + if ( base->in != NULL ) + producer = base->in[ 0 ]; + + return producer; +} + +/** Default implementation of get_frame. +*/ + +static int service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ) +{ + mlt_service_base *base = this->local; + if ( index < base->count ) + { + mlt_service producer = base->in[ index ]; + if ( producer != NULL ) + return mlt_service_get_frame( producer, frame, index ); + } + *frame = mlt_frame_init( ); + return 0; +} + +/** Return the properties object. +*/ + +mlt_properties mlt_service_properties( mlt_service self ) +{ + return self != NULL ? &self->parent : NULL; +} + +/** Recursively apply attached filters +*/ + +void mlt_service_apply_filters( mlt_service this, mlt_frame frame, int index ) +{ + int i; + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties service_properties = MLT_SERVICE_PROPERTIES( this ); + mlt_service_base *base = this->local; + mlt_position position = mlt_frame_get_position( frame ); + mlt_position this_in = mlt_properties_get_position( service_properties, "in" ); + mlt_position this_out = mlt_properties_get_position( service_properties, "out" ); + + if ( index == 0 || mlt_properties_get_int( service_properties, "_filter_private" ) == 0 ) + { + // Process the frame with the attached filters + for ( i = 0; i < base->filter_count; i ++ ) + { + if ( base->filters[ i ] != NULL ) + { + mlt_position in = mlt_filter_get_in( base->filters[ i ] ); + mlt_position out = mlt_filter_get_out( base->filters[ i ] ); + int disable = mlt_properties_get_int( MLT_FILTER_PROPERTIES( base->filters[ i ] ), "disable" ); + if ( !disable && ( ( in == 0 && out == 0 ) || ( position >= in && ( position <= out || out == 0 ) ) ) ) + { + mlt_properties_set_position( frame_properties, "in", in == 0 ? this_in : in ); + mlt_properties_set_position( frame_properties, "out", out == 0 ? this_out : out ); + mlt_filter_process( base->filters[ i ], frame ); + mlt_service_apply_filters( MLT_FILTER_SERVICE( base->filters[ i ] ), frame, index + 1 ); + } + } + } + } +} + +/** Obtain a frame. +*/ + +int mlt_service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ) +{ + int result = 0; + + // Lock the service + mlt_service_lock( this ); + + // Ensure that the frame is NULL + *frame = NULL; + + // Only process if we have a valid service + if ( this != NULL && this->get_frame != NULL ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + + result = this->get_frame( this, frame, index ); + + if ( result == 0 ) + { + mlt_properties_inc_ref( properties ); + properties = MLT_FRAME_PROPERTIES( *frame ); + if ( in >=0 && out > 0 ) + { + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); + } + mlt_service_apply_filters( this, *frame, 1 ); + mlt_deque_push_back( MLT_FRAME_SERVICE_STACK( *frame ), this ); + } + } + + // Make sure we return a frame + if ( *frame == NULL ) + *frame = mlt_frame_init( ); + + // Unlock the service + mlt_service_unlock( this ); + + return result; +} + +static void mlt_service_filter_changed( mlt_service owner, mlt_service this ) +{ + mlt_events_fire( MLT_SERVICE_PROPERTIES( this ), "service-changed", NULL ); +} + +/** Attach a filter. +*/ + +int mlt_service_attach( mlt_service this, mlt_filter filter ) +{ + int error = this == NULL || filter == NULL; + if ( error == 0 ) + { + int i = 0; + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + mlt_service_base *base = this->local; + + for ( i = 0; error == 0 && i < base->filter_count; i ++ ) + if ( base->filters[ i ] == filter ) + error = 1; + + if ( error == 0 ) + { + if ( base->filter_count == base->filter_size ) + { + base->filter_size += 10; + base->filters = realloc( base->filters, base->filter_size * sizeof( mlt_filter ) ); + } + + if ( base->filters != NULL ) + { + mlt_properties props = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_inc_ref( MLT_FILTER_PROPERTIES( filter ) ); + base->filters[ base->filter_count ++ ] = filter; + mlt_events_fire( properties, "service-changed", NULL ); + mlt_events_listen( props, this, "service-changed", ( mlt_listener )mlt_service_filter_changed ); + mlt_events_listen( props, this, "property-changed", ( mlt_listener )mlt_service_filter_changed ); + } + else + { + error = 2; + } + } + } + return error; +} + +/** Detach a filter. +*/ + +int mlt_service_detach( mlt_service this, mlt_filter filter ) +{ + int error = this == NULL || filter == NULL; + if ( error == 0 ) + { + int i = 0; + mlt_service_base *base = this->local; + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + + for ( i = 0; i < base->filter_count; i ++ ) + if ( base->filters[ i ] == filter ) + break; + + if ( i < base->filter_count ) + { + base->filters[ i ] = NULL; + for ( i ++ ; i < base->filter_count; i ++ ) + base->filters[ i - 1 ] = base->filters[ i ]; + base->filter_count --; + mlt_events_disconnect( MLT_FILTER_PROPERTIES( filter ), this ); + mlt_filter_close( filter ); + mlt_events_fire( properties, "service-changed", NULL ); + } + } + return error; +} + +/** Retrieve a filter. +*/ + +mlt_filter mlt_service_filter( mlt_service this, int index ) +{ + mlt_filter filter = NULL; + if ( this != NULL ) + { + mlt_service_base *base = this->local; + if ( index >= 0 && index < base->filter_count ) + filter = base->filters[ index ]; + } + return filter; +} + +/** Close the service. +*/ + +void mlt_service_close( mlt_service this ) +{ + mlt_service_lock( this ); + if ( this != NULL && mlt_properties_dec_ref( MLT_SERVICE_PROPERTIES( this ) ) <= 0 ) + { + mlt_service_unlock( this ); + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + mlt_service_base *base = this->local; + int i = 0; + int count = base->filter_count; + mlt_events_block( MLT_SERVICE_PROPERTIES( this ), this ); + while( count -- ) + mlt_service_detach( this, base->filters[ 0 ] ); + free( base->filters ); + for ( i = 0; i < base->count; i ++ ) + if ( base->in[ i ] != NULL ) + mlt_service_close( base->in[ i ] ); + this->parent.close = NULL; + free( base->in ); + pthread_mutex_destroy( &base->mutex ); + free( base ); + mlt_properties_close( &this->parent ); + } + } + else + { + mlt_service_unlock( this ); + } +} + diff --git a/src/framework/mlt_service.h b/src/framework/mlt_service.h new file mode 100644 index 0000000..fea7497 --- /dev/null +++ b/src/framework/mlt_service.h @@ -0,0 +1,69 @@ +/* + * mlt_service.h -- interface for all service classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_SERVICE_H_ +#define _MLT_SERVICE_H_ + +#include "mlt_properties.h" + +/** The interface definition for all services. +*/ + +struct mlt_service_s +{ + /* We're extending properties here */ + struct mlt_properties_s parent; + + /* Protected virtual */ + int ( *get_frame )( mlt_service self, mlt_frame_ptr frame, int index ); + mlt_destructor close; + void *close_object; + + /* Private data */ + void *local; + void *child; +}; + +/** The public API. +*/ + +#define MLT_SERVICE_PROPERTIES( service ) ( &( service )->parent ) + +extern int mlt_service_init( mlt_service self, void *child ); +extern void mlt_service_lock( mlt_service self ); +extern void mlt_service_unlock( mlt_service self ); +extern mlt_service_type mlt_service_identify( mlt_service self ); +extern int mlt_service_connect_producer( mlt_service self, mlt_service producer, int index ); +extern int mlt_service_get_frame( mlt_service self, mlt_frame_ptr frame, int index ); +extern mlt_properties mlt_service_properties( mlt_service self ); +extern mlt_service mlt_service_consumer( mlt_service self ); +extern mlt_service mlt_service_producer( mlt_service self ); +extern int mlt_service_attach( mlt_service self, mlt_filter filter ); +extern int mlt_service_detach( mlt_service self, mlt_filter filter ); +extern void mlt_service_apply_filters( mlt_service self, mlt_frame frame, int index ); +extern mlt_filter mlt_service_filter( mlt_service self, int index ); + +extern void mlt_service_close( mlt_service self ); + +/* I'm not sure about self one - leaving it out of docs for now (only used in consumer_westley) */ +extern mlt_service mlt_service_get_producer( mlt_service self ); + +#endif + diff --git a/src/framework/mlt_tokeniser.c b/src/framework/mlt_tokeniser.c new file mode 100644 index 0000000..06103cd --- /dev/null +++ b/src/framework/mlt_tokeniser.c @@ -0,0 +1,169 @@ +/* + * mlt_tokeniser.c -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include + +/* Application header files */ +#include "mlt_tokeniser.h" + +/** Initialise a tokeniser. +*/ + +mlt_tokeniser mlt_tokeniser_init( ) +{ + return calloc( 1, sizeof( mlt_tokeniser_t ) ); +} + +/** Clear the tokeniser. +*/ + +static void mlt_tokeniser_clear( mlt_tokeniser tokeniser ) +{ + int index = 0; + for ( index = 0; index < tokeniser->count; index ++ ) + free( tokeniser->tokens[ index ] ); + tokeniser->count = 0; + free( tokeniser->input ); + tokeniser->input = NULL; +} + +/** Append a string to the tokeniser. +*/ + +static int mlt_tokeniser_append( mlt_tokeniser tokeniser, char *token ) +{ + int error = 0; + + if ( tokeniser->count == tokeniser->size ) + { + tokeniser->size += 20; + tokeniser->tokens = realloc( tokeniser->tokens, tokeniser->size * sizeof( char * ) ); + } + + if ( tokeniser->tokens != NULL ) + { + tokeniser->tokens[ tokeniser->count ++ ] = strdup( token ); + } + else + { + tokeniser->count = 0; + error = -1; + } + return error; +} + +/** Parse a string by splitting on the delimiter provided. +*/ + +int mlt_tokeniser_parse_new( mlt_tokeniser tokeniser, char *string, char *delimiter ) +{ + int count = 0; + int length = strlen( string ); + int delimiter_size = strlen( delimiter ); + int index = 0; + char *token = strdup( string ); + + mlt_tokeniser_clear( tokeniser ); + tokeniser->input = strdup( string ); + strcpy( token, "" ); + + for ( index = 0; index < length; ) + { + char *start = string + index; + char *end = strstr( start, delimiter ); + + if ( end == NULL ) + { + strcat( token, start ); + mlt_tokeniser_append( tokeniser, token ); + index = length; + count ++; + } + else if ( start != end ) + { + strncat( token, start, end - start ); + index += end - start; + if ( strchr( token, '\"' ) == NULL || token[ strlen( token ) - 1 ] == '\"' ) + { + mlt_tokeniser_append( tokeniser, token ); + strcpy( token, "" ); + count ++; + } + else while ( strncmp( string + index, delimiter, delimiter_size ) == 0 ) + { + strncat( token, delimiter, delimiter_size ); + index += delimiter_size; + } + } + else + { + index += strlen( delimiter ); + } + } + + /* Special case - malformed string condition */ + if ( !strcmp( token, "" ) ) + { + count = 0 - ( count - 1 ); + mlt_tokeniser_append( tokeniser, token ); + } + + free( token ); + return count; +} + +/** Get the original input. +*/ + +char *mlt_tokeniser_get_input( mlt_tokeniser tokeniser ) +{ + return tokeniser->input; +} + +/** Get the number of tokens. +*/ + +int mlt_tokeniser_count( mlt_tokeniser tokeniser ) +{ + return tokeniser->count; +} + +/** Get a token as a string. +*/ + +char *mlt_tokeniser_get_string( mlt_tokeniser tokeniser, int index ) +{ + if ( index < tokeniser->count ) + return tokeniser->tokens[ index ]; + else + return NULL; +} + +/** Close the tokeniser. +*/ + +void mlt_tokeniser_close( mlt_tokeniser tokeniser ) +{ + mlt_tokeniser_clear( tokeniser ); + free( tokeniser->tokens ); + free( tokeniser ); +} diff --git a/src/framework/mlt_tokeniser.h b/src/framework/mlt_tokeniser.h new file mode 100644 index 0000000..357551d --- /dev/null +++ b/src/framework/mlt_tokeniser.h @@ -0,0 +1,46 @@ +/* + * mlt_tokeniser.h -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TOKENISER_H_ +#define _MLT_TOKENISER_H_ + +/** Structure for tokeniser. +*/ + +typedef struct +{ + char *input; + char **tokens; + int count; + int size; +} +*mlt_tokeniser, mlt_tokeniser_t; + +/** Remote parser API. +*/ + +extern mlt_tokeniser mlt_tokeniser_init( ); +extern int mlt_tokeniser_parse_new( mlt_tokeniser self, char *text, char *delimiter ); +extern char *mlt_tokeniser_get_input( mlt_tokeniser self ); +extern int mlt_tokeniser_count( mlt_tokeniser self ); +extern char *mlt_tokeniser_get_string( mlt_tokeniser self, int index ); +extern void mlt_tokeniser_close( mlt_tokeniser self ); + +#endif diff --git a/src/framework/mlt_tractor.c b/src/framework/mlt_tractor.c new file mode 100644 index 0000000..5af2839 --- /dev/null +++ b/src/framework/mlt_tractor.c @@ -0,0 +1,474 @@ +/* + * mlt_tractor.c -- tractor service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_tractor.h" +#include "mlt_frame.h" +#include "mlt_multitrack.h" +#include "mlt_field.h" + +#include +#include +#include +#include + +/** Forward references to static methods. +*/ + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int track ); +static void mlt_tractor_listener( mlt_multitrack tracks, mlt_tractor this ); + +/** Constructor for the tractor. +*/ + +mlt_tractor mlt_tractor_init( ) +{ + mlt_tractor this = calloc( sizeof( struct mlt_tractor_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + mlt_properties_set( properties, "resource", "" ); + mlt_properties_set( properties, "mlt_type", "mlt_producer" ); + mlt_properties_set( properties, "mlt_service", "tractor" ); + mlt_properties_set_int( properties, "in", 0 ); + mlt_properties_set_int( properties, "out", -1 ); + mlt_properties_set_int( properties, "length", 0 ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )mlt_tractor_close; + producer->close_object = this; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +mlt_tractor mlt_tractor_new( ) +{ + mlt_tractor this = calloc( sizeof( struct mlt_tractor_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_field field = mlt_field_new( multitrack, this ); + mlt_properties props = MLT_PRODUCER_PROPERTIES( producer ); + + mlt_properties_set( props, "resource", "" ); + mlt_properties_set( props, "mlt_type", "mlt_producer" ); + mlt_properties_set( props, "mlt_service", "tractor" ); + mlt_properties_set_position( props, "in", 0 ); + mlt_properties_set_position( props, "out", 0 ); + mlt_properties_set_position( props, "length", 0 ); + mlt_properties_set_data( props, "multitrack", multitrack, 0, ( mlt_destructor )mlt_multitrack_close, NULL ); + mlt_properties_set_data( props, "field", field, 0, ( mlt_destructor )mlt_field_close, NULL ); + + mlt_events_listen( MLT_MULTITRACK_PROPERTIES( multitrack ), this, "producer-changed", ( mlt_listener )mlt_tractor_listener ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )mlt_tractor_close; + producer->close_object = this; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +/** Get the service object associated to the tractor. +*/ + +mlt_service mlt_tractor_service( mlt_tractor this ) +{ + return MLT_PRODUCER_SERVICE( &this->parent ); +} + +/** Get the producer object associated to the tractor. +*/ + +mlt_producer mlt_tractor_producer( mlt_tractor this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties object associated to the tractor. +*/ + +mlt_properties mlt_tractor_properties( mlt_tractor this ) +{ + return MLT_PRODUCER_PROPERTIES( &this->parent ); +} + +/** Get the field this tractor is harvesting. +*/ + +mlt_field mlt_tractor_field( mlt_tractor this ) +{ + return mlt_properties_get_data( MLT_TRACTOR_PROPERTIES( this ), "field", NULL ); +} + +/** Get the multitrack this tractor is pulling. +*/ + +mlt_multitrack mlt_tractor_multitrack( mlt_tractor this ) +{ + return mlt_properties_get_data( MLT_TRACTOR_PROPERTIES( this ), "multitrack", NULL ); +} + +/** Ensure the tractors in/out points match the multitrack. +*/ + +void mlt_tractor_refresh( mlt_tractor this ) +{ + mlt_multitrack multitrack = mlt_tractor_multitrack( this ); + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( multitrack ); + mlt_properties self = MLT_TRACTOR_PROPERTIES( this ); + mlt_events_block( properties, self ); + mlt_events_block( self, self ); + mlt_multitrack_refresh( multitrack ); + mlt_properties_set_position( self, "in", 0 ); + mlt_properties_set_position( self, "out", mlt_properties_get_position( properties, "out" ) ); + mlt_events_unblock( self, self ); + mlt_events_unblock( properties, self ); + mlt_properties_set_position( self, "length", mlt_properties_get_position( properties, "length" ) ); +} + +static void mlt_tractor_listener( mlt_multitrack tracks, mlt_tractor this ) +{ + mlt_tractor_refresh( this ); +} + +/** Connect the tractor. +*/ + +int mlt_tractor_connect( mlt_tractor this, mlt_service producer ) +{ + int ret = mlt_service_connect_producer( MLT_TRACTOR_SERVICE( this ), producer, 0 ); + + // This is the producer we're going to connect to + if ( ret == 0 ) + this->producer = producer; + + return ret; +} + +/** Set the producer for a specific track. +*/ + +int mlt_tractor_set_track( mlt_tractor this, mlt_producer producer, int index ) +{ + return mlt_multitrack_connect( mlt_tractor_multitrack( this ), producer, index ); +} + +/** Get the producer for a specific track. +*/ + +mlt_producer mlt_tractor_get_track( mlt_tractor this, int index ) +{ + return mlt_multitrack_track( mlt_tractor_multitrack( this ), index ); +} + +static int producer_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + uint8_t *data = NULL; + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_frame frame = mlt_frame_pop_service( this ); + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties_set( frame_properties, "rescale.interp", mlt_properties_get( properties, "rescale.interp" ) ); + mlt_properties_set_int( frame_properties, "resize_alpha", mlt_properties_get_int( properties, "resize_alpha" ) ); + mlt_properties_set_int( frame_properties, "distort", mlt_properties_get_int( properties, "distort" ) ); + mlt_properties_set_double( frame_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "consumer_aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "consumer_deinterlace", mlt_properties_get_int( properties, "consumer_deinterlace" ) ); + mlt_properties_set( frame_properties, "deinterlace_method", mlt_properties_get( properties, "deinterlace_method" ) ); + mlt_properties_set_int( frame_properties, "normalised_width", mlt_properties_get_int( properties, "normalised_width" ) ); + mlt_properties_set_int( frame_properties, "normalised_height", mlt_properties_get_int( properties, "normalised_height" ) ); + mlt_frame_get_image( frame, buffer, format, width, height, writable ); + mlt_properties_set_data( properties, "image", *buffer, *width * *height * 2, NULL, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_frame_get_aspect_ratio( frame ) ); + mlt_properties_set_int( properties, "progressive", mlt_properties_get_int( frame_properties, "progressive" ) ); + mlt_properties_set_int( properties, "distort", mlt_properties_get_int( frame_properties, "distort" ) ); + data = mlt_frame_get_alpha_mask( frame ); + mlt_properties_set_data( properties, "alpha", data, 0, NULL, NULL ); + return 0; +} + +static int producer_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_frame frame = mlt_frame_pop_audio( this ); + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + mlt_properties_set_data( properties, "audio", *buffer, 0, NULL, NULL ); + mlt_properties_set_int( properties, "frequency", *frequency ); + mlt_properties_set_int( properties, "channels", *channels ); + return 0; +} + +static void destroy_data_queue( void *arg ) +{ + if ( arg != NULL ) + { + // Assign the correct type + mlt_deque queue = arg; + + // Iterate through each item and destroy them + while ( mlt_deque_peek_front( queue ) != NULL ) + mlt_properties_close( mlt_deque_pop_back( queue ) ); + + // Close the deque + mlt_deque_close( queue ); + } +} + +/** Get the next frame. + + TODO: This function needs to be redesigned... +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int track ) +{ + mlt_tractor this = parent->child; + + // We only respond to the first track requests + if ( track == 0 && this->producer != NULL ) + { + int i = 0; + int done = 0; + mlt_frame temp = NULL; + int count = 0; + int image_count = 0; + + // Get the properties of the parent producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Try to obtain the multitrack associated to the tractor + mlt_multitrack multitrack = mlt_properties_get_data( properties, "multitrack", NULL ); + + // Or a specific producer + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + + // The output frame will hold the 'global' data feeds (ie: those which are targetted for the final frame) + mlt_deque data_queue = mlt_deque_init( ); + + // Determine whether this tractor feeds to the consumer or stops here + int global_feed = mlt_properties_get_int( properties, "global_feed" ); + + // If we don't have one, we're in trouble... + if ( multitrack != NULL ) + { + // Used to garbage collect all frames + char label[ 30 ]; + + // Get the id of the tractor + char *id = mlt_properties_get( properties, "_unique_id" ); + + // Will be used to store the frame properties object + mlt_properties frame_properties = NULL; + + // We'll store audio and video frames to use here + mlt_frame audio = NULL; + mlt_frame video = NULL; + mlt_frame first_video = NULL; + + // Temporary properties + mlt_properties temp_properties = NULL; + + // Get the multitrack's producer + mlt_producer target = MLT_MULTITRACK_PRODUCER( multitrack ); + mlt_producer_seek( target, mlt_producer_frame( parent ) ); + mlt_producer_set_speed( target, mlt_producer_get_speed( parent ) ); + + // We will create one frame and attach everything to it + *frame = mlt_frame_init( ); + + // Get the properties of the frame + frame_properties = MLT_FRAME_PROPERTIES( *frame ); + + // Loop through each of the tracks we're harvesting + for ( i = 0; !done; i ++ ) + { + // Get a frame from the producer + mlt_service_get_frame( this->producer, &temp, i ); + + // Get the temporary properties + temp_properties = MLT_FRAME_PROPERTIES( temp ); + + // Check for last track + done = mlt_properties_get_int( temp_properties, "last_track" ); + + // Handle fx only tracks + if ( mlt_properties_get_int( temp_properties, "fx_cut" ) ) + { + int hide = ( video == NULL ? 1 : 0 ) | ( audio == NULL ? 2 : 0 ); + mlt_properties_set_int( temp_properties, "hide", hide ); + } + + // We store all frames with a destructor on the output frame + sprintf( label, "_%s_%d", id, count ++ ); + mlt_properties_set_data( frame_properties, label, temp, 0, ( mlt_destructor )mlt_frame_close, NULL ); + + // We want to append all 'final' feeds to the global queue + if ( !done && mlt_properties_get_data( temp_properties, "data_queue", NULL ) != NULL ) + { + // Move the contents of this queue on to the output frames data queue + mlt_deque sub_queue = mlt_properties_get_data( MLT_FRAME_PROPERTIES( temp ), "data_queue", NULL ); + mlt_deque temp = mlt_deque_init( ); + while ( global_feed && mlt_deque_count( sub_queue ) ) + { + mlt_properties p = mlt_deque_pop_back( sub_queue ); + if ( mlt_properties_get_int( p, "final" ) ) + mlt_deque_push_back( data_queue, p ); + else + mlt_deque_push_back( temp, p ); + } + while( mlt_deque_count( temp ) ) + mlt_deque_push_front( sub_queue, mlt_deque_pop_back( temp ) ); + mlt_deque_close( temp ); + } + + // Now do the same with the global queue but without the conditional behaviour + if ( mlt_properties_get_data( temp_properties, "global_queue", NULL ) != NULL ) + { + mlt_deque sub_queue = mlt_properties_get_data( MLT_FRAME_PROPERTIES( temp ), "global_queue", NULL ); + while ( mlt_deque_count( sub_queue ) ) + { + mlt_properties p = mlt_deque_pop_back( sub_queue ); + mlt_deque_push_back( data_queue, p ); + } + } + + // Pick up first video and audio frames + if ( !done && !mlt_frame_is_test_audio( temp ) && !( mlt_properties_get_int( temp_properties, "hide" ) & 2 ) ) + { + // Order of frame creation is starting to get problematic + if ( audio != NULL ) + { + mlt_deque_push_front( MLT_FRAME_AUDIO_STACK( temp ), producer_get_audio ); + mlt_deque_push_front( MLT_FRAME_AUDIO_STACK( temp ), audio ); + } + audio = temp; + } + if ( !done && !mlt_frame_is_test_card( temp ) && !( mlt_properties_get_int( temp_properties, "hide" ) & 1 ) ) + { + if ( video != NULL ) + { + mlt_deque_push_front( MLT_FRAME_IMAGE_STACK( temp ), producer_get_image ); + mlt_deque_push_front( MLT_FRAME_IMAGE_STACK( temp ), video ); + } + video = temp; + if ( first_video == NULL ) + first_video = temp; + + // Ensure that all frames know the aspect ratio of the background + mlt_properties_set_double( temp_properties, "output_ratio", + mlt_properties_get_double( MLT_FRAME_PROPERTIES( first_video ), "aspect_ratio" ) ); + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( temp ), "image_count", ++ image_count ); + image_count = 1; + } + } + + // Now stack callbacks + if ( audio != NULL ) + { + mlt_frame_push_audio( *frame, audio ); + mlt_frame_push_audio( *frame, producer_get_audio ); + } + + if ( video != NULL ) + { + mlt_properties video_properties = MLT_FRAME_PROPERTIES( first_video ); + mlt_frame_push_service( *frame, video ); + mlt_frame_push_service( *frame, producer_get_image ); + if ( global_feed ) + mlt_properties_set_data( frame_properties, "data_queue", data_queue, 0, NULL, NULL ); + mlt_properties_set_data( video_properties, "global_queue", data_queue, 0, destroy_data_queue, NULL ); + mlt_properties_set_int( frame_properties, "width", mlt_properties_get_int( video_properties, "width" ) ); + mlt_properties_set_int( frame_properties, "height", mlt_properties_get_int( video_properties, "height" ) ); + mlt_properties_set_int( frame_properties, "real_width", mlt_properties_get_int( video_properties, "real_width" ) ); + mlt_properties_set_int( frame_properties, "real_height", mlt_properties_get_int( video_properties, "real_height" ) ); + mlt_properties_set_int( frame_properties, "progressive", mlt_properties_get_int( video_properties, "progressive" ) ); + mlt_properties_set_double( frame_properties, "aspect_ratio", mlt_properties_get_double( video_properties, "aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "image_count", image_count ); + } + else + { + destroy_data_queue( data_queue ); + } + + mlt_frame_set_position( *frame, mlt_producer_frame( parent ) ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_audio", audio == NULL ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", video == NULL ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "consumer_lock_service", this, 0, NULL, NULL ); + } + else if ( producer != NULL ) + { + mlt_producer_seek( producer, mlt_producer_frame( parent ) ); + mlt_producer_set_speed( producer, mlt_producer_get_speed( parent ) ); + mlt_service_get_frame( this->producer, frame, track ); + } + else + { + fprintf( stderr, "tractor without a multitrack!!\n" ); + mlt_service_get_frame( this->producer, frame, track ); + } + + // Prepare the next frame + mlt_producer_prepare_next( parent ); + + // Indicate our found status + return 0; + } + else + { + // Generate a test card + *frame = mlt_frame_init( ); + return 0; + } +} + +/** Close the tractor. +*/ + +void mlt_tractor_close( mlt_tractor this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_TRACTOR_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + mlt_producer_close( &this->parent ); + free( this ); + } +} + diff --git a/src/framework/mlt_tractor.h b/src/framework/mlt_tractor.h new file mode 100644 index 0000000..acad893 --- /dev/null +++ b/src/framework/mlt_tractor.h @@ -0,0 +1,52 @@ +/* + * mlt_tractor.h -- tractor service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TRACTOR_H_ +#define _MLT_TRACTOR_H_ + +#include "mlt_producer.h" + +/** Private structure. +*/ + +struct mlt_tractor_s +{ + struct mlt_producer_s parent; + mlt_service producer; +}; + +#define MLT_TRACTOR_PRODUCER( tractor ) ( &( tractor )->parent ) +#define MLT_TRACTOR_SERVICE( tractor ) MLT_PRODUCER_SERVICE( MLT_TRACTOR_PRODUCER( tractor ) ) +#define MLT_TRACTOR_PROPERTIES( tractor ) MLT_SERVICE_PROPERTIES( MLT_TRACTOR_SERVICE( tractor ) ) + +extern mlt_tractor mlt_tractor_init( ); +extern mlt_tractor mlt_tractor_new( ); +extern mlt_service mlt_tractor_service( mlt_tractor self ); +extern mlt_producer mlt_tractor_producer( mlt_tractor self ); +extern mlt_properties mlt_tractor_properties( mlt_tractor self ); +extern mlt_field mlt_tractor_field( mlt_tractor self ); +extern mlt_multitrack mlt_tractor_multitrack( mlt_tractor self ); +extern int mlt_tractor_connect( mlt_tractor self, mlt_service service ); +extern void mlt_tractor_refresh( mlt_tractor self ); +extern int mlt_tractor_set_track( mlt_tractor self, mlt_producer producer, int index ); +extern mlt_producer mlt_tractor_get_track( mlt_tractor self, int index ); +extern void mlt_tractor_close( mlt_tractor self ); + +#endif diff --git a/src/framework/mlt_transition.c b/src/framework/mlt_transition.c new file mode 100644 index 0000000..5606c60 --- /dev/null +++ b/src/framework/mlt_transition.c @@ -0,0 +1,326 @@ +/* + * mlt_transition.c -- abstraction for all transition services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_transition.h" +#include "mlt_frame.h" + +#include +#include +#include + +/** Forward references. +*/ + +static int transition_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + +/** Constructor. +*/ + +int mlt_transition_init( mlt_transition this, void *child ) +{ + mlt_service service = &this->parent; + memset( this, 0, sizeof( struct mlt_transition_s ) ); + this->child = child; + if ( mlt_service_init( service, this ) == 0 ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + service->get_frame = transition_get_frame; + service->close = ( mlt_destructor )mlt_transition_close; + service->close_object = this; + + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "a_track", 0 ); + mlt_properties_set_int( properties, "b_track", 1 ); + + return 0; + } + return 1; +} + +/** Create a new transition. +*/ + +mlt_transition mlt_transition_new( ) +{ + mlt_transition this = calloc( 1, sizeof( struct mlt_transition_s ) ); + if ( this != NULL ) + mlt_transition_init( this, NULL ); + return this; +} + +/** Get the service associated to the transition. +*/ + +mlt_service mlt_transition_service( mlt_transition this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties interface. +*/ + +mlt_properties mlt_transition_properties( mlt_transition this ) +{ + return MLT_TRANSITION_PROPERTIES( this ); +} + +/** Connect this transition with a producers a and b tracks. +*/ + +int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track ) +{ + int ret = mlt_service_connect_producer( &this->parent, producer, a_track ); + if ( ret == 0 ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + this->producer = producer; + mlt_properties_set_int( properties, "a_track", a_track ); + mlt_properties_set_int( properties, "b_track", b_track ); + } + return ret; +} + +/** Set the in and out points. +*/ + +void mlt_transition_set_in_and_out( mlt_transition this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); +} + +/** Get the index of the a track. +*/ + +int mlt_transition_get_a_track( mlt_transition this ) +{ + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "a_track" ); +} + +/** Get the index of the b track. +*/ + +int mlt_transition_get_b_track( mlt_transition this ) +{ + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "b_track" ); +} + +/** Get the in point. +*/ + +mlt_position mlt_transition_get_in( mlt_transition this ) +{ + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( this ), "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_transition_get_out( mlt_transition this ) +{ + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( this ), "out" ); +} + +/** Process the frame. + + If we have no process method (unlikely), we simply return the a_frame unmolested. +*/ + +mlt_frame mlt_transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +{ + if ( this->process == NULL ) + return a_frame; + else + return this->process( this, a_frame, b_frame ); +} + +/** Get a frame from this transition. + + The logic is complex here. A transition is typically applied to frames on the a and + b tracks specified in the connect method above and only if both contain valid info + for the transition type (this is either audio or image). + + However, the fixed a_track may not always contain data of the correct type, eg: + + +---------+ +-------+ + |c1 | |c5 | <-- A(0,1) <-- B(0,2) <-- get frame + +---------+ +---------+-+-----+ | | + |c4 | <------+ | + +----------+-----------+-+---------+ | + |c2 |c3 | <-----------------+ + +----------+-------------+ + + During the overlap of c1 and c2, there is nothing for the A transition to do, so this + results in a no operation, but B is triggered. During the overlap of c2 and c3, again, + the A transition is inactive and because the B transition is pointing at track 0, + it too would be inactive. This isn't an ideal situation - it's better if the B + transition simply treats the frames from c3 as though they're the a track. + + For this to work, we cache all frames coming from all tracks between the a and b + tracks. Before we process, we determine that the b frame contains someting of the + right type and then we determine which frame to use as the a frame (selecting a + matching frame from a_track to b_track - 1). If both frames contain data of the + correct type, we process the transition. + + This method is invoked for each track and we return the cached frames as needed. + We clear the cache only when the requested frame is flagged as a 'last_track' frame. +*/ + +static int transition_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + int error = 0; + mlt_transition this = service->child; + + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + int accepts_blanks = mlt_properties_get_int( properties, "accepts_blanks" ); + int a_track = mlt_properties_get_int( properties, "a_track" ); + int b_track = mlt_properties_get_int( properties, "b_track" ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + int always_active = mlt_properties_get_int( properties, "always_active" ); + int type = mlt_properties_get_int( properties, "_transition_type" ); + int reverse_order = 0; + + // Ensure that we have the correct order + if ( a_track > b_track ) + { + reverse_order = 1; + a_track = b_track; + b_track = mlt_properties_get_int( properties, "a_track" ); + } + + // Only act on this operation once per multitrack iteration from the tractor + if ( !this->held ) + { + int active = 0; + int i = 0; + int a_frame = a_track; + int b_frame = b_track; + mlt_position position; + int ( *invalid )( mlt_frame ) = type == 1 ? mlt_frame_is_test_card : mlt_frame_is_test_audio; + + // Initialise temporary store + if ( this->frames == NULL ) + this->frames = calloc( sizeof( mlt_frame ), b_track + 1 ); + + // Get all frames between a and b + for( i = a_track; i <= b_track; i ++ ) + mlt_service_get_frame( this->producer, &this->frames[ i ], i ); + + // We're holding these frames until the last_track frame property is received + this->held = 1; + + // When we need to locate the a_frame + switch( type ) + { + case 1: + case 2: + // Some transitions (esp. audio) may accept blank frames + active = accepts_blanks; + + // If we're not active then... + if ( !active ) + { + // Hunt for the a_frame + while( a_frame <= b_frame && invalid( this->frames[ a_frame ] ) ) + a_frame ++; + + // Determine if we're active now + active = a_frame != b_frame && !invalid( this->frames[ b_frame ] ); + } + break; + + default: + fprintf( stderr, "invalid transition type\n" ); + break; + } + + // Now handle the non-always active case + if ( active && !always_active ) + { + // For non-always-active transitions, we need the current position of the a frame + position = mlt_frame_get_position( this->frames[ a_frame ] ); + + // If a is in range, we're active + active = position >= in && position <= out; + } + + // Finally, process the a and b frames + if ( active ) + { + mlt_frame a_frame_ptr = this->frames[ !reverse_order ? a_frame : b_frame ]; + mlt_frame b_frame_ptr = this->frames[ !reverse_order ? b_frame : a_frame ]; + int a_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide" ); + int b_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide" ); + if ( !( a_hide & type ) && !( b_hide & type ) ) + { + // Process the transition + *frame = mlt_transition_process( this, a_frame_ptr, b_frame_ptr ); + + // We need to ensure that the tractor doesn't consider this frame for output + if ( *frame == a_frame_ptr ) + b_hide |= type; + else + a_hide |= type; + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide", a_hide ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide", b_hide ); + } + } + } + + // Obtain the frame from the cache or the producer we're attached to + if ( index >= a_track && index <= b_track ) + *frame = this->frames[ index ]; + else + error = mlt_service_get_frame( this->producer, frame, index ); + + // Determine if that was the last track + this->held = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( *frame ), "last_track" ); + + return error; +} + +/** Close the transition. +*/ + +void mlt_transition_close( mlt_transition this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_TRANSITION_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + if ( this->close != NULL ) + { + this->close( this ); + } + else + { + mlt_service_close( &this->parent ); + free( this->frames ); + free( this ); + } + } +} diff --git a/src/framework/mlt_transition.h b/src/framework/mlt_transition.h new file mode 100644 index 0000000..d1e32de --- /dev/null +++ b/src/framework/mlt_transition.h @@ -0,0 +1,70 @@ +/* + * mlt_transition.h -- abstraction for all transition services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TRANSITION_H_ +#define _MLT_TRANSITION_H_ + +#include "mlt_service.h" + +/** The interface definition for all transitions. +*/ + +struct mlt_transition_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + void ( *close )( mlt_transition ); + + /* protected transition method */ + mlt_frame ( *process )( mlt_transition, mlt_frame, mlt_frame ); + + /* Protected */ + void *child; + + /* track and in/out points */ + mlt_service producer; + + /* Private */ + mlt_frame *frames; + int held; +}; + +/** Public final methods +*/ + +#define MLT_TRANSITION_SERVICE( transition ) ( &( transition )->parent ) +#define MLT_TRANSITION_PROPERTIES( transition ) MLT_SERVICE_PROPERTIES( MLT_TRANSITION_SERVICE( transition ) ) + +extern int mlt_transition_init( mlt_transition self, void *child ); +extern mlt_transition mlt_transition_new( ); +extern mlt_service mlt_transition_service( mlt_transition self ); +extern mlt_properties mlt_transition_properties( mlt_transition self ); +extern int mlt_transition_connect( mlt_transition self, mlt_service producer, int a_track, int b_track ); +extern void mlt_transition_set_in_and_out( mlt_transition self, mlt_position in, mlt_position out ); +extern int mlt_transition_get_a_track( mlt_transition self ); +extern int mlt_transition_get_b_track( mlt_transition self ); +extern mlt_position mlt_transition_get_in( mlt_transition self ); +extern mlt_position mlt_transition_get_out( mlt_transition self ); +extern mlt_frame mlt_transition_process( mlt_transition self, mlt_frame a_frame, mlt_frame b_frame ); +extern void mlt_transition_close( mlt_transition self ); + +#endif diff --git a/src/framework/mlt_types.h b/src/framework/mlt_types.h new file mode 100644 index 0000000..dde83e0 --- /dev/null +++ b/src/framework/mlt_types.h @@ -0,0 +1,110 @@ +/* + * mlt_types.h -- provides forward definitions of all public types + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TYPES_H_ +#define _MLT_TYPES_H_ + +#ifndef GCC_VERSION +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#include + +#include "mlt_pool.h" + +typedef enum +{ + mlt_image_none = 0, + mlt_image_rgb24, + mlt_image_rgb24a, + mlt_image_yuv422, + mlt_image_yuv420p, + mlt_image_opengl +} +mlt_image_format; + +typedef enum +{ + mlt_audio_none = 0, + mlt_audio_pcm +} +mlt_audio_format; + +typedef enum +{ + mlt_whence_relative_start, + mlt_whence_relative_current, + mlt_whence_relative_end +} +mlt_whence; + +typedef enum +{ + invalid_type, + unknown_type, + producer_type, + playlist_type, + tractor_type, + multitrack_type, + filter_type, + transition_type, + consumer_type, + field_type +} +mlt_service_type; + +/* I don't want to break anyone's applications without warning. -Zach */ +#undef DOUBLE_MLT_POSITION +#ifdef DOUBLE_MLT_POSITION +typedef double mlt_position; +#else +typedef int32_t mlt_position; +#endif + +typedef struct mlt_frame_s *mlt_frame, **mlt_frame_ptr; +typedef struct mlt_properties_s *mlt_properties; +typedef struct mlt_event_struct *mlt_event; +typedef struct mlt_service_s *mlt_service; +typedef struct mlt_producer_s *mlt_producer; +typedef struct mlt_playlist_s *mlt_playlist; +typedef struct mlt_multitrack_s *mlt_multitrack; +typedef struct mlt_filter_s *mlt_filter; +typedef struct mlt_transition_s *mlt_transition; +typedef struct mlt_tractor_s *mlt_tractor; +typedef struct mlt_field_s *mlt_field; +typedef struct mlt_consumer_s *mlt_consumer; +typedef struct mlt_parser_s *mlt_parser; +typedef struct mlt_deque_s *mlt_deque; +typedef struct mlt_geometry_s *mlt_geometry; +typedef struct mlt_geometry_item_s *mlt_geometry_item; +typedef struct mlt_profile_s *mlt_profile; + +typedef void ( *mlt_destructor )( void * ); +typedef char *( *mlt_serialiser )( void *, int length ); + +#define MLT_SERVICE(x) ( ( mlt_service )( x ) ) +#define MLT_PRODUCER(x) ( ( mlt_producer )( x ) ) +#define MLT_MULTITRACK(x) ( ( mlt_multitrack )( x ) ) +#define MLT_PLAYLIST(x) ( ( mlt_playlist )( x ) ) +#define MLT_TRACTOR(x) ( ( mlt_tractor )( x ) ) +#define MLT_FILTER(x) ( ( mlt_filter )( x ) ) +#define MLT_TRANSITION(x) ( ( mlt_transition )( x ) ) + +#endif diff --git a/src/humperdink/Makefile b/src/humperdink/Makefile new file mode 100644 index 0000000..47ba3ed --- /dev/null +++ b/src/humperdink/Makefile @@ -0,0 +1,38 @@ +include ../../config.mak + +TARGET = humperdink + +OBJS = client.o \ + io.o \ + remote.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../valerie -L../framework -lvalerie -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/humperdink/client.c b/src/humperdink/client.c new file mode 100644 index 0000000..084899a --- /dev/null +++ b/src/humperdink/client.c @@ -0,0 +1,1025 @@ +/* + * client.c -- Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include + +/* Application header files */ +#include "client.h" +#include "io.h" + +/** Clip navigation enumeration. +*/ + +typedef enum +{ + absolute, + relative +} +dv_demo_whence; + +/** Function prototype for menu handling. +*/ + +typedef valerie_error_code (*demo_function)( dv_demo ); + +/** The menu structure. +*/ + +typedef struct +{ + char *description; + struct menu_item + { + char *option; + demo_function function; + } + array[ 50 ]; +} +*dv_demo_menu, dv_demo_menu_t; + +/** Forward reference to menu runner. +*/ + +extern valerie_error_code dv_demo_run_menu( dv_demo, dv_demo_menu ); + +/** Foward references. +*/ + +extern valerie_error_code dv_demo_list_nodes( dv_demo ); +extern valerie_error_code dv_demo_add_unit( dv_demo ); +extern valerie_error_code dv_demo_select_unit( dv_demo ); +extern valerie_error_code dv_demo_execute( dv_demo ); +extern valerie_error_code dv_demo_load( dv_demo ); +extern valerie_error_code dv_demo_transport( dv_demo ); +static void *dv_demo_status_thread( void * ); + +/** Connected menu definition. +*/ + +dv_demo_menu_t connected_menu = +{ + "Connected Menu", + { + { "Add Unit", dv_demo_add_unit }, + { "Select Unit", dv_demo_select_unit }, + { "Command Shell", dv_demo_execute }, + { NULL, NULL } + } +}; + +/** Initialise the demo structure. +*/ + +dv_demo dv_demo_init( valerie_parser parser ) +{ + dv_demo this = malloc( sizeof( dv_demo_t ) ); + if ( this != NULL ) + { + int index = 0; + memset( this, 0, sizeof( dv_demo_t ) ); + strcpy( this->last_directory, "/" ); + for ( index = 0; index < 4; index ++ ) + { + this->queues[ index ].unit = index; + this->queues[ index ].position = -1; + } + this->parser = parser; + } + return this; +} + +/** Display a status record. +*/ + +void dv_demo_show_status( dv_demo demo, valerie_status status ) +{ + if ( status->unit == demo->selected_unit && demo->showing ) + { + char temp[ 1024 ] = ""; + + sprintf( temp, "U%d ", demo->selected_unit ); + + switch( status->status ) + { + case unit_offline: + strcat( temp, "offline " ); + break; + case unit_undefined: + strcat( temp, "undefined " ); + break; + case unit_not_loaded: + strcat( temp, "unloaded " ); + break; + case unit_stopped: + strcat( temp, "stopped " ); + break; + case unit_playing: + strcat( temp, "playing " ); + break; + case unit_paused: + strcat( temp, "paused " ); + break; + case unit_disconnected: + strcat( temp, "disconnect" ); + break; + default: + strcat( temp, "unknown " ); + break; + } + + sprintf( temp + strlen( temp ), " %9d %9d %9d ", status->in, status->position, status->out ); + strcat( temp, status->clip ); + + printf( "%-80.80s\r", temp ); + fflush( stdout ); + } +} + +/** Determine action to carry out as dictated by the client unit queue. +*/ + +void dv_demo_queue_action( dv_demo demo, valerie_status status ) +{ + dv_demo_queue queue = &demo->queues[ status->unit ]; + + /* SPECIAL CASE STATUS NOTIFICATIONS TO IGNORE */ + + /* When we've issued a LOAD on the previous notification, then ignore this one. */ + if ( queue->ignore ) + { + queue->ignore --; + return; + } + + if ( queue->mode && status->status != unit_offline && queue->head != queue->tail ) + { + if ( ( status->position >= status->out && status->speed > 0 ) || status->status == unit_not_loaded ) + { + queue->position = ( queue->position + 1 ) % 50; + if ( queue->position == queue->tail ) + queue->position = queue->head; + valerie_unit_load( demo->dv_status, status->unit, queue->list[ queue->position ] ); + if ( status->status == unit_not_loaded ) + valerie_unit_play( demo->dv, queue->unit ); + queue->ignore = 1; + } + else if ( ( status->position <= status->in && status->speed < 0 ) || status->status == unit_not_loaded ) + { + if ( queue->position == -1 ) + queue->position = queue->head; + valerie_unit_load( demo->dv_status, status->unit, queue->list[ queue->position ] ); + if ( status->status == unit_not_loaded ) + valerie_unit_play( demo->dv, queue->unit ); + queue->position = ( queue->position - 1 ) % 50; + queue->ignore = 1; + } + } +} + +/** Status thread. +*/ + +static void *dv_demo_status_thread( void *arg ) +{ + dv_demo demo = arg; + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv_status ); + + while ( !demo->terminated ) + { + if ( valerie_notifier_wait( notifier, &status ) != -1 ) + { + dv_demo_queue_action( demo, &status ); + dv_demo_show_status( demo, &status ); + if ( status.status == unit_disconnected ) + demo->disconnected = 1; + } + } + + return NULL; +} + +/** Turn on/off status display. +*/ + +void dv_demo_change_status( dv_demo demo, int flag ) +{ + if ( demo->disconnected && flag ) + { + valerie_error_code error = valerie_connect( demo->dv ); + if ( error == valerie_ok ) + demo->disconnected = 0; + else + beep(); + } + + if ( flag ) + { + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + demo->showing = 1; + dv_demo_show_status( demo, &status ); + } + else + { + demo->showing = 0; + printf( "%-80.80s\r", " " ); + fflush( stdout ); + } +} + +/** Add a unit. +*/ + +valerie_error_code dv_demo_add_unit( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + valerie_nodes nodes = valerie_nodes_init( demo->dv ); + valerie_units units = valerie_units_init( demo->dv ); + + if ( valerie_nodes_count( nodes ) != -1 && valerie_units_count( units ) != -1 ) + { + char pressed; + valerie_node_entry_t node; + valerie_unit_entry_t unit; + int node_index = 0; + int unit_index = 0; + + printf( "Select a Node\n\n" ); + + for ( node_index = 0; node_index < valerie_nodes_count( nodes ); node_index ++ ) + { + valerie_nodes_get( nodes, node_index, &node ); + printf( "%d: %s - %s ", node_index + 1, node.guid, node.name ); + for ( unit_index = 0; unit_index < valerie_units_count( units ); unit_index ++ ) + { + valerie_units_get( units, unit_index, &unit ); + if ( !strcmp( unit.guid, node.guid ) ) + printf( "[U%d] ", unit.unit ); + } + printf( "\n" ); + } + + printf( "0. Exit\n\n" ); + + printf( "Node: " ); + + while ( ( pressed = get_keypress( ) ) != '0' ) + { + node_index = pressed - '1'; + if ( node_index >= 0 && node_index < valerie_nodes_count( nodes ) ) + { + int unit; + printf( "%c\n\n", pressed ); + valerie_nodes_get( nodes, node_index, &node ); + if ( valerie_unit_add( demo->dv, node.guid, &unit ) == valerie_ok ) + { + printf( "Unit added as U%d\n", unit ); + demo->selected_unit = unit; + } + else + { + int index = 0; + valerie_response response = valerie_get_last_response( demo->dv ); + printf( "Failed to add unit:\n\n" ); + for( index = 1; index < valerie_response_count( response ) - 1; index ++ ) + printf( "%s\n", valerie_response_get_line( response, index ) ); + } + printf( "\n" ); + wait_for_any_key( NULL ); + break; + } + else + { + beep( ); + } + } + } + else + { + printf( "Invalid response from the server.\n\n" ); + wait_for_any_key( NULL ); + } + + valerie_nodes_close( nodes ); + valerie_units_close( units ); + + return error; +} + +/** Select a unit. +*/ + +valerie_error_code dv_demo_select_unit( dv_demo demo ) +{ + int terminated = 0; + int refresh = 1; + + while ( !terminated ) + { + valerie_units units = valerie_units_init( demo->dv ); + + if ( valerie_units_count( units ) > 0 ) + { + valerie_unit_entry_t unit; + int index = 0; + char key = '\0'; + + if ( refresh ) + { + printf( "Select a Unit\n\n" ); + + for ( index = 0; index < valerie_units_count( units ); index ++ ) + { + valerie_units_get( units, index, &unit ); + printf( "%d: U%d - %s [%s]\n", index + 1, + unit.unit, + unit.guid, + unit.online ? "online" : "offline" ); + } + printf( "0: Exit\n\n" ); + + printf( "Unit [%d]: ", demo->selected_unit + 1 ); + refresh = 0; + } + + key = get_keypress( ); + + if ( key == '\r' ) + key = demo->selected_unit + '1'; + + if ( key != '0' ) + { + if ( key >= '1' && key < '1' + valerie_units_count( units ) ) + { + demo->selected_unit = key - '1'; + printf( "%c\n\n", key ); + dv_demo_load( demo ); + refresh = 1; + } + else + { + beep( ); + } + } + else + { + printf( "0\n\n" ); + terminated = 1; + } + } + else if ( valerie_units_count( units ) == 0 ) + { + printf( "No units added - add a unit first\n\n" ); + dv_demo_add_unit( demo ); + } + else + { + printf( "Unable to obtain Unit List.\n" ); + terminated = 1; + } + + valerie_units_close( units ); + } + + return valerie_ok; +} + +/** Execute an arbitrary command. +*/ + +valerie_error_code dv_demo_execute( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + char command[ 10240 ]; + int terminated = 0; + + printf( "Miracle Shell\n" ); + printf( "Enter an empty command to exit.\n\n" ); + + while ( !terminated ) + { + terminated = 1; + printf( "Command> " ); + + if ( chomp( io_get_string( command, 10240, "" ) ) != NULL ) + { + if ( strcmp( command, "" ) ) + { + int index = 0; + valerie_response response = NULL; + error = valerie_execute( demo->dv, 10240, command ); + printf( "\n" ); + response = valerie_get_last_response( demo->dv ); + for ( index = 0; index < valerie_response_count( response ); index ++ ) + { + char *line = valerie_response_get_line( response, index ); + printf( "%4d: %s\n", index, line ); + } + printf( "\n" ); + terminated = 0; + } + } + } + + printf( "\n" ); + + return error; +} + +/** Add a file to the queue. +*/ + +valerie_error_code dv_demo_queue_add( dv_demo demo, dv_demo_queue queue, char *file ) +{ + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + + if ( ( queue->tail + 1 ) % 50 == queue->head ) + queue->head = ( queue->head + 1 ) % 50; + strcpy( queue->list[ queue->tail ], file ); + queue->tail = ( queue->tail + 1 ) % 50; + + valerie_notifier_get( notifier, &status, queue->unit ); + valerie_notifier_put( notifier, &status ); + + return valerie_ok; +} + +/** Basic queue maintenance and status reports. +*/ + +valerie_error_code dv_demo_queue_maintenance( dv_demo demo, dv_demo_queue queue ) +{ + printf( "Queue Maintenance for Unit %d\n\n", queue->unit ); + + if ( !queue->mode ) + { + char ch; + printf( "Activate queueing? [Y] " ); + ch = get_keypress( ); + if ( ch == 'y' || ch == 'Y' || ch == '\r' ) + queue->mode = 1; + printf( "\n\n" ); + } + + if ( queue->mode ) + { + int terminated = 0; + int last_position = -2; + + term_init( ); + + while ( !terminated ) + { + int first = ( queue->position + 1 ) % 50; + int index = first; + + if ( first == queue->tail ) + index = first = queue->head; + + if ( queue->head == queue->tail ) + { + if ( last_position == -2 ) + { + printf( "Queue is empty\n" ); + printf( "\n" ); + printf( "0 = exit, t = turn off queueing\n\n" ); + last_position = -1; + } + } + else if ( last_position != queue->position ) + { + printf( "Order of play\n\n" ); + + do + { + printf( "%c%02d: %s\n", index == first ? '*' : ' ', index, queue->list[ index ] + 1 ); + index = ( index + 1 ) % 50; + if ( index == queue->tail ) + index = queue->head; + } + while( index != first ); + + printf( "\n" ); + printf( "0 = exit, t = turn off queueing, c = clear queue\n\n" ); + last_position = queue->position; + } + + dv_demo_change_status( demo, 1 ); + + switch( term_read( ) ) + { + case -1: + break; + case '0': + terminated = 1; + break; + case 't': + terminated = 1; + queue->mode = 0; + break; + case 'c': + queue->head = queue->tail = 0; + queue->position = -1; + last_position = -2; + break; + } + + dv_demo_change_status( demo, 0 ); + } + + term_exit( ); + } + + return valerie_ok; +} + +/** Load a file to the selected unit. Horrible function - sorry :-/. Not a good + demo.... +*/ + +valerie_error_code dv_demo_load( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + int terminated = 0; + int refresh = 1; + int start = 0; + + strcpy( demo->current_directory, demo->last_directory ); + + term_init( ); + + while ( !terminated ) + { + valerie_dir dir = valerie_dir_init( demo->dv, demo->current_directory ); + + if ( valerie_dir_count( dir ) == -1 ) + { + printf( "Invalid directory - retrying %s\n", demo->last_directory ); + valerie_dir_close( dir ); + dir = valerie_dir_init( demo->dv, demo->last_directory ); + if ( valerie_dir_count( dir ) == -1 ) + { + printf( "Invalid directory - going back to /\n" ); + valerie_dir_close( dir ); + dir = valerie_dir_init( demo->dv, "/" ); + strcpy( demo->current_directory, "/" ); + } + else + { + strcpy( demo->current_directory, demo->last_directory ); + } + } + + terminated = valerie_dir_count( dir ) == -1; + + if ( !terminated ) + { + int index = 0; + int selected = 0; + int max = 9; + int end = 0; + + end = valerie_dir_count( dir ); + + strcpy( demo->last_directory, demo->current_directory ); + + while ( !selected && !terminated ) + { + valerie_dir_entry_t entry; + int pressed; + + if ( refresh ) + { + char *action = "Load & Play"; + if ( demo->queues[ demo->selected_unit ].mode ) + action = "Queue"; + printf( "%s from %s\n\n", action, demo->current_directory ); + if ( strcmp( demo->current_directory, "/" ) ) + printf( "-: Parent directory\n" ); + for ( index = start; index < end && ( index - start ) < max; index ++ ) + { + valerie_dir_get( dir, index, &entry ); + printf( "%d: %s\n", index - start + 1, entry.name ); + } + while ( ( index ++ % 9 ) != 0 ) + printf( "\n" ); + printf( "\n" ); + if ( start + max < end ) + printf( "space = more files" ); + else if ( end > max ) + printf( "space = return to start of list" ); + if ( start > 0 ) + printf( ", b = previous files" ); + printf( "\n" ); + printf( "0 = abort, t = transport, x = execute command, q = queue maintenance\n\n" ); + refresh = 0; + } + + dv_demo_change_status( demo, 1 ); + + pressed = term_read( ); + switch( pressed ) + { + case -1: + break; + case '0': + terminated = 1; + break; + case 'b': + refresh = start - max >= 0; + if ( refresh ) + start = start - max; + break; + case ' ': + refresh = start + max < end; + if ( refresh ) + { + start = start + max; + } + else if ( end > max ) + { + start = 0; + refresh = 1; + } + break; + case '-': + if ( strcmp( demo->current_directory, "/" ) ) + { + selected = 1; + ( *strrchr( demo->current_directory, '/' ) ) = '\0'; + ( *( strrchr( demo->current_directory, '/' ) + 1 ) ) = '\0'; + } + break; + case 't': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_transport( demo ); + term_init( ); + selected = 1; + break; + case 'x': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_execute( demo ); + term_init( ); + selected = 1; + break; + case 'q': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_queue_maintenance( demo, &demo->queues[ demo->selected_unit ] ); + term_init( ); + selected = 1; + break; + default: + if ( pressed >= '1' && pressed <= '9' ) + { + if ( ( start + pressed - '1' ) < end ) + { + valerie_dir_get( dir, start + pressed - '1', &entry ); + selected = 1; + strcat( demo->current_directory, entry.name ); + } + } + break; + } + + dv_demo_change_status( demo, 0 ); + } + + valerie_dir_close( dir ); + } + + if ( !terminated && demo->current_directory[ strlen( demo->current_directory ) - 1 ] != '/' ) + { + if ( demo->queues[ demo->selected_unit ].mode == 0 ) + { + error = valerie_unit_load( demo->dv, demo->selected_unit, demo->current_directory ); + valerie_unit_play( demo->dv, demo->selected_unit ); + } + else + { + dv_demo_queue_add( demo, &demo->queues[ demo->selected_unit ], demo->current_directory ); + printf( "File %s added to queue.\n", demo->current_directory ); + } + strcpy( demo->current_directory, demo->last_directory ); + refresh = 0; + } + else + { + refresh = 1; + start = 0; + } + } + + term_exit( ); + + return error; +} + +/** Set the in point of the clip on the select unit. +*/ + +valerie_error_code dv_demo_set_in( dv_demo demo ) +{ + int position = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_parser_get_notifier( demo->parser ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + position = status.position; + return valerie_unit_set_in( demo->dv, demo->selected_unit, position ); +} + +/** Set the out point of the clip on the selected unit. +*/ + +valerie_error_code dv_demo_set_out( dv_demo demo ) +{ + int position = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_parser_get_notifier( demo->parser ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + position = status.position; + return valerie_unit_set_out( demo->dv, demo->selected_unit, position ); +} + +/** Clear the in and out points on the selected unit. +*/ + +valerie_error_code dv_demo_clear_in_out( dv_demo demo ) +{ + return valerie_unit_clear_in_out( demo->dv, demo->selected_unit ); +} + +/** Goto a user specified frame on the selected unit. +*/ + +valerie_error_code dv_demo_goto( dv_demo demo ) +{ + int frame = 0; + printf( "Frame: " ); + if ( get_int( &frame, 0 ) ) + return valerie_unit_goto( demo->dv, demo->selected_unit, frame ); + return valerie_ok; +} + +/** Manipulate playback on the selected unit. +*/ + +valerie_error_code dv_demo_transport( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + int refresh = 1; + int terminated = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + + while ( !terminated ) + { + if ( refresh ) + { + printf( " +----+ +------+ +----+ +------+ +---+ +-----+ +------+ +-----+ +---+ \n" ); + printf( " |1=-5| |2=-2.5| |3=-1| |4=-0.5| |5=1| |6=0.5| |7=1.25| |8=2.5| |9=5| \n" ); + printf( " +----+ +------+ +----+ +------+ +---+ +-----+ +------+ +-----+ +---+ \n" ); + printf( "\n" ); + printf( "+----------------------------------------------------------------------+\n" ); + printf( "| 0 = quit, x = eXecute, 'space' = pause |\n" ); + printf( "| g = goto a frame, q = queue maintenance |\n" ); + printf( "| h = step -1, j = end of clip, k = start of clip, l = step 1 |\n" ); + printf( "| eof handling: p = pause, r = repeat, t = terminate |\n" ); + printf( "| i = set in point, o = set out point, c = clear in/out |\n" ); + printf( "| u = use point settings, d = don't use point settings |\n" ); + printf( "+----------------------------------------------------------------------+\n" ); + printf( "\n" ); + term_init( ); + refresh = 0; + } + + dv_demo_change_status( demo, 1 ); + + switch( term_read( ) ) + { + case '0': + terminated = 1; + break; + case -1: + break; + case ' ': + error = valerie_unit_pause( demo->dv, demo->selected_unit ); + break; + case '1': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -5000 ); + break; + case '2': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -2500 ); + break; + case '3': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -1000 ); + break; + case '4': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -500 ); + break; + case '5': + error = valerie_unit_play( demo->dv, demo->selected_unit ); + break; + case '6': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 500 ); + break; + case '7': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 1250 ); + break; + case '8': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 2500 ); + break; + case '9': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 5000 ); + break; + case 's': + error = valerie_unit_goto( demo->dv, demo->selected_unit, 0 ); + break; + case 'h': + error = valerie_unit_step( demo->dv, demo->selected_unit, -1 ); + break; + case 'j': + valerie_notifier_get( notifier, &status, demo->selected_unit ); + error = valerie_unit_goto( demo->dv, demo->selected_unit, status.tail_out ); + break; + case 'k': + valerie_notifier_get( notifier, &status, demo->selected_unit ); + error = valerie_unit_goto( demo->dv, demo->selected_unit, status.in ); + break; + case 'l': + error = valerie_unit_step( demo->dv, demo->selected_unit, 1 ); + break; + case 'p': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "pause" ); + break; + case 'r': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "loop" ); + break; + case 't': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "stop" ); + break; + case 'i': + error = dv_demo_set_in( demo ); + break; + case 'o': + error = dv_demo_set_out( demo ); + break; + case 'g': + dv_demo_change_status( demo, 0 ); + term_exit( ); + error = dv_demo_goto( demo ); + refresh = 1; + break; + case 'c': + error = dv_demo_clear_in_out( demo ); + break; + case 'u': + error = valerie_unit_set( demo->dv, demo->selected_unit, "points", "use" ); + break; + case 'd': + error = valerie_unit_set( demo->dv, demo->selected_unit, "points", "ignore" ); + break; + case 'x': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_execute( demo ); + refresh = 1; + break; + case 'q': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_queue_maintenance( demo, &demo->queues[ demo->selected_unit ] ); + refresh = 1; + break; + } + + dv_demo_change_status( demo, 0 ); + } + + term_exit( ); + + return error; +} + +/** Recursive menu execution. +*/ + +valerie_error_code dv_demo_run_menu( dv_demo demo, dv_demo_menu menu ) +{ + char *items = "123456789abcdefghijklmnopqrstuvwxyz"; + int refresh_menu = 1; + int terminated = 0; + int item_count = 0; + int item_selected = 0; + int index = 0; + char key; + + while( !terminated ) + { + + if ( refresh_menu ) + { + printf( "%s\n\n", menu->description ); + for ( index = 0; menu->array[ index ].option != NULL; index ++ ) + printf( "%c: %s\n", items[ index ], menu->array[ index ].option ); + printf( "0: Exit\n\n" ); + printf( "Select Option: " ); + refresh_menu = 0; + item_count = index; + } + + key = get_keypress( ); + + if ( demo->disconnected && key != '0' ) + { + valerie_error_code error = valerie_connect( demo->dv ); + if ( error == valerie_ok ) + demo->disconnected = 0; + else + beep(); + } + + if ( !demo->disconnected || key == '0' ) + { + item_selected = strchr( items, key ) - items; + + if ( key == '0' ) + { + printf( "%c\n\n", key ); + terminated = 1; + } + else if ( item_selected >= 0 && item_selected < item_count ) + { + printf( "%c\n\n", key ); + menu->array[ item_selected ].function( demo ); + refresh_menu = 1; + } + else + { + beep( ); + } + } + } + + return valerie_ok; +} + +/** Entry point for main menu. +*/ + +void dv_demo_run( dv_demo this ) +{ + this->dv = valerie_init( this->parser ); + this->dv_status = valerie_init( this->parser ); + if ( valerie_connect( this->dv ) == valerie_ok ) + { + pthread_create( &this->thread, NULL, dv_demo_status_thread, this ); + dv_demo_run_menu( this, &connected_menu ); + this->terminated = 1; + pthread_join( this->thread, NULL ); + this->terminated = 0; + } + else + { + printf( "Unable to connect." ); + wait_for_any_key( "" ); + } + + valerie_close( this->dv_status ); + valerie_close( this->dv ); + + printf( "Demo Exit.\n" ); +} + +/** Close the demo structure. +*/ + +void dv_demo_close( dv_demo demo ) +{ + free( demo ); +} diff --git a/src/humperdink/client.h b/src/humperdink/client.h new file mode 100644 index 0000000..ac29481 --- /dev/null +++ b/src/humperdink/client.h @@ -0,0 +1,66 @@ +/* + * client.h -- Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_CLIENT_H_ +#define _DEMO_CLIENT_H_ + +#include +#include +#include + +/** Queue for unit playback +*/ + +typedef struct +{ + int mode; + int unit; + int position; + int head; + int tail; + char list[ 50 ][ PATH_MAX + NAME_MAX ]; + int ignore; +} +*dv_demo_queue, dv_demo_queue_t; + +/** Structure for storing app state. +*/ + +typedef struct +{ + int disconnected; + valerie_parser parser; + valerie dv; + valerie dv_status; + int selected_unit; + char current_directory[ 512 ]; + char last_directory[ 512 ]; + int showing; + int terminated; + pthread_t thread; + dv_demo_queue_t queues[ MAX_UNITS ]; +} +*dv_demo, dv_demo_t; + +extern dv_demo dv_demo_init( valerie_parser ); +extern void dv_demo_run( dv_demo ); +extern void dv_demo_close( dv_demo ); + +#endif diff --git a/src/humperdink/io.c b/src/humperdink/io.c new file mode 100644 index 0000000..d5c4c81 --- /dev/null +++ b/src/humperdink/io.c @@ -0,0 +1,205 @@ +/* + * io.c -- Valerie client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "io.h" + +char *chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +char *trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +char *strip_quotes( char *input ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, '\"' ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == '\"' ) + strcpy( input, input + 1 ); + } + return input; +} + +char *io_get_string( char *output, int maxlength, char *use ) +{ + char *value = NULL; + strcpy( output, use ); + if ( trim( chomp( fgets( output, maxlength, stdin ) ) ) != NULL ) + { + if ( !strcmp( output, "" ) ) + strcpy( output, use ); + value = output; + } + return value; +} + +int *get_int( int *output, int use ) +{ + int *value = NULL; + char temp[ 132 ]; + *output = use; + if ( trim( chomp( fgets( temp, 132, stdin ) ) ) != NULL ) + { + if ( strcmp( temp, "" ) ) + *output = atoi( temp ); + value = output; + } + return value; +} + +/** This stores the previous settings +*/ + +static struct termios oldtty; +static int mode = 0; + +/** This is called automatically on application exit to restore the + previous tty settings. +*/ + +void term_exit(void) +{ + if ( mode == 1 ) + { + tcsetattr( 0, TCSANOW, &oldtty ); + mode = 0; + } +} + +/** Init terminal so that we can grab keys without blocking. +*/ + +void term_init( ) +{ + struct termios tty; + + tcgetattr( 0, &tty ); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[ VMIN ] = 1; + tty.c_cc[ VTIME ] = 0; + + tcsetattr( 0, TCSANOW, &tty ); + + mode = 1; + + atexit( term_exit ); +} + +/** Check for a keypress without blocking infinitely. + Returns: ASCII value of keypress or -1 if no keypress detected. +*/ + +int term_read( ) +{ + int n = 1; + unsigned char ch; + struct timeval tv; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds ); + tv.tv_sec = 1; + tv.tv_usec = 0; + n = select( 1, &rfds, NULL, NULL, &tv ); + if (n > 0) + { + n = read( 0, &ch, 1 ); + tcflush( 0, TCIFLUSH ); + if (n == 1) + return ch; + return n; + } + return -1; +} + +char get_keypress( ) +{ + char value = '\0'; + int pressed = 0; + + fflush( stdout ); + + term_init( ); + while ( ( pressed = term_read( ) ) == -1 ) ; + term_exit( ); + + value = (char)pressed; + + return value; +} + +void wait_for_any_key( char *message ) +{ + if ( message == NULL ) + printf( "Press any key to continue: " ); + else + printf( "%s", message ); + + get_keypress( ); + + printf( "\n\n" ); +} + +void beep( ) +{ + printf( "%c", 7 ); + fflush( stdout ); +} diff --git a/src/humperdink/io.h b/src/humperdink/io.h new file mode 100644 index 0000000..33d449f --- /dev/null +++ b/src/humperdink/io.h @@ -0,0 +1,36 @@ +/* + * io.h -- Valerie client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_IO_H_ +#define _DEMO_IO_H_ + +extern char *chomp( char * ); +extern char *trim( char * ); +extern char *strip_quotes( char * ); +extern char *io_get_string( char *, int, char * ); +extern int *get_int( int *, int ); +extern void term_init( ); +extern int term_read( ); +extern void term_exit( ); +extern char get_keypress( ); +extern void wait_for_any_key( char * ); +extern void beep( ); + +#endif diff --git a/src/humperdink/remote.c b/src/humperdink/remote.c new file mode 100644 index 0000000..6c267a4 --- /dev/null +++ b/src/humperdink/remote.c @@ -0,0 +1,73 @@ +/* + * remote.c -- Remote Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include + +#include + +/* Application header files */ +#include "client.h" +#include "io.h" + +/** Connect to a remote server. +*/ + +static valerie_parser create_parser( ) +{ + char server[ 132 ]; + int port; + valerie_parser parser = NULL; + + printf( "Connecting to a Server\n\n" ); + + printf( "Server [localhost]: " ); + + if ( io_get_string( server, sizeof( server ), "localhost" ) != NULL ) + { + printf( "Port [5250]: " ); + + if ( get_int( &port, 5250 ) != NULL ) + parser = valerie_parser_init_remote( server, port ); + } + + printf( "\n" ); + + return parser; +} + +/** Main function. +*/ + +int main( int argc, char **argv ) +{ + valerie_parser parser = create_parser( ); + + if ( parser != NULL ) + { + dv_demo demo = dv_demo_init( parser ); + dv_demo_run( demo ); + dv_demo_close( demo ); + valerie_parser_close( parser ); + } + + return 0; +} diff --git a/src/inigo/Makefile b/src/inigo/Makefile new file mode 100644 index 0000000..0e80dbf --- /dev/null +++ b/src/inigo/Makefile @@ -0,0 +1,37 @@ +include ../../config.mak + +TARGET = inigo + +OBJS = inigo.o \ + io.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/inigo/configure b/src/inigo/configure new file mode 100755 index 0000000..e69de29 diff --git a/src/inigo/inigo.c b/src/inigo/inigo.c new file mode 100644 index 0000000..78b668f --- /dev/null +++ b/src/inigo/inigo.c @@ -0,0 +1,381 @@ +/* + * inigo.c -- MLT command line utility + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include + +#ifdef __DARWIN__ +#include +#endif + +#include "io.h" + +static void transport_action( mlt_producer producer, char *value ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + mlt_multitrack multitrack = mlt_properties_get_data( properties, "multitrack", NULL ); + mlt_consumer consumer = mlt_properties_get_data( properties, "transport_consumer", NULL ); + + mlt_properties_set_int( properties, "stats_off", 0 ); + + if ( strlen( value ) == 1 ) + { + switch( value[ 0 ] ) + { + case 'q': + mlt_properties_set_int( properties, "done", 1 ); + break; + case '0': + mlt_producer_set_speed( producer, 1 ); + mlt_producer_seek( producer, 0 ); + break; + case '1': + mlt_producer_set_speed( producer, -10 ); + break; + case '2': + mlt_producer_set_speed( producer, -5 ); + break; + case '3': + mlt_producer_set_speed( producer, -2 ); + break; + case '4': + mlt_producer_set_speed( producer, -1 ); + break; + case '5': + mlt_producer_set_speed( producer, 0 ); + break; + case '6': + case ' ': + mlt_producer_set_speed( producer, 1 ); + break; + case '7': + mlt_producer_set_speed( producer, 2 ); + break; + case '8': + mlt_producer_set_speed( producer, 5 ); + break; + case '9': + mlt_producer_set_speed( producer, 10 ); + break; + case 'd': + if ( multitrack != NULL ) + { + int i = 0; + mlt_position last = -1; + fprintf( stderr, "\n" ); + for ( i = 0; 1; i ++ ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_start, i ); + if ( time == last ) + break; + last = time; + fprintf( stderr, "%d: %d\n", i, (int)time ); + } + } + break; + + case 'g': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, 0 ); + mlt_producer_seek( producer, time ); + } + break; + case 'H': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_seek( producer, position - ( mlt_producer_get_fps( producer ) * 60 ) ); + } + break; + case 'h': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_set_speed( producer, 0 ); + mlt_producer_seek( producer, position - 1 ); + } + break; + case 'j': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, 1 ); + mlt_producer_seek( producer, time ); + } + break; + case 'k': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, -1 ); + mlt_producer_seek( producer, time ); + } + break; + case 'l': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + if ( mlt_producer_get_speed( producer ) != 0 ) + mlt_producer_set_speed( producer, 0 ); + else + mlt_producer_seek( producer, position + 1 ); + } + break; + case 'L': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_seek( producer, position + ( mlt_producer_get_fps( producer ) * 60 ) ); + } + break; + } + + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( consumer ), "refresh", 1 ); + } + + mlt_properties_set_int( properties, "stats_off", 0 ); +} + +static mlt_consumer create_consumer( char *id, mlt_producer producer ) +{ + char *arg = id != NULL ? strchr( id, ':' ) : NULL; + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_consumer consumer = mlt_factory_consumer( id, arg ); + if ( consumer != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + mlt_properties_set_data( properties, "transport_callback", transport_action, 0, NULL, NULL ); + mlt_properties_set_data( properties, "transport_producer", producer, 0, NULL, NULL ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( producer ), "transport_consumer", consumer, 0, NULL, NULL ); + } + return consumer; +} + +#ifdef __DARWIN__ + +static void event_handling( mlt_producer producer, mlt_consumer consumer ) +{ + SDL_Event event; + + while ( SDL_PollEvent( &event ) ) + { + switch( event.type ) + { + case SDL_QUIT: + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( consumer ), "done", 1 ); + break; + + case SDL_KEYDOWN: + if ( event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 ) + { + char keyboard[ 2 ] = { event.key.keysym.unicode, 0 }; + transport_action( producer, keyboard ); + } + break; + } + } +} + +#endif + +static void transport( mlt_producer producer, mlt_consumer consumer ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + int silent = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "silent" ); + struct timespec tm = { 0, 40000 }; + + if ( mlt_properties_get_int( properties, "done" ) == 0 && !mlt_consumer_is_stopped( consumer ) ) + { + if ( !silent ) + { + term_init( ); + + fprintf( stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n" ); + fprintf( stderr, "|1=-10| |2= -5| |3= -2| |4= -1| |5= 0| |6= 1| |7= 2| |8= 5| |9= 10|\n" ); + fprintf( stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n" ); + + fprintf( stderr, "+---------------------------------------------------------------------+\n" ); + fprintf( stderr, "| H = back 1 minute, L = forward 1 minute |\n" ); + fprintf( stderr, "| h = previous frame, l = next frame |\n" ); + fprintf( stderr, "| g = start of clip, j = next clip, k = previous clip |\n" ); + fprintf( stderr, "| 0 = restart, q = quit, space = play |\n" ); + fprintf( stderr, "+---------------------------------------------------------------------+\n" ); + } + + while( mlt_properties_get_int( properties, "done" ) == 0 && !mlt_consumer_is_stopped( consumer ) ) + { + int value = silent ? -1 : term_read( ); + + if ( value != -1 ) + { + char string[ 2 ] = { value, 0 }; + transport_action( producer, string ); + } + +#ifdef __DARWIN__ + event_handling( producer, consumer ); +#endif + + if ( !silent && mlt_properties_get_int( properties, "stats_off" ) == 0 ) + fprintf( stderr, "Current Position: %10d\r", (int)mlt_producer_position( producer ) ); + + if ( silent ) + nanosleep( &tm, NULL ); + } + + if ( !silent ) + fprintf( stderr, "\n" ); + } +} + +int main( int argc, char **argv ) +{ + int i; + mlt_consumer consumer = NULL; + mlt_producer inigo = NULL; + FILE *store = NULL; + char *name = NULL; + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + // Construct the factory + mlt_factory_init( NULL ); + + // Check for serialisation switch first + for ( i = 1; i < argc; i ++ ) + { + if ( !strcmp( argv[ i ], "-serialise" ) ) + { + name = argv[ ++ i ]; + if ( strstr( name, ".inigo" ) ) + store = fopen( name, "w" ); + } + } + + // Get inigo producer + if ( argc > 1 ) + inigo = mlt_factory_producer( "inigo", &argv[ 1 ] ); + + if ( argc > 1 && inigo != NULL && mlt_producer_get_length( inigo ) > 0 ) + { + // Get inigo's properties + mlt_properties inigo_props = MLT_PRODUCER_PROPERTIES( inigo ); + + // Get the last group + mlt_properties group = mlt_properties_get_data( inigo_props, "group", 0 ); + + // Parse the arguments + for ( i = 1; i < argc; i ++ ) + { + if ( !strcmp( argv[ i ], "-consumer" ) ) + { + consumer = create_consumer( argv[ ++ i ], inigo ); + while ( argv[ i + 1 ] != NULL && strstr( argv[ i + 1 ], "=" ) ) + mlt_properties_parse( group, argv[ ++ i ] ); + } + else if ( !strcmp( argv[ i ], "-serialise" ) ) + { + i ++; + } + else + { + if ( store != NULL ) + fprintf( store, "%s\n", argv[ i ] ); + + i ++; + + while ( argv[ i ] != NULL && argv[ i ][ 0 ] != '-' ) + { + if ( store != NULL ) + fprintf( store, "%s\n", argv[ i ] ); + i += 1; + } + + i --; + } + } + + // If we have no consumer, default to sdl + if ( store == NULL && consumer == NULL ) + consumer = create_consumer( NULL, inigo ); + + if ( consumer != NULL && store == NULL ) + { + // Apply group settings + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + mlt_properties_inherit( properties, group ); + + // Connect consumer to inigo + mlt_consumer_connect( consumer, MLT_PRODUCER_SERVICE( inigo ) ); + + // Start the consumer + mlt_consumer_start( consumer ); + + // Transport functionality + transport( inigo, consumer ); + + // Stop the consumer + mlt_consumer_stop( consumer ); + } + else if ( store != NULL ) + { + fprintf( stderr, "Project saved as %s.\n", name ); + fclose( store ); + } + } + else + { + fprintf( stderr, "Usage: inigo [ -group [ name=value ]* ]\n" + " [ -consumer id[:arg] [ name=value ]* ]\n" + " [ -filter filter[:arg] [ name=value ] * ]\n" + " [ -attach filter[:arg] [ name=value ] * ]\n" + " [ -mix length [ -mixer transition ]* ]\n" + " [ -transition id[:arg] [ name=value ] * ]\n" + " [ -blank frames ]\n" + " [ -track ]\n" + " [ -split relative-frame ]\n" + " [ -join clips ]\n" + " [ -repeat times ]\n" + " [ producer [ name=value ] * ]+\n" ); + } + + // Close the consumer + if ( consumer != NULL ) + mlt_consumer_close( consumer ); + + // Close the producer + if ( inigo != NULL ) + mlt_producer_close( inigo ); + + // Close the factory + mlt_factory_close( ); + + return 0; +} diff --git a/src/inigo/io.c b/src/inigo/io.c new file mode 100644 index 0000000..91a586f --- /dev/null +++ b/src/inigo/io.c @@ -0,0 +1,196 @@ +/* + * io.c -- inigo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "io.h" + +char *chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +char *trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +char *strip_quotes( char *input ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, '\"' ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == '\"' ) + strcpy( input, input + 1 ); + } + return input; +} + +int *get_int( int *output, int use ) +{ + int *value = NULL; + char temp[ 132 ]; + *output = use; + if ( trim( chomp( fgets( temp, 132, stdin ) ) ) != NULL ) + { + if ( strcmp( temp, "" ) ) + *output = atoi( temp ); + value = output; + } + return value; +} + +/** This stores the previous settings +*/ + +static struct termios oldtty; +static int mode = 0; + +/** This is called automatically on application exit to restore the + previous tty settings. +*/ + +void term_exit(void) +{ + if ( mode == 1 ) + { + tcsetattr( 0, TCSANOW, &oldtty ); + mode = 0; + } +} + +/** Init terminal so that we can grab keys without blocking. +*/ + +void term_init( ) +{ + struct termios tty; + + tcgetattr( 0, &tty ); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[ VMIN ] = 1; + tty.c_cc[ VTIME ] = 0; + + tcsetattr( 0, TCSANOW, &tty ); + + mode = 1; + + atexit( term_exit ); +} + +/** Check for a keypress without blocking infinitely. + Returns: ASCII value of keypress or -1 if no keypress detected. +*/ + +int term_read( ) +{ + int n = 1; + unsigned char ch; + struct timeval tv; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds ); + tv.tv_sec = 0; + tv.tv_usec = 40000; + n = select( 1, &rfds, NULL, NULL, &tv ); + if (n > 0) + { + n = read( 0, &ch, 1 ); + tcflush( 0, TCIFLUSH ); + if (n == 1) + return ch; + return n; + } + return -1; +} + +char get_keypress( ) +{ + char value = '\0'; + int pressed = 0; + + fflush( stdout ); + + term_init( ); + while ( ( pressed = term_read( ) ) == -1 ) ; + term_exit( ); + + value = (char)pressed; + + return value; +} + +void wait_for_any_key( char *message ) +{ + if ( message == NULL ) + printf( "Press any key to continue: " ); + else + printf( "%s", message ); + + get_keypress( ); + + printf( "\n\n" ); +} + +void beep( ) +{ + printf( "%c", 7 ); + fflush( stdout ); +} diff --git a/src/inigo/io.h b/src/inigo/io.h new file mode 100644 index 0000000..1a35944 --- /dev/null +++ b/src/inigo/io.h @@ -0,0 +1,45 @@ +/* + * io.h -- inigo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_IO_H_ +#define _DEMO_IO_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern char *chomp( char * ); +extern char *trim( char * ); +extern char *strip_quotes( char * ); +extern char *get_string( char *, int, char * ); +extern int *get_int( int *, int ); +extern void term_init( ); +extern int term_read( ); +extern void term_exit( ); +extern char get_keypress( ); +extern void wait_for_any_key( char * ); +extern void beep( ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/Makefile b/src/miracle/Makefile new file mode 100644 index 0000000..7fa8a29 --- /dev/null +++ b/src/miracle/Makefile @@ -0,0 +1,71 @@ +include ../../config.mak + +TARGET = miracle + +ifneq ($(targetos), Darwin) +LIBNAME = libmiracle$(LIBSUF) +LIBTARGET = $(LIBNAME).$(version) +SHFLAGS += -Wl,-soname,$(LIBTARGET) +else +LIBNAME = libmiracle$(LIBSUF) +LIBTARGET = libmiracle.$(version)$(LIBSUF) +SHFLAGS += -install_name $(libdir)/$(LIBTARGET) +endif + +APP_OBJS = miracle.o + +LIB_OBJS = miracle_log.o \ + miracle_server.o \ + miracle_connection.o \ + miracle_local.o \ + miracle_unit.o \ + miracle_commands.o \ + miracle_unit_commands.o + +INCS = miracle_server.h \ + miracle_local.h \ + miracle_log.h + +OBJS = $(APP_OBJS) $(LIB_OBJS) + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../valerie -lvalerie -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(APP_OBJS) $(LIBTARGET) + $(CC) -o $@ $(APP_OBJS) -L. -lmiracle $(LDFLAGS) + +$(LIBTARGET): $(LIB_OBJS) + $(CC) $(SHFLAGS) -o $@ $(LIB_OBJS) $(LDFLAGS) + ln -sf $(LIBTARGET) $(LIBNAME) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) $(LIBNAME) $(LIBTARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + install -m 755 $(LIBTARGET) $(DESTDIR)$(libdir) + ln -sf $(LIBTARGET) $(DESTDIR)$(libdir)/$(LIBNAME) + mkdir -p "$(DESTDIR)$(prefix)/include/mlt/miracle" + install -m 644 $(INCS) "$(DESTDIR)$(prefix)/include/mlt/miracle" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + rm -f "$(DESTDIR)$(libdir)/$(LIBTARGET)" + rm -f "$(DESTDIR)$(libdir)/$(LIBNAME)" + rm -rf "$(DESTDIR)$(prefix)/include/mlt/miracle" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/miracle/configure b/src/miracle/configure new file mode 100755 index 0000000..85d890d --- /dev/null +++ b/src/miracle/configure @@ -0,0 +1,2 @@ +#!/bin/sh +echo "miracle -I$prefix/include/mlt -D_REENTRANT -L$libdir -lmiracle" >> ../../packages.dat diff --git a/src/miracle/miracle.c b/src/miracle/miracle.c new file mode 100644 index 0000000..04b04be --- /dev/null +++ b/src/miracle/miracle.c @@ -0,0 +1,122 @@ +/* + * miracle.c -- MLT Video TCP Server + * + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Authors: + * Dan Dennedy + * Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Application header files */ +#include "miracle_server.h" +#include "miracle_log.h" + +/** Our dv server. +*/ + +static miracle_server server = NULL; + +/** atexit shutdown handler for the server. +*/ + +static void main_cleanup( ) +{ + miracle_server_close( server ); +} + +/** Report usage and exit. +*/ + +void usage( char *app ) +{ + fprintf( stderr, "Usage: %s [-test] [-port NNNN]\n", app ); + exit( 0 ); +} + +/** The main function. +*/ + +int main( int argc, char **argv ) +{ + int error = 0; + int index = 0; + int background = 1; + struct timespec tm = { 5, 0 }; + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + mlt_factory_init( NULL ); + + server = miracle_server_init( argv[ 0 ] ); + + for ( index = 1; index < argc; index ++ ) + { + if ( !strcmp( argv[ index ], "-port" ) ) + miracle_server_set_port( server, atoi( argv[ ++ index ] ) ); + else if ( !strcmp( argv[ index ], "-proxy" ) ) + miracle_server_set_proxy( server, argv[ ++ index ] ); + else if ( !strcmp( argv[ index ], "-test" ) ) + background = 0; + else + usage( argv[ 0 ] ); + } + + /* Optionally detatch ourselves from the controlling tty */ + + if ( background ) + { + if ( fork() ) + return 0; + setsid(); + miracle_log_init( log_syslog, LOG_INFO ); + } + else + { + miracle_log_init( log_stderr, LOG_DEBUG ); + } + + atexit( main_cleanup ); + + /* Set the config script */ + miracle_server_set_config( server, "/etc/miracle.conf" ); + + /* Execute the server */ + error = miracle_server_execute( server ); + + /* We need to wait until we're exited.. */ + while ( !server->shutdown ) + nanosleep( &tm, NULL ); + + return error; +} diff --git a/src/miracle/miracle_commands.c b/src/miracle/miracle_commands.c new file mode 100644 index 0000000..c00e9f6 --- /dev/null +++ b/src/miracle/miracle_commands.c @@ -0,0 +1,248 @@ +/* + * global_commands.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "miracle_unit.h" +#include "miracle_commands.h" +#include "miracle_log.h" + +static miracle_unit g_units[MAX_UNITS]; + + +/** Return the miracle_unit given a numeric index. +*/ + +miracle_unit miracle_get_unit( int n ) +{ + if (n < MAX_UNITS) + return g_units[n]; + else + return NULL; +} + +/** Destroy the miracle_unit given its numeric index. +*/ + +void miracle_delete_unit( int n ) +{ + if (n < MAX_UNITS) + { + miracle_unit unit = miracle_get_unit(n); + if (unit != NULL) + { + miracle_unit_close( unit ); + g_units[ n ] = NULL; + miracle_log( LOG_NOTICE, "Deleted unit U%d.", n ); + } + } +} + +/** Destroy all allocated units on the server. +*/ + +void miracle_delete_all_units( void ) +{ + int i; + for (i = 0; i < MAX_UNITS; i++) + { + if ( miracle_get_unit(i) != NULL ) + { + miracle_unit_close( miracle_get_unit(i) ); + miracle_log( LOG_NOTICE, "Deleted unit U%d.", i ); + } + } +} + +/** Add a DV virtual vtr to the server. +*/ +response_codes miracle_add_unit( command_argument cmd_arg ) +{ + int i = 0; + for ( i = 0; i < MAX_UNITS; i ++ ) + if ( g_units[ i ] == NULL ) + break; + + if ( i < MAX_UNITS ) + { + char *arg = cmd_arg->argument; + g_units[ i ] = miracle_unit_init( i, arg ); + if ( g_units[ i ] != NULL ) + { + miracle_unit_set_notifier( g_units[ i ], valerie_parser_get_notifier( cmd_arg->parser ), cmd_arg->root_dir ); + valerie_response_printf( cmd_arg->response, 10, "U%1d\n\n", i ); + } + return g_units[ i ] != NULL ? RESPONSE_SUCCESS_N : RESPONSE_ERROR; + } + valerie_response_printf( cmd_arg->response, 1024, "no more units can be created\n\n" ); + + return RESPONSE_ERROR; +} + + +/** List all AV/C nodes on the bus. +*/ +response_codes miracle_list_nodes( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_SUCCESS_N; + return error; +} + + +/** List units already added to server. +*/ +response_codes miracle_list_units( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_SUCCESS_N; + int i = 0; + + for ( i = 0; i < MAX_UNITS; i ++ ) + { + miracle_unit unit = miracle_get_unit( i ); + if ( unit != NULL ) + { + mlt_properties properties = unit->properties; + char *constructor = mlt_properties_get( properties, "constructor" ); + int node = mlt_properties_get_int( properties, "node" ); + int online = !mlt_properties_get_int( properties, "offline" ); + valerie_response_printf( cmd_arg->response, 1024, "U%d %02d %s %d\n", i, node, constructor, online ); + } + } + valerie_response_printf( cmd_arg->response, 1024, "\n" ); + + return error; +} + +static int filter_files( const struct dirent *de ) +{ + return de->d_name[ 0 ] != '.'; +} + +/** List clips in a directory. +*/ +response_codes miracle_list_clips( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_BAD_FILE; + const char *dir_name = (const char*) cmd_arg->argument; + DIR *dir; + char fullname[1024]; + struct dirent **de = NULL; + int i, n; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, dir_name ); + dir = opendir( fullname ); + if (dir != NULL) + { + struct stat info; + error = RESPONSE_SUCCESS_N; + n = scandir( fullname, &de, filter_files, alphasort ); + for (i = 0; i < n; i++ ) + { + snprintf( fullname, 1023, "%s%s/%s", cmd_arg->root_dir, dir_name, de[i]->d_name ); + if ( stat( fullname, &info ) == 0 && S_ISDIR( info.st_mode ) ) + valerie_response_printf( cmd_arg->response, 1024, "\"%s/\"\n", de[i]->d_name ); + } + for (i = 0; i < n; i++ ) + { + snprintf( fullname, 1023, "%s%s/%s", cmd_arg->root_dir, dir_name, de[i]->d_name ); + if ( lstat( fullname, &info ) == 0 && + ( S_ISREG( info.st_mode ) || S_ISLNK( info.st_mode ) || ( strstr( fullname, ".clip" ) && info.st_mode | S_IXUSR ) ) ) + valerie_response_printf( cmd_arg->response, 1024, "\"%s\" %llu\n", de[i]->d_name, (unsigned long long) info.st_size ); + free( de[ i ] ); + } + free( de ); + closedir( dir ); + valerie_response_write( cmd_arg->response, "\n", 1 ); + } + + return error; +} + +/** Set a server configuration property. +*/ + +response_codes miracle_set_global_property( command_argument cmd_arg ) +{ + char *key = (char*) cmd_arg->argument; + char *value = NULL; + + value = strchr( key, '=' ); + if (value == NULL) + return RESPONSE_OUT_OF_RANGE; + *value = 0; + value++; + miracle_log( LOG_DEBUG, "SET %s = %s", key, value ); + + if ( strncasecmp( key, "root", 1024) == 0 ) + { + int len = strlen(value); + int i; + + /* stop all units and unload clips */ + for (i = 0; i < MAX_UNITS; i++) + { + if (g_units[i] != NULL) + miracle_unit_terminate( g_units[i] ); + } + + /* set the property */ + strncpy( cmd_arg->root_dir, value, 1023 ); + + /* add a trailing slash if needed */ + if ( len && cmd_arg->root_dir[ len - 1 ] != '/') + { + cmd_arg->root_dir[ len ] = '/'; + cmd_arg->root_dir[ len + 1 ] = '\0'; + } + } + else + return RESPONSE_OUT_OF_RANGE; + + return RESPONSE_SUCCESS; +} + +/** Get a server configuration property. +*/ + +response_codes miracle_get_global_property( command_argument cmd_arg ) +{ + char *key = (char*) cmd_arg->argument; + + if ( strncasecmp( key, "root", 1024) == 0 ) + { + valerie_response_write( cmd_arg->response, cmd_arg->root_dir, strlen(cmd_arg->root_dir) ); + return RESPONSE_SUCCESS_1; + } + else + return RESPONSE_OUT_OF_RANGE; + + return RESPONSE_SUCCESS; +} + + diff --git a/src/miracle/miracle_commands.h b/src/miracle/miracle_commands.h new file mode 100644 index 0000000..947554d --- /dev/null +++ b/src/miracle/miracle_commands.h @@ -0,0 +1,52 @@ +/* + * global_commands.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef _GLOBAL_COMMANDS_H_ +#define _GLOBAL_COMMANDS_H_ + +#include +#include "miracle_unit.h" +#include "miracle_connection.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern miracle_unit miracle_get_unit( int ); +extern void miracle_delete_unit( int ); +extern void miracle_delete_all_units( void ); +extern int miracle_unit_status( int n, valerie_status status, int root_offset ); +//extern void raw1394_start_service_threads( void ); +//extern void raw1394_stop_service_threads( void ); + +extern response_codes miracle_add_unit( command_argument ); +extern response_codes miracle_list_nodes( command_argument ); +extern response_codes miracle_list_units( command_argument ); +extern response_codes miracle_list_clips( command_argument ); +extern response_codes miracle_set_global_property( command_argument ); +extern response_codes miracle_get_global_property( command_argument ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_connection.c b/src/miracle/miracle_connection.c new file mode 100644 index 0000000..b7f30fd --- /dev/null +++ b/src/miracle/miracle_connection.c @@ -0,0 +1,292 @@ +/* + * miracle_connection.c -- DV Connection Handler + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Application header files */ +#include "miracle_commands.h" +#include "miracle_connection.h" +#include "miracle_server.h" +#include "miracle_log.h" + +/** This is a generic replacement for fgets which operates on a file + descriptor. Unlike fgets, we can also specify a line terminator. Maximum + of (max - 1) chars can be read into buf from fd. If we reach the + end-of-file, *eof_chk is set to 1. +*/ + +int fdgetline( int fd, char *buf, int max, char line_terminator, int *eof_chk ) +{ + int count = 0; + char tmp [1]; + *eof_chk = 0; + + if (fd) + while (count < max - 1) { + if (read (fd, tmp, 1) > 0) { + if (tmp [0] != line_terminator) + buf [count++] = tmp [0]; + else + break; + +/* Is it an EOF character (ctrl-D, i.e. ascii 4)? If so we definitely want + to break. */ + + if (tmp [0] == 4) { + *eof_chk = 1; + break; + } + } else { + *eof_chk = 1; + break; + } + } + + buf [count] = '\0'; + + return count; +} + +static int connection_initiate( int ); +static int connection_send( int, valerie_response ); +static int connection_read( int, char *, int ); +static void connection_close( int ); + +static int connection_initiate( int fd ) +{ + int error = 0; + valerie_response response = valerie_response_init( ); + valerie_response_set_error( response, 100, "VTR Ready" ); + error = connection_send( fd, response ); + valerie_response_close( response ); + return error; +} + +static int connection_send( int fd, valerie_response response ) +{ + int error = 0; + int index = 0; + int code = valerie_response_get_error_code( response ); + + if ( code != -1 ) + { + int items = valerie_response_count( response ); + + if ( items == 0 ) + valerie_response_set_error( response, 500, "Unknown error" ); + + if ( code == 200 && items > 2 ) + valerie_response_set_error( response, 201, "OK" ); + else if ( code == 200 && items > 1 ) + valerie_response_set_error( response, 202, "OK" ); + + code = valerie_response_get_error_code( response ); + items = valerie_response_count( response ); + + for ( index = 0; !error && index < items; index ++ ) + { + char *line = valerie_response_get_line( response, index ); + int length = strlen( line ); + if ( length == 0 && index != valerie_response_count( response ) - 1 && write( fd, " ", 1 ) != 1 ) + error = -1; + else if ( length > 0 && write( fd, line, length ) != length ) + error = -1; + if ( write( fd, "\r\n", 2 ) != 2 ) + error = -1; + } + + if ( ( code == 201 || code == 500 ) && strcmp( valerie_response_get_line( response, items - 1 ), "" ) ) + write( fd, "\r\n", 2 ); + } + else + { + char *message = "500 Empty Response\r\n\r\n"; + write( fd, message, strlen( message ) ); + } + + return error; +} + +static int connection_read( int fd, char *command, int length ) +{ + int eof_chk; + int nchars = fdgetline( fd, command, length, '\n', &eof_chk ); + char *cr = strchr( command, '\r'); + if ( cr != NULL ) + cr[0] = '\0'; + if ( eof_chk || strncasecmp( command, "BYE", 3 ) == 0 ) + nchars = 0; + return nchars; +} + +int connection_status( int fd, valerie_notifier notifier ) +{ + int error = 0; + int index = 0; + valerie_status_t status; + char text[ 10240 ]; + valerie_socket socket = valerie_socket_init_fd( fd ); + + for ( index = 0; !error && index < MAX_UNITS; index ++ ) + { + valerie_notifier_get( notifier, &status, index ); + valerie_status_serialise( &status, text, sizeof( text ) ); + error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text ); + } + + while ( !error ) + { + if ( valerie_notifier_wait( notifier, &status ) == 0 ) + { + valerie_status_serialise( &status, text, sizeof( text ) ); + error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text ); + } + else + { + struct timeval tv = { 0, 0 }; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( fd, &rfds ); + + if ( select( socket->fd + 1, &rfds, NULL, NULL, &tv ) ) + error = 1; + } + } + + valerie_socket_close( socket ); + + return error; +} + +static void connection_close( int fd ) +{ + close( fd ); +} + +void *parser_thread( void *arg ) +{ + struct hostent *he; + connection_t *connection = arg; + mlt_properties owner = connection->owner; + char address[ 512 ]; + char command[ 1024 ]; + int fd = connection->fd; + valerie_parser parser = connection->parser; + valerie_response response = NULL; + + /* Get the connecting clients ip information */ + he = gethostbyaddr( (char *) &( connection->sin.sin_addr.s_addr ), sizeof(u_int32_t), AF_INET); + if ( he != NULL ) + strcpy( address, he->h_name ); + else + inet_ntop( AF_INET, &( connection->sin.sin_addr.s_addr), address, 32 ); + + miracle_log( LOG_NOTICE, "Connection established with %s (%d)", address, fd ); + + /* Execute the commands received. */ + if ( connection_initiate( fd ) == 0 ) + { + int error = 0; + + while( !error && connection_read( fd, command, 1024 ) ) + { + response = NULL; + + if ( !strncmp( command, "PUSH ", 5 ) ) + { + char temp[ 20 ]; + int bytes; + char *buffer = NULL; + int total = 0; + mlt_service service = NULL; + + connection_read( fd, temp, 20 ); + bytes = atoi( temp ); + buffer = malloc( bytes + 1 ); + while ( total < bytes ) + { + int count = read( fd, buffer + total, bytes - total ); + if ( count >= 0 ) + total += count; + else + break; + } + buffer[ bytes ] = '\0'; + if ( bytes > 0 && total == bytes ) + { + if ( mlt_properties_get( owner, "push-parser-off" ) == 0 ) + { + service = ( mlt_service )mlt_factory_producer( "westley-xml", buffer ); + mlt_events_fire( owner, "push-received", &response, command, service, NULL ); + if ( response == NULL ) + response = valerie_parser_push( parser, command, service ); + } + else + { + response = valerie_parser_received( parser, command, buffer ); + } + } + error = connection_send( fd, response ); + valerie_response_close( response ); + mlt_service_close( service ); + free( buffer ); + } + else if ( strncmp( command, "STATUS", 6 ) ) + { + mlt_events_fire( owner, "command-received", &response, command, NULL ); + if ( response == NULL ) + response = valerie_parser_execute( parser, command ); + miracle_log( LOG_INFO, "%s \"%s\" %d", address, command, valerie_response_get_error_code( response ) ); + error = connection_send( fd, response ); + valerie_response_close( response ); + } + else + { + error = connection_status( fd, valerie_parser_get_notifier( parser ) ); + } + } + } + + /* Free the resources associated with this connection. */ + connection_close( fd ); + + miracle_log( LOG_NOTICE, "Connection with %s (%d) closed", address, fd ); + + free( connection ); + + return NULL; +} diff --git a/src/miracle/miracle_connection.h b/src/miracle/miracle_connection.h new file mode 100644 index 0000000..ea6d1e8 --- /dev/null +++ b/src/miracle/miracle_connection.h @@ -0,0 +1,92 @@ +/* + * miracle_connection.h -- DV Connection Handler + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DV_CONNECTION_H_ +#define _DV_CONNECTION_H_ + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Connection structure +*/ + +typedef struct +{ + mlt_properties owner; + int fd; + struct sockaddr_in sin; + valerie_parser parser; +} +connection_t; + +/** Enumeration for responses. +*/ + +typedef enum +{ + RESPONSE_SUCCESS = 200, + RESPONSE_SUCCESS_N = 201, + RESPONSE_SUCCESS_1 = 202, + RESPONSE_UNKNOWN_COMMAND = 400, + RESPONSE_TIMEOUT = 401, + RESPONSE_MISSING_ARG = 402, + RESPONSE_INVALID_UNIT = 403, + RESPONSE_BAD_FILE = 404, + RESPONSE_OUT_OF_RANGE = 405, + RESPONSE_TOO_MANY_FILES = 406, + RESPONSE_ERROR = 500 +} +response_codes; + +/* the following struct is passed as the single argument + to all command callback functions */ + +typedef struct +{ + valerie_parser parser; + valerie_response response; + valerie_tokeniser tokeniser; + char *command; + int unit; + void *argument; + char *root_dir; +} +command_argument_t, *command_argument; + +/* A handler is defined as follows. */ +typedef int (*command_handler_t) ( command_argument ); + + +extern void *parser_thread( void *arg ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_local.c b/src/miracle/miracle_local.c new file mode 100644 index 0000000..62daa84 --- /dev/null +++ b/src/miracle/miracle_local.c @@ -0,0 +1,597 @@ +/* + * miracle_local.c -- Local Miracle Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include + +/* Needed for backtrace on linux */ +#ifdef linux +#include +#endif + +/* Valerie header files */ +#include + +/* MLT header files. */ +#include + +/* Application header files */ +#include "miracle_local.h" +#include "miracle_connection.h" +#include "miracle_commands.h" +#include "miracle_unit_commands.h" +#include "miracle_log.h" + +/** Private miracle_local structure. +*/ + +typedef struct +{ + valerie_parser parser; + char root_dir[1024]; +} +*miracle_local, miracle_local_t; + +/** Forward declarations. +*/ + +static valerie_response miracle_local_connect( miracle_local ); +static valerie_response miracle_local_execute( miracle_local, char * ); +static valerie_response miracle_local_push( miracle_local, char *, mlt_service ); +static valerie_response miracle_local_receive( miracle_local, char *, char * ); +static void miracle_local_close( miracle_local ); +response_codes miracle_help( command_argument arg ); +response_codes miracle_run( command_argument arg ); +response_codes miracle_shutdown( command_argument arg ); + +/** DV Parser constructor. +*/ + +valerie_parser miracle_parser_init_local( ) +{ + valerie_parser parser = malloc( sizeof( valerie_parser_t ) ); + miracle_local local = malloc( sizeof( miracle_local_t ) ); + + if ( parser != NULL ) + { + memset( parser, 0, sizeof( valerie_parser_t ) ); + + parser->connect = (parser_connect)miracle_local_connect; + parser->execute = (parser_execute)miracle_local_execute; + parser->push = (parser_push)miracle_local_push; + parser->received = (parser_received)miracle_local_receive; + parser->close = (parser_close)miracle_local_close; + parser->real = local; + + if ( local != NULL ) + { + memset( local, 0, sizeof( miracle_local_t ) ); + local->parser = parser; + local->root_dir[0] = '/'; + } + + // Construct the factory + mlt_factory_init( getenv( "MLT_REPOSITORY" ) ); + } + return parser; +} + +/** response status code/message pair +*/ + +typedef struct +{ + int code; + char *message; +} +responses_t; + +/** response messages +*/ + +static responses_t responses [] = +{ + {RESPONSE_SUCCESS, "OK"}, + {RESPONSE_SUCCESS_N, "OK"}, + {RESPONSE_SUCCESS_1, "OK"}, + {RESPONSE_UNKNOWN_COMMAND, "Unknown command"}, + {RESPONSE_TIMEOUT, "Operation timed out"}, + {RESPONSE_MISSING_ARG, "Argument missing"}, + {RESPONSE_INVALID_UNIT, "Unit not found"}, + {RESPONSE_BAD_FILE, "Failed to locate or open clip"}, + {RESPONSE_OUT_OF_RANGE, "Argument value out of range"}, + {RESPONSE_TOO_MANY_FILES, "Too many files open"}, + {RESPONSE_ERROR, "Server Error"} +}; + +/** Argument types. +*/ + +typedef enum +{ + ATYPE_NONE, + ATYPE_FLOAT, + ATYPE_STRING, + ATYPE_INT, + ATYPE_PAIR +} +arguments_types; + +/** A command definition. +*/ + +typedef struct +{ +/* The command string corresponding to this operation (e.g. "play") */ + char *command; +/* The function associated with it */ + response_codes (*operation) ( command_argument ); +/* a boolean to indicate if this is a unit or global command + unit commands require a unit identifier as first argument */ + int is_unit; +/* What type is the argument (RTTI :-) ATYPE_whatever */ + int type; +/* online help information */ + char *help; +} +command_t; + +/* The following define the queue of commands available to the user. The + first entry is the name of the command (the string which must be typed), + the second command is the function associated with it, the third argument + is for the type of the argument, and the last argument specifies whether + this is something which should be handled immediately or whether it + should be queued (only robot motion commands need to be queued). */ + +static command_t vocabulary[] = +{ + {"BYE", NULL, 0, ATYPE_NONE, "Terminates the session. Units are not removed and task queue is not flushed."}, + {"HELP", miracle_help, 0, ATYPE_NONE, "Display this information!"}, + {"NLS", miracle_list_nodes, 0, ATYPE_NONE, "List the AV/C nodes on the 1394 bus."}, + {"UADD", miracle_add_unit, 0, ATYPE_STRING, "Create a new DV unit (virtual VTR) to transmit to receiver specified in GUID argument."}, + {"ULS", miracle_list_units, 0, ATYPE_NONE, "Lists the units that have already been added to the server."}, + {"CLS", miracle_list_clips, 0, ATYPE_STRING, "Lists the clips at directory name argument."}, + {"SET", miracle_set_global_property, 0, ATYPE_PAIR, "Set a server configuration property."}, + {"GET", miracle_get_global_property, 0, ATYPE_STRING, "Get a server configuration property."}, + {"RUN", miracle_run, 0, ATYPE_STRING, "Run a batch file." }, + {"LIST", miracle_list, 1, ATYPE_NONE, "List the playlist associated to a unit."}, + {"LOAD", miracle_load, 1, ATYPE_STRING, "Load clip specified in absolute filename argument."}, + {"INSERT", miracle_insert, 1, ATYPE_STRING, "Insert a clip at the given clip index."}, + {"REMOVE", miracle_remove, 1, ATYPE_NONE, "Remove a clip at the given clip index."}, + {"CLEAN", miracle_clean, 1, ATYPE_NONE, "Clean a unit by removing all but the currently playing clip."}, + {"WIPE", miracle_wipe, 1, ATYPE_NONE, "Clean a unit by removing everything before the currently playing clip."}, + {"CLEAR", miracle_clear, 1, ATYPE_NONE, "Clear a unit by removing all clips."}, + {"MOVE", miracle_move, 1, ATYPE_INT, "Move a clip to another clip index."}, + {"APND", miracle_append, 1, ATYPE_STRING, "Append a clip specified in absolute filename argument."}, + {"PLAY", miracle_play, 1, ATYPE_NONE, "Play a loaded clip at speed -2000 to 2000 where 1000 = normal forward speed."}, + {"STOP", miracle_stop, 1, ATYPE_NONE, "Stop a loaded and playing clip."}, + {"PAUSE", miracle_pause, 1, ATYPE_NONE, "Pause a playing clip."}, + {"REW", miracle_rewind, 1, ATYPE_NONE, "Rewind a unit. If stopped, seek to beginning of clip. If playing, play fast backwards."}, + {"FF", miracle_ff, 1, ATYPE_NONE, "Fast forward a unit. If stopped, seek to beginning of clip. If playing, play fast forwards."}, + {"STEP", miracle_step, 1, ATYPE_INT, "Step argument number of frames forward or backward."}, + {"GOTO", miracle_goto, 1, ATYPE_INT, "Jump to frame number supplied as argument."}, + {"SIN", miracle_set_in_point, 1, ATYPE_INT, "Set the IN point of the loaded clip to frame number argument. -1 = reset in point to 0"}, + {"SOUT", miracle_set_out_point, 1, ATYPE_INT, "Set the OUT point of the loaded clip to frame number argument. -1 = reset out point to maximum."}, + {"USTA", miracle_get_unit_status, 1, ATYPE_NONE, "Report information about the unit."}, + {"USET", miracle_set_unit_property, 1, ATYPE_PAIR, "Set a unit configuration property."}, + {"UGET", miracle_get_unit_property, 1, ATYPE_STRING, "Get a unit configuration property."}, + {"XFER", miracle_transfer, 1, ATYPE_STRING, "Transfer the unit's clip to another unit specified as argument."}, + {"SHUTDOWN", miracle_shutdown, 0, ATYPE_NONE, "Shutdown the server."}, + {NULL, NULL, 0, ATYPE_NONE, NULL} +}; + +/** Usage message +*/ + +static char helpstr [] = + "Miracle -- A Multimedia Playout Server\n" + " Copyright (C) 2002-2003 Ushodaya Enterprises Limited\n" + " Authors:\n" + " Dan Dennedy \n" + " Charles Yates \n" + "Available commands:\n"; + +/** Lookup the response message for a status code. +*/ + +inline char *get_response_msg( int code ) +{ + int i = 0; + for ( i = 0; responses[ i ].message != NULL && code != responses[ i ].code; i ++ ) ; + return responses[ i ].message; +} + +/** Tell the user the miracle command set +*/ + +response_codes miracle_help( command_argument cmd_arg ) +{ + int i = 0; + + valerie_response_printf( cmd_arg->response, 10240, "%s", helpstr ); + + for ( i = 0; vocabulary[ i ].command != NULL; i ++ ) + valerie_response_printf( cmd_arg->response, 1024, + "%-10.10s%s\n", + vocabulary[ i ].command, + vocabulary[ i ].help ); + + valerie_response_printf( cmd_arg->response, 2, "\n" ); + + return RESPONSE_SUCCESS_N; +} + +/** Execute a batch file. +*/ + +response_codes miracle_run( command_argument cmd_arg ) +{ + valerie_response temp = valerie_parser_run( cmd_arg->parser, (char *)cmd_arg->argument ); + + if ( temp != NULL ) + { + int index = 0; + + valerie_response_set_error( cmd_arg->response, + valerie_response_get_error_code( temp ), + valerie_response_get_error_string( temp ) ); + + for ( index = 1; index < valerie_response_count( temp ); index ++ ) + valerie_response_printf( cmd_arg->response, 10240, "%s\n", valerie_response_get_line( temp, index ) ); + + valerie_response_close( temp ); + } + + return valerie_response_get_error_code( cmd_arg->response ); +} + +response_codes miracle_shutdown( command_argument cmd_arg ) +{ + exit( 0 ); + return RESPONSE_SUCCESS; +} + +/** Processes 'thread' id +*/ + +static pthread_t self; + +/* Signal handler to deal with various shutdown signals. Basically this + should clean up and power down the motor. Note that the death of any + child thread will kill all thrads. */ + +void signal_handler( int sig ) +{ + if ( pthread_equal( self, pthread_self( ) ) ) + { + +#ifdef _GNU_SOURCE + miracle_log( LOG_DEBUG, "Received %s - shutting down.", strsignal(sig) ); +#else + miracle_log( LOG_DEBUG, "Received signal %i - shutting down.", sig ); +#endif + + exit(EXIT_SUCCESS); + } +} + +static void sigsegv_handler() +{ +#ifdef linux + void *array[ 10 ]; + size_t size; + char **strings; + size_t i; + + miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" + "Dumping stack from the offending thread\n\n" ); + size = backtrace( array, 10 ); + strings = backtrace_symbols( array, size ); + + miracle_log( LOG_CRIT, "Obtained %zd stack frames.\n", size ); + + for ( i = 0; i < size; i++ ) + miracle_log( LOG_CRIT, "%s", strings[ i ] ); + + free( strings ); + + miracle_log( LOG_CRIT, "\nDone dumping - exiting.\n" ); +#else + miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" ); +#endif + exit( EXIT_FAILURE ); +} + + + +/** Local 'connect' function. +*/ + +static valerie_response miracle_local_connect( miracle_local local ) +{ + valerie_response response = valerie_response_init( ); + + self = pthread_self( ); + + valerie_response_set_error( response, 100, "VTR Ready" ); + + signal( SIGHUP, signal_handler ); + signal( SIGINT, signal_handler ); + signal( SIGTERM, SIG_DFL ); + signal( SIGSTOP, signal_handler ); + signal( SIGPIPE, signal_handler ); + signal( SIGALRM, signal_handler ); + signal( SIGCHLD, SIG_IGN ); + if ( getenv( "MLT_SIGSEGV" ) ) + signal( SIGSEGV, sigsegv_handler ); + + return response; +} + +/** Set the error and determine the message associated to this command. +*/ + +void miracle_command_set_error( command_argument cmd, response_codes code ) +{ + valerie_response_set_error( cmd->response, code, get_response_msg( code ) ); +} + +/** Parse the unit argument. +*/ + +int miracle_command_parse_unit( command_argument cmd, int argument ) +{ + int unit = -1; + char *string = valerie_tokeniser_get_string( cmd->tokeniser, argument ); + if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 ) + unit = atoi( string + 1 ); + return unit; +} + +/** Parse a normal argument. +*/ + +void *miracle_command_parse_argument( command_argument cmd, int argument, arguments_types type, char *command ) +{ + void *ret = NULL; + char *value = valerie_tokeniser_get_string( cmd->tokeniser, argument ); + + if ( value != NULL ) + { + switch( type ) + { + case ATYPE_NONE: + break; + + case ATYPE_FLOAT: + ret = malloc( sizeof( float ) ); + if ( ret != NULL ) + *( float * )ret = atof( value ); + break; + + case ATYPE_STRING: + ret = strdup( value ); + break; + + case ATYPE_PAIR: + if ( strchr( command, '=' ) ) + { + char *ptr = strchr( command, '=' ); + while ( *( ptr - 1 ) != ' ' ) + ptr --; + ret = strdup( ptr ); + ptr = ret; + while( ptr[ strlen( ptr ) - 1 ] == ' ' ) + ptr[ strlen( ptr ) - 1 ] = '\0'; + } + break; + + case ATYPE_INT: + ret = malloc( sizeof( int ) ); + if ( ret != NULL ) + *( int * )ret = atoi( value ); + break; + } + } + + return ret; +} + +/** Get the error code - note that we simply the success return. +*/ + +response_codes miracle_command_get_error( command_argument cmd ) +{ + response_codes ret = valerie_response_get_error_code( cmd->response ); + if ( ret == RESPONSE_SUCCESS_N || ret == RESPONSE_SUCCESS_1 ) + ret = RESPONSE_SUCCESS; + return ret; +} + +/** Execute the command. +*/ + +static valerie_response miracle_local_execute( miracle_local local, char *command ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_UNKNOWN_COMMAND ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + char *value = valerie_tokeniser_get_string( cmd.tokeniser, 0 ); + int found = 0; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + /* Search the vocabulary array for value */ + for ( index = 1; !found && vocabulary[ index ].command != NULL; index ++ ) + if ( ( found = !strcasecmp( vocabulary[ index ].command, value ) ) ) + break; + + /* If we found something, the handle the args and call the handler. */ + if ( found ) + { + int position = 1; + + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + if ( vocabulary[ index ].is_unit ) + { + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + } + + if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS ) + { + cmd.argument = miracle_command_parse_argument( &cmd, position, vocabulary[ index ].type, command ); + if ( cmd.argument == NULL && vocabulary[ index ].type != ATYPE_NONE ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + } + + if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS ) + { + response_codes error = vocabulary[ index ].operation( &cmd ); + miracle_command_set_error( &cmd, error ); + } + + free( cmd.argument ); + } + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +static valerie_response miracle_local_receive( miracle_local local, char *command, char *doc ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + int position = 1; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + + miracle_receive( &cmd, doc ); + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + free( cmd.argument ); + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +static valerie_response miracle_local_push( miracle_local local, char *command, mlt_service service ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + int position = 1; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + + miracle_push( &cmd, service ); + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + free( cmd.argument ); + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +/** Close the parser. +*/ + +static void miracle_local_close( miracle_local local ) +{ + miracle_delete_all_units(); +#ifdef linux + //pthread_kill_other_threads_np(); + miracle_log( LOG_DEBUG, "Clean shutdown." ); + //free( local ); + //mlt_factory_close( ); +#endif +} diff --git a/src/miracle/miracle_local.h b/src/miracle/miracle_local.h new file mode 100644 index 0000000..06f9439 --- /dev/null +++ b/src/miracle/miracle_local.h @@ -0,0 +1,41 @@ +/* + * miracle_local.h -- Local Miracle Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _MIRACLE_LOCAL_H_ +#define _MIRACLE_LOCAL_H_ + +/* Application header files */ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Local parser API. +*/ + +extern valerie_parser miracle_parser_init_local( ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_log.c b/src/miracle/miracle_log.c new file mode 100644 index 0000000..8741843 --- /dev/null +++ b/src/miracle/miracle_log.c @@ -0,0 +1,57 @@ +/* + * miracle_log.c -- logging facility implementation + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "miracle_log.h" + +static int log_output = log_stderr; +static int threshold = LOG_DEBUG; + +void miracle_log_init( enum log_output method, int new_threshold ) +{ + log_output = method; + threshold = new_threshold; + if (method == log_syslog) + openlog( "miracle", LOG_CONS, LOG_DAEMON ); + +} + +void miracle_log( int priority, char *format, ... ) +{ + va_list list; + va_start( list, format ); + if ( LOG_PRI(priority) <= threshold ) + { + if ( log_output == log_syslog ) + { + vsyslog( priority, format, list ); + } + else + { + char line[1024]; + if ( snprintf( line, 1024, "(%d) %s\n", priority, format ) != 0 ) + vfprintf( stderr, line, list ); + } + } + va_end( list ); +} diff --git a/src/miracle/miracle_log.h b/src/miracle/miracle_log.h new file mode 100644 index 0000000..044343f --- /dev/null +++ b/src/miracle/miracle_log.h @@ -0,0 +1,43 @@ +/* + * miracle_log.h -- logging facility header + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum log_output { + log_stderr, + log_syslog +}; + +void miracle_log_init( enum log_output method, int threshold ); +void miracle_log( int priority, char *format, ... ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_server.c b/src/miracle/miracle_server.c new file mode 100644 index 0000000..bde78b5 --- /dev/null +++ b/src/miracle/miracle_server.c @@ -0,0 +1,323 @@ +/* + * miracle_server.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Application header files */ +#include "miracle_server.h" +#include "miracle_connection.h" +#include "miracle_local.h" +#include "miracle_log.h" +#include "miracle_commands.h" +#include +#include + +#define VERSION "0.0.1" + +static void miracle_command_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ] ); +} + +static void miracle_doc_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ], ( char * )args[ 2 ] ); +} + +static void miracle_push_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ], ( mlt_service )args[ 2 ] ); +} + +/** Initialise a server structure. +*/ + +miracle_server miracle_server_init( char *id ) +{ + miracle_server server = malloc( sizeof( miracle_server_t ) ); + if ( server != NULL ) + memset( server, 0, sizeof( miracle_server_t ) ); + if ( server != NULL && mlt_properties_init( &server->parent, server ) == 0 ) + { + server->id = id; + server->port = DEFAULT_TCP_PORT; + server->socket = -1; + server->shutdown = 1; + mlt_events_init( &server->parent ); + mlt_events_register( &server->parent, "command-received", ( mlt_transmitter )miracle_command_received ); + mlt_events_register( &server->parent, "doc-received", ( mlt_transmitter )miracle_doc_received ); + mlt_events_register( &server->parent, "push-received", ( mlt_transmitter )miracle_push_received ); + } + return server; +} + +const char *miracle_server_id( miracle_server server ) +{ + return server != NULL && server->id != NULL ? server->id : "miracle"; +} + +void miracle_server_set_config( miracle_server server, char *config ) +{ + if ( server != NULL ) + { + free( server->config ); + server->config = config != NULL ? strdup( config ) : NULL; + } +} + +/** Set the port of the server. +*/ + +void miracle_server_set_port( miracle_server server, int port ) +{ + server->port = port; +} + +void miracle_server_set_proxy( miracle_server server, char *proxy ) +{ + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + server->proxy = 1; + server->remote_port = DEFAULT_TCP_PORT; + valerie_tokeniser_parse_new( tokeniser, proxy, ":" ); + strcpy( server->remote_server, valerie_tokeniser_get_string( tokeniser, 0 ) ); + if ( valerie_tokeniser_count( tokeniser ) == 2 ) + server->remote_port = atoi( valerie_tokeniser_get_string( tokeniser, 1 ) ); + valerie_tokeniser_close( tokeniser ); +} + +/** Wait for a connection. +*/ + +static int miracle_server_wait_for_connect( miracle_server server ) +{ + struct timeval tv; + fd_set rfds; + + /* Wait for a 1 second. */ + tv.tv_sec = 1; + tv.tv_usec = 0; + + FD_ZERO( &rfds ); + FD_SET( server->socket, &rfds ); + + return select( server->socket + 1, &rfds, NULL, NULL, &tv); +} + +/** Run the server thread. +*/ + +static void *miracle_server_run( void *arg ) +{ + miracle_server server = arg; + pthread_t cmd_parse_info; + connection_t *tmp = NULL; + pthread_attr_t thread_attributes; + socklen_t socksize; + + socksize = sizeof( struct sockaddr ); + + miracle_log( LOG_NOTICE, "%s version %s listening on port %i", server->id, VERSION, server->port ); + + /* Create the initial thread. We want all threads to be created detached so + their resources get freed automatically. (CY: ... hmmph...) */ + pthread_attr_init( &thread_attributes ); + pthread_attr_setdetachstate( &thread_attributes, PTHREAD_CREATE_DETACHED ); + + while ( !server->shutdown ) + { + /* Wait for a new connection. */ + if ( miracle_server_wait_for_connect( server ) ) + { + /* Create a new block of data to hold a copy of the incoming connection for + our server thread. The thread should free this when it terminates. */ + + tmp = (connection_t*) malloc( sizeof(connection_t) ); + tmp->owner = &server->parent; + tmp->parser = server->parser; + tmp->fd = accept( server->socket, (struct sockaddr*) &(tmp->sin), &socksize ); + + /* Pass the connection to a parser thread :-/ */ + if ( tmp->fd != -1 ) + pthread_create( &cmd_parse_info, &thread_attributes, parser_thread, tmp ); + } + } + + miracle_log( LOG_NOTICE, "%s version %s server terminated.", server->id, VERSION ); + + return NULL; +} + +/** Execute the server thread. +*/ + +int miracle_server_execute( miracle_server server ) +{ + int error = 0; + valerie_response response = NULL; + int index = 0; + struct sockaddr_in ServerAddr; + int flag = 1; + + server->shutdown = 0; + + ServerAddr.sin_family = AF_INET; + ServerAddr.sin_port = htons( server->port ); + ServerAddr.sin_addr.s_addr = INADDR_ANY; + + /* Create socket, and bind to port. Listen there. Backlog = 5 + should be sufficient for listen (). */ + server->socket = socket( AF_INET, SOCK_STREAM, 0 ); + + if ( server->socket == -1 ) + { + server->shutdown = 1; + perror( "socket" ); + miracle_log( LOG_ERR, "%s unable to create socket.", server->id ); + return -1; + } + + setsockopt( server->socket, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof( int ) ); + + if ( bind( server->socket, (struct sockaddr *) &ServerAddr, sizeof (ServerAddr) ) != 0 ) + { + server->shutdown = 1; + perror( "bind" ); + miracle_log( LOG_ERR, "%s unable to bind to port %d.", server->id, server->port ); + return -1; + } + + if ( listen( server->socket, 5 ) != 0 ) + { + server->shutdown = 1; + perror( "listen" ); + miracle_log( LOG_ERR, "%s unable to listen on port %d.", server->id, server->port ); + return -1; + } + + fcntl( server->socket, F_SETFL, O_NONBLOCK ); + + if ( !server->proxy ) + { + miracle_log( LOG_NOTICE, "Starting server on %d.", server->port ); + server->parser = miracle_parser_init_local( ); + } + else + { + miracle_log( LOG_NOTICE, "Starting proxy for %s:%d on %d.", server->remote_server, server->remote_port, server->port ); + server->parser = valerie_parser_init_remote( server->remote_server, server->remote_port ); + } + + response = valerie_parser_connect( server->parser ); + + if ( response != NULL && valerie_response_get_error_code( response ) == 100 ) + { + /* read configuration file */ + if ( response != NULL && !server->proxy && server->config != NULL ) + { + valerie_response_close( response ); + response = valerie_parser_run( server->parser, server->config ); + + if ( valerie_response_count( response ) > 1 ) + { + if ( valerie_response_get_error_code( response ) > 299 ) + miracle_log( LOG_ERR, "Error evaluating server configuration. Processing stopped." ); + for ( index = 0; index < valerie_response_count( response ); index ++ ) + miracle_log( LOG_DEBUG, "%4d: %s", index, valerie_response_get_line( response, index ) ); + } + } + + if ( response != NULL ) + { + int result; + valerie_response_close( response ); + result = pthread_create( &server->thread, NULL, miracle_server_run, server ); + if ( result ) + { + miracle_log( LOG_CRIT, "Failed to launch TCP listener thread" ); + error = -1; + } + } + } + else + { + miracle_log( LOG_ERR, "Error connecting to parser. Processing stopped." ); + server->shutdown = 1; + error = -1; + } + + return error; +} + +/** Fetch a units properties +*/ + +mlt_properties miracle_server_fetch_unit( miracle_server server, int index ) +{ + miracle_unit unit = miracle_get_unit( index ); + return unit != NULL ? unit->properties : NULL; +} + +/** Shutdown the server. +*/ + +void miracle_server_shutdown( miracle_server server ) +{ + if ( server != NULL && !server->shutdown ) + { + server->shutdown = 1; + pthread_join( server->thread, NULL ); + miracle_server_set_config( server, NULL ); + valerie_parser_close( server->parser ); + server->parser = NULL; + close( server->socket ); + } +} + +/** Close the server. +*/ + +void miracle_server_close( miracle_server server ) +{ + if ( server != NULL && mlt_properties_dec_ref( &server->parent ) <= 0 ) + { + mlt_properties_close( &server->parent ); + miracle_server_shutdown( server ); + free( server ); + } +} diff --git a/src/miracle/miracle_server.h b/src/miracle/miracle_server.h new file mode 100644 index 0000000..efc5f70 --- /dev/null +++ b/src/miracle/miracle_server.h @@ -0,0 +1,76 @@ +/* + * miracle_server.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _MIRACLE_SERVER_H_ +#define _MIRACLE_SERVER_H_ + +/* System header files */ +#include + +/* Application header files */ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Servers default port +*/ + +#define DEFAULT_TCP_PORT 5250 + +/** Structure for the server +*/ + +typedef struct +{ + struct mlt_properties_s parent; + char *id; + int port; + int socket; + valerie_parser parser; + pthread_t thread; + int shutdown; + int proxy; + char remote_server[ 50 ]; + int remote_port; + char *config; +} +*miracle_server, miracle_server_t; + +/** API for the server +*/ + +extern miracle_server miracle_server_init( char * ); +extern const char *miracle_server_id( miracle_server ); +extern void miracle_server_set_config( miracle_server, char * ); +extern void miracle_server_set_port( miracle_server, int ); +extern void miracle_server_set_proxy( miracle_server, char * ); +extern int miracle_server_execute( miracle_server ); +extern mlt_properties miracle_server_fetch_unit( miracle_server, int ); +extern void miracle_server_shutdown( miracle_server ); +extern void miracle_server_close( miracle_server ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_unit.c b/src/miracle/miracle_unit.c new file mode 100644 index 0000000..611c395 --- /dev/null +++ b/src/miracle/miracle_unit.c @@ -0,0 +1,769 @@ +/* + * miracle_unit.c -- Transmission Unit Implementation + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "miracle_unit.h" +#include "miracle_log.h" +#include "miracle_local.h" + +#include + +/* Forward references */ +static void miracle_unit_status_communicate( miracle_unit ); + +/** Allocate a new DV transmission unit. + + \return A new miracle_unit handle. +*/ + +miracle_unit miracle_unit_init( int index, char *constructor ) +{ + miracle_unit this = NULL; + mlt_consumer consumer = NULL; + + char *id = strdup( constructor ); + char *arg = strchr( id, ':' ); + + if ( arg != NULL ) + *arg ++ = '\0'; + + consumer = mlt_factory_consumer( id, arg ); + + if ( consumer != NULL ) + { + mlt_playlist playlist = mlt_playlist_init( ); + this = calloc( sizeof( miracle_unit_t ), 1 ); + this->properties = mlt_properties_new( ); + mlt_properties_init( this->properties, this ); + mlt_properties_set_int( this->properties, "unit", index ); + mlt_properties_set_int( this->properties, "generation", 0 ); + mlt_properties_set( this->properties, "constructor", constructor ); + mlt_properties_set( this->properties, "id", id ); + mlt_properties_set( this->properties, "arg", arg ); + mlt_properties_set_data( this->properties, "consumer", consumer, 0, ( mlt_destructor )mlt_consumer_close, NULL ); + mlt_properties_set_data( this->properties, "playlist", playlist, 0, ( mlt_destructor )mlt_playlist_close, NULL ); + mlt_consumer_connect( consumer, MLT_PLAYLIST_SERVICE( playlist ) ); + } + + return this; +} + +static char *strip_root( miracle_unit unit, char *file ) +{ + mlt_properties properties = unit->properties; + char *root = mlt_properties_get( properties, "root" ); + if ( file != NULL && root != NULL ) + { + int length = strlen( root ); + if ( root[ length - 1 ] == '/' ) + length --; + if ( !strncmp( file, root, length ) ) + file += length; + } + return file; +} + +/** Communicate the current status to all threads waiting on the notifier. +*/ + +static void miracle_unit_status_communicate( miracle_unit unit ) +{ + if ( unit != NULL ) + { + mlt_properties properties = unit->properties; + char *root_dir = mlt_properties_get( properties, "root" ); + valerie_notifier notifier = mlt_properties_get_data( properties, "notifier", NULL ); + valerie_status_t status; + + if ( root_dir != NULL && notifier != NULL ) + { + if ( miracle_unit_get_status( unit, &status ) == 0 ) + /* if ( !( ( status.status == unit_playing || status.status == unit_paused ) && + strcmp( status.clip, "" ) && + !strcmp( status.tail_clip, "" ) && + status.position == 0 && + status.in == 0 && + status.out == 0 ) ) */ + valerie_notifier_put( notifier, &status ); + } + } +} + +/** Set the notifier info +*/ + +void miracle_unit_set_notifier( miracle_unit this, valerie_notifier notifier, char *root_dir ) +{ + mlt_properties properties = this->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_properties playlist_properties = MLT_PLAYLIST_PROPERTIES( playlist ); + + mlt_properties_set( properties, "root", root_dir ); + mlt_properties_set_data( properties, "notifier", notifier, 0, NULL, NULL ); + mlt_properties_set_data( playlist_properties, "notifier_arg", this, 0, NULL, NULL ); + mlt_properties_set_data( playlist_properties, "notifier", miracle_unit_status_communicate, 0, NULL, NULL ); + + miracle_unit_status_communicate( this ); +} + +/** Create or locate a producer for the file specified. +*/ + +static mlt_producer locate_producer( miracle_unit unit, char *file ) +{ + return mlt_factory_producer( "fezzik", file ); +} + +/** Update the generation count. +*/ + +static void update_generation( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + int generation = mlt_properties_get_int( properties, "generation" ); + mlt_properties_set_int( properties, "generation", ++ generation ); +} + +/** Wipe all clips on the playlist for this unit. +*/ + +static void clear_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_clear( playlist ); + mlt_producer_seek( producer, 0 ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + + update_generation( unit ); +} + +/** Wipe all but the playing clip from the unit. +*/ + +static void clean_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int current = mlt_playlist_current_clip( playlist ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_position position = mlt_producer_frame( producer ); + double speed = mlt_producer_get_speed( producer ); + mlt_playlist_get_clip_info( playlist, &info, current ); + + if ( info.producer != NULL ) + { + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( info.producer ) ); + position -= info.start; + clear_unit( unit ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, info.producer, info.frame_in, info.frame_out ); + mlt_producer_seek( producer, position ); + mlt_producer_set_speed( producer, speed ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_producer_close( info.producer ); + } + + update_generation( unit ); +} + +/** Remove everything up to the current clip from the unit. +*/ + +static void wipe_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int current = mlt_playlist_current_clip( playlist ); + mlt_playlist_get_clip_info( playlist, &info, current ); + + if ( info.producer != NULL && info.start > 0 ) + { + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_remove_region( playlist, 0, info.start - 1 ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + } + + update_generation( unit ); +} + +/** Generate a report on all loaded clips. +*/ + +void miracle_unit_report_list( miracle_unit unit, valerie_response response ) +{ + int i; + mlt_properties properties = unit->properties; + int generation = mlt_properties_get_int( properties, "generation" ); + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + + valerie_response_printf( response, 1024, "%d\n", generation ); + + for ( i = 0; i < mlt_playlist_count( playlist ); i ++ ) + { + mlt_playlist_clip_info info; + char *title; + mlt_playlist_get_clip_info( playlist , &info, i ); + title = mlt_properties_get( MLT_PRODUCER_PROPERTIES( info.producer ), "title" ); + if ( title == NULL ) + title = strip_root( unit, info.resource ); + valerie_response_printf( response, 10240, "%d \"%s\" %d %d %d %d %.2f\n", + i, + title, + info.frame_in, + info.frame_out, + info.frame_count, + info.length, + info.fps ); + } + valerie_response_printf( response, 1024, "\n" ); +} + +/** Load a clip into the unit clearing existing play list. + + \todo error handling + \param unit A miracle_unit handle. + \param clip The absolute file name of the clip to load. + \param in The starting frame (-1 for 0) + \param out The ending frame (-1 for maximum) +*/ + +valerie_error_code miracle_unit_load( miracle_unit unit, char *clip, int32_t in, int32_t out, int flush ) +{ + // Now try to create a producer + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + int original = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( playlist ) ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, instance, in, out ); + mlt_playlist_remove_region( playlist, 0, original ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "loaded clip %s", clip ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +valerie_error_code miracle_unit_insert( miracle_unit unit, char *clip, int index, int32_t in, int32_t out ) +{ + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + fprintf( stderr, "inserting clip %s before %d\n", clip, index ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_insert( playlist, instance, index, in, out ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "inserted clip %s at %d", clip, index ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +valerie_error_code miracle_unit_remove( miracle_unit unit, int index ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_remove( playlist, index ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "removed clip at %d", index ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_clean( miracle_unit unit ) +{ + clean_unit( unit ); + miracle_log( LOG_DEBUG, "Cleaned playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_wipe( miracle_unit unit ) +{ + wipe_unit( unit ); + miracle_log( LOG_DEBUG, "Wiped playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_clear( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + clear_unit( unit ); + mlt_consumer_purge( consumer ); + miracle_log( LOG_DEBUG, "Cleared playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_move( miracle_unit unit, int src, int dest ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_move( playlist, src, dest ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "moved clip %d to %d", src, dest ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +/** Add a clip to the unit play list. + + \todo error handling + \param unit A miracle_unit handle. + \param clip The absolute file name of the clip to load. + \param in The starting frame (-1 for 0) + \param out The ending frame (-1 for maximum) +*/ + +valerie_error_code miracle_unit_append( miracle_unit unit, char *clip, int32_t in, int32_t out ) +{ + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, instance, in, out ); + miracle_log( LOG_DEBUG, "appended clip %s", clip ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +/** Add an mlt_service to the playlist + + \param unit A miracle_unit handle. + \param service the service to add +*/ + +valerie_error_code miracle_unit_append_service( miracle_unit unit, mlt_service service ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append( playlist, ( mlt_producer )service ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "appended clip" ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +/** Start playing the unit. + + \todo error handling + \param unit A miracle_unit handle. + \param speed An integer that specifies the playback rate as a + percentage multiplied by 100. +*/ + +void miracle_unit_play( miracle_unit_t *unit, int speed ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + mlt_producer_set_speed( producer, ( double )speed / 1000 ); + mlt_consumer_start( consumer ); + miracle_unit_status_communicate( unit ); +} + +/** Stop playback. + + Terminates the dv_pump and halts dv1394 transmission. + + \param unit A miracle_unit handle. +*/ + +void miracle_unit_terminate( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_producer_set_speed( producer, 0 ); + mlt_consumer_stop( consumer ); + miracle_unit_status_communicate( unit ); +} + +/** Query the status of unit playback. + + \param unit A miracle_unit handle. + \return 1 if the unit is not playing, 0 if playing. +*/ + +int miracle_unit_has_terminated( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + return mlt_consumer_is_stopped( consumer ); +} + +/** Transfer the currently loaded clip to another unit +*/ + +int miracle_unit_transfer( miracle_unit dest_unit, miracle_unit src_unit ) +{ + int i; + mlt_properties dest_properties = dest_unit->properties; + mlt_playlist dest_playlist = mlt_properties_get_data( dest_properties, "playlist", NULL ); + mlt_properties src_properties = src_unit->properties; + mlt_playlist src_playlist = mlt_properties_get_data( src_properties, "playlist", NULL ); + mlt_playlist tmp_playlist = mlt_playlist_init( ); + + for ( i = 0; i < mlt_playlist_count( src_playlist ); i ++ ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( src_playlist, &info, i ); + if ( info.producer != NULL ) + mlt_playlist_append_io( tmp_playlist, info.producer, info.frame_in, info.frame_out ); + } + + clear_unit( src_unit ); + + mlt_service_lock( MLT_PLAYLIST_SERVICE( dest_playlist ) ); + + for ( i = 0; i < mlt_playlist_count( tmp_playlist ); i ++ ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( tmp_playlist, &info, i ); + if ( info.producer != NULL ) + mlt_playlist_append_io( dest_playlist, info.producer, info.frame_in, info.frame_out ); + } + + mlt_service_unlock( MLT_PLAYLIST_SERVICE( dest_playlist ) ); + + update_generation( dest_unit ); + miracle_unit_status_communicate( dest_unit ); + + mlt_playlist_close( tmp_playlist ); + + return 0; +} + +/** Determine if unit is offline. +*/ + +int miracle_unit_is_offline( miracle_unit unit ) +{ + return 0; +} + +/** Obtain the status for a given unit +*/ + +int miracle_unit_get_status( miracle_unit unit, valerie_status status ) +{ + int error = unit == NULL; + + memset( status, 0, sizeof( valerie_status_t ) ); + + if ( !error ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_producer clip = mlt_playlist_current( playlist ); + + mlt_playlist_clip_info info; + int clip_index = mlt_playlist_current_clip( playlist ); + mlt_playlist_get_clip_info( playlist, &info, clip_index ); + + if ( info.resource != NULL && strcmp( info.resource, "" ) ) + { + char *title = mlt_properties_get( MLT_PRODUCER_PROPERTIES( info.producer ), "title" ); + if ( title == NULL ) + title = strip_root( unit, info.resource ); + strncpy( status->clip, title, sizeof( status->clip ) ); + status->speed = (int)( mlt_producer_get_speed( producer ) * 1000.0 ); + status->fps = mlt_producer_get_fps( producer ); + status->in = info.frame_in; + status->out = info.frame_out; + status->position = mlt_producer_frame( clip ); + status->length = mlt_producer_get_length( clip ); + strncpy( status->tail_clip, title, sizeof( status->tail_clip ) ); + status->tail_in = info.frame_in; + status->tail_out = info.frame_out; + status->tail_position = mlt_producer_frame( clip ); + status->tail_length = mlt_producer_get_length( clip ); + status->clip_index = mlt_playlist_current_clip( playlist ); + status->seek_flag = 1; + } + + status->generation = mlt_properties_get_int( properties, "generation" ); + + if ( miracle_unit_has_terminated( unit ) ) + status->status = unit_stopped; + else if ( !strcmp( status->clip, "" ) ) + status->status = unit_not_loaded; + else if ( status->speed == 0 ) + status->status = unit_paused; + else + status->status = unit_playing; + } + else + { + status->status = unit_undefined; + } + + status->unit = mlt_properties_get_int( unit->properties, "unit" ); + + return error; +} + +/** Change position in the playlist. +*/ + +void miracle_unit_change_position( miracle_unit unit, int clip, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_playlist_clip_info info; + + if ( clip < 0 ) + { + clip = 0; + position = 0; + } + else if ( clip >= mlt_playlist_count( playlist ) ) + { + clip = mlt_playlist_count( playlist ) - 1; + position = INT_MAX; + } + + if ( mlt_playlist_get_clip_info( playlist, &info, clip ) == 0 ) + { + int32_t frame_start = info.start; + int32_t frame_offset = position; + + if ( frame_offset < 0 ) + frame_offset = info.frame_out; + if ( frame_offset < info.frame_in ) + frame_offset = info.frame_in; + if ( frame_offset >= info.frame_out ) + frame_offset = info.frame_out; + + mlt_producer_seek( producer, frame_start + frame_offset - info.frame_in ); + } + + miracle_unit_status_communicate( unit ); +} + +/** Get the index of the current clip. +*/ + +int miracle_unit_get_current_clip( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + int clip_index = mlt_playlist_current_clip( playlist ); + return clip_index; +} + +/** Set a clip's in point +*/ + +int miracle_unit_set_clip_in( miracle_unit unit, int index, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int error = mlt_playlist_get_clip_info( playlist, &info, index ); + + if ( error == 0 ) + { + miracle_unit_play( unit, 0 ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + error = mlt_playlist_resize_clip( playlist, index, position, info.frame_out ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_change_position( unit, index, 0 ); + } + + return error; +} + +/** Set a clip's out point. +*/ + +int miracle_unit_set_clip_out( miracle_unit unit, int index, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int error = mlt_playlist_get_clip_info( playlist, &info, index ); + + if ( error == 0 ) + { + miracle_unit_play( unit, 0 ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + error = mlt_playlist_resize_clip( playlist, index, info.frame_in, position ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + miracle_unit_change_position( unit, index, -1 ); + } + + return error; +} + +/** Step by specified position. +*/ + +void miracle_unit_step( miracle_unit unit, int32_t offset ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_position position = mlt_producer_frame( producer ); + mlt_producer_seek( producer, position + offset ); +} + +/** Set the unit's clip mode regarding in and out points. +*/ + +//void miracle_unit_set_mode( miracle_unit unit, dv_player_clip_mode mode ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //if ( player != NULL ) + //dv_player_set_clip_mode( player, mode ); + //miracle_unit_status_communicate( unit ); +//} + +/** Get the unit's clip mode regarding in and out points. +*/ + +//dv_player_clip_mode miracle_unit_get_mode( miracle_unit unit ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //return dv_player_get_clip_mode( player ); +//} + +/** Set the unit's clip mode regarding eof handling. +*/ + +//void miracle_unit_set_eof_action( miracle_unit unit, dv_player_eof_action action ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //dv_player_set_eof_action( player, action ); + //miracle_unit_status_communicate( unit ); +//} + +/** Get the unit's clip mode regarding eof handling. +*/ + +//dv_player_eof_action miracle_unit_get_eof_action( miracle_unit unit ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //return dv_player_get_eof_action( player ); +//} + +int miracle_unit_set( miracle_unit unit, char *name_value ) +{ + mlt_properties properties = NULL; + + if ( strncmp( name_value, "consumer.", 9 ) ) + { + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + properties = MLT_PLAYLIST_PROPERTIES( playlist ); + } + else + { + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + properties = MLT_CONSUMER_PROPERTIES( consumer ); + name_value += 9; + } + + return mlt_properties_parse( properties, name_value ); +} + +char *miracle_unit_get( miracle_unit unit, char *name ) +{ + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( playlist ); + return mlt_properties_get( properties, name ); +} + +/** Release the unit + + \todo error handling + \param unit A miracle_unit handle. +*/ + +void miracle_unit_close( miracle_unit unit ) +{ + if ( unit != NULL ) + { + miracle_log( LOG_DEBUG, "closing unit..." ); + miracle_unit_terminate( unit ); + mlt_properties_close( unit->properties ); + free( unit ); + miracle_log( LOG_DEBUG, "... unit closed." ); + } +} + diff --git a/src/miracle/miracle_unit.h b/src/miracle/miracle_unit.h new file mode 100644 index 0000000..0b185d0 --- /dev/null +++ b/src/miracle/miracle_unit.h @@ -0,0 +1,82 @@ +/* + * dvunit.h -- Transmission Unit Header + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DV_UNIT_H_ +#define _DV_UNIT_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + mlt_properties properties; +} +miracle_unit_t, *miracle_unit; + +extern miracle_unit miracle_unit_init( int index, char *arg ); +extern void miracle_unit_report_list( miracle_unit unit, valerie_response response ); +extern void miracle_unit_allow_stdin( miracle_unit unit, int flag ); +extern valerie_error_code miracle_unit_load( miracle_unit unit, char *clip, int32_t in, int32_t out, int flush ); +extern valerie_error_code miracle_unit_insert( miracle_unit unit, char *clip, int index, int32_t in, int32_t out ); +extern valerie_error_code miracle_unit_append( miracle_unit unit, char *clip, int32_t in, int32_t out ); +extern valerie_error_code miracle_unit_append_service( miracle_unit unit, mlt_service service ); +extern valerie_error_code miracle_unit_remove( miracle_unit unit, int index ); +extern valerie_error_code miracle_unit_clean( miracle_unit unit ); +extern valerie_error_code miracle_unit_wipe( miracle_unit unit ); +extern valerie_error_code miracle_unit_clear( miracle_unit unit ); +extern valerie_error_code miracle_unit_move( miracle_unit unit, int src, int dest ); +extern int miracle_unit_transfer( miracle_unit dest_unit, miracle_unit src_unit ); +extern void miracle_unit_play( miracle_unit_t *unit, int speed ); +extern void miracle_unit_terminate( miracle_unit ); +extern int miracle_unit_has_terminated( miracle_unit ); +extern int miracle_unit_get_nodeid( miracle_unit unit ); +extern int miracle_unit_get_channel( miracle_unit unit ); +extern int miracle_unit_is_offline( miracle_unit unit ); +extern void miracle_unit_set_notifier( miracle_unit, valerie_notifier, char * ); +extern int miracle_unit_get_status( miracle_unit, valerie_status ); +extern void miracle_unit_change_position( miracle_unit, int, int32_t position ); +extern void miracle_unit_change_speed( miracle_unit unit, int speed ); +extern int miracle_unit_set_clip_in( miracle_unit unit, int index, int32_t position ); +extern int miracle_unit_set_clip_out( miracle_unit unit, int index, int32_t position ); +//extern void miracle_unit_set_mode( miracle_unit unit, dv_player_clip_mode mode ); +//extern dv_player_clip_mode miracle_unit_get_mode( miracle_unit unit ); +//extern void miracle_unit_set_eof_action( miracle_unit unit, dv_player_eof_action mode ); +//extern dv_player_eof_action miracle_unit_get_eof_action( miracle_unit unit ); +extern void miracle_unit_step( miracle_unit unit, int32_t offset ); +extern void miracle_unit_close( miracle_unit unit ); +extern void miracle_unit_suspend( miracle_unit ); +extern void miracle_unit_restore( miracle_unit ); +extern int miracle_unit_set( miracle_unit, char *name_value ); +extern char * miracle_unit_get( miracle_unit, char *name ); +extern int miracle_unit_get_current_clip( miracle_unit ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_unit_commands.c b/src/miracle/miracle_unit_commands.c new file mode 100644 index 0000000..7cc3b9a --- /dev/null +++ b/src/miracle/miracle_unit_commands.c @@ -0,0 +1,485 @@ +/* + * unit_commands.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "miracle_unit.h" +#include "miracle_commands.h" +#include "miracle_log.h" + +int miracle_load( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + int flush = 1; + char *service; + + if ( filename[0] == '!' ) + { + flush = 0; + filename ++; + } + + service = strchr( filename, ':' ); + if ( service != NULL ) + { + service = filename; + filename = strchr( service, ':' ); + *filename ++ = '\0'; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s:%s%s", service, cmd_arg->root_dir, filename ); + } + else + { + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + } + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int32_t in = -1, out = -1; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 5 ) + { + in = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 3 ) ); + out = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + } + if ( miracle_unit_load( unit, fullname, in, out, flush ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_list( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit( cmd_arg->unit ); + + if ( unit != NULL ) + { + miracle_unit_report_list( unit, cmd_arg->response ); + return RESPONSE_SUCCESS; + } + + return RESPONSE_INVALID_UNIT; +} + +static int parse_clip( command_argument cmd_arg, int arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = miracle_unit_get_current_clip( unit ); + + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) > arg ) + { + char *token = valerie_tokeniser_get_string( cmd_arg->tokeniser, arg ); + if ( token[ 0 ] == '+' ) + clip += atoi( token + 1 ); + else if ( token[ 0 ] == '-' ) + clip -= atoi( token + 1 ); + else + clip = atoi( token ); + } + + return clip; +} + +int miracle_insert( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + long in = -1, out = -1; + int index = parse_clip( cmd_arg, 3 ); + + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 6 ) + { + in = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + out = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 5 ) ); + } + + switch( miracle_unit_insert( unit, fullname, index, in, out ) ) + { + case valerie_ok: + return RESPONSE_SUCCESS; + default: + return RESPONSE_BAD_FILE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_remove( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int index = parse_clip( cmd_arg, 2 ); + + if ( miracle_unit_remove( unit, index ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_clean( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_clean( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_wipe( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_wipe( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_clear( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_clear( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_move( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if ( unit != NULL ) + { + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) > 2 ) + { + int src = parse_clip( cmd_arg, 2 ); + int dest = parse_clip( cmd_arg, 3 ); + + if ( miracle_unit_move( unit, src, dest ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + else + { + return RESPONSE_MISSING_ARG; + } + } + else + { + return RESPONSE_INVALID_UNIT; + } + + return RESPONSE_SUCCESS; +} + +int miracle_append( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int32_t in = -1, out = -1; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 5 ) + { + in = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 3 ) ); + out = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + } + switch ( miracle_unit_append( unit, fullname, in, out ) ) + { + case valerie_ok: + return RESPONSE_SUCCESS; + default: + return RESPONSE_BAD_FILE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_push( command_argument cmd_arg, mlt_service service ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit != NULL && service != NULL ) + if ( miracle_unit_append_service( unit, service ) == valerie_ok ) + return RESPONSE_SUCCESS; + return RESPONSE_BAD_FILE; +} + +int miracle_receive( command_argument cmd_arg, char *doc ) +{ + mlt_producer producer = mlt_factory_producer( "westley-xml", doc ); + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit != NULL && producer != NULL ) + { + if ( miracle_unit_append_service( unit, MLT_PRODUCER_SERVICE( producer ) ) == valerie_ok ) + { + mlt_producer_close( producer ); + return RESPONSE_SUCCESS; + } + } + mlt_producer_close( producer ); + return RESPONSE_BAD_FILE; +} + +int miracle_play( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if ( unit == NULL ) + { + return RESPONSE_INVALID_UNIT; + } + else + { + int speed = 1000; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 3 ) + speed = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 2 ) ); + miracle_unit_play( unit, speed ); + } + + return RESPONSE_SUCCESS; +} + +int miracle_stop( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_terminate( unit ); + return RESPONSE_SUCCESS; +} + +int miracle_pause( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, 0 ); + return RESPONSE_SUCCESS; +} + +int miracle_rewind( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, -2000 ); + return RESPONSE_SUCCESS; +} + +int miracle_step( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + miracle_unit_play( unit, 0 ); + miracle_unit_step( unit, *(int*) cmd_arg->argument ); + } + return RESPONSE_SUCCESS; +} + +int miracle_goto( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if (unit == NULL || miracle_unit_is_offline(unit)) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_change_position( unit, clip, *(int*) cmd_arg->argument ); + return RESPONSE_SUCCESS; +} + +int miracle_ff( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, 2000 ); + return RESPONSE_SUCCESS; +} + +int miracle_set_in_point( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + { + int position = *(int *) cmd_arg->argument; + + switch( miracle_unit_set_clip_in( unit, clip, position ) ) + { + case -1: + return RESPONSE_BAD_FILE; + case -2: + return RESPONSE_OUT_OF_RANGE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_set_out_point( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + { + int position = *(int *) cmd_arg->argument; + + switch( miracle_unit_set_clip_out( unit, clip, position ) ) + { + case -1: + return RESPONSE_BAD_FILE; + case -2: + return RESPONSE_OUT_OF_RANGE; + } + } + + return RESPONSE_SUCCESS; +} + +int miracle_get_unit_status( command_argument cmd_arg ) +{ + valerie_status_t status; + int error = miracle_unit_get_status( miracle_get_unit( cmd_arg->unit ), &status ); + + if ( error == -1 ) + return RESPONSE_INVALID_UNIT; + else + { + char text[ 10240 ]; + valerie_response_printf( cmd_arg->response, sizeof( text ), valerie_status_serialise( &status, text, sizeof( text ) ) ); + return RESPONSE_SUCCESS_1; + } + return 0; +} + + +int miracle_set_unit_property( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *name_value = (char*) cmd_arg->argument; + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_set( unit, name_value ); + return RESPONSE_SUCCESS; +} + +int miracle_get_unit_property( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *name = (char*) cmd_arg->argument; + char *value = miracle_unit_get( unit, name ); + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else if ( value != NULL ) + valerie_response_printf( cmd_arg->response, 1024, "%s\n", value ); + return RESPONSE_SUCCESS; +} + + +int miracle_transfer( command_argument cmd_arg ) +{ + miracle_unit src_unit = miracle_get_unit(cmd_arg->unit); + int dest_unit_id = -1; + char *string = (char*) cmd_arg->argument; + if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 ) + dest_unit_id = atoi( string + 1 ); + + if ( src_unit != NULL && dest_unit_id != -1 ) + { + miracle_unit dest_unit = miracle_get_unit( dest_unit_id ); + if ( dest_unit != NULL && !miracle_unit_is_offline(dest_unit) && dest_unit != src_unit ) + { + miracle_unit_transfer( dest_unit, src_unit ); + return RESPONSE_SUCCESS; + } + } + return RESPONSE_INVALID_UNIT; +} diff --git a/src/miracle/miracle_unit_commands.h b/src/miracle/miracle_unit_commands.h new file mode 100644 index 0000000..67fc269 --- /dev/null +++ b/src/miracle/miracle_unit_commands.h @@ -0,0 +1,61 @@ +/* + * unit_commands.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef _UNIT_COMMANDS_H_ +#define _UNIT_COMMANDS_H_ + +#include "miracle_connection.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern response_codes miracle_list( command_argument ); +extern response_codes miracle_load( command_argument ); +extern response_codes miracle_insert( command_argument ); +extern response_codes miracle_remove( command_argument ); +extern response_codes miracle_clean( command_argument ); +extern response_codes miracle_wipe( command_argument ); +extern response_codes miracle_clear( command_argument ); +extern response_codes miracle_move( command_argument ); +extern response_codes miracle_append( command_argument ); +extern response_codes miracle_play( command_argument ); +extern response_codes miracle_stop( command_argument ); +extern response_codes miracle_pause( command_argument ); +extern response_codes miracle_rewind( command_argument ); +extern response_codes miracle_step( command_argument ); +extern response_codes miracle_goto( command_argument ); +extern response_codes miracle_ff( command_argument ); +extern response_codes miracle_set_in_point( command_argument ); +extern response_codes miracle_set_out_point( command_argument ); +extern response_codes miracle_get_unit_status( command_argument ); +extern response_codes miracle_set_unit_property( command_argument ); +extern response_codes miracle_get_unit_property( command_argument ); +extern response_codes miracle_transfer( command_argument ); +extern response_codes miracle_push( command_argument, mlt_service ); +extern response_codes miracle_receive( command_argument, char * ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/Makefile b/src/modules/Makefile new file mode 100644 index 0000000..d43d65e --- /dev/null +++ b/src/modules/Makefile @@ -0,0 +1,32 @@ +include ../../config.mak +include make.inc + +all clean depend: + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) -C $$subdir $@ || exit 1; \ + fi \ + done + +distclean: + rm -f consumers.dat filters.dat producers.dat transitions.dat make.inc; \ + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) -C $$subdir $@ || exit 1; \ + fi \ + done + +install: + install -m 644 producers.dat filters.dat transitions.dat consumers.dat "$(DESTDIR)$(prefix)/lib/mlt/modules" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + fi \ + done + +uninstall: + rm -rf "$(DESTDIR)$(prefix)/lib/mlt/modules" + diff --git a/src/modules/avformat/Makefile b/src/modules/avformat/Makefile new file mode 100644 index 0000000..4e14f3c --- /dev/null +++ b/src/modules/avformat/Makefile @@ -0,0 +1,62 @@ +include ../../../config.mak +include config.mak + +TARGET = ../libmltavformat$(LIBSUF) + +OBJS = factory.o \ + producer_avformat.o \ + consumer_avformat.o \ + filter_avcolour_space.o \ + filter_avresample.o + +ifdef MMX_FLAGS + OBJS += filter_avdeinterlace.o +endif + +CFLAGS+=-I../.. + +LDFLAGS+=-L../../framework + +LDFLAGS+=-lavformat$(AVFORMAT_SUFFIX) -lavcodec$(AVFORMAT_SUFFIX) -lavutil$(AVFORMAT_SUFFIX) $(EXTRA_LIBS) -lmlt + +ifdef SWSCALE + CFLAGS+=-DSWSCALE + LDFLAGS+=-lswscale$(AVFORMAT_SUFFIX) +endif + +ifdef LOCAL_FFMPEG + LOCAL_FFMPEG_OBJS = ffmpeg/libavformat/libavformat$(AVFORMAT_SUFFIX) \ + ffmpeg/libavcodec/libavcodec$(AVFORMAT_SUFFIX) \ + ffmpeg/libavutil/libavutil$(AVFORMAT_SUFFIX) +endif + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(LOCAL_FFMPEG_OBJS): + if [ $(LOCAL_FFMPEG) ] ; then \ + $(MAKE) -C ffmpeg lib ; \ + fi + +$(TARGET): $(OBJS) $(LOCAL_FFMPEG_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg dep ; fi + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg distclean ; fi + rm -f .depend + +clean: + #if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg clean ; fi + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/avformat/config.mak b/src/modules/avformat/config.mak new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/modules/avformat/config.mak @@ -0,0 +1 @@ + diff --git a/src/modules/avformat/configure b/src/modules/avformat/configure new file mode 100755 index 0000000..8a72d9e --- /dev/null +++ b/src/modules/avformat/configure @@ -0,0 +1,159 @@ +#!/bin/sh + + +if [ "$help" = "1" ] +then + cat << EOF +FFMPEG/avformat options: + + --avformat-svn - Obtain ffmpeg from Subversion + --avformat-svn-extra - Add extra configure options for --avformat-svn + --avformat-shared=path - Link against a shared installation of ffmpeg (default) + --avformat-static=path - Link against a static ffmpeg dev tree + --avformat-ldextra=libs - Provide additional libs to link with + --avformat-suffix=suff - Specify a custom suffix for an ffmpeg shared build + --avformat-swscale - Use ffmpeg libswcale instead of img_convert + +EOF + +else + targetos=$(uname -s) + case $targetos in + Darwin) + export LIBSUF=.dylib + ;; + Linux) + export LIBSUF=.so + ;; + *) + ;; + esac + + bits=$(uname -m) + case $bits in + x86_64) + export LIBDIR=lib64 + ;; + *) + export LIBDIR=lib + ;; + esac + + echo > config.mak + + export static_ffmpeg= + export shared_ffmpeg=`which ffmpeg` + export extra_libs= + export svn_ffmpeg= + export svn_ffmpeg_extra= + export avformat_suffix= + export swscale= + + if [ "$shared_ffmpeg" != "" -a -f "$shared_ffmpeg" ] + then + # Chop ffmpeg + shared_ffmpeg=`dirname $shared_ffmpeg` + # Chop bin + shared_ffmpeg=`dirname $shared_ffmpeg` + fi + + for i in "$@" + do + case $i in + --avformat-static=* ) static_ffmpeg="${i#--avformat-static=}" ;; + --avformat-shared=* ) shared_ffmpeg="${i#--avformat-shared=}" ;; + --avformat-ldextra=* ) extra_libs="${i#--avformat-ldextra=}" ;; + --avformat-svn ) svn_ffmpeg=true ;; + --avformat-svn-extra=* ) svn_ffmpeg_extra="${i#--avformat-svn-extra=}" ;; + --avformat-cvs ) svn_ffmpeg=true ;; + --avformat-suffix=* ) avformat_suffix="${i#--avformat-suffix=}" ;; + --avformat-swscale ) swscale=true ;; + --avformat-swscaler ) swscale=true ;; + esac + done + + if [ "$svn_ffmpeg" != "" ] + then + if [ "$gpl" = "true" ] + then + enable_gpl="--enable-gpl" + if [ "$swscale" != "" ] + then + enable_swscale="--enable-swscaler" + echo "SWSCALE=1" >> config.mak + fi + elif [ "$swscale" != "" ] + then + echo + echo "ERROR ERROR ERROR ERROR ERROR ERROR" + echo "--enable-gpl is required to use --avformat-swscale with --avformat-svn!" + echo + exit + fi + if [ ! -d "ffmpeg" ] + then + echo + echo "Checking out ffmpeg/avformat - no password required" + echo + svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg + fi + [ -d "ffmpeg" ] && ( cd ffmpeg ; ./configure $enable_gpl $enable_swscale $svn_ffmpeg_extra ) + #[ ! -f "ffmpeg/ffmpeg.patch" ] && ( cd ffmpeg ; cp ../ffmpeg.patch . ; patch -p0 < ffmpeg.patch ) + echo "CFLAGS+=-I`pwd`/ffmpeg/libavformat -I`pwd`/ffmpeg/libavcodec -I`pwd`/ffmpeg/libavutil -I`pwd`/ffmpeg/libswscale" >> config.mak + echo "LDFLAGS+=-L`pwd`/ffmpeg/libavformat -L`pwd`/ffmpeg/libavcodec -L`pwd`/ffmpeg/libavutil -L`pwd`/ffmpeg/libswscale" >> config.mak + [ $targetos = "Darwin" ] && + echo "LDFLAGS+=-single_module" >> config.mak + echo "LOCAL_FFMPEG=1" >> config.mak + extra_libs="$extra_libs -lz" + elif [ "$static_ffmpeg" != "" ] + then + if [ -d "$static_ffmpeg" ] + then + echo "CFLAGS+=-I$static_ffmpeg/libavformat -I$static_ffmpeg/libavcodec -I$static_ffmpeg/libavutil" >> config.mak + echo "LDFLAGS+=-L$static_ffmpeg/libavformat -L$static_ffmpeg/libavcodec -L$static_ffmpeg/libavutil" >> config.mak + [ $targetos = "Darwin" ] && + echo "LDFLAGS+=-single_module" >> config.mak + if [ "$swscale" != "" ] + then + echo "CFLAGS+=-I$static_ffmpeg/libswscale" >> config.mak + echo "LDFLAGS+=-L$static_ffmpeg/libswscale" >> config.mak + echo "SWSCALE=1" >> config.mak + fi + else + echo "avformat: Invalid path specified: $static_ffmpeg" + touch ../disable-avformat + echo 0 + fi + else + if [ -d "$shared_ffmpeg/include/ffmpeg" -a -e "$shared_ffmpeg/$LIBDIR/libavformat$avformat_suffix$LIBSUF" ] + then + echo "CFLAGS+=-I$shared_ffmpeg/include/ffmpeg " >> config.mak + echo "LDFLAGS+=-L$shared_ffmpeg/$LIBDIR" >> config.mak + [ "$swscale" != "" ] && echo "SWSCALE=1" >> config.mak + else + echo "avformat: No build environment found. " + echo " Try configuring mlt with --avformat-svn." + touch ../disable-avformat + exit 0 + fi + fi + + echo "EXTRA_LIBS=$extra_libs" >> config.mak + echo "AVFORMAT_SUFFIX=$avformat_suffix" >> config.mak + +cat << EOF >> ../producers.dat +avformat libmltavformat$LIBSUF +EOF + +cat << EOF >> ../filters.dat +avdeinterlace libmltavformat$LIBSUF +avresample libmltavformat$LIBSUF +avcolour_space libmltavformat$LIBSUF +EOF + +cat << EOF >> ../consumers.dat +avformat libmltavformat$LIBSUF +EOF + +fi + diff --git a/src/modules/avformat/consumer_avformat.c b/src/modules/avformat/consumer_avformat.c new file mode 100644 index 0000000..d17f3f3 --- /dev/null +++ b/src/modules/avformat/consumer_avformat.c @@ -0,0 +1,1192 @@ +/* + * consumer_avformat.c -- an encoder based on avformat + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "consumer_avformat.h" + +// mlt Header files +#include + +// System header files +#include +#include +#include +#include +#include +#include +#include + +// avformat header files +#include +#ifdef SWSCALE +#include +#endif + +// +// This structure should be extended and made globally available in mlt +// + +typedef struct +{ + int16_t *buffer; + int size; + int used; + double time; + int frequency; + int channels; +} +*sample_fifo, sample_fifo_s; + +sample_fifo sample_fifo_init( int frequency, int channels ) +{ + sample_fifo this = calloc( 1, sizeof( sample_fifo_s ) ); + this->frequency = frequency; + this->channels = channels; + return this; +} + +// sample_fifo_clear and check are temporarily aborted (not working as intended) + +void sample_fifo_clear( sample_fifo this, double time ) +{ + int words = ( float )( time - this->time ) * this->frequency * this->channels; + if ( ( int )( ( float )time * 100 ) < ( int )( ( float )this->time * 100 ) && this->used > words && words > 0 ) + { + memmove( this->buffer, &this->buffer[ words ], ( this->used - words ) * sizeof( int16_t ) ); + this->used -= words; + this->time = time; + } + else if ( ( int )( ( float )time * 100 ) != ( int )( ( float )this->time * 100 ) ) + { + this->used = 0; + this->time = time; + } +} + +void sample_fifo_check( sample_fifo this, double time ) +{ + if ( this->used == 0 ) + { + if ( ( int )( ( float )time * 100 ) < ( int )( ( float )this->time * 100 ) ) + this->time = time; + } +} + +void sample_fifo_append( sample_fifo this, int16_t *samples, int count ) +{ + if ( ( this->size - this->used ) < count ) + { + this->size += count * 5; + this->buffer = realloc( this->buffer, this->size * sizeof( int16_t ) ); + } + + memcpy( &this->buffer[ this->used ], samples, count * sizeof( int16_t ) ); + this->used += count; +} + +int sample_fifo_used( sample_fifo this ) +{ + return this->used; +} + +int sample_fifo_fetch( sample_fifo this, int16_t *samples, int count ) +{ + if ( count > this->used ) + count = this->used; + + memcpy( samples, this->buffer, count * sizeof( int16_t ) ); + this->used -= count; + memmove( this->buffer, &this->buffer[ count ], this->used * sizeof( int16_t ) ); + + this->time += ( double )count / this->channels / this->frequency; + + return count; +} + +void sample_fifo_close( sample_fifo this ) +{ + free( this->buffer ); + free( this ); +} + +// Forward references. +static int consumer_start( mlt_consumer this ); +static int consumer_stop( mlt_consumer this ); +static int consumer_is_stopped( mlt_consumer this ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer this ); + +/** Initialise the dv consumer. +*/ + +mlt_consumer consumer_avformat_init( char *arg ) +{ + // Allocate the consumer + mlt_consumer this = mlt_consumer_new( ); + + // If memory allocated and initialises without error + if ( this != NULL ) + { + // Get properties from the consumer + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Assign close callback + this->close = consumer_close; + + // Interpret the argument + if ( arg != NULL ) + mlt_properties_set( properties, "target", arg ); + + // sample and frame queue + mlt_properties_set_data( properties, "frame_queue", mlt_deque_init( ), 0, ( mlt_destructor )mlt_deque_close, NULL ); + + // Set avformat defaults (all lifted from ffmpeg.c) + mlt_properties_set_int( properties, "audio_bit_rate", 128000 ); + mlt_properties_set_int( properties, "video_bit_rate", 200 * 1000 ); + mlt_properties_set_int( properties, "video_bit_rate_tolerance", 4000 * 1000 ); + mlt_properties_set_int( properties, "gop_size", 12 ); + mlt_properties_set_int( properties, "b_frames", 0 ); + mlt_properties_set_int( properties, "mb_decision", FF_MB_DECISION_SIMPLE ); + mlt_properties_set_double( properties, "qscale", 0 ); + mlt_properties_set_int( properties, "me_method", ME_EPZS ); + mlt_properties_set_int( properties, "mb_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "ildct_cmp", FF_CMP_VSAD ); + mlt_properties_set_int( properties, "sub_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "pre_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "pre_me", 0 ); + mlt_properties_set_double( properties, "lumi_mask", 0 ); + mlt_properties_set_double( properties, "dark_mask", 0 ); + mlt_properties_set_double( properties, "scplx_mask", 0 ); + mlt_properties_set_double( properties, "tcplx_mask", 0 ); + mlt_properties_set_double( properties, "p_mask", 0 ); + mlt_properties_set_int( properties, "qns", 0 ); + mlt_properties_set_int( properties, "video_qmin", 2 ); + mlt_properties_set_int( properties, "video_qmax", 31 ); + mlt_properties_set_int( properties, "video_lmin", 2*FF_QP2LAMBDA ); + mlt_properties_set_int( properties, "video_lmax", 31*FF_QP2LAMBDA ); + mlt_properties_set_int( properties, "video_mb_qmin", 2 ); + mlt_properties_set_int( properties, "video_mb_qmax", 31 ); + mlt_properties_set_int( properties, "video_qdiff", 3 ); + mlt_properties_set_double( properties, "video_qblur", 0.5 ); + mlt_properties_set_double( properties, "video_qcomp", 0.5 ); + mlt_properties_set_int( properties, "video_rc_max_rate", 0 ); + mlt_properties_set_int( properties, "video_rc_min_rate", 0 ); + mlt_properties_set_int( properties, "video_rc_buffer_size", 0 ); + mlt_properties_set_double( properties, "video_rc_buffer_aggressivity", 1.0 ); + mlt_properties_set_double( properties, "video_rc_initial_cplx", 0 ); + mlt_properties_set_double( properties, "video_i_qfactor", -0.8 ); + mlt_properties_set_double( properties, "video_b_qfactor", 1.25 ); + mlt_properties_set_double( properties, "video_i_qoffset", 0 ); + mlt_properties_set_double( properties, "video_b_qoffset", 1.25 ); + mlt_properties_set_int( properties, "video_intra_quant_bias", FF_DEFAULT_QUANT_BIAS ); + mlt_properties_set_int( properties, "video_inter_quant_bias", FF_DEFAULT_QUANT_BIAS ); + mlt_properties_set_int( properties, "dct_algo", 0 ); + mlt_properties_set_int( properties, "idct_algo", 0 ); + mlt_properties_set_int( properties, "me_threshold", 0 ); + mlt_properties_set_int( properties, "mb_threshold", 0 ); + mlt_properties_set_int( properties, "intra_dc_precision", 0 ); + mlt_properties_set_int( properties, "strict", 0 ); + mlt_properties_set_int( properties, "error_rate", 0 ); + mlt_properties_set_int( properties, "noise_reduction", 0 ); + mlt_properties_set_int( properties, "sc_threshold", 0 ); + mlt_properties_set_int( properties, "me_range", 0 ); + mlt_properties_set_int( properties, "coder", 0 ); + mlt_properties_set_int( properties, "context", 0 ); + mlt_properties_set_int( properties, "predictor", 0 ); + mlt_properties_set_int( properties, "ildct", 0 ); + mlt_properties_set_int( properties, "ilme", 0 ); + + // Ensure termination at end of the stream + mlt_properties_set_int( properties, "terminate_on_pause", 1 ); + + // Set up start/stop/terminated callbacks + this->start = consumer_start; + this->stop = consumer_stop; + this->is_stopped = consumer_is_stopped; + } + + // Return this + return this; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Get the width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + + // Obtain the size property + char *size = mlt_properties_get( properties, "size" ); + + // Interpret it + if ( size != NULL ) + { + int tw, th; + if ( sscanf( size, "%dx%d", &tw, &th ) == 2 && tw > 0 && th > 0 ) + { + width = tw; + height = th; + } + else + { + fprintf( stderr, "consumer_avformat: Invalid size property %s - ignoring.\n", size ); + } + } + + // Now ensure we honour the multiple of two requested by libavformat + mlt_properties_set_int( properties, "width", ( width / 2 ) * 2 ); + mlt_properties_set_int( properties, "height", ( height / 2 ) * 2 ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, this ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're running + if ( mlt_properties_get_int( properties, "running" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "running", 0 ); + + // Wait for termination + pthread_join( *thread, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + return !mlt_properties_get_int( properties, "running" ); +} + +/** Add an audio output stream +*/ + +static AVStream *add_audio_stream( mlt_consumer this, AVFormatContext *oc, int codec_id ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Create a new stream + AVStream *st = av_new_stream( oc, 1 ); + + // If created, then initialise from properties + if ( st != NULL ) + { + AVCodecContext *c = st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_AUDIO; + + // Put sample parameters + c->bit_rate = mlt_properties_get_int( properties, "audio_bit_rate" ); + c->sample_rate = mlt_properties_get_int( properties, "frequency" ); + c->channels = mlt_properties_get_int( properties, "channels" ); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + // Allow the user to override the audio fourcc + if ( mlt_properties_get( properties, "afourcc" ) ) + { + char *tail = NULL; + char *arg = mlt_properties_get( properties, "afourcc" ); + int tag = strtol( arg, &tail, 0); + if( !tail || *tail ) + tag = arg[ 0 ] + ( arg[ 1 ] << 8 ) + ( arg[ 2 ] << 16 ) + ( arg[ 3 ] << 24 ); + c->codec_tag = tag; + } + } + else + { + fprintf( stderr, "Could not allocate a stream for audio\n" ); + } + + return st; +} + +static int open_audio( AVFormatContext *oc, AVStream *st, int audio_outbuf_size ) +{ + // We will return the audio input size from here + int audio_input_frame_size = 0; + + // Get the context + AVCodecContext *c = st->codec; + + // Find the encoder + AVCodec *codec = avcodec_find_encoder( c->codec_id ); + + // Continue if codec found and we can open it + if ( codec != NULL && avcodec_open(c, codec) >= 0 ) + { + // ugly hack for PCM codecs (will be removed ASAP with new PCM + // support to compute the input frame size in samples + if ( c->frame_size <= 1 ) + { + audio_input_frame_size = audio_outbuf_size / c->channels; + switch(st->codec->codec_id) + { + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_U16BE: + audio_input_frame_size >>= 1; + break; + default: + break; + } + } + else + { + audio_input_frame_size = c->frame_size; + } + + // Some formats want stream headers to be seperate (hmm) + if( !strcmp( oc->oformat->name, "mp4" ) || + !strcmp( oc->oformat->name, "mov" ) || + !strcmp( oc->oformat->name, "3gp" ) ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + else + { + fprintf( stderr, "Unable to encode audio - disabling audio output.\n" ); + } + + return audio_input_frame_size; +} + +static void close_audio( AVFormatContext *oc, AVStream *st ) +{ + avcodec_close( st->codec ); +} + +/** Add a video output stream +*/ + +static AVStream *add_video_stream( mlt_consumer this, AVFormatContext *oc, int codec_id ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Create a new stream + AVStream *st = av_new_stream( oc, 0 ); + + if ( st != NULL ) + { + char *pix_fmt = mlt_properties_get( properties, "pix_fmt" ); + double ar = mlt_properties_get_double( properties, "display_ratio" ); + AVCodecContext *c = st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_VIDEO; + + // put sample parameters + c->bit_rate = mlt_properties_get_int( properties, "video_bit_rate" ); + c->bit_rate_tolerance = mlt_properties_get_int( properties, "video_bit_rate_tolerance" ); + c->width = mlt_properties_get_int( properties, "width" ); + c->height = mlt_properties_get_int( properties, "height" ); + c->time_base.num = mlt_properties_get_int( properties, "frame_rate_den" ); + c->time_base.den = mlt_properties_get_int( properties, "frame_rate_num" ); + c->gop_size = mlt_properties_get_int( properties, "gop_size" ); + c->pix_fmt = pix_fmt ? avcodec_get_pix_fmt( pix_fmt ) : PIX_FMT_YUV420P; + + if ( mlt_properties_get_int( properties, "b_frames" ) ) + { + c->max_b_frames = mlt_properties_get_int( properties, "b_frames" ); + c->b_frame_strategy = 0; + c->b_quant_factor = 2.0; + } + + c->mb_decision = mlt_properties_get_int( properties, "mb_decision" ); + c->sample_aspect_ratio = av_d2q( ar * c->height / c->width , 255); + c->mb_cmp = mlt_properties_get_int( properties, "mb_cmp" ); + c->ildct_cmp = mlt_properties_get_int( properties, "ildct_cmp" ); + c->me_sub_cmp = mlt_properties_get_int( properties, "sub_cmp" ); + c->me_cmp = mlt_properties_get_int( properties, "cmp" ); + c->me_pre_cmp = mlt_properties_get_int( properties, "pre_cmp" ); + c->pre_me = mlt_properties_get_int( properties, "pre_me" ); + c->lumi_masking = mlt_properties_get_double( properties, "lumi_mask" ); + c->dark_masking = mlt_properties_get_double( properties, "dark_mask" ); + c->spatial_cplx_masking = mlt_properties_get_double( properties, "scplx_mask" ); + c->temporal_cplx_masking = mlt_properties_get_double( properties, "tcplx_mask" ); + c->p_masking = mlt_properties_get_double( properties, "p_mask" ); + c->quantizer_noise_shaping= mlt_properties_get_int( properties, "qns" ); + c->qmin = mlt_properties_get_int( properties, "video_qmin" ); + c->qmax = mlt_properties_get_int( properties, "video_qmax" ); + c->lmin = mlt_properties_get_int( properties, "video_lmin" ); + c->lmax = mlt_properties_get_int( properties, "video_lmax" ); + c->mb_qmin = mlt_properties_get_int( properties, "video_mb_qmin" ); + c->mb_qmax = mlt_properties_get_int( properties, "video_mb_qmax" ); + c->max_qdiff = mlt_properties_get_int( properties, "video_qdiff" ); + c->qblur = mlt_properties_get_double( properties, "video_qblur" ); + c->qcompress = mlt_properties_get_double( properties, "video_qcomp" ); + + if ( mlt_properties_get_double( properties, "qscale" ) > 0 ) + { + c->flags |= CODEC_FLAG_QSCALE; + st->quality = FF_QP2LAMBDA * mlt_properties_get_double( properties, "qscale" ); + } + + // Allow the user to override the video fourcc + if ( mlt_properties_get( properties, "vfourcc" ) ) + { + char *tail = NULL; + const char *arg = mlt_properties_get( properties, "vfourcc" ); + int tag = strtol( arg, &tail, 0); + if( !tail || *tail ) + tag = arg[ 0 ] + ( arg[ 1 ] << 8 ) + ( arg[ 2 ] << 16 ) + ( arg[ 3 ] << 24 ); + c->codec_tag = tag; + } + + // Some formats want stream headers to be seperate + if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + c->rc_max_rate = mlt_properties_get_int( properties, "video_rc_max_rate" ); + c->rc_min_rate = mlt_properties_get_int( properties, "video_rc_min_rate" ); + c->rc_buffer_size = mlt_properties_get_int( properties, "video_rc_buffer_size" ); + c->rc_initial_buffer_occupancy = c->rc_buffer_size*3/4; + c->rc_buffer_aggressivity= mlt_properties_get_double( properties, "video_rc_buffer_aggressivity" ); + c->rc_initial_cplx= mlt_properties_get_double( properties, "video_rc_initial_cplx" ); + c->i_quant_factor = mlt_properties_get_double( properties, "video_i_qfactor" ); + c->b_quant_factor = mlt_properties_get_double( properties, "video_b_qfactor" ); + c->i_quant_offset = mlt_properties_get_double( properties, "video_i_qoffset" ); + c->b_quant_offset = mlt_properties_get_double( properties, "video_b_qoffset" ); + c->intra_quant_bias = mlt_properties_get_int( properties, "video_intra_quant_bias" ); + c->inter_quant_bias = mlt_properties_get_int( properties, "video_inter_quant_bias" ); + c->dct_algo = mlt_properties_get_int( properties, "dct_algo" ); + c->idct_algo = mlt_properties_get_int( properties, "idct_algo" ); + c->me_threshold= mlt_properties_get_int( properties, "me_threshold" ); + c->mb_threshold= mlt_properties_get_int( properties, "mb_threshold" ); + c->intra_dc_precision= mlt_properties_get_int( properties, "intra_dc_precision" ); + c->strict_std_compliance = mlt_properties_get_int( properties, "strict" ); + c->error_rate = mlt_properties_get_int( properties, "error_rate" ); + c->noise_reduction= mlt_properties_get_int( properties, "noise_reduction" ); + c->scenechange_threshold= mlt_properties_get_int( properties, "sc_threshold" ); + c->me_range = mlt_properties_get_int( properties, "me_range" ); + c->coder_type= mlt_properties_get_int( properties, "coder" ); + c->context_model= mlt_properties_get_int( properties, "context" ); + c->prediction_method= mlt_properties_get_int( properties, "predictor" ); + c->me_method = mlt_properties_get_int( properties, "me_method" ); + if ( mlt_properties_get_int( properties, "progressive" ) == 0 && + mlt_properties_get_int( properties, "deinterlace" ) == 0 ) + { + if ( mlt_properties_get_int( properties, "ildct" ) ) + c->flags |= CODEC_FLAG_INTERLACED_DCT; + if ( mlt_properties_get_int( properties, "ilme" ) ) + c->flags |= CODEC_FLAG_INTERLACED_ME; + } + } + else + { + fprintf( stderr, "Could not allocate a stream for video\n" ); + } + + return st; +} + +static AVFrame *alloc_picture( int pix_fmt, int width, int height ) +{ + // Allocate a frame + AVFrame *picture = avcodec_alloc_frame(); + + // Determine size of the + int size = avpicture_get_size(pix_fmt, width, height); + + // Allocate the picture buf + uint8_t *picture_buf = av_malloc(size); + + // If we have both, then fill the image + if ( picture != NULL && picture_buf != NULL ) + { + // Fill the frame with the allocated buffer + avpicture_fill( (AVPicture *)picture, picture_buf, pix_fmt, width, height); + } + else + { + // Something failed - clean up what we can + av_free( picture ); + av_free( picture_buf ); + picture = NULL; + } + + return picture; +} + +static int open_video(AVFormatContext *oc, AVStream *st) +{ + // Get the codec + AVCodecContext *video_enc = st->codec; + + // find the video encoder + AVCodec *codec = avcodec_find_encoder( video_enc->codec_id ); + + if( codec && codec->pix_fmts ) + { + const enum PixelFormat *p = codec->pix_fmts; + for( ; *p!=-1; p++ ) + { + if( *p == video_enc->pix_fmt ) + break; + } + if( *p == -1 ) + video_enc->pix_fmt = codec->pix_fmts[ 0 ]; + } + + // Open the codec safely + return codec != NULL && avcodec_open( video_enc, codec ) >= 0; +} + +void close_video(AVFormatContext *oc, AVStream *st) +{ + avcodec_close(st->codec); +} + +static inline long time_difference( struct timeval *time1 ) +{ + struct timeval time2; + gettimeofday( &time2, NULL ); + return time2.tv_sec * 1000000 + time2.tv_usec - time1->tv_sec * 1000000 - time1->tv_usec; +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer this = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the terminate on pause property + int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + int terminated = 0; + + // Determine if feed is slow (for realtime stuff) + int real_time_output = mlt_properties_get_int( properties, "real_time" ); + + // Time structures + struct timeval ante; + + // Get the frame rate + int fps = mlt_properties_get_double( properties, "fps" ); + + // Get width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + int img_width = width; + int img_height = height; + + // Get default audio properties + mlt_audio_format aud_fmt = mlt_audio_pcm; + int channels = mlt_properties_get_int( properties, "channels" ); + int frequency = mlt_properties_get_int( properties, "frequency" ); + int16_t *pcm = NULL; + int samples = 0; + + // AVFormat audio buffer and frame size + int audio_outbuf_size = 10000; + uint8_t *audio_outbuf = av_malloc( audio_outbuf_size ); + int audio_input_frame_size = 0; + + // AVFormat video buffer and frame count + int frame_count = 0; + int video_outbuf_size = ( 1024 * 1024 ); + uint8_t *video_outbuf = av_malloc( video_outbuf_size ); + + // Used for the frame properties + mlt_frame frame = NULL; + mlt_properties frame_properties = NULL; + + // Get the queues + mlt_deque queue = mlt_properties_get_data( properties, "frame_queue", NULL ); + sample_fifo fifo = mlt_properties_get_data( properties, "sample_fifo", NULL ); + + // Need two av pictures for converting + AVFrame *output = NULL; + AVFrame *input = alloc_picture( PIX_FMT_YUV422, width, height ); + + // For receiving images from an mlt_frame + uint8_t *image; + mlt_image_format img_fmt = mlt_image_yuv422; + + // For receiving audio samples back from the fifo + int16_t *buffer = av_malloc( 48000 * 2 ); + int count = 0; + + // Allocate the context + AVFormatContext *oc = av_alloc_format_context( ); + + // Streams + AVStream *audio_st = NULL; + AVStream *video_st = NULL; + + // Time stamps + double audio_pts = 0; + double video_pts = 0; + + // Loop variable + int i; + + // Frames despatched + long int frames = 0; + long int total_time = 0; + + // Determine the format + AVOutputFormat *fmt = NULL; + char *filename = mlt_properties_get( properties, "target" ); + char *format = mlt_properties_get( properties, "format" ); + char *vcodec = mlt_properties_get( properties, "vcodec" ); + char *acodec = mlt_properties_get( properties, "acodec" ); + + // Used to store and override codec ids + int audio_codec_id; + int video_codec_id; + + // Check for user selected format first + if ( format != NULL ) + fmt = guess_format( format, NULL, NULL ); + + // Otherwise check on the filename + if ( fmt == NULL && filename != NULL ) + fmt = guess_format( NULL, filename, NULL ); + + // Otherwise default to mpeg + if ( fmt == NULL ) + fmt = guess_format( "mpeg", NULL, NULL ); + + // We need a filename - default to stdout? + if ( filename == NULL || !strcmp( filename, "" ) ) + filename = "pipe:"; + + // Get the codec ids selected + audio_codec_id = fmt->audio_codec; + video_codec_id = fmt->video_codec; + + // Check for audio codec overides + if ( acodec != NULL ) + { + AVCodec *p = first_avcodec; + while( p != NULL ) + { + if ( !strcmp( p->name, acodec ) && p->type == CODEC_TYPE_AUDIO ) + break; + p = p->next; + } + if ( p != NULL ) + audio_codec_id = p->id; + else + fprintf( stderr, "consumer_avcodec: audio codec %s unrecognised - ignoring\n", acodec ); + } + + // Check for video codec overides + if ( vcodec != NULL ) + { + AVCodec *p = first_avcodec; + while( p != NULL ) + { + if ( !strcmp( p->name, vcodec ) && p->type == CODEC_TYPE_VIDEO ) + break; + p = p->next; + } + if ( p != NULL ) + video_codec_id = p->id; + else + fprintf( stderr, "consumer_avcodec: video codec %s unrecognised - ignoring\n", vcodec ); + } + + // Update the output context + + // Write metadata + char *tmp = NULL; + int metavalue; + + tmp = mlt_properties_get( properties, "meta.attr.title.markup"); + if (tmp != NULL) snprintf( oc->title, sizeof(oc->title), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.comment.markup"); + if (tmp != NULL) snprintf( oc->comment, sizeof(oc->comment), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.author.markup"); + if (tmp != NULL) snprintf( oc->author, sizeof(oc->author), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.copyright.markup"); + if (tmp != NULL) snprintf( oc->copyright, sizeof(oc->copyright), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.album.markup"); + if (tmp != NULL) snprintf( oc->album, sizeof(oc->album), "%s", tmp ); + + metavalue = mlt_properties_get_int( properties, "meta.attr.year.markup"); + if (metavalue != 0) oc->year = metavalue; + + metavalue = mlt_properties_get_int( properties, "meta.attr.track.markup"); + if (metavalue != 0) oc->track = metavalue; + + oc->oformat = fmt; + snprintf( oc->filename, sizeof(oc->filename), "%s", filename ); + + // Add audio and video streams + if ( fmt->video_codec != CODEC_ID_NONE ) + video_st = add_video_stream( this, oc, video_codec_id ); + if ( fmt->audio_codec != CODEC_ID_NONE ) + audio_st = add_audio_stream( this, oc, audio_codec_id ); + + // Set the parameters (even though we have none...) + if ( av_set_parameters(oc, NULL) >= 0 ) + { + if ( video_st && !open_video( oc, video_st ) ) + video_st = NULL; + if ( audio_st ) + audio_input_frame_size = open_audio( oc, audio_st, audio_outbuf_size ); + + // Open the output file, if needed + if ( !( fmt->flags & AVFMT_NOFILE ) ) + { + if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) + { + fprintf(stderr, "Could not open '%s'\n", filename); + mlt_properties_set_int( properties, "running", 0 ); + } + } + + // Write the stream header, if any + if ( mlt_properties_get_int( properties, "running" ) ) + av_write_header( oc ); + } + else + { + fprintf(stderr, "Invalid output format parameters\n"); + mlt_properties_set_int( properties, "running", 0 ); + } + + // Allocate picture + if ( video_st ) + output = alloc_picture( video_st->codec->pix_fmt, width, height ); + + // Last check - need at least one stream + if ( audio_st == NULL && video_st == NULL ) + mlt_properties_set_int( properties, "running", 0 ); + + // Get the starting time (can ignore the times above) + gettimeofday( &ante, NULL ); + + // Loop while running + while( mlt_properties_get_int( properties, "running" ) && !terminated ) + { + // Get the frame + frame = mlt_consumer_rt_frame( this ); + + // Check that we have a frame to work with + if ( frame != NULL ) + { + // Increment frames despatched + frames ++; + + // Default audio args + frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Check for the terminated condition + terminated = terminate_on_pause && mlt_properties_get_double( frame_properties, "_speed" ) == 0.0; + + // Get audio and append to the fifo + if ( !terminated && audio_st ) + { + samples = mlt_sample_calculator( fps, frequency, count ++ ); + mlt_frame_get_audio( frame, &pcm, &aud_fmt, &frequency, &channels, &samples ); + + // Create the fifo if we don't have one + if ( fifo == NULL ) + { + fifo = sample_fifo_init( frequency, channels ); + mlt_properties_set_data( properties, "sample_fifo", fifo, 0, ( mlt_destructor )sample_fifo_close, NULL ); + } + + if ( mlt_properties_get_double( frame_properties, "_speed" ) != 1.0 ) + memset( pcm, 0, samples * channels * 2 ); + + // Append the samples + sample_fifo_append( fifo, pcm, samples * channels ); + total_time += ( samples * 1000000 ) / frequency; + } + + // Encode the image + if ( !terminated && video_st ) + mlt_deque_push_back( queue, frame ); + else + mlt_frame_close( frame ); + } + + // While we have stuff to process, process... + while ( 1 ) + { + if (audio_st) + audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; + else + audio_pts = 0.0; + + if (video_st) + video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den; + else + video_pts = 0.0; + + // Write interleaved audio and video frames + if ( !video_st || ( video_st && audio_st && audio_pts < video_pts ) ) + { + if ( channels * audio_input_frame_size < sample_fifo_used( fifo ) ) + { + AVCodecContext *c; + AVPacket pkt; + av_init_packet( &pkt ); + + c = audio_st->codec; + + sample_fifo_fetch( fifo, buffer, channels * audio_input_frame_size ); + + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, buffer ); + // Write the compressed frame in the media file + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts = av_rescale_q( c->coded_frame->pts, c->time_base, audio_st->time_base ); + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= audio_st->index; + pkt.data= audio_outbuf; + + if ( pkt.size ) + if ( av_interleaved_write_frame( oc, &pkt ) != 0) + fprintf(stderr, "Error while writing audio frame\n"); + + audio_pts += c->frame_size; + } + else + { + break; + } + } + else if ( video_st ) + { + if ( mlt_deque_count( queue ) ) + { + int out_size, ret; + AVCodecContext *c; + + frame = mlt_deque_pop_front( queue ); + frame_properties = MLT_FRAME_PROPERTIES( frame ); + + c = video_st->codec; + + if ( mlt_properties_get_int( frame_properties, "rendered" ) ) + { + int i = 0; + int j = 0; + uint8_t *p; + uint8_t *q; + + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + + mlt_frame_get_image( frame, &image, &img_fmt, &img_width, &img_height, 0 ); + + q = image; + + // Convert the mlt frame to an AVPicture + for ( i = 0; i < height; i ++ ) + { + p = input->data[ 0 ] + i * input->linesize[ 0 ]; + j = width; + while( j -- ) + { + *p ++ = *q ++; + *p ++ = *q ++; + } + } + + // Do the colour space conversion +#ifdef SWSCALE + struct SwsContext *context = sws_getContext( width, height, PIX_FMT_YUV422, + width, height, video_st->codec->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); + sws_scale( context, input->data, input->linesize, 0, height, + output->data, output->linesize); + sws_freeContext( context ); +#else + img_convert( ( AVPicture * )output, video_st->codec->pix_fmt, ( AVPicture * )input, PIX_FMT_YUV422, width, height ); +#endif + + // Apply the alpha if applicable + if ( video_st->codec->pix_fmt == PIX_FMT_RGBA32 ) + { + uint8_t *alpha = mlt_frame_get_alpha_mask( frame ); + register int n; + + for ( i = 0; i < height; i ++ ) + { + n = ( width + 7 ) / 8; + p = output->data[ 0 ] + i * output->linesize[ 0 ]; + + #ifndef __DARWIN__ + p += 3; + #endif + + switch( width % 8 ) + { + case 0: do { *p = *alpha++; p += 4; + case 7: *p = *alpha++; p += 4; + case 6: *p = *alpha++; p += 4; + case 5: *p = *alpha++; p += 4; + case 4: *p = *alpha++; p += 4; + case 3: *p = *alpha++; p += 4; + case 2: *p = *alpha++; p += 4; + case 1: *p = *alpha++; p += 4; + } + while( --n ); + } + } + } + } + + if (oc->oformat->flags & AVFMT_RAWPICTURE) + { + // raw video case. The API will change slightly in the near future for that + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= (uint8_t *)output; + pkt.size= sizeof(AVPicture); + + ret = av_write_frame(oc, &pkt); + video_pts += c->frame_size; + } + else + { + // Set the quality + output->quality = video_st->quality; + + // Set frame interlace hints + output->interlaced_frame = !mlt_properties_get_int( frame_properties, "progressive" ); + output->top_field_first = mlt_properties_get_int( frame_properties, "top_field_first" ); + + // Encode the image + out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, output ); + + // If zero size, it means the image was buffered + if (out_size > 0) + { + AVPacket pkt; + av_init_packet( &pkt ); + + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, video_st->time_base ); + if( c->coded_frame && c->coded_frame->key_frame ) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= video_outbuf; + pkt.size= out_size; + + // write the compressed frame in the media file + ret = av_interleaved_write_frame(oc, &pkt); + video_pts += c->frame_size; + } + else + { + fprintf( stderr, "Error with video encode\n" ); + } + } + frame_count++; + mlt_frame_close( frame ); + } + else + { + break; + } + } + } + + if ( real_time_output && frames % 12 == 0 ) + { + long passed = time_difference( &ante ); + if ( fifo != NULL ) + { + long pending = ( ( ( long )sample_fifo_used( fifo ) * 1000 ) / frequency ) * 1000; + passed -= pending; + } + if ( passed < total_time ) + { + long total = ( total_time - passed ); + struct timespec t = { total / 1000000, ( total % 1000000 ) * 1000 }; + nanosleep( &t, NULL ); + } + } + } + +#ifdef FLUSH + if ( ! real_time_output ) + { + // Flush audio fifo + if ( audio_st && audio_st->codec->frame_size > 1 ) for (;;) + { + AVCodecContext *c = audio_st->codec; + AVPacket pkt; + av_init_packet( &pkt ); + pkt.size = 0; + + if ( /*( c->capabilities & CODEC_CAP_SMALL_LAST_FRAME ) &&*/ + ( channels * audio_input_frame_size < sample_fifo_used( fifo ) ) ) + { + sample_fifo_fetch( fifo, buffer, channels * audio_input_frame_size ); + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, buffer ); + } + if ( pkt.size <= 0 ) + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, NULL ); + if ( pkt.size <= 0 ) + break; + + // Write the compressed frame in the media file + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts = av_rescale_q( c->coded_frame->pts, c->time_base, audio_st->time_base ); + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index = audio_st->index; + pkt.data = audio_outbuf; + if ( av_interleaved_write_frame( oc, &pkt ) != 0 ) + { + fprintf(stderr, "Error while writing flushed audio frame\n"); + break; + } + } + + // Flush video + if ( video_st && !( oc->oformat->flags & AVFMT_RAWPICTURE ) ) for (;;) + { + AVCodecContext *c = video_st->codec; + AVPacket pkt; + av_init_packet( &pkt ); + + // Encode the image + pkt.size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, NULL ); + if ( pkt.size <= 0 ) + break; + + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, video_st->time_base ); + if( c->coded_frame && c->coded_frame->key_frame ) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index = video_st->index; + pkt.data = video_outbuf; + + // write the compressed frame in the media file + if ( av_interleaved_write_frame( oc, &pkt ) != 0 ) + { + fprintf(stderr, "Error while writing flushed video frame\n"); + break; + } + } + } +#endif + + // close each codec + if (video_st) + close_video(oc, video_st); + if (audio_st) + close_audio(oc, audio_st); + + // Write the trailer, if any + av_write_trailer(oc); + + // Free the streams + for(i = 0; i < oc->nb_streams; i++) + av_freep(&oc->streams[i]); + + // Close the output file + if (!(fmt->flags & AVFMT_NOFILE)) +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(0<<8)+0) + url_fclose(oc->pb); +#else + url_fclose(&oc->pb); +#endif + + // Clean up input and output frames + if ( output ) + av_free( output->data[0] ); + av_free( output ); + av_free( input->data[0] ); + av_free( input ); + av_free( video_outbuf ); + av_free( buffer ); + + // Free the stream + av_free(oc); + + // Just in case we terminated on pause + mlt_properties_set_int( properties, "running", 0 ); + + mlt_consumer_stopped( this ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer this ) +{ + // Stop the consumer + mlt_consumer_stop( this ); + + // Close the parent + mlt_consumer_close( this ); + + // Free the memory + free( this ); +} diff --git a/src/modules/avformat/consumer_avformat.h b/src/modules/avformat/consumer_avformat.h new file mode 100644 index 0000000..070d5ef --- /dev/null +++ b/src/modules/avformat/consumer_avformat.h @@ -0,0 +1,28 @@ +/* + * consumer_avformat.h -- avformat consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_AVFORMAT_H_ +#define _CONSUMER_AVFORMAT_H_ + +#include + +extern mlt_consumer consumer_avformat_init( char *file ); + +#endif diff --git a/src/modules/avformat/factory.c b/src/modules/avformat/factory.c new file mode 100644 index 0000000..e97b8d2 --- /dev/null +++ b/src/modules/avformat/factory.c @@ -0,0 +1,128 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include "producer_avformat.h" +#include "consumer_avformat.h" +#include "filter_avcolour_space.h" +#include "filter_avdeinterlace.h" +#include "filter_avresample.h" + +// ffmpeg Header files +#include + +// A static flag used to determine if avformat has been initialised +static int avformat_initialised = 0; + +// A locking mutex +static pthread_mutex_t avformat_mutex; + +#if 0 +// These 3 functions should override the alloc functions in libavformat +// but some formats or codecs seem to crash when used (wmv in particular) + +void *av_malloc( unsigned int size ) +{ + return mlt_pool_alloc( size ); +} + +void *av_realloc( void *ptr, unsigned int size ) +{ + return mlt_pool_realloc( ptr, size ); +} + +void av_free( void *ptr ) +{ + return mlt_pool_release( ptr ); +} +#endif + +void avformat_destroy( void *ignore ) +{ + // Clean up + // av_free_static( ); -XXX this is deprecated + + // Destroy the mutex + pthread_mutex_destroy( &avformat_mutex ); +} + +void avformat_lock( ) +{ + // Lock the mutex now + pthread_mutex_lock( &avformat_mutex ); +} + +void avformat_unlock( ) +{ + // Unlock the mutex now + pthread_mutex_unlock( &avformat_mutex ); +} + +static void avformat_init( ) +{ + // Initialise avformat if necessary + if ( avformat_initialised == 0 ) + { + avformat_initialised = 1; + pthread_mutex_init( &avformat_mutex, NULL ); + av_register_all( ); + mlt_factory_register_for_clean_up( NULL, avformat_destroy ); + av_log_set_level( -1 ); + } +} + +void *mlt_create_producer( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avformat" ) ) + return producer_avformat_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avcolour_space" ) ) + return filter_avcolour_space_init( arg ); +#ifdef USE_MMX + if ( !strcmp( id, "avdeinterlace" ) ) + return filter_avdeinterlace_init( arg ); +#endif + if ( !strcmp( id, "avresample" ) ) + return filter_avresample_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avformat" ) ) + return consumer_avformat_init( arg ); + return NULL; +} + diff --git a/src/modules/avformat/ffmpeg.patch b/src/modules/avformat/ffmpeg.patch new file mode 100644 index 0000000..88f9a0d --- /dev/null +++ b/src/modules/avformat/ffmpeg.patch @@ -0,0 +1,15 @@ +=================================================================== +RCS file: /cvsroot/ffmpeg/ffmpeg/libavcodec/ffv1.c,v +retrieving revision 1.20 +diff -u -r1.20 ffv1.c +--- libavcodec/ffv1.c 21 May 2004 14:37:16 -0000 1.20 ++++ libavcodec/ffv1.c 9 Jun 2004 15:04:31 -0000 +@@ -453,7 +453,7 @@ + + static void encode_rgb_frame(FFV1Context *s, uint32_t *src, int w, int h, int stride){ + int x, y, p, i; +- const int ring_size=2; ++ int ring_size=2; + int_fast16_t sample_buffer[3][ring_size][w+6], *sample[3][ring_size]; + s->run_index=0; + diff --git a/src/modules/avformat/filter_avcolour_space.c b/src/modules/avformat/filter_avcolour_space.c new file mode 100644 index 0000000..1ce4736 --- /dev/null +++ b/src/modules/avformat/filter_avcolour_space.c @@ -0,0 +1,243 @@ +/* + * filter_avcolour_space.c -- Colour space filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avcolour_space.h" + +#include + +// ffmpeg Header files +#include +#ifdef SWSCALE +#include +#endif + +#include +#include + +static inline int is_big_endian( ) +{ + union { int i; char c[ 4 ]; } big_endian_test; + big_endian_test.i = 1; + + return big_endian_test.c[ 0 ] != 1; +} + +static inline int convert_mlt_to_av_cs( mlt_image_format format ) +{ + int value = 0; + + switch( format ) + { + case mlt_image_rgb24: + value = PIX_FMT_RGB24; + break; + case mlt_image_rgb24a: + value = PIX_FMT_RGBA32; + break; + case mlt_image_yuv422: + value = PIX_FMT_YUV422; + break; + case mlt_image_yuv420p: + value = PIX_FMT_YUV420P; + break; + case mlt_image_opengl: + case mlt_image_none: + fprintf( stderr, "Invalid format...\n" ); + break; + } + + return value; +} + +static inline void convert_image( uint8_t *out, uint8_t *in, int out_fmt, int in_fmt, int width, int height ) +{ + AVPicture input; + AVPicture output; + avpicture_fill( &input, in, in_fmt, width, height ); + avpicture_fill( &output, out, out_fmt, width, height ); +#ifdef SWSCALE + struct SwsContext *context = sws_getContext( width, height, in_fmt, + width, height, out_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); + sws_scale( context, input.data, input.linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); +#else + img_convert( &output, out_fmt, &input, in_fmt, width, height ); +#endif +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_filter filter = mlt_frame_pop_service( this ); + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + int output_format = *format; + mlt_image_format forced = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "forced" ); + int error = 0; + + // Allow this filter to force processing in a colour space other than requested + *format = forced != 0 ? forced : *format; + + error = mlt_frame_get_image( this, image, format, width, height, 0 ); + + if ( error == 0 && *format != output_format && *image != NULL && output_format != mlt_image_opengl ) + { + int in_fmt = convert_mlt_to_av_cs( *format ); + int out_fmt = convert_mlt_to_av_cs( output_format ); + int size = avpicture_get_size( out_fmt, *width, *height ); + uint8_t *output = mlt_pool_alloc( size ); + convert_image( output, *image, out_fmt, in_fmt, *width, *height ); + + // Special case for alpha rgb input + if ( *format == mlt_image_rgb24a ) + { + register uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + register int len = *width * *height; + register uint8_t *bits = *image; + register int n = ( len + 7 ) / 8; + + if( !is_big_endian( ) ) + bits += 3; + + // Extract alpha mask from the image using Duff's Device + switch( len % 8 ) + { + case 0: do { *alpha ++ = *bits; bits += 4; + case 7: *alpha ++ = *bits; bits += 4; + case 6: *alpha ++ = *bits; bits += 4; + case 5: *alpha ++ = *bits; bits += 4; + case 4: *alpha ++ = *bits; bits += 4; + case 3: *alpha ++ = *bits; bits += 4; + case 2: *alpha ++ = *bits; bits += 4; + case 1: *alpha ++ = *bits; bits += 4; + } + while( --n ); + } + } + + // Update the output + *image = output; + *format = output_format; + mlt_properties_set_data( properties, "image", output, size, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "format", output_format ); + + // Special case for alpha rgb output + if ( *format == mlt_image_rgb24a ) + { + // Fetch the alpha + register uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + + if ( alpha != NULL ) + { + register uint8_t *bits = *image; + register int len = *width * *height; + register int n = ( len + 7 ) / 8; + + if( !is_big_endian( ) ) + bits += 3; + + // Merge the alpha mask into the RGB image using Duff's Device + switch( len % 8 ) + { + case 0: do { *bits = *alpha++; bits += 4; + case 7: *bits = *alpha++; bits += 4; + case 6: *bits = *alpha++; bits += 4; + case 5: *bits = *alpha++; bits += 4; + case 4: *bits = *alpha++; bits += 4; + case 3: *bits = *alpha++; bits += 4; + case 2: *bits = *alpha++; bits += 4; + case 1: *bits = *alpha++; bits += 4; + } + while( --n ); + } + } + } + } + else if ( error == 0 && *format != output_format && *image != NULL && output_format == mlt_image_opengl ) + { + if ( *format == mlt_image_yuv422 ) + { + int size = *width * *height * 4; + uint8_t *output = mlt_pool_alloc( size ); + int h = *height; + int w = *width; + uint8_t *o = output + size; + int ostride = w * 4; + uint8_t *p = *image; + uint8_t *alpha = mlt_frame_get_alpha_mask( this ) + *width * *height; + int r, g, b; + + while( h -- ) + { + w = *width; + o -= ostride; + alpha -= *width; + while( w >= 2 ) + { + YUV2RGB( *p, *( p + 1 ), *( p + 3 ), r, g, b ); + *o ++ = r; + *o ++ = g; + *o ++ = b; + *o ++ = *alpha ++; + YUV2RGB( *( p + 2 ), *( p + 1 ), *( p + 3 ), r, g, b ); + *o ++ = r; + *o ++ = g; + *o ++ = b; + *o ++ = *alpha ++; + w -= 2; + p += 4; + } + o -= ostride; + alpha -= *width; + } + + mlt_properties_set_data( properties, "image", output, size, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "format", output_format ); + *image = output; + *format = output_format; + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avcolour_space_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = filter_process; + return this; +} + diff --git a/src/modules/avformat/filter_avcolour_space.h b/src/modules/avformat/filter_avcolour_space.h new file mode 100644 index 0000000..10616ae --- /dev/null +++ b/src/modules/avformat/filter_avcolour_space.h @@ -0,0 +1,28 @@ +/* + * filter_avcolour_space.h -- colourspace filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVCOLOUR_SPACE_H +#define _FILTER_AVCOLOUR_SPACE_H + +#include + +extern mlt_filter filter_avcolour_space_init( void *arg ); + +#endif diff --git a/src/modules/avformat/filter_avdeinterlace.c b/src/modules/avformat/filter_avdeinterlace.c new file mode 100644 index 0000000..c6f2f8d --- /dev/null +++ b/src/modules/avformat/filter_avdeinterlace.c @@ -0,0 +1,350 @@ +/* + * filter_avdeinterlace.c -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avdeinterlace.h" + +#include + +#include +#include + +// ffmpeg Header files +#include + +#ifdef USE_MMX +#include "mmx.h" +#endif + +#ifdef USE_MMX +#define DEINT_INPLACE_LINE_LUM \ + movd_m2r(lum_m4[0],mm0);\ + movd_m2r(lum_m3[0],mm1);\ + movd_m2r(lum_m2[0],mm2);\ + movd_m2r(lum_m1[0],mm3);\ + movd_m2r(lum[0],mm4);\ + punpcklbw_r2r(mm7,mm0);\ + movd_r2m(mm2,lum_m4[0]);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + punpcklbw_r2r(mm7,mm3);\ + punpcklbw_r2r(mm7,mm4);\ + paddw_r2r(mm3,mm1);\ + psllw_i2r(1,mm2);\ + paddw_r2r(mm4,mm0);\ + psllw_i2r(2,mm1);\ + paddw_r2r(mm6,mm2);\ + paddw_r2r(mm2,mm1);\ + psubusw_r2r(mm0,mm1);\ + psrlw_i2r(3,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,lum_m2[0]); + +#define DEINT_LINE_LUM \ + movd_m2r(lum_m4[0],mm0);\ + movd_m2r(lum_m3[0],mm1);\ + movd_m2r(lum_m2[0],mm2);\ + movd_m2r(lum_m1[0],mm3);\ + movd_m2r(lum[0],mm4);\ + punpcklbw_r2r(mm7,mm0);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + punpcklbw_r2r(mm7,mm3);\ + punpcklbw_r2r(mm7,mm4);\ + paddw_r2r(mm3,mm1);\ + psllw_i2r(1,mm2);\ + paddw_r2r(mm4,mm0);\ + psllw_i2r(2,mm1);\ + paddw_r2r(mm6,mm2);\ + paddw_r2r(mm2,mm1);\ + psubusw_r2r(mm0,mm1);\ + psrlw_i2r(3,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,dst[0]); +#endif + +/* filter parameters: [-1 4 2 4 -1] // 8 */ +static inline void deinterlace_line(uint8_t *dst, + const uint8_t *lum_m4, const uint8_t *lum_m3, + const uint8_t *lum_m2, const uint8_t *lum_m1, + const uint8_t *lum, + int size) +{ +#ifndef USE_MMX + uint8_t *cm = cropTbl + MAX_NEG_CROP; + int sum; + + for(;size > 0;size--) { + sum = -lum_m4[0]; + sum += lum_m3[0] << 2; + sum += lum_m2[0] << 1; + sum += lum_m1[0] << 2; + sum += -lum[0]; + dst[0] = cm[(sum + 4) >> 3]; + lum_m4++; + lum_m3++; + lum_m2++; + lum_m1++; + lum++; + dst++; + } +#else + + { + mmx_t rounder; + rounder.uw[0]=4; + rounder.uw[1]=4; + rounder.uw[2]=4; + rounder.uw[3]=4; + pxor_r2r(mm7,mm7); + movq_m2r(rounder,mm6); + } + for (;size > 3; size-=4) { + DEINT_LINE_LUM + lum_m4+=4; + lum_m3+=4; + lum_m2+=4; + lum_m1+=4; + lum+=4; + dst+=4; + } +#endif +} +static inline void deinterlace_line_inplace(uint8_t *lum_m4, uint8_t *lum_m3, uint8_t *lum_m2, uint8_t *lum_m1, uint8_t *lum, + int size) +{ +#ifndef USE_MMX + uint8_t *cm = cropTbl + MAX_NEG_CROP; + int sum; + + for(;size > 0;size--) { + sum = -lum_m4[0]; + sum += lum_m3[0] << 2; + sum += lum_m2[0] << 1; + lum_m4[0]=lum_m2[0]; + sum += lum_m1[0] << 2; + sum += -lum[0]; + lum_m2[0] = cm[(sum + 4) >> 3]; + lum_m4++; + lum_m3++; + lum_m2++; + lum_m1++; + lum++; + } +#else + + { + mmx_t rounder; + rounder.uw[0]=4; + rounder.uw[1]=4; + rounder.uw[2]=4; + rounder.uw[3]=4; + pxor_r2r(mm7,mm7); + movq_m2r(rounder,mm6); + } + for (;size > 3; size-=4) { + DEINT_INPLACE_LINE_LUM + lum_m4+=4; + lum_m3+=4; + lum_m2+=4; + lum_m1+=4; + lum+=4; + } +#endif +} + +/* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The + top field is copied as is, but the bottom field is deinterlaced + against the top field. */ +static inline void deinterlace_bottom_field(uint8_t *dst, int dst_wrap, + const uint8_t *src1, int src_wrap, + int width, int height) +{ + const uint8_t *src_m2, *src_m1, *src_0, *src_p1, *src_p2; + int y; + + src_m2 = src1; + src_m1 = src1; + src_0=&src_m1[src_wrap]; + src_p1=&src_0[src_wrap]; + src_p2=&src_p1[src_wrap]; + for(y=0;y<(height-2);y+=2) { + memcpy(dst,src_m1,width); + dst += dst_wrap; + deinterlace_line(dst,src_m2,src_m1,src_0,src_p1,src_p2,width); + src_m2 = src_0; + src_m1 = src_p1; + src_0 = src_p2; + src_p1 += 2*src_wrap; + src_p2 += 2*src_wrap; + dst += dst_wrap; + } + memcpy(dst,src_m1,width); + dst += dst_wrap; + /* do last line */ + deinterlace_line(dst,src_m2,src_m1,src_0,src_0,src_0,width); +} + +static inline void deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, + int width, int height) +{ + uint8_t *src_m1, *src_0, *src_p1, *src_p2; + int y; + uint8_t *buf; + buf = (uint8_t*)av_malloc(width); + + src_m1 = src1; + memcpy(buf,src_m1,width); + src_0=&src_m1[src_wrap]; + src_p1=&src_0[src_wrap]; + src_p2=&src_p1[src_wrap]; + for(y=0;y<(height-2);y+=2) { + deinterlace_line_inplace(buf,src_m1,src_0,src_p1,src_p2,width); + src_m1 = src_p1; + src_0 = src_p2; + src_p1 += 2*src_wrap; + src_p2 += 2*src_wrap; + } + /* do last line */ + deinterlace_line_inplace(buf,src_m1,src_0,src_0,src_0,width); + av_free(buf); +} + + +/* deinterlace - if not supported return -1 */ +static int mlt_avpicture_deinterlace(AVPicture *dst, const AVPicture *src, + int pix_fmt, int width, int height) +{ + int i; + + if (pix_fmt != PIX_FMT_YUV420P && + pix_fmt != PIX_FMT_YUV422P && + pix_fmt != PIX_FMT_YUV422 && + pix_fmt != PIX_FMT_YUV444P && + pix_fmt != PIX_FMT_YUV411P) + return -1; + if ((width & 3) != 0 || (height & 3) != 0) + return -1; + + if ( pix_fmt != PIX_FMT_YUV422 ) + { + for(i=0;i<3;i++) { + if (i == 1) { + switch(pix_fmt) { + case PIX_FMT_YUV420P: + width >>= 1; + height >>= 1; + break; + case PIX_FMT_YUV422P: + width >>= 1; + break; + case PIX_FMT_YUV411P: + width >>= 2; + break; + default: + break; + } + } + if (src == dst) { + deinterlace_bottom_field_inplace(dst->data[i], dst->linesize[i], + width, height); + } else { + deinterlace_bottom_field(dst->data[i],dst->linesize[i], + src->data[i], src->linesize[i], + width, height); + } + } + } + else { + if (src == dst) { + deinterlace_bottom_field_inplace(dst->data[0], dst->linesize[0], + width, height); + } else { + deinterlace_bottom_field(dst->data[0],dst->linesize[0], + src->data[0], src->linesize[0], + width, height); + } + } + +#ifdef USE_MMX + emms(); +#endif + return 0; +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + int deinterlace = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "consumer_deinterlace" ); + + // Determine if we need a writable version or not + if ( deinterlace && !writable ) + writable = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ); + + // Get the input image + error = mlt_frame_get_image( this, image, format, width, height, writable ); + + // Check that we want progressive and we aren't already progressive + if ( deinterlace && *format == mlt_image_yuv422 && *image != NULL && !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ) ) + { + // Create a picture + AVPicture *output = mlt_pool_alloc( sizeof( AVPicture ) ); + + // Fill the picture + if ( *format == mlt_image_yuv422 ) + { + avpicture_fill( output, *image, PIX_FMT_YUV422, *width, *height ); + mlt_avpicture_deinterlace( output, output, PIX_FMT_YUV422, *width, *height ); + } + + // Free the picture + mlt_pool_release( output ); + + // Make sure that others know the frame is deinterlaced + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "progressive", 1 ); + } + + return error; +} + +/** Deinterlace filter processing - this should be lazy evaluation here... +*/ + +static mlt_frame deinterlace_process( mlt_filter this, mlt_frame frame ) +{ + // Push the get_image method on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avdeinterlace_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = deinterlace_process; + return this; +} + diff --git a/src/modules/avformat/filter_avdeinterlace.h b/src/modules/avformat/filter_avdeinterlace.h new file mode 100644 index 0000000..068a01a --- /dev/null +++ b/src/modules/avformat/filter_avdeinterlace.h @@ -0,0 +1,28 @@ +/* + * filter_avdeinterlace.h -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVDEINTERLACE_H_ +#define _FILTER_AVDEINTERLACE_H_ + +#include + +extern mlt_filter filter_avdeinterlace_init( void *arg ); + +#endif diff --git a/src/modules/avformat/filter_avresample.c b/src/modules/avformat/filter_avresample.c new file mode 100644 index 0000000..6fb2038 --- /dev/null +++ b/src/modules/avformat/filter_avresample.c @@ -0,0 +1,197 @@ +/* + * filter_avresample.c -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avresample.h" + +#include + +#include +#include +#include + +// ffmpeg Header files +#include + +/** Get the audio. +*/ + +static int resample_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the resample information + int output_rate = mlt_properties_get_int( filter_properties, "frequency" ); + int16_t *sample_buffer = mlt_properties_get_data( filter_properties, "buffer", NULL ); + + // Obtain the resample context if it exists + ReSampleContext *resample = mlt_properties_get_data( filter_properties, "audio_resample", NULL ); + + // Used to return number of channels in the source + int channels_avail = *channels; + + // Loop variable + int i; + + // If no resample frequency is specified, default to requested value + if ( output_rate == 0 ) + output_rate = *frequency; + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, &channels_avail, samples ); + + // Duplicate channels as necessary + if ( channels_avail < *channels ) + { + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + int j, k = 0; + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * channels_avail ) + k ]; + k = ( k + 1 ) % channels_avail; + } + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + else if ( channels_avail == 6 && *channels == 2 ) + { + // Nasty hack for ac3 5.1 audio - may be a cause of failure? + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + + // Drop all but the first *channels + for ( i = 0; i < *samples; i++ ) + { + new_buffer[ ( i * *channels ) + 0 ] = (*buffer)[ ( i * channels_avail ) + 2 ]; + new_buffer[ ( i * *channels ) + 1 ] = (*buffer)[ ( i * channels_avail ) + 3 ]; + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + + // Return now if no work to do + if ( output_rate != *frequency ) + { + // Will store number of samples created + int used = 0; + + // Create a resampler if nececessary + if ( resample == NULL || *frequency != mlt_properties_get_int( filter_properties, "last_frequency" ) ) + { + // Create the resampler + resample = audio_resample_init( *channels, *channels, output_rate, *frequency ); + + // And store it on properties + mlt_properties_set_data( filter_properties, "audio_resample", resample, 0, ( mlt_destructor )audio_resample_close, NULL ); + + // And remember what it was created for + mlt_properties_set_int( filter_properties, "last_frequency", *frequency ); + } + + // Resample the audio + used = audio_resample( resample, sample_buffer, *buffer, *samples ); + + // Resize if necessary + if ( used > *samples ) + { + *buffer = mlt_pool_realloc( *buffer, *samples * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( properties, "audio", *buffer, *channels * used * sizeof( int16_t ), mlt_pool_release, NULL ); + } + + // Copy samples + memcpy( *buffer, sample_buffer, *channels * used * sizeof( int16_t ) ); + + // Update output variables + *samples = used; + *frequency = output_rate; + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Only call this if we have a means to get audio + if ( mlt_frame_is_test_audio( frame ) == 0 ) + { + // Push the filter on to the stack + mlt_frame_push_audio( frame, this ); + + // Assign our get_audio method + mlt_frame_push_audio( frame, resample_get_audio ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avresample_init( char *arg ) +{ + // Create a filter + mlt_filter this = mlt_filter_new( ); + + // Initialise if successful + if ( this != NULL ) + { + // Calculate size of the buffer + int size = AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ); + + // Allocate the buffer + int16_t *buffer = mlt_pool_alloc( size ); + + // Assign the process method + this->process = filter_process; + + // Deal with argument + if ( arg != NULL ) + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "frequency", arg ); + + // Default to 2 channel output + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", 2 ); + + // Store the buffer + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "buffer", buffer, size, mlt_pool_release, NULL ); + } + + return this; +} diff --git a/src/modules/avformat/filter_avresample.h b/src/modules/avformat/filter_avresample.h new file mode 100644 index 0000000..2f36e4e --- /dev/null +++ b/src/modules/avformat/filter_avresample.h @@ -0,0 +1,28 @@ +/* + * filter_avresample.h -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVRESAMPLE_H_ +#define _FILTER_AVRESAMPLE_H_ + +#include + +extern mlt_filter filter_avresample_init( char *arg ); + +#endif diff --git a/src/modules/avformat/mmx.h b/src/modules/avformat/mmx.h new file mode 100644 index 0000000..7e94cfd --- /dev/null +++ b/src/modules/avformat/mmx.h @@ -0,0 +1,243 @@ +/* + * mmx.h + * Copyright (C) 1997-2001 H. Dietz and R. Fisher + */ +#ifndef AVCODEC_I386MMX_H +#define AVCODEC_I386MMX_H + +/* + * The type of an value that fits in an MMX register (note that long + * long constant values MUST be suffixed by LL and unsigned long long + * values by ULL, lest they be truncated by the compiler) + */ + +typedef union { + long long q; /* Quadword (64-bit) value */ + unsigned long long uq; /* Unsigned Quadword */ + int d[2]; /* 2 Doubleword (32-bit) values */ + unsigned int ud[2]; /* 2 Unsigned Doubleword */ + short w[4]; /* 4 Word (16-bit) values */ + unsigned short uw[4]; /* 4 Unsigned Word */ + char b[8]; /* 8 Byte (8-bit) values */ + unsigned char ub[8]; /* 8 Unsigned Byte */ + float s[2]; /* Single-precision (32-bit) value */ +} mmx_t; /* On an 8-byte (64-bit) boundary */ + + +#define mmx_i2r(op,imm,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "i" (imm) ) + +#define mmx_m2r(op,mem,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "m" (mem)) + +#define mmx_r2m(op,reg,mem) \ + __asm__ __volatile__ (#op " %%" #reg ", %0" \ + : "=m" (mem) \ + : /* nothing */ ) + +#define mmx_r2r(op,regs,regd) \ + __asm__ __volatile__ (#op " %" #regs ", %" #regd) + + +#define emms() __asm__ __volatile__ ("emms") + +#define movd_m2r(var,reg) mmx_m2r (movd, var, reg) +#define movd_r2m(reg,var) mmx_r2m (movd, reg, var) +#define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd) + +#define movq_m2r(var,reg) mmx_m2r (movq, var, reg) +#define movq_r2m(reg,var) mmx_r2m (movq, reg, var) +#define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd) + +#define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg) +#define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd) +#define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg) +#define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd) + +#define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg) +#define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd) + +#define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg) +#define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd) +#define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg) +#define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd) +#define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg) +#define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd) + +#define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg) +#define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd) +#define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg) +#define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd) + +#define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg) +#define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd) +#define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg) +#define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd) + +#define pand_m2r(var,reg) mmx_m2r (pand, var, reg) +#define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd) + +#define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg) +#define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd) + +#define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg) +#define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd) +#define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg) +#define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd) +#define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg) +#define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd) + +#define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg) +#define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd) +#define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg) +#define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd) +#define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg) +#define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd) + +#define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg) +#define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd) + +#define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg) +#define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd) + +#define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg) +#define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd) + +#define por_m2r(var,reg) mmx_m2r (por, var, reg) +#define por_r2r(regs,regd) mmx_r2r (por, regs, regd) + +#define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg) +#define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg) +#define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd) +#define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg) +#define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg) +#define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd) +#define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg) +#define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg) +#define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd) + +#define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg) +#define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg) +#define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd) +#define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg) +#define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg) +#define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd) + +#define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg) +#define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg) +#define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd) +#define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg) +#define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg) +#define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd) +#define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg) +#define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg) +#define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd) + +#define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg) +#define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd) +#define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg) +#define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd) +#define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg) +#define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd) + +#define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg) +#define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd) +#define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg) +#define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd) + +#define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg) +#define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd) +#define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg) +#define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd) + +#define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg) +#define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd) +#define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg) +#define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd) +#define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg) +#define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd) + +#define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg) +#define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd) +#define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg) +#define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd) +#define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg) +#define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd) + +#define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg) +#define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd) + + +/* 3DNOW extensions */ + +#define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg) +#define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd) + + +/* AMD MMX extensions - also available in intel SSE */ + + +#define mmx_m2ri(op,mem,reg,imm) \ + __asm__ __volatile__ (#op " %1, %0, %%" #reg \ + : /* nothing */ \ + : "X" (mem), "X" (imm)) +#define mmx_r2ri(op,regs,regd,imm) \ + __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ + : /* nothing */ \ + : "X" (imm) ) + +#define mmx_fetch(mem,hint) \ + __asm__ __volatile__ ("prefetch" #hint " %0" \ + : /* nothing */ \ + : "X" (mem)) + + +#define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg) + +#define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var) + +#define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg) +#define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd) +#define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg) +#define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd) + +#define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm) + +#define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm) + +#define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg) +#define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd) + +#define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg) +#define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd) + +#define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg) +#define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd) + +#define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg) +#define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd) + +#define pmovmskb(mmreg,reg) \ + __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) + +#define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg) +#define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd) + +#define prefetcht0(mem) mmx_fetch (mem, t0) +#define prefetcht1(mem) mmx_fetch (mem, t1) +#define prefetcht2(mem) mmx_fetch (mem, t2) +#define prefetchnta(mem) mmx_fetch (mem, nta) + +#define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg) +#define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd) + +#define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm) +#define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm) + +#define sfence() __asm__ __volatile__ ("sfence\n\t") + +#endif /* AVCODEC_I386MMX_H */ diff --git a/src/modules/avformat/producer_avformat.c b/src/modules/avformat/producer_avformat.c new file mode 100644 index 0000000..c3cf306 --- /dev/null +++ b/src/modules/avformat/producer_avformat.c @@ -0,0 +1,1067 @@ +/* + * producer_avformat.c -- avformat producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "producer_avformat.h" + +// MLT Header files +#include + +// ffmpeg Header files +#include +#ifdef SWSCALE +#include +#endif + +// System header files +#include +#include +#include +#include + +void avformat_lock( ); +void avformat_unlock( ); + +// Forward references. +static int producer_open( mlt_producer this, char *file ); +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); + +/** Constructor for libavformat. +*/ + +mlt_producer producer_avformat_init( char *file ) +{ + mlt_producer this = NULL; + + // Check that we have a non-NULL argument + if ( file != NULL ) + { + // Construct the producer + this = calloc( 1, sizeof( struct mlt_producer_s ) ); + + // Initialise it + if ( mlt_producer_init( this, NULL ) == 0 ) + { + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Set the resource property (required for all producers) + mlt_properties_set( properties, "resource", file ); + + // Register our get_frame implementation + this->get_frame = producer_get_frame; + + // Open the file + if ( producer_open( this, file ) != 0 ) + { + // Clean up + mlt_producer_close( this ); + this = NULL; + } + } + } + + return this; +} + +/** Find the default streams. +*/ + +static void find_default_streams( AVFormatContext *context, int *audio_index, int *video_index ) +{ + int i; + + // Allow for multiple audio and video streams in the file and select first of each (if available) + for( i = 0; i < context->nb_streams; i++ ) + { + // Get the codec context + AVCodecContext *codec_context = context->streams[ i ]->codec; + + if ( avcodec_find_decoder( codec_context->codec_id ) == NULL ) + continue; + + // Determine the type and obtain the first index of each type + switch( codec_context->codec_type ) + { + case CODEC_TYPE_VIDEO: + if ( *video_index < 0 ) + *video_index = i; + break; + case CODEC_TYPE_AUDIO: + if ( *audio_index < 0 ) + *audio_index = i; + break; + default: + break; + } + } +} + +/** Producer file destructor. +*/ + +static void producer_file_close( void *context ) +{ + if ( context != NULL ) + { + // Lock the mutex now + avformat_lock( ); + + // Close the file + av_close_input_file( context ); + + // Unlock the mutex now + avformat_unlock( ); + } +} + +/** Producer file destructor. +*/ + +static void producer_codec_close( void *codec ) +{ + if ( codec != NULL ) + { + // Lock the mutex now + avformat_lock( ); + + // Close the file + avcodec_close( codec ); + + // Unlock the mutex now + avformat_unlock( ); + } +} + +/** Open the file. +*/ + +static int producer_open( mlt_producer this, char *file ) +{ + // Return an error code (0 == no error) + int error = 0; + + // Context for avformat + AVFormatContext *context = NULL; + + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // We will treat everything with the producer fps + double fps = mlt_producer_get_fps( this ); + + // Lock the mutex now + avformat_lock( ); + + // If "MRL", then create AVInputFormat + AVInputFormat *format = NULL; + AVFormatParameters *params = NULL; + char *standard = NULL; + char *mrl = strchr( file, ':' ); + + // AV option (0 = both, 1 = video, 2 = audio) + int av = 0; + + // Setting lowest log level + av_log_set_level( -1 ); + + // Only if there is not a protocol specification that avformat can handle + if ( mrl && !url_exist( file ) ) + { + // 'file' becomes format abbreviation + mrl[0] = 0; + + // Lookup the format + format = av_find_input_format( file ); + + // Eat the format designator + file = ++mrl; + + if ( format ) + { + // Allocate params + params = calloc( sizeof( AVFormatParameters ), 1 ); + + // These are required by video4linux (defaults) + params->width = 640; + params->height = 480; + params->time_base= (AVRational){1,25}; + // params->device = file; + params->channels = 2; + params->sample_rate = 48000; + } + + // XXX: this does not work anymore since avdevice + // TODO: make producer_avddevice? + // Parse out params + mrl = strchr( file, '?' ); + while ( mrl ) + { + mrl[0] = 0; + char *name = strdup( ++mrl ); + char *value = strchr( name, ':' ); + if ( value ) + { + value[0] = 0; + value++; + char *t = strchr( value, '&' ); + if ( t ) + t[0] = 0; + if ( !strcmp( name, "frame_rate" ) ) + params->time_base.den = atoi( value ); + else if ( !strcmp( name, "frame_rate_base" ) ) + params->time_base.num = atoi( value ); + else if ( !strcmp( name, "sample_rate" ) ) + params->sample_rate = atoi( value ); + else if ( !strcmp( name, "channels" ) ) + params->channels = atoi( value ); + else if ( !strcmp( name, "width" ) ) + params->width = atoi( value ); + else if ( !strcmp( name, "height" ) ) + params->height = atoi( value ); + else if ( !strcmp( name, "standard" ) ) + { + standard = strdup( value ); + params->standard = standard; + } + else if ( !strcmp( name, "av" ) ) + av = atoi( value ); + } + free( name ); + mrl = strchr( mrl, '&' ); + } + } + + // Now attempt to open the file + error = av_open_input_file( &context, file, format, 0, params ) < 0; + + // Cleanup AVFormatParameters + free( standard ); + free( params ); + + // If successful, then try to get additional info + if ( error == 0 ) + { + // Get the stream info + error = av_find_stream_info( context ) < 0; + + // Continue if no error + if ( error == 0 ) + { + // We will default to the first audio and video streams found + int audio_index = -1; + int video_index = -1; + int av_bypass = 0; + + // Now set properties where we can (use default unknowns if required) + if ( context->duration != AV_NOPTS_VALUE ) + { + // This isn't going to be accurate for all formats + mlt_position frames = ( mlt_position )( ( ( double )context->duration / ( double )AV_TIME_BASE ) * fps + 0.5 ); + mlt_properties_set_position( properties, "out", frames - 1 ); + mlt_properties_set_position( properties, "length", frames ); + } + + // Find default audio and video streams + find_default_streams( context, &audio_index, &video_index ); + + if ( context->start_time != AV_NOPTS_VALUE ) + mlt_properties_set_double( properties, "_start_time", context->start_time ); + + // Check if we're seekable (something funny about mpeg here :-/) + if ( strcmp( file, "pipe:" ) && strncmp( file, "http://", 6 ) ) + { + mlt_properties_set_int( properties, "seekable", av_seek_frame( context, -1, mlt_properties_get_double( properties, "_start_time" ), AVSEEK_FLAG_BACKWARD ) >= 0 ); + mlt_properties_set_data( properties, "dummy_context", context, 0, producer_file_close, NULL ); + av_open_input_file( &context, file, NULL, 0, NULL ); + av_find_stream_info( context ); + } + else + av_bypass = 1; + + // Store selected audio and video indexes on properties + mlt_properties_set_int( properties, "audio_index", audio_index ); + mlt_properties_set_int( properties, "video_index", video_index ); + mlt_properties_set_int( properties, "_last_position", -1 ); + + // Fetch the width, height and aspect ratio + if ( video_index != -1 ) + { + AVCodecContext *codec_context = context->streams[ video_index ]->codec; + mlt_properties_set_int( properties, "width", codec_context->width ); + mlt_properties_set_int( properties, "height", codec_context->height ); + mlt_properties_set_double( properties, "aspect_ratio", av_q2d( codec_context->sample_aspect_ratio ) ); + } + + // Read Metadata + if (context->title != NULL) + mlt_properties_set(properties, "meta.attr.title.markup", context->title ); + if (context->author != NULL) + mlt_properties_set(properties, "meta.attr.author.markup", context->author ); + if (context->copyright != NULL) + mlt_properties_set(properties, "meta.attr.copyright.markup", context->copyright ); + if (context->comment != NULL) + mlt_properties_set(properties, "meta.attr.comment.markup", context->comment ); + if (context->album != NULL) + mlt_properties_set(properties, "meta.attr.album.markup", context->album ); + if (context->year != 0) + mlt_properties_set_int(properties, "meta.attr.year.markup", context->year ); + if (context->track != 0) + mlt_properties_set_int(properties, "meta.attr.track.markup", context->track ); + + // We're going to cheat here - for a/v files, we will have two contexts (reasoning will be clear later) + if ( av == 0 && !av_bypass && audio_index != -1 && video_index != -1 ) + { + // We'll use the open one as our video_context + mlt_properties_set_data( properties, "video_context", context, 0, producer_file_close, NULL ); + + // And open again for our audio context + av_open_input_file( &context, file, NULL, 0, NULL ); + av_find_stream_info( context ); + + // Audio context + mlt_properties_set_data( properties, "audio_context", context, 0, producer_file_close, NULL ); + } + else if ( av != 2 && video_index != -1 ) + { + // We only have a video context + mlt_properties_set_data( properties, "video_context", context, 0, producer_file_close, NULL ); + } + else if ( audio_index != -1 ) + { + // We only have an audio context + mlt_properties_set_data( properties, "audio_context", context, 0, producer_file_close, NULL ); + } + else + { + // Something has gone wrong + error = -1; + } + + mlt_properties_set_int( properties, "av_bypass", av_bypass ); + } + } + + // Unlock the mutex now + avformat_unlock( ); + + return error; +} + +/** Convert a frame position to a time code. +*/ + +static double producer_time_of_frame( mlt_producer this, mlt_position position ) +{ + return ( double )position / mlt_producer_get_fps( this ); +} + +static inline void convert_image( AVFrame *frame, uint8_t *buffer, int pix_fmt, mlt_image_format format, int width, int height ) +{ +#ifdef SWSCALE + if ( format == mlt_image_yuv420p ) + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + output.data[0] = buffer; + output.data[1] = buffer + width * height; + output.data[2] = buffer + ( 3 * width * height ) / 2; + output.linesize[0] = width; + output.linesize[1] = width >> 1; + output.linesize[2] = width >> 1; + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } + else if ( format == mlt_image_rgb24 ) + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_RGB24, width, height ); + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } + else + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_YUYV422, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_YUYV422, width, height ); + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } +#else + if ( format == mlt_image_yuv420p ) + { + AVPicture pict; + pict.data[0] = buffer; + pict.data[1] = buffer + width * height; + pict.data[2] = buffer + ( 3 * width * height ) / 2; + pict.linesize[0] = width; + pict.linesize[1] = width >> 1; + pict.linesize[2] = width >> 1; + img_convert( &pict, PIX_FMT_YUV420P, (AVPicture *)frame, pix_fmt, width, height ); + } + else if ( format == mlt_image_rgb24 ) + { + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_RGB24, width, height ); + img_convert( &output, PIX_FMT_RGB24, (AVPicture *)frame, pix_fmt, width, height ); + } + else + { + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_YUV422, width, height ); + img_convert( &output, PIX_FMT_YUV422, (AVPicture *)frame, pix_fmt, width, height ); + } +#endif +} + +/** Get an image from a frame. +*/ + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the properties from the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the frame number of this frame + mlt_position position = mlt_properties_get_position( frame_properties, "avformat_position" ); + + // Get the producer + mlt_producer this = mlt_properties_get_data( frame_properties, "avformat_producer", NULL ); + + // Get the producer properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the video_context + AVFormatContext *context = mlt_properties_get_data( properties, "video_context", NULL ); + + // Get the video_index + int index = mlt_properties_get_int( properties, "video_index" ); + + // Obtain the expected frame numer + mlt_position expected = mlt_properties_get_position( properties, "_video_expected" ); + + // Get the video stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Packet + AVPacket pkt; + + // Get the conversion frame + AVFrame *av_frame = mlt_properties_get_data( properties, "av_frame", NULL ); + + // Special case pause handling flag + int paused = 0; + + // Special case ffwd handling + int ignore = 0; + + // We may want to use the source fps if available + double source_fps = mlt_properties_get_double( properties, "source_fps" ); + double fps = mlt_producer_get_fps( this ); + + // This is the physical frame position in the source + int req_position = ( int )( position / fps * source_fps + 0.5 ); + + // Get the seekable status + int seekable = mlt_properties_get_int( properties, "seekable" ); + + // Generate the size in bytes + int size = 0; + + // Hopefully provide better support for streams... + int av_bypass = mlt_properties_get_int( properties, "av_bypass" ); + + // Determines if we have to decode all frames in a sequence + int must_decode = 1; + + // Set the result arguments that we know here (only *buffer is now required) + *width = codec_context->width; + *height = codec_context->height; + + switch ( *format ) + { + case mlt_image_yuv420p: + size = *width * 3 * ( *height + 1 ) / 2; + break; + case mlt_image_rgb24: + size = *width * ( *height + 1 ) * 3; + break; + default: + *format = mlt_image_yuv422; + size = *width * ( *height + 1 ) * 2; + break; + } + + // Set this on the frame properties + mlt_properties_set_int( frame_properties, "width", *width ); + mlt_properties_set_int( frame_properties, "height", *height ); + + // Construct the output image + *buffer = mlt_pool_alloc( size ); + + // Temporary hack to improve intra frame only + must_decode = strcmp( codec_context->codec->name, "mjpeg" ) && + strcmp( codec_context->codec->name, "rawvideo" ) && + strcmp( codec_context->codec->name, "dvvideo" ); + + // Seek if necessary + if ( position != expected ) + { + if ( av_frame != NULL && position + 1 == expected ) + { + // We're paused - use last image + paused = 1; + } + else if ( !seekable && position > expected && ( position - expected ) < 250 ) + { + // Fast forward - seeking is inefficient for small distances - just ignore following frames + ignore = ( int )( ( position - expected ) / fps * source_fps ); + } + else if ( seekable && ( position < expected || position - expected >= 12 ) ) + { + // Calculate the timestamp for the requested frame + int64_t timestamp = ( int64_t )( ( double )req_position / source_fps * AV_TIME_BASE + 0.5 ); + if ( ( uint64_t )context->start_time != AV_NOPTS_VALUE ) + timestamp += context->start_time; + if ( must_decode ) + timestamp -= AV_TIME_BASE; + if ( timestamp < 0 ) + timestamp = 0; + + // Set to the timestamp + av_seek_frame( context, -1, timestamp, AVSEEK_FLAG_BACKWARD ); + + // Remove the cached info relating to the previous position + mlt_properties_set_int( properties, "_current_position", -1 ); + mlt_properties_set_int( properties, "_last_position", -1 ); + mlt_properties_set_data( properties, "av_frame", NULL, 0, NULL, NULL ); + av_frame = NULL; + } + } + + // Duplicate the last image if necessary (see comment on rawvideo below) + int current_position = mlt_properties_get_int( properties, "_current_position" ); + int got_picture = mlt_properties_get_int( properties, "_got_picture" ); + if ( av_frame != NULL && got_picture && ( paused || current_position >= req_position ) && av_bypass == 0 ) + { + // Duplicate it + convert_image( av_frame, *buffer, codec_context->pix_fmt, *format, *width, *height ); + + // Set this on the frame properties + mlt_properties_set_data( frame_properties, "image", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + } + else + { + int ret = 0; + int int_position = 0; + got_picture = 0; + + av_init_packet( &pkt ); + + // Construct an AVFrame for YUV422 conversion + if ( av_frame == NULL ) + { + av_frame = avcodec_alloc_frame( ); + mlt_properties_set_data( properties, "av_frame", av_frame, 0, av_free, NULL ); + } + + while( ret >= 0 && !got_picture ) + { + // Read a packet + ret = av_read_frame( context, &pkt ); + + // We only deal with video from the selected video_index + if ( ret >= 0 && pkt.stream_index == index && pkt.size > 0 ) + { + // Determine time code of the packet + int_position = ( int )( av_q2d( stream->time_base ) * pkt.dts * source_fps + 0.5 ); + if ( context->start_time != AV_NOPTS_VALUE ) + int_position -= ( int )( context->start_time * source_fps / AV_TIME_BASE + 0.5 ); + int last_position = mlt_properties_get_int( properties, "_last_position" ); + if ( int_position == last_position ) + int_position = last_position + 1; + mlt_properties_set_int( properties, "_last_position", int_position ); + + // Decode the image + if ( must_decode || int_position >= req_position ) + ret = avcodec_decode_video( codec_context, av_frame, &got_picture, pkt.data, pkt.size ); + + if ( got_picture ) + { + // Handle ignore + if ( int_position < req_position ) + { + ignore = 0; + got_picture = 0; + } + else if ( int_position >= req_position ) + { + ignore = 0; + } + else if ( ignore -- ) + { + got_picture = 0; + } + } + } + + // Now handle the picture if we have one + if ( got_picture ) + { + mlt_properties_set_int( frame_properties, "progressive", !av_frame->interlaced_frame ); + mlt_properties_set_int( frame_properties, "top_field_first", av_frame->top_field_first ); + convert_image( av_frame, *buffer, codec_context->pix_fmt, *format, *width, *height ); + mlt_properties_set_data( frame_properties, "image", *buffer, size, (mlt_destructor)mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "_current_position", int_position ); + mlt_properties_set_int( properties, "_got_picture", 1 ); + } + + // We're finished with this packet regardless + av_free_packet( &pkt ); + } + } + + // Very untidy - for rawvideo, the packet contains the frame, hence the free packet + // above will break the pause behaviour - so we wipe the frame now + if ( !strcmp( codec_context->codec->name, "rawvideo" ) ) + mlt_properties_set_data( properties, "av_frame", NULL, 0, NULL, NULL ); + + // Set the field order property for this frame + mlt_properties_set_int( frame_properties, "top_field_first", mlt_properties_get_int( properties, "top_field_first" ) ); + + // Regardless of speed, we expect to get the next frame (cos we ain't too bright) + mlt_properties_set_position( properties, "_video_expected", position + 1 ); + + return 0; +} + +/** Set up video handling. +*/ + +static void producer_set_up_video( mlt_producer this, mlt_frame frame ) +{ + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the video_context + AVFormatContext *context = mlt_properties_get_data( properties, "video_context", NULL ); + + // Get the video_index + int index = mlt_properties_get_int( properties, "video_index" ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + if ( context != NULL && index != -1 ) + { + // Get the video stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Get the codec + AVCodec *codec = mlt_properties_get_data( properties, "video_codec", NULL ); + + // Initialise the codec if necessary + if ( codec == NULL ) + { + // Find the codec + codec = avcodec_find_decoder( codec_context->codec_id ); + + // If we don't have a codec and we can't initialise it, we can't do much more... + if ( codec != NULL && avcodec_open( codec_context, codec ) >= 0 ) + { + // Now store the codec with its destructor + mlt_properties_set_data( properties, "video_codec", codec_context, 0, producer_codec_close, NULL ); + } + else + { + // Remember that we can't use this later + mlt_properties_set_int( properties, "video_index", -1 ); + } + } + + // No codec, no show... + if ( codec != NULL ) + { + double source_fps = 0; + int norm_aspect_ratio = mlt_properties_get_int( properties, "norm_aspect_ratio" ); + double force_aspect_ratio = mlt_properties_get_double( properties, "force_aspect_ratio" ); + double aspect_ratio; + + // XXX: We won't know the real aspect ratio until an image is decoded + // but we do need it now (to satisfy filter_resize) - take a guess based + // on pal/ntsc + if ( force_aspect_ratio > 0.0 ) + { + aspect_ratio = force_aspect_ratio; + } + else if ( !norm_aspect_ratio && codec_context->sample_aspect_ratio.num > 0 ) + { + aspect_ratio = av_q2d( codec_context->sample_aspect_ratio ); + } + else + { + int is_pal = mlt_producer_get_fps( this ) == 25.0; + aspect_ratio = is_pal ? 59.0/54.0 : 10.0/11.0; + } + + // Determine the fps + source_fps = ( double )codec_context->time_base.den / ( codec_context->time_base.num == 0 ? 1 : codec_context->time_base.num ); + + // We'll use fps if it's available + if ( source_fps > 0 ) + mlt_properties_set_double( properties, "source_fps", source_fps ); + else + mlt_properties_set_double( properties, "source_fps", mlt_producer_get_fps( this ) ); + mlt_properties_set_double( properties, "aspect_ratio", aspect_ratio ); + + // Set the width and height + mlt_properties_set_int( frame_properties, "width", codec_context->width ); + mlt_properties_set_int( frame_properties, "height", codec_context->height ); + mlt_properties_set_double( frame_properties, "aspect_ratio", aspect_ratio ); + + mlt_frame_push_get_image( frame, producer_get_image ); + mlt_properties_set_data( frame_properties, "avformat_producer", this, 0, NULL, NULL ); + } + else + { + mlt_properties_set_int( frame_properties, "test_image", 1 ); + } + } + else + { + mlt_properties_set_int( frame_properties, "test_image", 1 ); + } +} + +/** Get the audio from a frame. +*/ + +static int producer_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties from the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the frame number of this frame + mlt_position position = mlt_properties_get_position( frame_properties, "avformat_position" ); + + // Get the producer + mlt_producer this = mlt_properties_get_data( frame_properties, "avformat_producer", NULL ); + + // Get the producer properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the audio_context + AVFormatContext *context = mlt_properties_get_data( properties, "audio_context", NULL ); + + // Get the audio_index + int index = mlt_properties_get_int( properties, "audio_index" ); + + // Get the seekable status + int seekable = mlt_properties_get_int( properties, "seekable" ); + + // Obtain the expected frame numer + mlt_position expected = mlt_properties_get_position( properties, "_audio_expected" ); + + // Obtain the resample context if it exists (not always needed) + ReSampleContext *resample = mlt_properties_get_data( properties, "audio_resample", NULL ); + + // Obtain the audio buffer + int16_t *audio_buffer = mlt_properties_get_data( properties, "audio_buffer", NULL ); + + // Get amount of audio used + int audio_used = mlt_properties_get_int( properties, "_audio_used" ); + + // Calculate the real time code + double real_timecode = producer_time_of_frame( this, position ); + + // Get the audio stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Packet + AVPacket pkt; + + // Number of frames to ignore (for ffwd) + int ignore = 0; + + // Flag for paused (silence) + int paused = 0; + + // Check for resample and create if necessary + if ( resample == NULL && codec_context->channels <= 2 ) + { + // Create the resampler + resample = audio_resample_init( *channels, codec_context->channels, *frequency, codec_context->sample_rate ); + + // And store it on properties + mlt_properties_set_data( properties, "audio_resample", resample, 0, ( mlt_destructor )audio_resample_close, NULL ); + } + else if ( resample == NULL ) + { + *channels = codec_context->channels; + *frequency = codec_context->sample_rate; + } + + // Check for audio buffer and create if necessary + if ( audio_buffer == NULL ) + { + // Allocate the audio buffer + audio_buffer = mlt_pool_alloc( AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ) ); + + // And store it on properties for reuse + mlt_properties_set_data( properties, "audio_buffer", audio_buffer, 0, ( mlt_destructor )mlt_pool_release, NULL ); + } + + // Seek if necessary + if ( position != expected ) + { + if ( position + 1 == expected ) + { + // We're paused - silence required + paused = 1; + } + else if ( !seekable && position > expected && ( position - expected ) < 250 ) + { + // Fast forward - seeking is inefficient for small distances - just ignore following frames + ignore = position - expected; + } + else if ( position < expected || position - expected >= 12 ) + { + // Set to the real timecode + if ( av_seek_frame( context, -1, mlt_properties_get_double( properties, "_start_time" ) + real_timecode * 1000000.0, AVSEEK_FLAG_BACKWARD ) != 0 ) + paused = 1; + + // Clear the usage in the audio buffer + audio_used = 0; + } + } + + // Get the audio if required + if ( !paused ) + { + int ret = 0; + int got_audio = 0; + int16_t *temp = av_malloc( sizeof( int16_t ) * AVCODEC_MAX_AUDIO_FRAME_SIZE ); + + av_init_packet( &pkt ); + + while( ret >= 0 && !got_audio ) + { + // Check if the buffer already contains the samples required + if ( audio_used >= *samples && ignore == 0 ) + { + got_audio = 1; + break; + } + + // Read a packet + ret = av_read_frame( context, &pkt ); + + int len = pkt.size; + uint8_t *ptr = pkt.data; + + // We only deal with audio from the selected audio_index + while ( ptr != NULL && ret >= 0 && pkt.stream_index == index && len > 0 ) + { + int data_size = sizeof( int16_t ) * AVCODEC_MAX_AUDIO_FRAME_SIZE; + + // Decode the audio +#if (LIBAVCODEC_VERSION_INT >= ((51<<16)+(29<<8)+0)) + ret = avcodec_decode_audio2( codec_context, temp, &data_size, ptr, len ); +#else + ret = avcodec_decode_audio( codec_context, temp, &data_size, ptr, len ); +#endif + if ( ret < 0 ) + { + ret = 0; + break; + } + + len -= ret; + ptr += ret; + + if ( data_size > 0 ) + { + if ( resample != NULL ) + { + audio_used += audio_resample( resample, &audio_buffer[ audio_used * *channels ], temp, data_size / ( codec_context->channels * sizeof( int16_t ) ) ); + } + else + { + memcpy( &audio_buffer[ audio_used * *channels ], temp, data_size ); + audio_used += data_size / ( codec_context->channels * sizeof( int16_t ) ); + } + + // Handle ignore + while ( ignore && audio_used > *samples ) + { + ignore --; + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * sizeof( int16_t ) ); + } + } + + // If we're behind, ignore this packet + float current_pts = av_q2d( stream->time_base ) * pkt.pts; + if ( seekable && ( !ignore && current_pts <= ( real_timecode - 0.02 ) ) ) + ignore = 1; + } + + // We're finished with this packet regardless + av_free_packet( &pkt ); + } + + *buffer = mlt_pool_alloc( *samples * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( frame_properties, "audio", *buffer, 0, ( mlt_destructor )mlt_pool_release, NULL ); + + // Now handle the audio if we have enough + if ( audio_used >= *samples ) + { + memcpy( *buffer, audio_buffer, *samples * *channels * sizeof( int16_t ) ); + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * *channels * sizeof( int16_t ) ); + } + else + { + memset( *buffer, 0, *samples * *channels * sizeof( int16_t ) ); + } + + // Store the number of audio samples still available + mlt_properties_set_int( properties, "_audio_used", audio_used ); + + // Release the temporary audio + av_free( temp ); + } + else + { + // Get silence and don't touch the context + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + } + + // Regardless of speed (other than paused), we expect to get the next frame + if ( !paused ) + mlt_properties_set_position( properties, "_audio_expected", position + 1 ); + + return 0; +} + +/** Set up audio handling. +*/ + +static void producer_set_up_audio( mlt_producer this, mlt_frame frame ) +{ + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the audio_context + AVFormatContext *context = mlt_properties_get_data( properties, "audio_context", NULL ); + + // Get the audio_index + int index = mlt_properties_get_int( properties, "audio_index" ); + + // Deal with audio context + if ( context != NULL && index != -1 ) + { + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the audio stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Get the codec + AVCodec *codec = mlt_properties_get_data( properties, "audio_codec", NULL ); + + // Initialise the codec if necessary + if ( codec == NULL ) + { + // Find the codec + codec = avcodec_find_decoder( codec_context->codec_id ); + + // If we don't have a codec and we can't initialise it, we can't do much more... + if ( codec != NULL && avcodec_open( codec_context, codec ) >= 0 ) + { + // Now store the codec with its destructor + mlt_properties_set_data( properties, "audio_codec", codec_context, 0, producer_codec_close, NULL ); + + } + else + { + // Remember that we can't use this later + mlt_properties_set_int( properties, "audio_index", -1 ); + } + } + + // No codec, no show... + if ( codec != NULL ) + { + mlt_frame_push_audio( frame, producer_get_audio ); + mlt_properties_set_data( frame_properties, "avformat_producer", this, 0, NULL, NULL ); + mlt_properties_set_int( frame_properties, "frequency", codec_context->sample_rate ); + mlt_properties_set_int( frame_properties, "channels", codec_context->channels ); + } + } +} + +/** Our get frame implementation. +*/ + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Create an empty frame + *frame = mlt_frame_init( ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Set the position of this producer + mlt_properties_set_position( MLT_FRAME_PROPERTIES( *frame ), "avformat_position", mlt_producer_frame( this ) ); + + // Set up the video + producer_set_up_video( this, *frame ); + + // Set up the audio + producer_set_up_audio( this, *frame ); + + // Set the aspect_ratio + mlt_properties_set_double( MLT_FRAME_PROPERTIES( *frame ), "aspect_ratio", mlt_properties_get_double( MLT_PRODUCER_PROPERTIES( this ), "aspect_ratio" ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( this ); + + return 0; +} diff --git a/src/modules/avformat/producer_avformat.h b/src/modules/avformat/producer_avformat.h new file mode 100644 index 0000000..5517d4d --- /dev/null +++ b/src/modules/avformat/producer_avformat.h @@ -0,0 +1,28 @@ +/* + * producer_avformat.h -- avformat producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_AVFORMAT_H_ +#define _PRODUCER_AVFORMAT_H_ + +#include + +extern mlt_producer producer_avformat_init( char *file ); + +#endif diff --git a/src/modules/configure b/src/modules/configure new file mode 100755 index 0000000..ee65413 --- /dev/null +++ b/src/modules/configure @@ -0,0 +1,36 @@ +#!/bin/sh + +# Clean up disables if not in help mode +[ "$help" != "1" ] && rm -f disable-* producers.dat filters.dat transitions.dat consumers.dat + +# Create the make.inc file +echo SUBDIRS = `find . -maxdepth 1 -type d | grep -v CVS | grep -v "^.$" | sed 's/\.\///'` > make.inc + +# Iterate through arguments +for i in "$@" +do + case $i in + --disable-* ) touch disable-${i#--disable-} ;; + esac +done + +# Iterate through each of the components +for i in * +do + if [ -x $i/configure -a \( "$help" = "1" -o ! -f disable-$i \) ] + then + if [ "$gpl" = "true" -o ! -f $i/gpl ] + then + [ "$help" = "0" ] && echo "Configuring modules/$i:" + olddir2=`pwd` + cd $i + ./configure "$@" + [ $? != 0 ] && exit 1 + cd $olddir2 + elif [ "$help" = "0" ] + then + touch disable-$i + fi + fi +done + diff --git a/src/modules/core/Makefile b/src/modules/core/Makefile new file mode 100644 index 0000000..3511589 --- /dev/null +++ b/src/modules/core/Makefile @@ -0,0 +1,61 @@ +include ../../../config.mak + +TARGET = ../libmltcore$(LIBSUF) + +OBJS = factory.o \ + producer_colour.o \ + producer_noise.o \ + producer_ppm.o \ + filter_brightness.o \ + filter_channelcopy.o \ + filter_data_feed.o \ + filter_data_show.o \ + filter_gamma.o \ + filter_greyscale.o \ + filter_luma.o \ + filter_mirror.o \ + filter_mono.o \ + filter_obscure.o \ + filter_region.o \ + filter_rescale.o \ + filter_resize.o \ + filter_transition.o \ + filter_watermark.o \ + transition_composite.o \ + transition_luma.o \ + transition_mix.o \ + transition_region.o \ + consumer_null.o + +ASM_OBJS = + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(ASM_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(ASM_OBJS) $(LDFLAGS) + +composite_line_yuv_mmx.o: composite_line_yuv_mmx.S + $(CC) -o $@ -c composite_line_yuv_mmx.S + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(ASM_OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -m 644 ../data_fx.properties "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/core/composite_line_yuv_mmx.S b/src/modules/core/composite_line_yuv_mmx.S new file mode 100644 index 0000000..27af4dc --- /dev/null +++ b/src/modules/core/composite_line_yuv_mmx.S @@ -0,0 +1,211 @@ + .file "composite_line_yuv_mmx" + .version "01.01" + +gcc2_compiled.: +.data + +.text + .align 16 + +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +.globl composite_line_yuv_mmx + .type composite_line_yuv_mmx,@function +composite_line_yuv_mmx: +#else +.globl _composite_line_yuv_mmx +_composite_line_yuv_mmx: +#endif + +/* + * Arguments + * + * dest: 8(%ebp) %esi + * src: 12(%ebp) + * width_src: 16(%ebp) + * alpha: 20(%ebp) + * weight: 24(%ebp) + * luma: 28(%ebp) + * softness: 32(%ebp) + */ + +/* + * Function call entry + */ + pushl %ebp + movl %esp,%ebp + subl $28,%esp + pushl %edi + pushl %esi + pushl %ebx + +/* Initialise */ + movl 8(%ebp), %esi # get dest + movl $0, %edx # j = 0 + +.loop: + + movl $0xffff, %ecx # a = 255 + cmpl $0, 20(%ebp) # if alpha == NULL + je .noalpha + movl 20(%ebp), %edi # a = alpha[ j ] + movb (%edi,%edx), %cl +.noalpha: + movl %ecx, -24(%ebp) # save ecx + + movl 24(%ebp), %eax # mix = weight + cmpl $0, 28(%ebp) # if luma == NULL + je .noluma + movl 28(%ebp), %edi # mix = ... + movl %edx, %ebx + sall $1, %ebx + movw (%edi,%ebx), %bx # luma[ j*2 ] + cmpl %ebx, %eax + jl .luma0 + movl %ebx, %ecx + addl 32(%ebp), %ecx # + softness + cmpl %ecx, %eax + jge .luma1 + /* TODO: linear interpolate between edges */ + subw %bx, %ax + sall $8, %eax + subw %bx, %cx + movl %edx, %ebx + divw %cx + movl %ebx, %edx + jmp .noluma +.luma0: + movl $0, %eax + jmp .noluma +.luma1: + movl $0xffff, %eax +.noluma: + shrl $8, %eax + + movl %edx, %ebx # edx will be destroyed by mulw + movl -24(%ebp), %ecx # restore ecx + mull %ecx # mix = mix * a... + movl %ebx, %edx # restore edx + shrl $8, %eax # >>8 + andl $0xff, %eax + +/* put alpha and (1-alpha) into mm0 */ +/* 0 aa 0 1-a 0 aa 0 1-a */ + + /* duplicate word */ + movl %eax, %ecx + shll $16, %ecx + orl %eax, %ecx + + movd %ecx, %mm1 + + /* (1 << 16) - mix */ + movl $0x000000ff, %ecx + subl %eax, %ecx + andl $0xff, %ecx + + /* duplicate word */ + movl %ecx, %eax + shll $16, %eax + orl %eax, %ecx + + movd %ecx, %mm0 + + /* unpack words into double words */ + punpcklwd %mm1, %mm0 + +/* put src yuv and dest yuv into mm1 */ +/* 0 UVs 0 UVd 0 Ys 0 Yd */ + + movl 12(%ebp), %edi # get src + movb (%edi), %cl + shll $8, %ecx + movb 1(%edi), %al + shll $24, %eax + orl %eax, %ecx + + movb (%esi), %al # get dest + orl %eax, %ecx + movb 1(%esi), %al + shll $16, %eax + orl %eax, %ecx + + movd %ecx, %mm1 + punpcklbw %mm4, %mm1 + +/* alpha composite */ + pmaddwd %mm1, %mm0 + psrld $8, %mm0 + +/* store result */ + movd %mm0, %eax + movb %al, (%esi) + pextrw $2, %mm0, %eax + movl $128, %eax + movb %al, 1(%esi) + +/* for..next */ + addl $1, %edx # j++ + cmpl 16(%ebp), %edx # if ( j == width_src ) + jge .out + + addl $2, %esi + addl $2, 12(%ebp) + + jmp .loop + +.out: + emms + leal -40(%ebp),%esp + popl %ebx + popl %esi + popl %edi + movl %ebp,%esp + popl %ebp + ret + + +/********************************************/ + +.align 8 +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +.globl composite_have_mmx + .type composite_have_mmx,@function +composite_have_mmx: +#else +.globl _composite_have_mmx +_composite_have_mmx: +#endif + + push %ebx + +# Check if bit 21 in flags word is writeable + + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + + je .notfound + +# OK, we have CPUID + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz .notfound + + movl $1, %eax + jmp .out2 + +.notfound: + movl $0, %eax +.out2: + popl %ebx + ret diff --git a/src/modules/core/configure b/src/modules/core/configure new file mode 100755 index 0000000..160844b --- /dev/null +++ b/src/modules/core/configure @@ -0,0 +1,42 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +color libmltcore$LIBSUF +colour libmltcore$LIBSUF +noise libmltcore$LIBSUF +ppm libmltcore$LIBSUF +EOF + +cat << EOF >> ../filters.dat +brightness libmltcore$LIBSUF +channelcopy libmltcore$LIBSUF +data_feed libmltcore$LIBSUF +data_show libmltcore$LIBSUF +gamma libmltcore$LIBSUF +greyscale libmltcore$LIBSUF +luma libmltcore$LIBSUF +mirror libmltcore$LIBSUF +mono libmltcore$LIBSUF +obscure libmltcore$LIBSUF +region libmltcore$LIBSUF +rescale libmltcore$LIBSUF +resize libmltcore$LIBSUF +transition libmltcore$LIBSUF +watermark libmltcore$LIBSUF +EOF + +cat << EOF >> ../transitions.dat +composite libmltcore$LIBSUF +luma libmltcore$LIBSUF +mix libmltcore$LIBSUF +region libmltcore$LIBSUF +EOF + +cat << EOF >> ../consumers.dat +null libmltcore$LIBSUF +EOF + +fi diff --git a/src/modules/core/consumer_null.c b/src/modules/core/consumer_null.c new file mode 100644 index 0000000..6b1fb01 --- /dev/null +++ b/src/modules/core/consumer_null.c @@ -0,0 +1,183 @@ +/* + * consumer_null.c -- a null consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "consumer_null.h" + +// mlt Header files +#include + +// System header files +#include +#include +#include +#include + +// Forward references. +static int consumer_start( mlt_consumer this ); +static int consumer_stop( mlt_consumer this ); +static int consumer_is_stopped( mlt_consumer this ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer this ); + +/** Initialise the dv consumer. +*/ + +mlt_consumer consumer_null_init( char *arg ) +{ + // Allocate the consumer + mlt_consumer this = mlt_consumer_new( ); + + // If memory allocated and initialises without error + if ( this != NULL ) + { + // Assign close callback + this->close = consumer_close; + + // Set up start/stop/terminated callbacks + this->start = consumer_start; + this->stop = consumer_stop; + this->is_stopped = consumer_is_stopped; + } + + // Return this + return this; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + mlt_properties_set_int( properties, "joined", 0 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, this ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're running + if ( !mlt_properties_get_int( properties, "joined" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "running", 0 ); + mlt_properties_set_int( properties, "joined", 1 ); + + // Wait for termination + pthread_join( *thread, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + return !mlt_properties_get_int( properties, "running" ); +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer this = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Convenience functionality + int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + int terminated = 0; + + // Frame and size + mlt_frame frame = NULL; + + // Loop while running + while( !terminated && mlt_properties_get_int( properties, "running" ) ) + { + // Get the frame + frame = mlt_consumer_rt_frame( this ); + + // Check for termination + if ( terminate_on_pause && frame != NULL ) + terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0; + + // Check that we have a frame to work with + if ( frame != NULL ) + { + // Close the frame + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + mlt_frame_close( frame ); + } + } + + // Indicate that the consumer is stopped + mlt_properties_set_int( properties, "running", 0 ); + mlt_consumer_stopped( this ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer this ) +{ + // Stop the consumer + mlt_consumer_stop( this ); + + // Close the parent + mlt_consumer_close( this ); + + // Free the memory + free( this ); +} diff --git a/src/modules/core/consumer_null.h b/src/modules/core/consumer_null.h new file mode 100644 index 0000000..2b9f204 --- /dev/null +++ b/src/modules/core/consumer_null.h @@ -0,0 +1,28 @@ +/* + * consumer_null.h -- null consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_NULL_H_ +#define _CONSUMER_NULL_H + +#include + +extern mlt_consumer consumer_null_init( char *arg ); + +#endif diff --git a/src/modules/core/factory.c b/src/modules/core/factory.c new file mode 100644 index 0000000..380a247 --- /dev/null +++ b/src/modules/core/factory.c @@ -0,0 +1,112 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_colour.h" +#include "producer_noise.h" +#include "producer_ppm.h" +#include "filter_brightness.h" +#include "filter_channelcopy.h" +#include "filter_data.h" +#include "filter_gamma.h" +#include "filter_greyscale.h" +#include "filter_luma.h" +#include "filter_mirror.h" +#include "filter_mono.h" +#include "filter_obscure.h" +#include "filter_rescale.h" +#include "filter_resize.h" +#include "filter_region.h" +#include "filter_transition.h" +#include "filter_watermark.h" +#include "transition_composite.h" +#include "transition_luma.h" +#include "transition_mix.h" +#include "transition_region.h" +#include "consumer_null.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "color" ) ) + return producer_colour_init( arg ); + if ( !strcmp( id, "colour" ) ) + return producer_colour_init( arg ); + if ( !strcmp( id, "noise" ) ) + return producer_noise_init( arg ); + if ( !strcmp( id, "ppm" ) ) + return producer_ppm_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "brightness" ) ) + return filter_brightness_init( arg ); + if ( !strcmp( id, "channelcopy" ) ) + return filter_channelcopy_init( arg ); + if ( !strcmp( id, "data_feed" ) ) + return filter_data_feed_init( arg ); + if ( !strcmp( id, "data_show" ) ) + return filter_data_show_init( arg ); + if ( !strcmp( id, "gamma" ) ) + return filter_gamma_init( arg ); + if ( !strcmp( id, "greyscale" ) ) + return filter_greyscale_init( arg ); + if ( !strcmp( id, "luma" ) ) + return filter_luma_init( arg ); + if ( !strcmp( id, "mirror" ) ) + return filter_mirror_init( arg ); + if ( !strcmp( id, "mono" ) ) + return filter_mono_init( arg ); + if ( !strcmp( id, "obscure" ) ) + return filter_obscure_init( arg ); + if ( !strcmp( id, "region" ) ) + return filter_region_init( arg ); + if ( !strcmp( id, "rescale" ) ) + return filter_rescale_init( arg ); + if ( !strcmp( id, "resize" ) ) + return filter_resize_init( arg ); + else if ( !strcmp( id, "transition" ) ) + return filter_transition_init( arg ); + if ( !strcmp( id, "watermark" ) ) + return filter_watermark_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + if ( !strcmp( id, "composite" ) ) + return transition_composite_init( arg ); + if ( !strcmp( id, "luma" ) ) + return transition_luma_init( arg ); + if ( !strcmp( id, "mix" ) ) + return transition_mix_init( arg ); + if ( !strcmp( id, "region" ) ) + return transition_region_init( arg ); + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "null" ) ) + return consumer_null_init( arg ); + return NULL; +} diff --git a/src/modules/core/filter_brightness.c b/src/modules/core/filter_brightness.c new file mode 100644 index 0000000..6a79e9f --- /dev/null +++ b/src/modules/core/filter_brightness.c @@ -0,0 +1,103 @@ +/* + * filter_brightness.c -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_brightness.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // Get the brightness level + double level = mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "brightness" ); + + // Only process if level is something other than 1 + if ( level != 1.0 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + int32_t x = 0; + int32_t m = level * ( 1 << 16 ); + + while ( p != q ) + { + x = ( *p * m ) >> 16; + *p = x < 16 ? 16 : x > 235 ? 235 : x; + p += 2; + } + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the starting brightness level + double level = fabs( mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "start" ) ); + + // If there is an end adjust gain to the range + if ( mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "end" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + mlt_position time = mlt_frame_get_position( frame ); + double position = ( double )( time - in ) / ( double )( out - in + 1 ); + double end = fabs( mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "end" ) ); + level += ( end - level ) * position; + } + + // Push the frame filter + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "brightness", level ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_brightness_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "start", arg == NULL ? "1" : arg ); + } + return this; +} + diff --git a/src/modules/core/filter_brightness.h b/src/modules/core/filter_brightness.h new file mode 100644 index 0000000..b76bed4 --- /dev/null +++ b/src/modules/core/filter_brightness.h @@ -0,0 +1,28 @@ +/* + * filter_brightness.h -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_BRIGHTNESS_H_ +#define _FILTER_BRIGHTNESS_H_ + +#include + +extern mlt_filter filter_brightness_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_channelcopy.c b/src/modules/core/filter_channelcopy.c new file mode 100644 index 0000000..bf7ab0b --- /dev/null +++ b/src/modules/core/filter_channelcopy.c @@ -0,0 +1,97 @@ +/* + * filter_channelcopy.c -- copy one audio channel to another + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_channelcopy.h" + +#include + +#include +#include +#define __USE_ISOC99 1 +#include + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the a frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + int i, j; + int from = mlt_properties_get_int( properties, "channelcopy.from" ); + int to = mlt_properties_get_int( properties, "channelcopy.to" ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + // Duplicate channels as necessary + { + int size = *channels * *samples * 2; + int16_t *new_buffer = mlt_pool_alloc( size ); + + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * *channels ) + ( j == to ? from : j ) ]; + } + } + *buffer = new_buffer; + } + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + mlt_properties frame_props = MLT_FRAME_PROPERTIES( frame ); + + // Propogate the parameters + mlt_properties_set_int( frame_props, "channelcopy.to", mlt_properties_get_int( properties, "to" ) ); + mlt_properties_set_int( frame_props, "channelcopy.from", mlt_properties_get_int( properties, "from" ) ); + + // Override the get_audio method + mlt_frame_push_audio( frame, filter_get_audio ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_channelcopy_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "to", atoi( arg ) ); + else + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "to", 1 ); + } + return this; +} diff --git a/src/modules/core/filter_channelcopy.h b/src/modules/core/filter_channelcopy.h new file mode 100644 index 0000000..2f0441a --- /dev/null +++ b/src/modules/core/filter_channelcopy.h @@ -0,0 +1,28 @@ +/* + * filter_channelcopy.h -- copy audio channel from one to another + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_CHANNELCOPY_H_ +#define _FILTER_CHANNELCOPY_H_ + +#include + +extern mlt_filter filter_channelcopy_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_data.h b/src/modules/core/filter_data.h new file mode 100644 index 0000000..aed4647 --- /dev/null +++ b/src/modules/core/filter_data.h @@ -0,0 +1,30 @@ +/* + * filter_data.h -- data feed and show filters + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FILTER_DATA_H_ +#define _MLT_FILTER_DATA_H_ + +#include + +extern mlt_filter filter_data_feed_init( char * ); +extern mlt_filter filter_data_show_init( char * ); + +#endif + diff --git a/src/modules/core/filter_data_feed.c b/src/modules/core/filter_data_feed.c new file mode 100644 index 0000000..0c1b1bb --- /dev/null +++ b/src/modules/core/filter_data_feed.c @@ -0,0 +1,178 @@ +/* + * filter_data_feed.c -- data feed filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "filter_data.h" +#include + +/** This filter should be used in conjuction with the data_show filter. + The concept of the data_feed is that it can be used to pass titles + or images to render on the frame, but doesn't actually do it + itself. data_feed imposes few rules on what's passed on and the + validity is confirmed in data_show before use. +*/ + +/** Data queue destructor. +*/ + +static void destroy_data_queue( void *arg ) +{ + if ( arg != NULL ) + { + // Assign the correct type + mlt_deque queue = arg; + + // Iterate through each item and destroy them + while ( mlt_deque_peek_front( queue ) != NULL ) + mlt_properties_close( mlt_deque_pop_back( queue ) ); + + // Close the deque + mlt_deque_close( queue ); + } +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( this ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the data queue + mlt_deque data_queue = mlt_properties_get_data( frame_properties, "data_queue", NULL ); + + // Get the type of the data feed + char *type = mlt_properties_get( filter_properties, "type" ); + + // Get the in and out points of this filter + int in = mlt_filter_get_in( this ); + int out = mlt_filter_get_out( this ); + + // Create the data queue if it doesn't exist + if ( data_queue == NULL ) + { + // Create the queue + data_queue = mlt_deque_init( ); + + // Assign it to the frame with the destructor + mlt_properties_set_data( frame_properties, "data_queue", data_queue, 0, destroy_data_queue, NULL ); + } + + // Now create the data feed + if ( data_queue != NULL && type != NULL && !strcmp( type, "attr_check" ) ) + { + int i = 0; + int count = mlt_properties_count( frame_properties ); + + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( frame_properties, i ); + + // Only deal with meta.attr.name values here - these should have a value of 1 to be considered + // Additional properties of the form are meta.attr.name.property are passed down on the feed + if ( !strncmp( name, "meta.attr.", 10 ) && strchr( name + 10, '.' ) == NULL && mlt_properties_get_int( frame_properties, name ) == 1 ) + { + // Temp var to hold name + '.' for pass method + char temp[ 132 ]; + + // Create a new data feed + mlt_properties feed = mlt_properties_new( ); + + // Assign it the base properties + mlt_properties_set( feed, "id", mlt_properties_get( filter_properties, "_unique_id" ) ); + mlt_properties_set( feed, "type", strrchr( name, '.' ) + 1 ); + mlt_properties_set_position( feed, "position", mlt_frame_get_position( frame ) ); + + // Assign in/out of service we're connected to + mlt_properties_set_position( feed, "in", mlt_properties_get_position( frame_properties, "in" ) ); + mlt_properties_set_position( feed, "out", mlt_properties_get_position( frame_properties, "out" ) ); + + // Pass all meta properties + sprintf( temp, "%s.", name ); + mlt_properties_pass( feed, frame_properties, temp ); + + // Push it on to the queue + mlt_deque_push_back( data_queue, feed ); + + // Make sure this attribute only gets processed once + mlt_properties_set_int( frame_properties, name, 0 ); + } + } + } + else if ( data_queue != NULL ) + { + // Create a new data feed + mlt_properties feed = mlt_properties_new( ); + + // Assign it the base properties + mlt_properties_set( feed, "id", mlt_properties_get( filter_properties, "_unique_id" ) ); + mlt_properties_set( feed, "type", type ); + mlt_properties_set_position( feed, "position", mlt_frame_get_position( frame ) ); + + // Assign in/out of service we're connected to + mlt_properties_set_position( feed, "in", mlt_properties_get_position( frame_properties, "in" ) ); + mlt_properties_set_position( feed, "out", mlt_properties_get_position( frame_properties, "out" ) ); + + // Correct in/out to the filter if specified + if ( in != 0 ) + mlt_properties_set_position( feed, "in", in ); + if ( out != 0 ) + mlt_properties_set_position( feed, "out", out ); + + // Pass the properties which start with a "feed." prefix + // Note that 'feed.text' in the filter properties becomes 'text' on the feed + mlt_properties_pass( feed, filter_properties, "feed." ); + + // Push it on to the queue + mlt_deque_push_back( data_queue, feed ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_data_feed_init( char *arg ) +{ + // Create the filter + mlt_filter this = mlt_filter_new( ); + + // Initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Assign the argument (default to titles) + mlt_properties_set( properties, "type", arg == NULL ? "titles" : arg ); + + // Specify the processing method + this->process = filter_process; + } + + return this; +} + diff --git a/src/modules/core/filter_data_show.c b/src/modules/core/filter_data_show.c new file mode 100644 index 0000000..6931f50 --- /dev/null +++ b/src/modules/core/filter_data_show.c @@ -0,0 +1,344 @@ +/* + * filter_data_show.c -- data feed filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_data.h" +#include +#include +#include + +/** Handle the profile. +*/ + +static mlt_filter obtain_filter( mlt_filter filter, char *type ) +{ + // Result to return + mlt_filter result = NULL; + + // Miscelaneous variable + int i = 0; + int type_len = strlen( type ); + + // Get the properties of the data show filter + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the profile properties + mlt_properties profile_properties = mlt_properties_get_data( filter_properties, "profile_properties", NULL ); + + // Obtain the profile_properties if we haven't already + if ( profile_properties == NULL ) + { + char temp[ 512 ]; + + // Get the profile requested + char *profile = mlt_properties_get( filter_properties, "resource" ); + + // If none is specified, pick up the default for this normalisation + if ( profile == NULL ) + sprintf( temp, "%s/feeds/%s/data_fx.properties", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ) ); + else if ( strchr( profile, '%' ) ) + sprintf( temp, "%s/feeds/%s/%s", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ), strchr( profile, '%' ) + 1 ); + else + strcpy( temp, profile ); + + // Load the specified profile or use the default + profile_properties = mlt_properties_load( temp ); + + // Store for later retrieval + mlt_properties_set_data( filter_properties, "profile_properties", profile_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + if ( profile_properties != NULL ) + { + for ( i = 0; i < mlt_properties_count( profile_properties ); i ++ ) + { + char *name = mlt_properties_get_name( profile_properties, i ); + char *value = mlt_properties_get_value( profile_properties, i ); + + if ( result == NULL && !strcmp( name, type ) && result == NULL ) + result = mlt_factory_filter( value, NULL ); + else if ( result != NULL && !strncmp( name, type, type_len ) && name[ type_len ] == '.' ) + mlt_properties_set( MLT_FILTER_PROPERTIES( result ), name + type_len + 1, value ); + else if ( result != NULL ) + break; + } + } + + return result; +} + +/** Retrieve medatata value +*/ + +char* metadata_value(mlt_properties properties, char* name) +{ + if (name == NULL) return NULL; + char *meta = malloc( strlen(name) + 18 ); + sprintf( meta, "meta.attr.%s.markup", name); + char *result = mlt_properties_get( properties, meta); + free(meta); + return result; +} + +/** Convert frames to Timecode +*/ + +char* frame_to_timecode( int frames , int fps) +{ + if (fps == 0) return strdup("-"); + char *res = malloc(12); + int seconds = frames / (int) fps; + frames = frames % ((int) fps); + int minutes = seconds / 60; + seconds = seconds % 60; + int hours = minutes / 60; + minutes = minutes % 60; + sprintf(res, "%.2d:%.2d:%.2d:%.2d", hours, minutes, seconds, frames); + return res; +} + +/** Process the frame for the requested type +*/ + +static int process_feed( mlt_properties feed, mlt_filter filter, mlt_frame frame ) +{ + // Error return + int error = 1; + + // Get the properties of the data show filter + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the type requested by the feeding filter + char *type = mlt_properties_get( feed, "type" ); + + // Fetch the filter associated to this type + mlt_filter requested = mlt_properties_get_data( filter_properties, type, NULL ); + + // If it doesn't exist, then create it now + if ( requested == NULL ) + { + // Source filter from profile + requested = obtain_filter( filter, type ); + + // Store it on the properties for subsequent retrieval/destruction + mlt_properties_set_data( filter_properties, type, requested, 0, ( mlt_destructor )mlt_filter_close, NULL ); + } + + // If we have one, then process it now... + if ( requested != NULL ) + { + int i = 0; + mlt_properties properties = MLT_FILTER_PROPERTIES( requested ); + static char *prefix = "properties."; + int len = strlen( prefix ); + + // Determine if this is an absolute or relative feed + int absolute = mlt_properties_get_int( feed, "absolute" ); + + // Make do with what we have + int length = !absolute ? + mlt_properties_get_int( feed, "out" ) - mlt_properties_get_int( feed, "in" ) + 1 : + mlt_properties_get_int( feed, "out" ) + 1; + + // Repeat period + int period = mlt_properties_get_int( properties, "period" ); + period = period == 0 ? 1 : period; + + // Pass properties from feed into requested + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + { + char *name = mlt_properties_get_name( properties, i ); + char *key = mlt_properties_get_value( properties, i ); + if ( !strncmp( name, prefix, len ) ) + { + if ( !strncmp( name + len, "length[", 7 ) ) + { + mlt_properties_set_position( properties, key, ( length - period ) / period ); + } + else + { + char *value = mlt_properties_get( feed, name + len ); + if ( value != NULL ) + { + // check for metadata keywords in metadata markup if user requested so + if ( mlt_properties_get_int( filter_properties, "dynamic" ) == 1 && !strcmp( name + strlen( name ) - 6, "markup") ) + { + // Find keywords which should be surrounded by '#', like: #title# + char* keywords = strtok( value, "#" ); + char result[512] = ""; // XXX: how much is enough? + int ct = 0; + int fromStart = ( value[0] == '#' ) ? 1 : 0; + + while ( keywords != NULL ) + { + if ( ct % 2 == fromStart ) + { + // backslash in front of # suppresses substitution + if ( keywords[ strlen( keywords ) -1 ] == '\\' ) + { + // keep characters except backslash + strncat( result, keywords, strlen( keywords ) -1 ); + strcat( result, "#" ); + ct++; + } + else + { + strcat( result, keywords ); + } + } + else if ( !strcmp( keywords, "timecode" ) ) + { + // special case: replace #timecode# with current frame timecode + int pos = mlt_properties_get_int( feed, "position" ); + char *tc = frame_to_timecode( pos, mlt_profile_fps( NULL ) ); + strcat( result, tc ); + free( tc ); + } + else + { + // replace keyword with metadata value + char *metavalue = metadata_value( MLT_FRAME_PROPERTIES( frame ), keywords ); + strcat( result, metavalue ? metavalue : "-" ); + } + keywords = strtok( NULL, "#" ); + ct++; + } + mlt_properties_set( properties, key, (char*) result ); + } + else mlt_properties_set( properties, key, value ); + } + } + } + } + + // Set the original position on the frame + if ( absolute == 0 ) + mlt_frame_set_position( frame, mlt_properties_get_int( feed, "position" ) - mlt_properties_get_int( feed, "in" ) ); + else + mlt_frame_set_position( frame, mlt_properties_get_int( feed, "position" ) ); + + // Process the filter + mlt_filter_process( requested, frame ); + + // Should be ok... + error = 0; + } + + return error; +} + +void process_queue( mlt_deque data_queue, mlt_frame frame, mlt_filter filter ) +{ + if ( data_queue != NULL ) + { + // Create a new queue for those that we can't handle + mlt_deque temp_queue = mlt_deque_init( ); + + // Iterate through each entry on the queue + while ( mlt_deque_peek_front( data_queue ) != NULL ) + { + // Get the data feed + mlt_properties feed = mlt_deque_pop_front( data_queue ); + + if ( mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "debug" ) != NULL ) + mlt_properties_debug( feed, mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "debug" ), stderr ); + + // Process the data feed... + if ( process_feed( feed, filter, frame ) == 0 ) + mlt_properties_close( feed ); + else + mlt_deque_push_back( temp_queue, feed ); + } + + // Now put the unprocessed feeds back on the stack + while ( mlt_deque_peek_front( temp_queue ) ) + { + // Get the data feed + mlt_properties feed = mlt_deque_pop_front( temp_queue ); + + // Put it back on the data queue + mlt_deque_push_back( data_queue, feed ); + } + + // Close the temporary queue + mlt_deque_close( temp_queue ); + } +} + +/** Get the image. +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Pop the service + mlt_filter filter = mlt_frame_pop_service( frame ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Track specific + process_queue( mlt_properties_get_data( frame_properties, "data_queue", NULL ), frame, filter ); + + // Global + process_queue( mlt_properties_get_data( frame_properties, "global_queue", NULL ), frame, filter ); + + // Need to get the image + return mlt_frame_get_image( frame, image, format, width, height, 1 ); +} + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the filter + mlt_frame_push_service( frame, this ); + + // Register the get image method + mlt_frame_push_get_image( frame, filter_get_image ); + + // Return the frame + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_data_show_init( char *arg ) +{ + // Create the filter + mlt_filter this = mlt_filter_new( ); + + // Initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Assign the argument (default to titles) + mlt_properties_set( properties, "resource", arg == NULL ? NULL : arg ); + + // Specify the processing method + this->process = filter_process; + } + + return this; +} + diff --git a/src/modules/core/filter_gamma.c b/src/modules/core/filter_gamma.c new file mode 100644 index 0000000..e1a5197 --- /dev/null +++ b/src/modules/core/filter_gamma.c @@ -0,0 +1,89 @@ +/* + * filter_gamma.c -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_gamma.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // Get the gamma value + double gamma = mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "gamma" ); + + if ( gamma != 1.0 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + + // Calculate the look up table + double exp = 1 / gamma; + uint8_t lookup[ 256 ]; + int i; + + for( i = 0; i < 256; i ++ ) + lookup[ i ] = ( uint8_t )( pow( ( double )i / 255.0, exp ) * 255 ); + + while ( p != q ) + { + *p = lookup[ *p ]; + p += 2; + } + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + double gamma = mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "gamma" ); + gamma = gamma <= 0 ? 1 : gamma; + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "gamma", gamma ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_gamma_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "gamma", arg == NULL ? "1" : arg ); + } + return this; +} diff --git a/src/modules/core/filter_gamma.h b/src/modules/core/filter_gamma.h new file mode 100644 index 0000000..c8066cc --- /dev/null +++ b/src/modules/core/filter_gamma.h @@ -0,0 +1,28 @@ +/* + * filter_gamma.h -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_GAMMA_H_ +#define _FILTER_GAMMA_H_ + +#include + +extern mlt_filter filter_gamma_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_greyscale.c b/src/modules/core/filter_greyscale.c new file mode 100644 index 0000000..c9f8d7a --- /dev/null +++ b/src/modules/core/filter_greyscale.c @@ -0,0 +1,63 @@ +/* + * filter_greyscale.c -- greyscale filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_greyscale.h" + +#include + +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + if ( error == 0 && *format == mlt_image_yuv422 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + while ( p ++ != q ) + *p ++ = 128; + } + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_greyscale_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = filter_process; + return this; +} + diff --git a/src/modules/core/filter_greyscale.h b/src/modules/core/filter_greyscale.h new file mode 100644 index 0000000..bbea94a --- /dev/null +++ b/src/modules/core/filter_greyscale.h @@ -0,0 +1,28 @@ +/* + * filter_greyscale.h -- greyscale filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_GREYSCALE_H_ +#define _FILTER_GREYSCALE_H_ + +#include + +extern mlt_filter filter_greyscale_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_luma.c b/src/modules/core/filter_luma.c new file mode 100644 index 0000000..f6b4582 --- /dev/null +++ b/src/modules/core/filter_luma.c @@ -0,0 +1,128 @@ +/* + * filter_luma.c -- luma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_luma.h" + +#include +#include +#include +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + mlt_filter filter = mlt_frame_pop_service( this ); + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + mlt_transition luma = mlt_properties_get_data( properties, "luma", NULL ); + mlt_frame b_frame = mlt_properties_get_data( properties, "frame", NULL ); + mlt_properties b_frame_props = b_frame ? MLT_FRAME_PROPERTIES( b_frame ) : NULL; + int out = mlt_properties_get_int( properties, "period" ); + + if ( out == 0 ) + out = 24; + + if ( luma == NULL ) + { + char *resource = mlt_properties_get( properties, "resource" ); + luma = mlt_factory_transition( "luma", resource ); + if ( luma != NULL ) + { + mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES( luma ); + mlt_properties_set_int( luma_properties, "in", 0 ); + mlt_properties_set_int( luma_properties, "out", out ); + mlt_properties_set_int( luma_properties, "reverse", 1 ); + mlt_properties_set_data( properties, "luma", luma, 0, ( mlt_destructor )mlt_transition_close, NULL ); + } + } + + if ( b_frame == NULL || mlt_properties_get_int( b_frame_props, "width" ) != *width || mlt_properties_get_int( b_frame_props, "height" ) != *height ) + { + b_frame = mlt_frame_init( ); + mlt_properties_set_data( properties, "frame", b_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + + if ( luma != NULL && + ( mlt_properties_get( properties, "blur" ) != NULL || + (int)mlt_frame_get_position( this ) % ( out + 1 ) != out ) ) + { + mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES( luma ); + mlt_properties_pass( luma_properties, properties, "luma." ); + mlt_transition_process( luma, this, b_frame ); + } + + error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + if ( error == 0 ) + { + mlt_properties a_props = MLT_FRAME_PROPERTIES( this ); + int size = 0; + uint8_t *src = mlt_properties_get_data( a_props, "image", &size ); + uint8_t *dst = mlt_pool_alloc( size ); + + if ( dst != NULL ) + { + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + memcpy( dst, src, size ); + mlt_properties_set_data( b_props, "image", dst, size, mlt_pool_release, NULL ); + mlt_properties_set_int( b_props, "width", *width ); + mlt_properties_set_int( b_props, "height", *height ); + mlt_properties_set_int( b_props, "format", *format ); + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the filter on to the stack + mlt_frame_push_service( frame, this ); + + // Push the get_image on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_luma_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set( properties, "resource", arg ); + } + return this; +} diff --git a/src/modules/core/filter_luma.h b/src/modules/core/filter_luma.h new file mode 100644 index 0000000..7293e63 --- /dev/null +++ b/src/modules/core/filter_luma.h @@ -0,0 +1,28 @@ +/* + * filter_luma.h -- luma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_LUMA_H_ +#define _FILTER_LUMA_H_ + +#include + +extern mlt_filter filter_luma_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_mirror.c b/src/modules/core/filter_mirror.c new file mode 100644 index 0000000..f3b5dc1 --- /dev/null +++ b/src/modules/core/filter_mirror.c @@ -0,0 +1,336 @@ +/* + * filter_mirror.c -- mirror filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_mirror.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Pop the mirror filter from the stack + mlt_filter this = mlt_frame_pop_service( frame ); + + // Get the mirror type + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Get the properties + char *mirror = mlt_properties_get( properties, "mirror" ); + + // Determine if reverse is required + int reverse = mlt_properties_get_int( properties, "reverse" ); + + // Get the image + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + // Get the alpha + uint8_t *alpha = mlt_frame_get_alpha_mask( frame ); + + // If we have an image of the right colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // We'll KISS here + int hh = *height / 2; + + if ( !strcmp( mirror, "horizontal" ) ) + { + uint8_t *p = NULL; + uint8_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = p + *width * 2; + a = alpha + i * *width; + b = a + *width - 1; + if ( !reverse ) + { + while ( p < q ) + { + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + q -= 4; + *a ++ = *b --; + *a ++ = *b --; + } + } + else + { + while ( p < q ) + { + *( q - 2 ) = *p ++; + *( q - 3 - uneven_w ) = *p ++; + *( q - 4 ) = *p ++; + *( q - 1 - uneven_w ) = *p ++; + q -= 4; + *b -- = *a ++; + *b -- = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "vertical" ) ) + { + uint16_t *end = ( uint16_t *)*image + *width * *height; + uint16_t *p = NULL; + uint16_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int j; + for ( i = 0; i < hh; i ++ ) + { + p = ( uint16_t * )*image + i * *width; + q = end - ( i + 1 ) * *width; + j = *width; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *p ++ = *q ++; + *a ++ = *b ++; + } + } + else + { + while ( j -- ) + { + *q ++ = *p ++; + *b ++ = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "diagonal" ) ) + { + uint8_t *end = ( uint8_t *)*image + *width * *height * 2; + uint8_t *p = NULL; + uint8_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int j; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = end - i * *width * 2; + j = ( ( *width * ( *height - i ) ) / *height ) / 2; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + q -= 4; + *a ++ = *b --; + *a ++ = *b --; + } + } + else + { + while ( j -- ) + { + *( q - 2 ) = *p ++; + *( q - 3 - uneven_w ) = *p ++; + *( q - 4 ) = *p ++; + *( q - 1 - uneven_w ) = *p ++; + q -= 4; + *b -- = *a ++; + *b -- = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "xdiagonal" ) ) + { + uint8_t *end = ( uint8_t *)*image + *width * *height * 2; + uint8_t *p = NULL; + uint8_t *q = NULL; + int i; + int j; + uint8_t *a = NULL; + uint8_t *b = NULL; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + ( i + 1 ) * *width * 2; + q = end - ( i + 1 ) * *width * 2; + j = ( ( *width * ( *height - i ) ) / *height ) / 2; + a = alpha + ( i + 1 ) * *width - 1; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *q ++ = *( p - 2 ); + *q ++ = *( p - 3 - uneven_w ); + *q ++ = *( p - 4 ); + *q ++ = *( p - 1 - uneven_w ); + p -= 4; + *b ++ = *a --; + *b ++ = *a --; + } + } + else + { + while ( j -- ) + { + *( p - 2 ) = *q ++; + *( p - 3 - uneven_w ) = *q ++; + *( p - 4 ) = *q ++; + *( p - 1 - uneven_w ) = *q ++; + p -= 4; + *a -- = *b ++; + *a -- = *b ++; + } + } + } + } + else if ( !strcmp( mirror, "flip" ) ) + { + uint8_t t[ 4 ]; + uint8_t *p = NULL; + uint8_t *q = NULL; + int i; + uint8_t *a = NULL; + uint8_t *b = NULL; + uint8_t c; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = p + *width * 2; + a = alpha + i * *width; + b = a + *width - 1; + while ( p < q ) + { + t[ 0 ] = p[ 0 ]; + t[ 1 ] = p[ 1 + uneven_w ]; + t[ 2 ] = p[ 2 ]; + t[ 3 ] = p[ 3 + uneven_w ]; + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + *( -- q ) = t[ 3 ]; + *( -- q ) = t[ 0 ]; + *( -- q ) = t[ 1 ]; + *( -- q ) = t[ 2 ]; + c = *a; + *a ++ = *b; + *b -- = c; + c = *a; + *a ++ = *b; + *b -- = c; + } + } + } + else if ( !strcmp( mirror, "flop" ) ) + { + uint16_t *end = ( uint16_t *)*image + *width * *height; + uint16_t *p = NULL; + uint16_t *q = NULL; + uint16_t t; + uint8_t *a = NULL; + uint8_t *b = NULL; + uint8_t c; + int i; + int j; + for ( i = 0; i < hh; i ++ ) + { + p = ( uint16_t * )*image + i * *width; + q = end - ( i + 1 ) * *width; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + j = *width; + while ( j -- ) + { + t = *p; + *p ++ = *q; + *q ++ = t; + c = *a; + *a ++ = *b; + *b ++ = c; + } + } + } + } + + // Return the error + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the service on to the stack + mlt_frame_push_service( frame, this ); + + // Push the filter method on to the stack + mlt_frame_push_service( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_mirror_init( void *arg ) +{ + // Construct a new filter + mlt_filter this = mlt_filter_new( ); + + // If we have a filter, initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Set the default mirror type + mlt_properties_set_or_default( properties, "mirror", arg, "horizontal" ); + + // Assign the process method + this->process = filter_process; + } + + // Return the filter + return this; +} + diff --git a/src/modules/core/filter_mirror.h b/src/modules/core/filter_mirror.h new file mode 100644 index 0000000..7229aab --- /dev/null +++ b/src/modules/core/filter_mirror.h @@ -0,0 +1,28 @@ +/* + * filter_mirror.h -- mirror filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_MIRROR_H_ +#define _FILTER_MIRROR_H_ + +#include + +extern mlt_filter filter_mirror_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_mono.c b/src/modules/core/filter_mono.c new file mode 100644 index 0000000..665dfdf --- /dev/null +++ b/src/modules/core/filter_mono.c @@ -0,0 +1,95 @@ +/* + * filter_mono.c -- mix all channels to a mono signal across n channels + * Copyright (C) 2003-2006 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_mono.h" + +#include + +#include +#include + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the a frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + int channels_out = mlt_properties_get_int( properties, "mono.channels" ); + int i, j, size; + int16_t *new_buffer; + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + size = *samples * channels_out * sizeof( int16_t ); + new_buffer = mlt_pool_alloc( size ); + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Mix + for ( i = 0; i < *samples; i++ ) + { + int16_t mixdown = 0; + for ( j = 0; j < *channels; j++ ) + mixdown += (*buffer)[ ( i * *channels ) + j ] / *channels; + for ( j = 0; j < channels_out; j++ ) + new_buffer[ ( i * channels_out ) + j ] = mixdown; + } + + // Apply results + *buffer = new_buffer; + *channels = channels_out; + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + mlt_properties frame_props = MLT_FRAME_PROPERTIES( frame ); + + // Propogate the parameters + mlt_properties_set_int( frame_props, "mono.channels", mlt_properties_get_int( properties, "channels" ) ); + + // Override the get_audio method + mlt_frame_push_audio( frame, filter_get_audio ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_mono_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", atoi( arg ) ); + else + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", 2 ); + } + return this; +} diff --git a/src/modules/core/filter_mono.h b/src/modules/core/filter_mono.h new file mode 100644 index 0000000..b109654 --- /dev/null +++ b/src/modules/core/filter_mono.h @@ -0,0 +1,28 @@ +/* + * filter_mono.h -- mix all channels to a mono signal across n channels + * Copyright (C) 2003-2006 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_MONO_H_ +#define _FILTER_MONO_H_ + +#include + +extern mlt_filter filter_mono_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_obscure.c b/src/modules/core/filter_obscure.c new file mode 100644 index 0000000..5773933 --- /dev/null +++ b/src/modules/core/filter_obscure.c @@ -0,0 +1,310 @@ +/* + * filter_obscure.c -- obscure filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_obscure.h" + +#include + +#include +#include + +/** Geometry struct. +*/ + +struct geometry_s +{ + int nw; + int nh; + float x; + float y; + float w; + float h; + int mask_w; + int mask_h; +}; + +/** Parse a value from a geometry string. +*/ + +static inline float parse_value( char **ptr, int normalisation, char delim, float defaults ) +{ + float value = defaults; + + if ( *ptr != NULL && **ptr != '\0' ) + { + char *end = NULL; + value = strtod( *ptr, &end ); + if ( end != NULL ) + { + if ( *end == '%' ) + value = ( value / 100.0 ) * normalisation; + while ( *end == delim || *end == '%' ) + end ++; + } + *ptr = end; + } + + return value; +} + +/** Parse a geometry property string. +*/ + +static void geometry_parse( struct geometry_s *geometry, struct geometry_s *defaults, char *property, int nw, int nh ) +{ + // Assign normalised width and height + geometry->nw = nw; + geometry->nh = nh; + + // Assign from defaults if available + if ( defaults != NULL ) + { + geometry->x = defaults->x; + geometry->y = defaults->y; + geometry->w = defaults->w; + geometry->h = defaults->h; + geometry->mask_w = defaults->mask_w; + geometry->mask_h = defaults->mask_h; + } + else + { + geometry->x = 0; + geometry->y = 0; + geometry->w = nw; + geometry->h = nh; + geometry->mask_w = 20; + geometry->mask_h = 20; + } + + // Parse the geomtry string + if ( property != NULL ) + { + char *ptr = property; + geometry->x = parse_value( &ptr, nw, ',', geometry->x ); + geometry->y = parse_value( &ptr, nh, ':', geometry->y ); + geometry->w = parse_value( &ptr, nw, 'x', geometry->w ); + geometry->h = parse_value( &ptr, nh, ':', geometry->h ); + geometry->mask_w = parse_value( &ptr, nw, 'x', geometry->mask_w ); + geometry->mask_h = parse_value( &ptr, nh, ' ', geometry->mask_h ); + } +} + +/** A Timism but not as clean ;-). +*/ + +static float lerp( float value, float lower, float upper ) +{ + if ( value < lower ) + return lower; + else if ( value > upper ) + return upper; + return value; +} + +/** Calculate real geometry. +*/ + +static void geometry_calculate( struct geometry_s *output, struct geometry_s *in, struct geometry_s *out, float position, int ow, int oh ) +{ + // Calculate this frames geometry + output->x = lerp( ( in->x + ( out->x - in->x ) * position ) / ( float )out->nw * ow, 0, ow ); + output->y = lerp( ( in->y + ( out->y - in->y ) * position ) / ( float )out->nh * oh, 0, oh ); + output->w = lerp( ( in->w + ( out->w - in->w ) * position ) / ( float )out->nw * ow, 0, ow - output->x ); + output->h = lerp( ( in->h + ( out->h - in->h ) * position ) / ( float )out->nh * oh, 0, oh - output->y ); + output->mask_w = in->mask_w + ( out->mask_w - in->mask_w ) * position; + output->mask_h = in->mask_h + ( out->mask_h - in->mask_h ) * position; +} + +/** Calculate the position for this frame. +*/ + +static float position_calculate( mlt_filter this, mlt_frame frame ) +{ + // Get the in and out position + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + + // Get the position of the frame + mlt_position position = mlt_frame_get_position( frame ); + + // Now do the calcs + return ( float )( position - in ) / ( float )( out - in + 1 ); +} + +/** The averaging function... +*/ + +static inline void obscure_average( uint8_t *start, int width, int height, int stride ) +{ + register int y; + register int x; + register int Y = ( *start + *( start + 2 ) ) / 2; + register int U = *( start + 1 ); + register int V = *( start + 3 ); + register uint8_t *p; + register int components = width >> 1; + + y = height; + while( y -- ) + { + p = start; + x = components; + while( x -- ) + { + Y = ( Y + *p ++ ) >> 1; + U = ( U + *p ++ ) >> 1; + Y = ( Y + *p ++ ) >> 1; + V = ( V + *p ++ ) >> 1; + } + start += stride; + } + + start -= height * stride; + y = height; + while( y -- ) + { + p = start; + x = components; + while( x -- ) + { + *p ++ = Y; + *p ++ = U; + *p ++ = Y; + *p ++ = V; + } + start += stride; + } +} + + +/** The obscurer rendering function... +*/ + +static void obscure_render( uint8_t *image, int width, int height, struct geometry_s result ) +{ + int area_x = result.x; + int area_y = result.y; + int area_w = result.w; + int area_h = result.h; + + int mw = result.mask_w; + int mh = result.mask_h; + int w; + int h; + int aw; + int ah; + + uint8_t *p = image + area_y * width * 2 + area_x * 2; + + for ( w = 0; w < area_w; w += mw ) + { + for ( h = 0; h < area_h; h += mh ) + { + aw = w + mw > area_w ? mw - ( w + mw - area_w ) : mw; + ah = h + mh > area_h ? mh - ( h + mh - area_h ) : mh; + if ( aw > 1 && ah > 1 ) + obscure_average( p + h * ( width << 1 ) + ( w << 1 ), aw, ah, width << 1 ); + } + } +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Pop the top of stack now + mlt_filter this = mlt_frame_pop_service( frame ); + + // Get the image from the frame + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + // Get the image from the frame + if ( error == 0 && *format == mlt_image_yuv422 ) + { + if ( this != NULL ) + { + // Get the filter properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Obtain the normalised width and height from the frame + int normalised_width = mlt_properties_get_int( frame_properties, "normalised_width" ); + int normalised_height = mlt_properties_get_int( frame_properties, "normalised_height" ); + + // Structures for geometry + struct geometry_s result; + struct geometry_s start; + struct geometry_s end; + + // Retrieve the position + float position = mlt_properties_get_double(frame_properties, "filter_position"); + + // Now parse the geometries + geometry_parse( &start, NULL, mlt_properties_get( properties, "start" ), normalised_width, normalised_height ); + geometry_parse( &end, &start, mlt_properties_get( properties, "end" ), normalised_width, normalised_height ); + + // Do the calculation + geometry_calculate( &result, &start, &end, position, *width, *height ); + + // Now actually render it + obscure_render( *image, *width, *height, result ); + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push this on to the service stack + mlt_frame_push_service( frame, this ); + + // Calculate the position for the filter effect + float position = position_calculate( this, frame ); + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "filter_position", position ); + + // Push the get image call + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_obscure_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + this->process = filter_process; + mlt_properties_set( properties, "start", arg != NULL ? arg : "0%,0%:100%x100%" ); + mlt_properties_set( properties, "end", "" ); + } + return this; +} + diff --git a/src/modules/core/filter_obscure.h b/src/modules/core/filter_obscure.h new file mode 100644 index 0000000..b5a781a --- /dev/null +++ b/src/modules/core/filter_obscure.h @@ -0,0 +1,28 @@ +/* + * filter_obscure.h -- obscure filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_OBSCURE_H_ +#define _FILTER_OBSCURE_H_ + +#include + +extern mlt_filter filter_obscure_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_region.c b/src/modules/core/filter_region.c new file mode 100644 index 0000000..4c905fc --- /dev/null +++ b/src/modules/core/filter_region.c @@ -0,0 +1,88 @@ +/* + * filter_region.c -- region filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_region.h" +#include "transition_region.h" + +#include + +#include +#include +#include + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the properties of the filter + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Get the region transition + mlt_transition transition = mlt_properties_get_data( properties, "_transition", NULL ); + + // Create the transition if not available + if ( transition == NULL ) + { + // Create the transition + transition = mlt_factory_transition( "region", NULL ); + + // Register with the filter + mlt_properties_set_data( properties, "_transition", transition, 0, ( mlt_destructor )mlt_transition_close, NULL ); + + // Pass a reference to this filter down + mlt_properties_set_data( MLT_TRANSITION_PROPERTIES( transition ), "_region_filter", this, 0, NULL, NULL ); + } + + // Pass all properties down + mlt_properties_pass( MLT_TRANSITION_PROPERTIES( transition ), properties, "" ); + + // Process the frame + return mlt_transition_process( transition, frame, NULL ); +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_region_init( void *arg ) +{ + // Create a new filter + mlt_filter this = mlt_filter_new( ); + + // Further initialisation + if ( this != NULL ) + { + // Get the properties from the filter + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Assign the filter process method + this->process = filter_process; + + // Resource defines the shape of the region + mlt_properties_set( properties, "resource", arg == NULL ? "rectangle" : arg ); + + // Ensure that attached filters are handled privately + mlt_properties_set_int( properties, "_filter_private", 1 ); + } + + // Return the filter + return this; +} + diff --git a/src/modules/core/filter_region.h b/src/modules/core/filter_region.h new file mode 100644 index 0000000..1e33fdc --- /dev/null +++ b/src/modules/core/filter_region.h @@ -0,0 +1,28 @@ +/* + * filter_region.h -- region filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_REGION_H_ +#define _FILTER_REGION_H_ + +#include + +extern mlt_filter filter_region_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_rescale.c b/src/modules/core/filter_rescale.c new file mode 100644 index 0000000..5d18a50 --- /dev/null +++ b/src/modules/core/filter_rescale.c @@ -0,0 +1,321 @@ +/* + * filter_rescale.c -- scale the producer video frame size to match the consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_rescale.h" + +#include + +#include +#include +#include +#include + +typedef int ( *image_scaler )( mlt_frame this, uint8_t **image, mlt_image_format iformat, mlt_image_format oformat, int iwidth, int iheight, int owidth, int oheight ); + +static void scale_alpha( mlt_frame this, int iwidth, int iheight, int owidth, int oheight ); + +static int filter_scale( mlt_frame this, uint8_t **image, mlt_image_format iformat, mlt_image_format oformat, int iwidth, int iheight, int owidth, int oheight ) +{ + // Get the properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the rescaling interpolsation + char *interps = mlt_properties_get( properties, "rescale.interp" ); + + // Carry out the rescaling + if ( iformat == mlt_image_yuv422 && oformat == mlt_image_yuv422 ) + { + // Scale the frame + mlt_frame_rescale_yuv422( this, owidth, oheight ); + + // Return the output + *image = mlt_properties_get_data( properties, "image", NULL ); + + // Scale the alpha channel only if exists and not correct size + int alpha_size = 0; + mlt_properties_get_data( properties, "alpha", &alpha_size ); + if ( alpha_size > 0 && alpha_size != ( owidth * oheight ) ) + scale_alpha( this, iwidth, iheight, owidth, oheight ); + } + else if ( iformat == mlt_image_rgb24 || iformat == mlt_image_rgb24a ) + { + int bpp = (iformat == mlt_image_rgb24a ? 4 : 3 ); + + // Create the yuv image + uint8_t *output = mlt_pool_alloc( iwidth * ( iheight + 1 ) * 2 ); + + if ( strcmp( interps, "none" ) && ( iwidth != owidth || iheight != oheight ) ) + { + // Extract YUV422 and alpha + if ( bpp == 4 ) + { + // Allocate the alpha mask + uint8_t *alpha = mlt_pool_alloc( iwidth * ( iheight + 1 ) ); + + // Convert the image and extract alpha + mlt_convert_rgb24a_to_yuv422( *image, iwidth, iheight, iwidth * 4, output, alpha ); + + mlt_properties_set_data( properties, "alpha", alpha, iwidth * ( iheight + 1 ), ( mlt_destructor )mlt_pool_release, NULL ); + + scale_alpha( this, iwidth, iheight, owidth, oheight ); + } + else + { + // No alpha to extract + mlt_convert_rgb24_to_yuv422( *image, iwidth, iheight, iwidth * 3, output ); + } + + mlt_properties_set_data( properties, "image", output, iwidth * ( iheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + + // Scale the frame + output = mlt_frame_rescale_yuv422( this, owidth, oheight ); + + } + else + { + // Extract YUV422 and alpha + if ( bpp == 4 ) + { + // Allocate the alpha mask + uint8_t *alpha = mlt_pool_alloc( owidth * ( oheight + 1 ) ); + + // Convert the image and extract alpha + mlt_convert_rgb24a_to_yuv422( *image, owidth, oheight, owidth * 4, output, alpha ); + + mlt_properties_set_data( properties, "alpha", alpha, owidth * ( oheight + 1 ), ( mlt_destructor )mlt_pool_release, NULL ); + + scale_alpha( this, iwidth, iheight, owidth, oheight ); + } + else + { + // No alpha to extract + mlt_convert_rgb24_to_yuv422( *image, owidth, oheight, owidth * 3, output ); + } + } + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + *image = output; + } + + return 0; +} + +static void scale_alpha( mlt_frame this, int iwidth, int iheight, int owidth, int oheight ) +{ + // Scale the alpha + uint8_t *output = NULL; + uint8_t *input = mlt_frame_get_alpha_mask( this ); + + if ( input != NULL ) + { + uint8_t *out_line; + int x, y; + int ox = ( iwidth << 10 ) / owidth; + int oy = ( iheight << 10 ) / oheight; + + output = mlt_pool_alloc( owidth * oheight ); + out_line = output; + + // Loop for the entirety of our output height. + for ( y = 0; y < oheight; y ++ ) + for ( x = 0; x < owidth; x ++ ) + *out_line ++ = *( input + ( ( 512 + ( y * oy * iwidth ) + x * ox ) >> 10 ) ); + + // Set it back on the frame + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "alpha", output, owidth * oheight, mlt_pool_release, NULL ); + } +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the frame properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the filter from the stack + mlt_filter filter = mlt_frame_pop_service( this ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the image scaler method + image_scaler scaler_method = mlt_properties_get_data( filter_properties, "method", NULL ); + + // Correct Width/height if necessary + if ( *width == 0 || *height == 0 ) + { + *width = mlt_properties_get_int( properties, "normalised_width" ); + *height = mlt_properties_get_int( properties, "normalised_height" ); + } + + // There can be problems with small images - avoid them (by hacking - gah) + if ( *width >= 6 && *height >= 6 ) + { + int iwidth = *width; + int iheight = *height; + int owidth = *width; + int oheight = *height; + char *interps = mlt_properties_get( properties, "rescale.interp" ); + int wanted_format = *format; + + // Default from the scaler if not specifed on the frame + if ( interps == NULL ) + { + interps = mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "interpolation" ); + mlt_properties_set( properties, "rescale.interp", interps ); + } + + // If real_width/height exist, we want that as minimum information + if ( mlt_properties_get_int( properties, "real_width" ) ) + { + iwidth = mlt_properties_get_int( properties, "real_width" ); + iheight = mlt_properties_get_int( properties, "real_height" ); + } + + // Let the producer know what we are actually requested to obtain + if ( *format == mlt_image_yuv422 && strcmp( interps, "none" ) ) + { + mlt_properties_set_int( properties, "rescale_width", *width ); + mlt_properties_set_int( properties, "rescale_height", *height ); + } + else + { + // When no scaling is requested, revert the requested dimensions if possible + mlt_properties_set_int( properties, "rescale_width", iwidth ); + mlt_properties_set_int( properties, "rescale_height", iheight ); + } + + // Get the image as requested + mlt_frame_get_image( this, image, format, &iwidth, &iheight, writable ); + + // Get rescale interpretation again, in case the producer wishes to override scaling + interps = mlt_properties_get( properties, "rescale.interp" ); + + if ( *image != NULL && ( *format != mlt_image_yuv422 || ( iwidth != owidth || iheight != oheight ) ) ) + { + // If the colour space is correct and scaling is off, do nothing + if ( *format == mlt_image_yuv422 && !strcmp( interps, "none" ) ) + { + *width = iwidth; + *height = iheight; + } + else if ( *format == mlt_image_yuv422 ) + { + // Call the local scaler + scaler_method( this, image, *format, mlt_image_yuv422, iwidth, iheight, owidth, oheight ); + *width = owidth; + *height = oheight; + } + else if ( *format == mlt_image_rgb24 && wanted_format == mlt_image_rgb24 ) + { + // Call the local scaler + scaler_method( this, image, *format, mlt_image_rgb24, iwidth, iheight, owidth, oheight ); + + // Return the output + *width = owidth; + *height = oheight; + } + else if ( *format == mlt_image_rgb24 || *format == mlt_image_rgb24a ) + { + // Call the local scaler + scaler_method( this, image, *format, mlt_image_yuv422, iwidth, iheight, owidth, oheight ); + + // Return the output + *format = mlt_image_yuv422; + *width = owidth; + *height = oheight; + } + else + { + *width = iwidth; + *height = iheight; + } + } + else + { + *width = iwidth; + *height = iheight; + } + } + else + { + // Store the requested width/height + int iwidth = *width; + int iheight = *height; + + // Get the image as requested + mlt_frame_get_image( this, image, format, &iwidth, &iheight, writable ); + + // Too small - for now just assign as though we got what we wanted + *width = iwidth; + *height = iheight; + } + + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the filter + mlt_frame_push_service( frame, this ); + + // Push the get image method + mlt_frame_push_service( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_rescale_init( char *arg ) +{ + // Create a new scaler + mlt_filter this = mlt_filter_new( ); + + // If successful, then initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Set the process method + this->process = filter_process; + + // Set the inerpolation + mlt_properties_set( properties, "interpolation", arg == NULL ? "bilinear" : arg ); + + // Set the method + mlt_properties_set_data( properties, "method", filter_scale, 0, NULL, NULL ); + } + + return this; +} + diff --git a/src/modules/core/filter_rescale.h b/src/modules/core/filter_rescale.h new file mode 100644 index 0000000..1d0e8d7 --- /dev/null +++ b/src/modules/core/filter_rescale.h @@ -0,0 +1,28 @@ +/* + * filter_rescale.h -- scale the producer video frame size to match the consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_RESCALE_H_ +#define _FILTER_RESCALE_H_ + +#include + +extern mlt_filter filter_rescale_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_resize.c b/src/modules/core/filter_resize.c new file mode 100644 index 0000000..dffe77d --- /dev/null +++ b/src/modules/core/filter_resize.c @@ -0,0 +1,201 @@ +/* + * filter_resize.c -- resizing filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_resize.h" + +#include + +#include +#include +#include +#include + +/** Swapbytes inline. +*/ + +static inline void swap_bytes( uint8_t *upper, uint8_t *lower ) +{ + uint8_t t = *lower; + *lower = *upper; + *upper = t; +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + + // Get the properties from the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Pop the top of stack now + mlt_filter filter = mlt_frame_pop_service( this ); + + // Retrieve the aspect ratio + double aspect_ratio = mlt_deque_pop_back_double( MLT_FRAME_IMAGE_STACK( this ) ); + + // Assign requested width/height from our subordinate + int owidth = *width; + int oheight = *height; + + // Check for the special case - no aspect ratio means no problem :-) + if ( aspect_ratio == 0.0 ) + aspect_ratio = mlt_properties_get_double( properties, "consumer_aspect_ratio" ); + + // Reset the aspect ratio + mlt_properties_set_double( properties, "aspect_ratio", aspect_ratio ); + + // Hmmm... + char *rescale = mlt_properties_get( properties, "rescale.interp" ); + if ( rescale != NULL && !strcmp( rescale, "none" ) ) + return mlt_frame_get_image( this, image, format, width, height, writable ); + + if ( mlt_properties_get_int( properties, "distort" ) == 0 ) + { + // Normalise the input and out display aspect + int normalised_width = mlt_properties_get_int( properties, "normalised_width" ); + int normalised_height = mlt_properties_get_int( properties, "normalised_height" ); + int real_width = mlt_properties_get_int( properties, "real_width" ); + int real_height = mlt_properties_get_int( properties, "real_height" ); + if ( real_width == 0 ) + real_width = mlt_properties_get_int( properties, "width" ); + if ( real_height == 0 ) + real_height = mlt_properties_get_int( properties, "height" ); + double input_ar = aspect_ratio * real_width / real_height; + double output_ar = mlt_properties_get_double( properties, "consumer_aspect_ratio" ) * owidth / oheight; + + //fprintf( stderr, "normalised %dx%d output %dx%d %f %f\n", normalised_width, normalised_height, owidth, oheight, ( float )output_ar, ( float )mlt_properties_get_double( properties, "consumer_aspect_ratio" ) * owidth / oheight ); + + // Optimised for the input_ar > output_ar case (e.g. widescreen on standard) + int scaled_width = rint( 0.5 + ( input_ar * normalised_width ) / output_ar ); + int scaled_height = normalised_height; + + // Now ensure that our images fit in the output frame + if ( scaled_width > normalised_width ) + { + scaled_width = normalised_width; + scaled_height = rint( 0.5 + ( output_ar * normalised_height ) / input_ar ); + } + + // Now calculate the actual image size that we want + owidth = rint( 0.5 + scaled_width * owidth / normalised_width ); + oheight = rint( 0.5 + scaled_height * oheight / normalised_height ); + + // Tell frame we have conformed the aspect to the consumer + mlt_frame_set_aspect_ratio( this, mlt_properties_get_double( properties, "consumer_aspect_ratio" ) ); + } + + mlt_properties_set_int( properties, "distort", 0 ); + + // Now pass on the calculations down the line + mlt_properties_set_int( properties, "resize_width", *width ); + mlt_properties_set_int( properties, "resize_height", *height ); + + // Now get the image + error = mlt_frame_get_image( this, image, format, &owidth, &oheight, writable ); + + // We only know how to process yuv422 at the moment + if ( error == 0 && *format == mlt_image_yuv422 && *image != NULL ) + { + // Get the requested scale operation + char *op = mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "scale" ); + + // Provides a manual override for misreported field order + if ( mlt_properties_get( properties, "meta.top_field_first" ) ) + mlt_properties_set_int( properties, "top_field_first", mlt_properties_get_int( properties, "meta.top_field_first" ) ); + + // Correct field order if needed + if ( mlt_properties_get_int( properties, "top_field_first" ) == 1 ) + { + // Get the input image, width and height + int size; + uint8_t *image = mlt_properties_get_data( properties, "image", &size ); + uint8_t *ptr = image + owidth * 2; + int h = oheight / 2; + int w = owidth; + + // Swap the lines around + while( h -- ) + { + w = owidth; + while( w -- ) + { + swap_bytes( image ++, ptr ++ ); + swap_bytes( image ++, ptr ++ ); + } + image += owidth * 2; + ptr += owidth * 2; + } + + // Set the normalised field order + mlt_properties_set_int( properties, "top_field_first", 0 ); + mlt_properties_set_int( properties, "meta.top_field_first", 0 ); + } + + if ( !strcmp( op, "affine" ) ) + { + *image = mlt_frame_rescale_yuv422( this, *width, *height ); + } + else if ( strcmp( op, "none" ) != 0 ) + { + *image = mlt_frame_resize_yuv422( this, *width, *height ); + } + else + { + *width = owidth; + *height = oheight; + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Store the aspect ratio reported by the source + mlt_deque_push_back_double( MLT_FRAME_IMAGE_STACK( frame ), mlt_frame_get_aspect_ratio( frame ) ); + + // Push this on to the service stack + mlt_frame_push_service( frame, this ); + + // Push the get_image method on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_resize_init( char *arg ) +{ + mlt_filter this = calloc( sizeof( struct mlt_filter_s ), 1 ); + if ( mlt_filter_init( this, this ) == 0 ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "scale", arg == NULL ? "off" : arg ); + } + return this; +} diff --git a/src/modules/core/filter_resize.h b/src/modules/core/filter_resize.h new file mode 100644 index 0000000..086faad --- /dev/null +++ b/src/modules/core/filter_resize.h @@ -0,0 +1,28 @@ +/* + * filter_resize.h -- resizing filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_RESIZE_H_ +#define _FILTER_RESIZE_H_ + +#include + +extern mlt_filter filter_resize_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_transition.c b/src/modules/core/filter_transition.c new file mode 100644 index 0000000..ff002e5 --- /dev/null +++ b/src/modules/core/filter_transition.c @@ -0,0 +1,114 @@ +/* + * filter_transition.c -- Convert any transition into a filter + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_transition.h" +#include +#include +#include + +/** Get the image via the transition. + NB: Not all transitions will accept a and b frames being the same... +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_transition transition = mlt_frame_pop_service( this ); + if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "image_count" ) >= 1 ) + mlt_transition_process( transition, this, this ); + return mlt_frame_get_image( this, image, format, width, height, writable ); +} + +/** Get the audio via the transition. + NB: Not all transitions will accept a and b frames being the same... +*/ + +static int filter_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Obtain the transition instance + mlt_transition transition = mlt_frame_pop_audio( this ); + mlt_transition_process( transition, this, this ); + return mlt_frame_get_audio( this, buffer, format, frequency, channels, samples ); +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Obtain the transition instance + mlt_transition transition = mlt_properties_get_data( MLT_FILTER_PROPERTIES( this ), "instance", NULL ); + + // If we haven't created the instance, do it now + if ( transition == NULL ) + { + char *name = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "transition" ); + transition = mlt_factory_transition( name, NULL ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "instance", transition, 0, ( mlt_destructor )mlt_transition_close, NULL ); + } + + // We may still not have a transition... + if ( transition != NULL ) + { + // Get the transition type + int type = mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( transition ), "_transition_type" ); + + // Set the basic info + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "in", mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "in" ) ); + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "out", mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "out" ) ); + + // Refresh with current user values + mlt_properties_pass( MLT_TRANSITION_PROPERTIES( transition ), MLT_FILTER_PROPERTIES( this ), "transition." ); + + if ( type & 1 && !mlt_frame_is_test_card( frame ) && !( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "hide" ) & 1 ) ) + { + mlt_frame_push_service( frame, transition ); + mlt_frame_push_get_image( frame, filter_get_image ); + } + if ( type & 2 && !mlt_frame_is_test_audio( frame ) && !( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "hide" ) & 2 ) ) + { + mlt_frame_push_audio( frame, transition ); + mlt_frame_push_audio( frame, filter_get_audio ); + } + + if ( type == 0 ) + mlt_properties_debug( MLT_TRANSITION_PROPERTIES( transition ), "unknown transition type", stderr ); + } + else + { + mlt_properties_debug( MLT_FILTER_PROPERTIES( this ), "no transition", stderr ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_transition_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "transition", arg ); + this->process = filter_process; + } + return this; +} + diff --git a/src/modules/core/filter_transition.h b/src/modules/core/filter_transition.h new file mode 100644 index 0000000..eed9453 --- /dev/null +++ b/src/modules/core/filter_transition.h @@ -0,0 +1,28 @@ +/* + * filter_transition.h -- Convert any transition into a filter + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_TRANSITION_H_ +#define _FILTER_TRANSITION_H_ + +#include + +extern mlt_filter filter_transition_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_watermark.c b/src/modules/core/filter_watermark.c new file mode 100644 index 0000000..bb85e0e --- /dev/null +++ b/src/modules/core/filter_watermark.c @@ -0,0 +1,263 @@ +/* + * filter_watermark.c -- watermark filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_watermark.h" + +#include +#include +#include +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Error we will return + int error = 0; + + // Get the watermark filter object + mlt_filter this = mlt_frame_pop_service( frame ); + + // Get the properties of the filter + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Get the producer from the filter + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + + // Get the composite from the filter + mlt_transition composite = mlt_properties_get_data( properties, "composite", NULL ); + + // Get the resource to use + char *resource = mlt_properties_get( properties, "resource" ); + + // Get the old resource + char *old_resource = mlt_properties_get( properties, "_old_resource" ); + + // Create a composite if we don't have one + if ( composite == NULL ) + { + // Create composite via the factory + composite = mlt_factory_transition( "composite", NULL ); + + // Register the composite for reuse/destruction + if ( composite != NULL ) + mlt_properties_set_data( properties, "composite", composite, 0, ( mlt_destructor )mlt_transition_close, NULL ); + } + + // If we have one + if ( composite != NULL ) + { + // Get the properties + mlt_properties composite_properties = MLT_TRANSITION_PROPERTIES( composite ); + + // Pass all the composite. properties on the filter down + mlt_properties_pass( composite_properties, properties, "composite." ); + + if ( mlt_properties_get( properties, "composite.out" ) == NULL ) + mlt_properties_set_int( composite_properties, "out", mlt_properties_get_int( properties, "_out" ) ); + + // Force a refresh + mlt_properties_set_int( composite_properties, "refresh", 1 ); + } + + // Create a producer if don't have one + if ( producer == NULL || ( old_resource != NULL && strcmp( resource, old_resource ) ) ) + { + // Get the factory producer service + char *factory = mlt_properties_get( properties, "factory" ); + + // Create the producer + producer = mlt_factory_producer( factory, resource ); + + // If we have one + if ( producer != NULL ) + { + // Register the producer for reuse/destruction + mlt_properties_set_data( properties, "producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + + // Ensure that we loop + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" ); + + // Set the old resource + mlt_properties_set( properties, "_old_resource", resource ); + } + } + + if ( producer != NULL ) + { + // Get the producer properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Now pass all producer. properties on the filter down + mlt_properties_pass( producer_properties, properties, "producer." ); + } + + // Only continue if we have both producer and composite + if ( composite != NULL && producer != NULL ) + { + // Get the service of the producer + mlt_service service = MLT_PRODUCER_SERVICE( producer ); + + // We will get the 'b frame' from the producer + mlt_frame b_frame = NULL; + + // Get the unique id of the filter (used to reacquire the producer position) + char *name = mlt_properties_get( properties, "_unique_id" ); + + // Get the original producer position + mlt_position position = mlt_properties_get_position( MLT_FRAME_PROPERTIES( frame ), name ); + + // Make sure the producer is in the correct position + mlt_producer_seek( producer, position ); + + // Resetting position to appease the composite transition + mlt_frame_set_position( frame, position ); + + // Get the b frame and process with composite if successful + if ( mlt_service_get_frame( service, &b_frame, 0 ) == 0 ) + { + // Get the a and b frame properties + mlt_properties a_props = MLT_FRAME_PROPERTIES( frame ); + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Set the b frame to be in the same position and have same consumer requirements + mlt_frame_set_position( b_frame, position ); + mlt_properties_set_double( b_props, "consumer_aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + mlt_properties_set_int( b_props, "consumer_deinterlace", mlt_properties_get_double( a_props, "consumer_deinterlace" ) ); + mlt_properties_set_int( b_props, "output_ratio", mlt_properties_get_double( a_props, "output_ratio" ) ); + + // Check for the special case - no aspect ratio means no problem :-) + if ( mlt_frame_get_aspect_ratio( b_frame ) == 0 ) + mlt_properties_set_double( b_props, "aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + if ( mlt_frame_get_aspect_ratio( frame ) == 0 ) + mlt_properties_set_double( a_props, "aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + + mlt_properties_set_int( b_props, "normalised_width", mlt_properties_get_int( a_props, "normalised_width" ) ); + mlt_properties_set_int( b_props, "normalised_height", mlt_properties_get_int( a_props, "normalised_height" ) ); + + if ( mlt_properties_get_int( properties, "distort" ) ) + { + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( composite ), "distort", 1 ); + mlt_properties_set_int( a_props, "distort", 1 ); + mlt_properties_set_int( b_props, "distort", 1 ); + } + + if ( mlt_properties_get_int( properties, "reverse" ) == 0 ) + { + // Apply all filters that are attached to this filter to the b frame + mlt_service_apply_filters( MLT_FILTER_SERVICE( this ), b_frame, 0 ); + + // Process the frame + mlt_transition_process( composite, frame, b_frame ); + + // Get the image + error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + } + else + { + char temp[ 132 ]; + int count = 0; + uint8_t *alpha = NULL; + char *rescale = mlt_properties_get( a_props, "rescale.interp" ); + if ( rescale == NULL || !strcmp( rescale, "none" ) ) + rescale = "hyper"; + mlt_transition_process( composite, b_frame, frame ); + mlt_properties_set_int( a_props, "consumer_deinterlace", 1 ); + mlt_properties_set_int( b_props, "consumer_deinterlace", 1 ); + mlt_properties_set( a_props, "rescale.interp", rescale ); + mlt_properties_set( b_props, "rescale.interp", rescale ); + mlt_service_apply_filters( MLT_FILTER_SERVICE( this ), b_frame, 0 ); + error = mlt_frame_get_image( b_frame, image, format, width, height, 1 ); + alpha = mlt_frame_get_alpha_mask( b_frame ); + mlt_properties_set_data( a_props, "image", *image, *width * *height * 2, NULL, NULL ); + mlt_properties_set_data( a_props, "alpha", alpha, *width * *height, NULL, NULL ); + mlt_properties_set_int( a_props, "width", *width ); + mlt_properties_set_int( a_props, "height", *height ); + mlt_properties_set_int( a_props, "progressive", 1 ); + mlt_properties_inc_ref( b_props ); + strcpy( temp, "_b_frame" ); + while( mlt_properties_get_data( a_props, temp, NULL ) != NULL ) + sprintf( temp, "_b_frame%d", count ++ ); + mlt_properties_set_data( a_props, temp, b_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + } + + // Close the b frame + mlt_frame_close( b_frame ); + } + else + { + // Get the image from the frame without running fx + error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get a unique name to store the frame position + char *name = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "_unique_id" ); + + // Assign the frame out point to the filter (just in case we need it later) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "_out", mlt_properties_get_int( properties, "out" ) ); + + // Assign the current position to the name + mlt_properties_set_position( properties, name, mlt_frame_get_position( frame ) - mlt_filter_get_in( this ) ); + + // Push the filter on to the stack + mlt_frame_push_service( frame, this ); + + // Push the get_image on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_watermark_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + this->process = filter_process; + mlt_properties_set( properties, "factory", "fezzik" ); + if ( arg != NULL ) + mlt_properties_set( properties, "resource", arg ); + // Ensure that attached filters are handled privately + mlt_properties_set_int( properties, "_filter_private", 1 ); + } + return this; +} + diff --git a/src/modules/core/filter_watermark.h b/src/modules/core/filter_watermark.h new file mode 100644 index 0000000..94ff584 --- /dev/null +++ b/src/modules/core/filter_watermark.h @@ -0,0 +1,28 @@ +/* + * filter_watermark.h -- watermark filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_WATERMARK_H_ +#define _FILTER_WATERMARK_H_ + +#include + +extern mlt_filter filter_watermark_init( void *arg ); + +#endif diff --git a/src/modules/core/producer_colour.c b/src/modules/core/producer_colour.c new file mode 100644 index 0000000..f65e2e2 --- /dev/null +++ b/src/modules/core/producer_colour.c @@ -0,0 +1,253 @@ +/* + * producer_colour.c -- raster image loader based upon gdk-pixbuf + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_colour.h" +#include +#include + +#include +#include +#include + + +typedef struct +{ + uint8_t r, g, b, a; +} rgba_color; + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_colour_init( char *colour ) +{ + mlt_producer producer = calloc( 1, sizeof( struct mlt_producer_s ) ); + if ( producer != NULL && mlt_producer_init( producer, NULL ) == 0 ) + { + // Get the properties interface + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Callback registration + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + // Set the default properties + mlt_properties_set( properties, "resource", colour == NULL ? "0x000000ff" : colour ); + mlt_properties_set( properties, "_resource", "" ); + mlt_properties_set_double( properties, "aspect_ratio", 0 ); + + return producer; + } + free( producer ); + return NULL; +} + +rgba_color parse_color( char *color ) +{ + rgba_color result = { 0xff, 0xff, 0xff, 0xff }; + + if ( strchr( color, '/' ) ) + color = strrchr( color, '/' ) + 1; + + if ( !strncmp( color, "0x", 2 ) ) + { + unsigned int temp = 0; + sscanf( color + 2, "%x", &temp ); + result.r = ( temp >> 24 ) & 0xff; + result.g = ( temp >> 16 ) & 0xff; + result.b = ( temp >> 8 ) & 0xff; + result.a = ( temp ) & 0xff; + } + else if ( !strcmp( color, "red" ) ) + { + result.r = 0xff; + result.g = 0x00; + result.b = 0x00; + } + else if ( !strcmp( color, "green" ) ) + { + result.r = 0x00; + result.g = 0xff; + result.b = 0x00; + } + else if ( !strcmp( color, "blue" ) ) + { + result.r = 0x00; + result.g = 0x00; + result.b = 0xff; + } + else if ( strcmp( color, "white" ) ) + { + unsigned int temp = 0; + sscanf( color, "%d", &temp ); + result.r = ( temp >> 24 ) & 0xff; + result.g = ( temp >> 16 ) & 0xff; + result.b = ( temp >> 8 ) & 0xff; + result.a = ( temp ) & 0xff; + } + + return result; +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // May need to know the size of the image to clone it + int size = 0; + + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the producer for this frame + mlt_producer producer = mlt_properties_get_data( properties, "producer_colour", NULL ); + + // Obtain properties of producer + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Get the current and previous colour strings + char *now = mlt_properties_get( producer_props, "resource" ); + char *then = mlt_properties_get( producer_props, "_resource" ); + + // Get the current image and dimensions cached in the producer + uint8_t *image = mlt_properties_get_data( producer_props, "image", &size ); + int current_width = mlt_properties_get_int( producer_props, "_width" ); + int current_height = mlt_properties_get_int( producer_props, "_height" ); + + // Parse the colour + rgba_color color = parse_color( now ); + + // See if we need to regenerate + if ( strcmp( now, then ) || *width != current_width || *height != current_height ) + { + // Color the image + uint8_t y, u, v; + int i = *height; + int j = 0; + int uneven = *width % 2; + int count = ( *width - uneven ) / 2; + uint8_t *p = NULL; + + // Allocate the image + size = *width * *height * 2; + image = mlt_pool_alloc( size ); + + // Update the producer + mlt_properties_set_data( producer_props, "image", image, size, mlt_pool_release, NULL ); + mlt_properties_set_int( producer_props, "_width", *width ); + mlt_properties_set_int( producer_props, "_height", *height ); + mlt_properties_set( producer_props, "_resource", now ); + + RGB2YUV( color.r, color.g, color.b, y, u, v ); + + p = image; + + while ( i -- ) + { + j = count; + while ( j -- ) + { + *p ++ = y; + *p ++ = u; + *p ++ = y; + *p ++ = v; + } + if ( uneven ) + { + *p ++ = y; + *p ++ = u; + } + } + } + + // Update the frame + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + + // Clone if necessary (deemed always necessary) + if ( 1 ) + { + // Create the alpha channel + uint8_t *alpha = mlt_pool_alloc( size >> 1 ); + + // Clone our image + uint8_t *copy = mlt_pool_alloc( size ); + memcpy( copy, image, size ); + + // We're going to pass the copy on + image = copy; + + // Initialise the alpha + if ( alpha ) + memset( alpha, color.a, size >> 1 ); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", copy, size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha, size >> 1, mlt_pool_release, NULL ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_props, "aspect_ratio" ) ); + } + + // Pass on the image + *buffer = image; + *format = mlt_image_yuv422; + + return 0; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Generate a frame + *frame = mlt_frame_init( ); + + if ( *frame != NULL ) + { + // Obtain properties of frame and producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Obtain properties of producer + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Set the producer on the frame properties + mlt_properties_set_data( properties, "producer_colour", producer, 0, NULL, NULL ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_props, "aspect_ratio" ) ); + + // colour is an alias for resource + if ( mlt_properties_get( producer_props, "colour" ) != NULL ) + mlt_properties_set( producer_props, "resource", mlt_properties_get( producer_props, "colour" ) ); + + // Push the get_image method + mlt_frame_push_get_image( *frame, producer_get_image ); + } + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer producer ) +{ + producer->close = NULL; + mlt_producer_close( producer ); + free( producer ); +} diff --git a/src/modules/core/producer_colour.h b/src/modules/core/producer_colour.h new file mode 100644 index 0000000..571c753 --- /dev/null +++ b/src/modules/core/producer_colour.h @@ -0,0 +1,28 @@ +/* + * producer_colour.h -- raster image loader based upon gdk-pixbuf + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_COLOUR_H_ +#define _PRODUCER_COLOUR_H_ + +#include + +extern mlt_producer producer_colour_init( char *filename ); + +#endif diff --git a/src/modules/core/producer_noise.c b/src/modules/core/producer_noise.c new file mode 100644 index 0000000..35933d8 --- /dev/null +++ b/src/modules/core/producer_noise.c @@ -0,0 +1,177 @@ +/* + * producer_noise.c -- noise generating producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_noise.h" +#include +#include + +#include +#include +#include + +/** Random number generator +*/ + +static unsigned int seed_x = 521288629; +static unsigned int seed_y = 362436069; + +static inline unsigned int fast_rand( ) +{ + static unsigned int a = 18000, b = 30903; + seed_x = a * ( seed_x & 65535 ) + ( seed_x >> 16 ); + seed_y = b * ( seed_y & 65535 ) + ( seed_y >> 16 ); + return ( ( seed_x << 16 ) + ( seed_y & 65535 ) ); +} + +// Foward declarations +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer this ); + +/** Initialise. +*/ + +mlt_producer producer_noise_init( void *arg ) +{ + // Create a new producer object + mlt_producer this = mlt_producer_new( ); + + // Initialise the producer + if ( this != NULL ) + { + // Callback registration + this->get_frame = producer_get_frame; + this->close = ( mlt_destructor )producer_close; + } + + return this; +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Calculate the size of the image + int size = *width * *height * 2; + + // Set the format being returned + *format = mlt_image_yuv422; + + // Allocate the image + *buffer = mlt_pool_alloc( size ); + + // Update the frame + mlt_properties_set_data( properties, "image", *buffer, size, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + + // Before we write to the image, make sure we have one + if ( *buffer != NULL ) + { + // Calculate the end of the buffer + uint8_t *p = *buffer + *width * *height * 2; + + // Value to hold a random number + uint32_t value; + + // Generate random noise + while ( p != *buffer ) + { + value = fast_rand( ) & 0xff; + *( -- p ) = 128; + *( -- p ) = value < 16 ? 16 : value > 240 ? 240 : value; + } + } + + return 0; +} + +static int producer_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the frame properties + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + int size = 0; + + // Correct the returns if necessary + *samples = *samples <= 0 ? 1920 : *samples; + *channels = *channels <= 0 ? 2 : *channels; + *frequency = *frequency <= 0 ? 48000 : *frequency; + + // Calculate the size of the buffer + size = *samples * *channels * sizeof( int16_t ); + + // Allocate the buffer + *buffer = mlt_pool_alloc( size ); + + // Make sure we got one and fill it + if ( *buffer != NULL ) + { + int16_t *p = *buffer + size / 2; + while ( p != *buffer ) + *( -- p ) = fast_rand( ) & 0x0f00; + } + + // Set the buffer for destruction + mlt_properties_set_data( properties, "audio", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + return 0; +} + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Generate a frame + *frame = mlt_frame_init( ); + + // Check that we created a frame and initialise it + if ( *frame != NULL ) + { + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Aspect ratio is whatever it needs to be + mlt_properties_set_double( properties, "aspect_ratio", 0 ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", 1 ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Push the get_image method + mlt_frame_push_get_image( *frame, producer_get_image ); + + // Specify the audio + mlt_frame_push_audio( *frame, producer_get_audio ); + } + + // Calculate the next timecode + mlt_producer_prepare_next( this ); + + return 0; +} + +static void producer_close( mlt_producer this ) +{ + this->close = NULL; + mlt_producer_close( this ); + free( this ); +} + diff --git a/src/modules/core/producer_noise.h b/src/modules/core/producer_noise.h new file mode 100644 index 0000000..6b2c063 --- /dev/null +++ b/src/modules/core/producer_noise.h @@ -0,0 +1,28 @@ +/* + * producer_noise.h -- noise generating producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_NOISE_H_ +#define _PRODUCER_NOISE_H_ + +#include + +extern mlt_producer producer_noise_init( void *arg ); + +#endif diff --git a/src/modules/core/producer_ppm.c b/src/modules/core/producer_ppm.c new file mode 100644 index 0000000..2fcc71c --- /dev/null +++ b/src/modules/core/producer_ppm.c @@ -0,0 +1,274 @@ +/* + * producer_ppm.c -- simple ppm test case + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_ppm.h" + +#include + +#include +#include + +typedef struct producer_ppm_s *producer_ppm; + +struct producer_ppm_s +{ + struct mlt_producer_s parent; + char *command; + FILE *video; + FILE *audio; + uint64_t expected; +}; + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_ppm_init( void *command ) +{ + producer_ppm this = calloc( sizeof( struct producer_ppm_s ), 1 ); + if ( this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + mlt_producer producer = &this->parent; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + if ( command != NULL ) + { + mlt_properties_set( properties, "resource", command ); + this->command = strdup( command ); + } + else + { + mlt_properties_set( properties, "resource", "ppm test" ); + } + + return producer; + } + free( this ); + return NULL; +} + +static int producer_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + if ( mlt_properties_get_int( properties, "has_image" ) ) + { + // Get the RGB image + uint8_t *rgb = mlt_properties_get_data( properties, "image", NULL ); + + // Get width and height + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + + // Convert to requested format + if ( *format == mlt_image_yuv422 ) + { + uint8_t *image = mlt_pool_alloc( *width * ( *height + 1 ) * 2 ); + mlt_convert_rgb24_to_yuv422( rgb, *width, *height, *width * 3, image ); + mlt_properties_set_data( properties, "image", image, *width * ( *height + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + *buffer = image; + } + else if ( *format == mlt_image_rgb24 ) + { + *buffer = rgb; + } + } + else + { + mlt_frame_get_image( this, buffer, format, width, height, writable ); + } + + return 0; +} + +FILE *producer_ppm_run_video( producer_ppm this ) +{ + if ( this->video == NULL ) + { + if ( this->command == NULL ) + { + this->video = popen( "image2raw -k -r 25 -ppm /usr/share/pixmaps/*.png", "r" ); + } + else + { + char command[ 1024 ]; + float fps = mlt_producer_get_fps( &this->parent ); + float position = mlt_producer_position( &this->parent ); + sprintf( command, "ffmpeg -i \"%s\" -ss %f -f imagepipe -r %f -img ppm - 2>/dev/null", this->command, position, fps ); + this->video = popen( command, "r" ); + } + } + return this->video; +} + +FILE *producer_ppm_run_audio( producer_ppm this ) +{ + if ( this->audio == NULL ) + { + if ( this->command != NULL ) + { + char command[ 1024 ]; + float position = mlt_producer_position( &this->parent ); + sprintf( command, "ffmpeg -i \"%s\" -ss %f -f s16le -ar 48000 -ac 2 - 2>/dev/null", this->command, position ); + this->audio = popen( command, "r" ); + } + } + return this->audio; +} + +static void producer_ppm_position( producer_ppm this, uint64_t requested ) +{ + if ( requested != this->expected ) + { + if ( this->video != NULL ) + pclose( this->video ); + this->video = NULL; + if ( this->audio != NULL ) + pclose( this->audio ); + this->audio = NULL; + } + + // This is the next frame we expect + this->expected = mlt_producer_frame( &this->parent ) + 1; + + // Open the pipe + this->video = producer_ppm_run_video( this ); + + // Open the audio pipe + this->audio = producer_ppm_run_audio( this ); + +} + +static int producer_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + FILE *pipe = mlt_properties_get_data( properties, "audio.pipe", NULL ); + + *frequency = 48000; + *channels = 2; + *samples = 1920; + + // Size + int size = *samples * *channels * 2; + + // Allocate an image + *buffer = malloc( size ); + + // Read it + if ( pipe != NULL ) + fread( *buffer, size, 1, pipe ); + else + memset( *buffer, 0, size ); + + // Pass the data on the frame properties + mlt_properties_set_data( properties, "audio", *buffer, size, free, NULL ); + + return 0; +} + +static int read_ppm_header( FILE *video, int *width, int *height ) +{ + int count = 0; + { + char temp[ 132 ]; + fgets( temp, 132, video ); + fgets( temp, 132, video ); + count += sscanf( temp, "%d %d", width, height ); + fgets( temp, 132, video ); + } + return count; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + producer_ppm this = producer->child; + int width; + int height; + + // Construct a test frame + *frame = mlt_frame_init( ); + + // Are we at the position expected? + producer_ppm_position( this, mlt_producer_frame( producer ) ); + + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + FILE *video = this->video; + FILE *audio = this->audio; + + // Read the video + if ( video != NULL && read_ppm_header( video, &width, &height ) == 2 ) + { + // Allocate an image + uint8_t *image = mlt_pool_alloc( width * ( height + 1 ) * 3 ); + + // Read it + fread( image, width * height * 3, 1, video ); + + // Pass the data on the frame properties + mlt_properties_set_data( properties, "image", image, width * ( height + 1 ) * 3, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", width ); + mlt_properties_set_int( properties, "height", height ); + mlt_properties_set_int( properties, "has_image", 1 ); + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_double( properties, "aspect_ratio", 1 ); + + // Push the image callback + mlt_frame_push_get_image( *frame, producer_get_image ); + } + else + { + // Push the image callback + mlt_frame_push_get_image( *frame, producer_get_image ); + } + + // Set the audio pipe + mlt_properties_set_data( properties, "audio.pipe", audio, 0, NULL, NULL ); + + // Hmm - register audio callback + mlt_frame_push_audio( *frame, producer_get_audio ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + producer_ppm this = parent->child; + if ( this->video ) + pclose( this->video ); + if ( this->audio ) + pclose( this->audio ); + free( this->command ); + parent->close = NULL; + mlt_producer_close( parent ); + free( this ); +} diff --git a/src/modules/core/producer_ppm.h b/src/modules/core/producer_ppm.h new file mode 100644 index 0000000..b0e3fc4 --- /dev/null +++ b/src/modules/core/producer_ppm.h @@ -0,0 +1,28 @@ +/* + * producer_ppm.h -- simple ppm test case + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_PPM_H_ +#define _PRODUCER_PPM_H_ + +#include + +extern mlt_producer producer_ppm_init( void *command ); + +#endif diff --git a/src/modules/core/transition_composite.c b/src/modules/core/transition_composite.c new file mode 100644 index 0000000..12231cc --- /dev/null +++ b/src/modules/core/transition_composite.c @@ -0,0 +1,1215 @@ +/* + * transition_composite.c -- compose one image over another using alpha channel + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "transition_composite.h" +#include + +#include +#include +#include +#include +#include + +typedef void ( *composite_line_fn )( uint8_t *dest, uint8_t *src, int width_src, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int softness ); + +/** Geometry struct. +*/ + +struct geometry_s +{ + struct mlt_geometry_item_s item; + int nw; // normalised width + int nh; // normalised height + int sw; // scaled width, not including consumer scale based upon w/nw + int sh; // scaled height, not including consumer scale based upon h/nh + int halign; // horizontal alignment: 0=left, 1=center, 2=right + int valign; // vertical alignment: 0=top, 1=middle, 2=bottom +}; + +/** Parse the alignment properties into the geometry. +*/ + +static int alignment_parse( char* align ) +{ + int ret = 0; + + if ( align == NULL ); + else if ( isdigit( align[ 0 ] ) ) + ret = atoi( align ); + else if ( align[ 0 ] == 'c' || align[ 0 ] == 'm' ) + ret = 1; + else if ( align[ 0 ] == 'r' || align[ 0 ] == 'b' ) + ret = 2; + + return ret; +} + +/** Calculate real geometry. +*/ + +static void geometry_calculate( mlt_transition this, struct geometry_s *output, double position ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + mlt_geometry geometry = mlt_properties_get_data( properties, "geometries", NULL ); + int mirror_off = mlt_properties_get_int( properties, "mirror_off" ); + int repeat_off = mlt_properties_get_int( properties, "repeat_off" ); + int length = mlt_geometry_get_length( geometry ); + + // Allow wrapping + if ( !repeat_off && position >= length && length != 0 ) + { + int section = position / length; + position -= section * length; + if ( !mirror_off && section % 2 == 1 ) + position = length - position; + } + + // Fetch the key for the position + mlt_geometry_fetch( geometry, &output->item, position ); +} + +static mlt_geometry transition_parse_keys( mlt_transition this, int normalised_width, int normalised_height ) +{ + // Loop variable for property interrogation + int i = 0; + + // Get the properties of the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Create an empty geometries object + mlt_geometry geometry = mlt_geometry_init( ); + + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + mlt_position out = mlt_transition_get_out( this ); + int length = out - in + 1; + double cycle = mlt_properties_get_double( properties, "cycle" ); + + // Get the new style geometry string + char *property = mlt_properties_get( properties, "geometry" ); + + // Allow a geometry repeat cycle + if ( cycle >= 1 ) + length = cycle; + else if ( cycle > 0 ) + length *= cycle; + + // Parse the geometry if we have one + mlt_geometry_parse( geometry, property, length, normalised_width, normalised_height ); + + // Check if we're using the old style geometry + if ( property == NULL ) + { + // DEPRECATED: Multiple keys for geometry information is inefficient and too rigid for + // practical use - while deprecated, it has been slightly extended too - keys can now + // be specified out of order, and can be blanked or NULL to simulate removal + + // Structure to use for parsing and inserting + struct mlt_geometry_item_s item; + + // Parse the start property + item.frame = 0; + if ( mlt_geometry_parse_item( geometry, &item, mlt_properties_get( properties, "start" ) ) == 0 ) + mlt_geometry_insert( geometry, &item ); + + // Parse the keys in between + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + { + // Get the name of the property + char *name = mlt_properties_get_name( properties, i ); + + // Check that it's valid + if ( !strncmp( name, "key[", 4 ) ) + { + // Get the value of the property + char *value = mlt_properties_get_value( properties, i ); + + // Determine the frame number + item.frame = atoi( name + 4 ); + + // Parse and add to the list + if ( mlt_geometry_parse_item( geometry, &item, value ) == 0 ) + mlt_geometry_insert( geometry, &item ); + else + fprintf( stderr, "Invalid Key - skipping %s = %s\n", name, value ); + } + } + + // Parse the end + item.frame = -1; + if ( mlt_geometry_parse_item( geometry, &item, mlt_properties_get( properties, "end" ) ) == 0 ) + mlt_geometry_insert( geometry, &item ); + } + + return geometry; +} + +/** Adjust position according to scaled size and alignment properties. +*/ + +static void alignment_calculate( struct geometry_s *geometry ) +{ + geometry->item.x += ( geometry->item.w - geometry->sw ) * geometry->halign / 2; + geometry->item.y += ( geometry->item.h - geometry->sh ) * geometry->valign / 2; +} + +/** Calculate the position for this frame. +*/ + +static int position_calculate( mlt_transition this, mlt_position position ) +{ + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + + // Now do the calcs + return position - in; +} + +/** Calculate the field delta for this frame - position between two frames. +*/ + +static inline double delta_calculate( mlt_transition this, mlt_frame frame, mlt_position position ) +{ + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + mlt_position out = mlt_transition_get_out( this ); + double length = out - in + 1; + + // Now do the calcs + double x = ( double )( position - in ) / length; + double y = ( double )( position + 1 - in ) / length; + + return length * ( y - x ) / 2.0; +} + +static int get_value( mlt_properties properties, char *preferred, char *fallback ) +{ + int value = mlt_properties_get_int( properties, preferred ); + if ( value == 0 ) + value = mlt_properties_get_int( properties, fallback ); + return value; +} + +/** A linear threshold determination function. +*/ + +static inline int32_t linearstep( int32_t edge1, int32_t edge2, int32_t a ) +{ + if ( a < edge1 ) + return 0; + + if ( a >= edge2 ) + return 0x10000; + + return ( ( a - edge1 ) << 16 ) / ( edge2 - edge1 ); +} + +/** A smoother, non-linear threshold determination function. +*/ + +static inline int32_t smoothstep( int32_t edge1, int32_t edge2, uint32_t a ) +{ + if ( a < edge1 ) + return 0; + + if ( a >= edge2 ) + return 0x10000; + + a = ( ( a - edge1 ) << 16 ) / ( edge2 - edge1 ); + + return ( ( ( a * a ) >> 16 ) * ( ( 3 << 16 ) - ( 2 * a ) ) ) >> 16; +} + +/** Load the luma map from PGM stream. +*/ + +static void luma_read_pgm( FILE *f, uint16_t **map, int *width, int *height ) +{ + uint8_t *data = NULL; + while (1) + { + char line[128]; + char comment[128]; + int i = 2; + int maxval; + int bpp; + uint16_t *p; + + line[127] = '\0'; + + // get the magic code + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + if ( line[0] != 'P' || line[1] != '5' ) + break; + + // skip white space and see if a new line must be fetched + for ( i = 2; i < 127 && line[i] != '\0' && isspace( line[i] ); i++ ); + if ( ( line[i] == '\0' || line[i] == '#' ) && fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + // get the dimensions + if ( line[0] == 'P' ) + i = sscanf( line, "P5 %d %d %d", width, height, &maxval ); + else + i = sscanf( line, "%d %d %d", width, height, &maxval ); + + // get the height value, if not yet + if ( i < 2 ) + { + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + i = sscanf( line, "%d", height ); + if ( i == 0 ) + break; + else + i = 2; + } + + // get the maximum gray value, if not yet + if ( i < 3 ) + { + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + i = sscanf( line, "%d", &maxval ); + if ( i == 0 ) + break; + } + + // determine if this is one or two bytes per pixel + bpp = maxval > 255 ? 2 : 1; + + // allocate temporary storage for the raw data + data = mlt_pool_alloc( *width * *height * bpp ); + if ( data == NULL ) + break; + + // read the raw data + if ( fread( data, *width * *height * bpp, 1, f ) != 1 ) + break; + + // allocate the luma bitmap + *map = p = (uint16_t*)mlt_pool_alloc( *width * *height * sizeof( uint16_t ) ); + if ( *map == NULL ) + break; + + // proces the raw data into the luma bitmap + for ( i = 0; i < *width * *height * bpp; i += bpp ) + { + if ( bpp == 1 ) + *p++ = data[ i ] << 8; + else + *p++ = ( data[ i ] << 8 ) + data[ i + 1 ]; + } + + break; + } + + if ( data != NULL ) + mlt_pool_release( data ); +} + +/** Generate a luma map from any YUV image. +*/ + +static void luma_read_yuv422( uint8_t *image, uint16_t **map, int width, int height ) +{ + int i; + + // allocate the luma bitmap + uint16_t *p = *map = ( uint16_t* )mlt_pool_alloc( width * height * sizeof( uint16_t ) ); + if ( *map == NULL ) + return; + + // proces the image data into the luma bitmap + for ( i = 0; i < width * height * 2; i += 2 ) + *p++ = ( image[ i ] - 16 ) * 299; // 299 = 65535 / 219 +} + +static inline int calculate_mix( uint16_t *luma, int j, int soft, int weight, int alpha ) +{ + return ( ( ( luma == NULL ) ? weight : smoothstep( luma[ j ], luma[ j ] + soft, weight + soft ) ) * alpha ) >> 8; +} + +static inline uint8_t sample_mix( uint8_t dest, uint8_t src, int mix ) +{ + return ( src * mix + dest * ( ( 1 << 16 ) - mix ) ) >> 16; +} + +/** Composite a source line over a destination line +*/ + +static void composite_line_yuv( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft ) +{ + register int j; + register int mix; + + for ( j = 0; j < width; j ++ ) + { + mix = calculate_mix( luma, j, soft, weight, *alpha_b ++ ); + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *alpha_a = ( mix >> 8 ) | *alpha_a; + alpha_a ++; + } +} + +static void composite_line_yuv_or( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft ) +{ + register int j; + register int mix; + + for ( j = 0; j < width; j ++ ) + { + mix = calculate_mix( luma, j, soft, weight, *alpha_b ++ | *alpha_a ); + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *alpha_a ++ = mix >> 8; + } +} + +static void composite_line_yuv_and( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft ) +{ + register int j; + register int mix; + + for ( j = 0; j < width; j ++ ) + { + mix = calculate_mix( luma, j, soft, weight, *alpha_b ++ & *alpha_a ); + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *alpha_a ++ = mix >> 8; + } +} + +static void composite_line_yuv_xor( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft ) +{ + register int j; + register int mix; + + for ( j = 0; j < width; j ++ ) + { + mix = calculate_mix( luma, j, soft, weight, *alpha_b ++ ^ *alpha_a ); + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *dest = sample_mix( *dest, *src++, mix ); + dest++; + *alpha_a ++ = mix >> 8; + } +} + +/** Composite function. +*/ + +static int composite_yuv( uint8_t *p_dest, int width_dest, int height_dest, uint8_t *p_src, int width_src, int height_src, uint8_t *alpha_b, uint8_t *alpha_a, struct geometry_s geometry, int field, uint16_t *p_luma, int32_t softness, composite_line_fn line_fn ) +{ + int ret = 0; + int i; + int x_src = 0, y_src = 0; + int32_t weight = ( ( 1 << 16 ) - 1 ) * ( geometry.item.mix / 100 ); + int step = ( field > -1 ) ? 2 : 1; + int bpp = 2; + int stride_src = width_src * bpp; + int stride_dest = width_dest * bpp; + + // Adjust to consumer scale + int x = rint( 0.5 + geometry.item.x * width_dest / geometry.nw ); + int y = rint( 0.5 + geometry.item.y * height_dest / geometry.nh ); + int uneven_x = ( x % 2 ); + + // optimization points - no work to do + if ( width_src <= 0 || height_src <= 0 ) + return ret; + + if ( ( x < 0 && -x >= width_src ) || ( y < 0 && -y >= height_src ) ) + return ret; + + // crop overlay off the left edge of frame + if ( x < 0 ) + { + x_src = -x; + width_src -= x_src; + x = 0; + } + + // crop overlay beyond right edge of frame + if ( x + width_src > width_dest ) + width_src = width_dest - x; + + // crop overlay off the top edge of the frame + if ( y < 0 ) + { + y_src = -y; + height_src -= y_src; + y = 0; + } + + // crop overlay below bottom edge of frame + if ( y + height_src > height_dest ) + height_src = height_dest - y; + + // offset pointer into overlay buffer based on cropping + p_src += x_src * bpp + y_src * stride_src; + + // offset pointer into frame buffer based upon positive coordinates only! + p_dest += ( x < 0 ? 0 : x ) * bpp + ( y < 0 ? 0 : y ) * stride_dest; + + // offset pointer into alpha channel based upon cropping + alpha_b += x_src + y_src * stride_src / bpp; + alpha_a += x + y * stride_dest / bpp; + + // offset pointer into luma channel based upon cropping + if ( p_luma ) + p_luma += x_src + y_src * stride_src / bpp; + + // Assuming lower field first + // Special care is taken to make sure the b_frame is aligned to the correct field. + // field 0 = lower field and y should be odd (y is 0-based). + // field 1 = upper field and y should be even. + if ( ( field > -1 ) && ( y % 2 == field ) ) + { + if ( ( field == 1 && y < height_dest - 1 ) || ( field == 0 && y == 0 ) ) + p_dest += stride_dest; + else + p_dest -= stride_dest; + } + + // On the second field, use the other lines from b_frame + if ( field == 1 ) + { + p_src += stride_src; + alpha_b += stride_src / bpp; + alpha_a += stride_dest / bpp; + height_src--; + } + + stride_src *= step; + stride_dest *= step; + int alpha_b_stride = stride_src / bpp; + int alpha_a_stride = stride_dest / bpp; + + p_src += uneven_x * 2; + width_src -= 2 * uneven_x; + alpha_b += uneven_x; + uneven_x = 0; + + // now do the compositing only to cropped extents + for ( i = 0; i < height_src; i += step ) + { + line_fn( p_dest, p_src, width_src, alpha_b, alpha_a, weight, p_luma, softness ); + + p_src += stride_src; + p_dest += stride_dest; + alpha_b += alpha_b_stride; + alpha_a += alpha_a_stride; + if ( p_luma ) + p_luma += alpha_b_stride; + } + + return ret; +} + + +/** Scale 16bit greyscale luma map using nearest neighbor. +*/ + +static inline void +scale_luma ( uint16_t *dest_buf, int dest_width, int dest_height, const uint16_t *src_buf, int src_width, int src_height, int invert ) +{ + register int i, j; + register int x_step = ( src_width << 16 ) / dest_width; + register int y_step = ( src_height << 16 ) / dest_height; + register int x, y = 0; + + for ( i = 0; i < dest_height; i++ ) + { + const uint16_t *src = src_buf + ( y >> 16 ) * src_width; + x = 0; + + for ( j = 0; j < dest_width; j++ ) + { + *dest_buf++ = src[ x >> 16 ] ^ invert; + x += x_step; + } + y += y_step; + } +} + +static uint16_t* get_luma( mlt_properties properties, int width, int height ) +{ + // The cached luma map information + int luma_width = mlt_properties_get_int( properties, "_luma.width" ); + int luma_height = mlt_properties_get_int( properties, "_luma.height" ); + uint16_t *luma_bitmap = mlt_properties_get_data( properties, "_luma.bitmap", NULL ); + int invert = mlt_properties_get_int( properties, "luma_invert" ); + + // If the filename property changed, reload the map + char *resource = mlt_properties_get( properties, "luma" ); + + char temp[ 512 ]; + + if ( luma_width == 0 || luma_height == 0 ) + { + luma_width = width; + luma_height = height; + } + + if ( resource != NULL && strchr( resource, '%' ) ) + { + // TODO: Clean up quick and dirty compressed/existence check + FILE *test; + sprintf( temp, "%s/lumas/%s/%s", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ), strchr( resource, '%' ) + 1 ); + test = fopen( temp, "r" ); + if ( test == NULL ) + strcat( temp, ".png" ); + else + fclose( test ); + resource = temp; + } + + if ( resource != NULL && ( luma_bitmap == NULL || luma_width != width || luma_height != height ) ) + { + uint16_t *orig_bitmap = mlt_properties_get_data( properties, "_luma.orig_bitmap", NULL ); + luma_width = mlt_properties_get_int( properties, "_luma.orig_width" ); + luma_height = mlt_properties_get_int( properties, "_luma.orig_height" ); + + // Load the original luma once + if ( orig_bitmap == NULL ) + { + char *extension = strrchr( resource, '.' ); + + // See if it is a PGM + if ( extension != NULL && strcmp( extension, ".pgm" ) == 0 ) + { + // Open PGM + FILE *f = fopen( resource, "r" ); + if ( f != NULL ) + { + // Load from PGM + luma_read_pgm( f, &orig_bitmap, &luma_width, &luma_height ); + fclose( f ); + + // Remember the original size for subsequent scaling + mlt_properties_set_data( properties, "_luma.orig_bitmap", orig_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "_luma.orig_width", luma_width ); + mlt_properties_set_int( properties, "_luma.orig_height", luma_height ); + } + } + else + { + // Get the factory producer service + char *factory = mlt_properties_get( properties, "factory" ); + + // Create the producer + mlt_producer producer = mlt_factory_producer( factory, resource ); + + // If we have one + if ( producer != NULL ) + { + // Get the producer properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Ensure that we loop + mlt_properties_set( producer_properties, "eof", "loop" ); + + // Now pass all producer. properties on the transition down + mlt_properties_pass( producer_properties, properties, "luma." ); + + // We will get the alpha frame from the producer + mlt_frame luma_frame = NULL; + + // Get the luma frame + if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &luma_frame, 0 ) == 0 ) + { + uint8_t *luma_image; + mlt_image_format luma_format = mlt_image_yuv422; + + // Get image from the luma producer + mlt_properties_set( MLT_FRAME_PROPERTIES( luma_frame ), "rescale.interp", "none" ); + mlt_frame_get_image( luma_frame, &luma_image, &luma_format, &luma_width, &luma_height, 0 ); + + // Generate the luma map + if ( luma_image != NULL && luma_format == mlt_image_yuv422 ) + luma_read_yuv422( luma_image, &orig_bitmap, luma_width, luma_height ); + + // Remember the original size for subsequent scaling + mlt_properties_set_data( properties, "_luma.orig_bitmap", orig_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "_luma.orig_width", luma_width ); + mlt_properties_set_int( properties, "_luma.orig_height", luma_height ); + + // Cleanup the luma frame + mlt_frame_close( luma_frame ); + } + + // Cleanup the luma producer + mlt_producer_close( producer ); + } + } + } + // Scale luma map + luma_bitmap = mlt_pool_alloc( width * height * sizeof( uint16_t ) ); + scale_luma( luma_bitmap, width, height, orig_bitmap, luma_width, luma_height, invert * ( ( 1 << 16 ) - 1 ) ); + + // Remember the scaled luma size to prevent unnecessary scaling + mlt_properties_set_int( properties, "_luma.width", width ); + mlt_properties_set_int( properties, "_luma.height", height ); + mlt_properties_set_data( properties, "_luma.bitmap", luma_bitmap, width * height * 2, mlt_pool_release, NULL ); + } + return luma_bitmap; +} + +/** Get the properly sized image from b_frame. +*/ + +static int get_b_frame_image( mlt_transition this, mlt_frame b_frame, uint8_t **image, int *width, int *height, struct geometry_s *geometry ) +{ + int ret = 0; + mlt_image_format format = mlt_image_yuv422; + + // Get the properties objects + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + uint8_t resize_alpha = mlt_properties_get_int( b_props, "resize_alpha" ); + + if ( mlt_properties_get_int( properties, "aligned" ) && mlt_properties_get_int( properties, "distort" ) == 0 && mlt_properties_get_int( b_props, "distort" ) == 0 && geometry->item.distort == 0 ) + { + // Adjust b_frame pixel aspect + int normalised_width = geometry->item.w; + int normalised_height = geometry->item.h; + int real_width = get_value( b_props, "real_width", "width" ); + int real_height = get_value( b_props, "real_height", "height" ); + double input_ar = mlt_properties_get_double( b_props, "aspect_ratio" ); + double consumer_ar = mlt_properties_get_double( b_props, "consumer_aspect_ratio" ); + double background_ar = mlt_properties_get_double( b_props, "output_ratio" ); + double output_ar = background_ar != 0.0 ? background_ar : consumer_ar; + int scaled_width = rint( 0.5 + ( input_ar == 0.0 ? output_ar : input_ar ) / output_ar * real_width ); + int scaled_height = real_height; + + // Now ensure that our images fit in the normalised frame + if ( scaled_width > normalised_width ) + { + scaled_height = rint( 0.5 + scaled_height * normalised_width / scaled_width ); + scaled_width = normalised_width; + } + if ( scaled_height > normalised_height ) + { + scaled_width = rint( 0.5 + scaled_width * normalised_height / scaled_height ); + scaled_height = normalised_height; + } + + // Honour the fill request - this will scale the image to fill width or height while maintaining a/r + // ????: Shouln't this be the default behaviour? + if ( mlt_properties_get_int( properties, "fill" ) && scaled_width > 0 && scaled_height > 0 ) + { + if ( scaled_height < normalised_height && scaled_width * normalised_height / scaled_height <= normalised_width ) + { + scaled_width = rint( 0.5 + scaled_width * normalised_height / scaled_height ); + scaled_height = normalised_height; + } + else if ( scaled_width < normalised_width && scaled_height * normalised_width / scaled_width < normalised_height ) + { + scaled_height = rint( 0.5 + scaled_height * normalised_width / scaled_width ); + scaled_width = normalised_width; + } + } + + // Save the new scaled dimensions + geometry->sw = scaled_width; + geometry->sh = scaled_height; + } + else + { + geometry->sw = geometry->item.w; + geometry->sh = geometry->item.h; + } + + // We want to ensure that we bypass resize now... + if ( resize_alpha == 0 ) + mlt_properties_set_int( b_props, "distort", mlt_properties_get_int( properties, "distort" ) ); + + // If we're not aligned, we want a non-transparent background + if ( mlt_properties_get_int( properties, "aligned" ) == 0 ) + mlt_properties_set_int( b_props, "resize_alpha", 255 ); + + // Take into consideration alignment for optimisation (titles are a special case) + if ( !mlt_properties_get_int( properties, "titles" ) ) + alignment_calculate( geometry ); + + // Adjust to consumer scale + *width = rint( 0.5 + geometry->sw * *width / geometry->nw ); + *height = rint( 0.5 + geometry->sh * *height / geometry->nh ); + + ret = mlt_frame_get_image( b_frame, image, &format, width, height, 1 ); + + // Set the frame back + mlt_properties_set_int( b_props, "resize_alpha", resize_alpha ); + + return ret && image != NULL; +} + + +static mlt_geometry composite_calculate( mlt_transition this, struct geometry_s *result, mlt_frame a_frame, double position ) +{ + // Get the properties from the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Get the properties from the frame + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // Structures for geometry + mlt_geometry start = mlt_properties_get_data( properties, "geometries", NULL ); + + // Obtain the normalised width and height from the a_frame + int normalised_width = mlt_properties_get_int( a_props, "normalised_width" ); + int normalised_height = mlt_properties_get_int( a_props, "normalised_height" ); + + char *name = mlt_properties_get( properties, "_unique_id" ); + char key[ 256 ]; + + sprintf( key, "%s.in", name ); + if ( mlt_properties_get( a_props, key ) ) + { + sscanf( mlt_properties_get( a_props, key ), "%f,%f,%f,%f,%f,%d,%d", &result->item.x, &result->item.y, &result->item.w, &result->item.h, &result->item.mix, &result->nw, &result->nh ); + } + else + { + // Now parse the geometries + if ( start == NULL ) + { + // Parse the transitions properties + start = transition_parse_keys( this, normalised_width, normalised_height ); + + // Assign to properties to ensure we get destroyed + mlt_properties_set_data( properties, "geometries", start, 0, ( mlt_destructor )mlt_geometry_close, NULL ); + } + else + { + int length = mlt_transition_get_out( this ) - mlt_transition_get_in( this ) + 1; + double cycle = mlt_properties_get_double( properties, "cycle" ); + if ( cycle > 1 ) + length = cycle; + else if ( cycle > 0 ) + length *= cycle; + mlt_geometry_refresh( start, mlt_properties_get( properties, "geometry" ), length, normalised_width, normalised_height ); + } + + // Do the calculation + geometry_calculate( this, result, position ); + + // Assign normalised info + result->nw = normalised_width; + result->nh = normalised_height; + } + + // Now parse the alignment + result->halign = alignment_parse( mlt_properties_get( properties, "halign" ) ); + result->valign = alignment_parse( mlt_properties_get( properties, "valign" ) ); + + return start; +} + +mlt_frame composite_copy_region( mlt_transition this, mlt_frame a_frame, mlt_position frame_position ) +{ + // Create a frame to return + mlt_frame b_frame = mlt_frame_init( ); + + // Get the properties of the a frame + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // Get the properties of the b frame + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Get the position + int position = position_calculate( this, frame_position ); + + // Get the unique id of the transition + char *name = mlt_properties_get( MLT_TRANSITION_PROPERTIES( this ), "_unique_id" ); + char key[ 256 ]; + + // Destination image + uint8_t *dest = NULL; + + // Get the image and dimensions + uint8_t *image = mlt_properties_get_data( a_props, "image", NULL ); + int width = mlt_properties_get_int( a_props, "width" ); + int height = mlt_properties_get_int( a_props, "height" ); + int format = mlt_properties_get_int( a_props, "format" ); + + // Pointers for copy operation + uint8_t *p; + + // Coordinates + int w = 0; + int h = 0; + int x = 0; + int y = 0; + + int ss = 0; + int ds = 0; + + // Will need to know region to copy + struct geometry_s result; + + // Calculate the region now + composite_calculate( this, &result, a_frame, position ); + + // Need to scale down to actual dimensions + x = rint( 0.5 + result.item.x * width / result.nw ); + y = rint( 0.5 + result.item.y * height / result.nh ); + w = rint( 0.5 + result.item.w * width / result.nw ); + h = rint( 0.5 + result.item.h * height / result.nh ); + + if ( x % 2 ) + { + x --; + w ++; + } + + // Store the key + sprintf( key, "%s.in=%d,%d,%d,%d,%f,%d,%d", name, x, y, w, h, result.item.mix, width, height ); + mlt_properties_parse( a_props, key ); + sprintf( key, "%s.out=%d,%d,%d,%d,%f,%d,%d", name, x, y, w, h, result.item.mix, width, height ); + mlt_properties_parse( a_props, key ); + + ds = w * 2; + ss = width * 2; + + // Now we need to create a new destination image + dest = mlt_pool_alloc( w * h * 2 ); + + // Assign to the new frame + mlt_properties_set_data( b_props, "image", dest, w * h * 2, mlt_pool_release, NULL ); + mlt_properties_set_int( b_props, "width", w ); + mlt_properties_set_int( b_props, "height", h ); + mlt_properties_set_int( b_props, "format", format ); + + if ( y < 0 ) + { + dest += ( ds * -y ); + h += y; + y = 0; + } + + if ( y + h > height ) + h -= ( y + h - height ); + + if ( x < 0 ) + { + dest += -x * 2; + w += x; + x = 0; + } + + if ( w > 0 && h > 0 ) + { + // Copy the region of the image + p = image + y * ss + x * 2; + + while ( h -- ) + { + memcpy( dest, p, w * 2 ); + dest += ds; + p += ss; + } + } + + // Assign this position to the b frame + mlt_frame_set_position( b_frame, frame_position ); + mlt_properties_set_int( b_props, "distort", 1 ); + + // Return the frame + return b_frame; +} + +/** Get the image. +*/ + +static int transition_get_image( mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the b frame from the stack + mlt_frame b_frame = mlt_frame_pop_frame( a_frame ); + + // Get the transition from the a frame + mlt_transition this = mlt_frame_pop_service( a_frame ); + + // Get in and out + double position = mlt_deque_pop_back_double( MLT_FRAME_IMAGE_STACK( a_frame ) ); + int out = mlt_frame_pop_service_int( a_frame ); + int in = mlt_frame_pop_service_int( a_frame ); + + // Get the properties from the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // TODO: clean up always_active behaviour + if ( mlt_properties_get_int( properties, "always_active" ) ) + { + mlt_events_block( properties, properties ); + mlt_properties_set_int( properties, "in", in ); + mlt_properties_set_int( properties, "out", out ); + mlt_events_unblock( properties, properties ); + } + + // This compositer is yuv422 only + *format = mlt_image_yuv422; + + if ( b_frame != NULL ) + { + // Get the properties of the a frame + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // Get the properties of the b frame + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Structures for geometry + struct geometry_s result; + + // Calculate the position + double delta = delta_calculate( this, a_frame, position ); + + // Get the image from the b frame + uint8_t *image_b = NULL; + int width_b = *width; + int height_b = *height; + + // Vars for alphas + uint8_t *alpha_a = NULL; + uint8_t *alpha_b = NULL; + + // Composites always need scaling... defaulting to lowest + char *rescale = mlt_properties_get( a_props, "rescale.interp" ); + if ( rescale == NULL || !strcmp( rescale, "none" ) ) + rescale = "nearest"; + mlt_properties_set( a_props, "rescale.interp", rescale ); + mlt_properties_set( b_props, "rescale.interp", rescale ); + + // Do the calculation + // NB: Locks needed here since the properties are being modified + mlt_service_lock( MLT_TRANSITION_SERVICE( this ) ); + composite_calculate( this, &result, a_frame, position ); + mlt_service_unlock( MLT_TRANSITION_SERVICE( this ) ); + + // Since we are the consumer of the b_frame, we must pass along these + // consumer properties from the a_frame + mlt_properties_set_double( b_props, "consumer_deinterlace", mlt_properties_get_double( a_props, "consumer_deinterlace" ) ); + mlt_properties_set( b_props, "consumer_deinterlace_method", mlt_properties_get( a_props, "consumer_deinterlace_method" ) ); + mlt_properties_set_double( b_props, "consumer_aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + + // TODO: Dangerous/temporary optimisation - if nothing to do, then do nothing + if ( mlt_properties_get_int( properties, "no_alpha" ) && + result.item.x == 0 && result.item.y == 0 && result.item.w == *width && result.item.h == *height && result.item.mix == 100 ) + { + mlt_frame_get_image( b_frame, image, format, width, height, 1 ); + if ( !mlt_frame_is_test_card( a_frame ) ) + mlt_frame_replace_image( a_frame, *image, *format, *width, *height ); + return 0; + } + + if ( a_frame == b_frame ) + { + double aspect_ratio = mlt_frame_get_aspect_ratio( b_frame ); + get_b_frame_image( this, b_frame, &image_b, &width_b, &height_b, &result ); + alpha_b = mlt_frame_get_alpha_mask( b_frame ); + mlt_properties_set_double( a_props, "aspect_ratio", aspect_ratio ); + } + + // Get the image from the a frame + mlt_frame_get_image( a_frame, image, format, width, height, 1 ); + alpha_a = mlt_frame_get_alpha_mask( a_frame ); + + // Optimisation - no compositing required + if ( result.item.mix == 0 || ( result.item.w == 0 && result.item.h == 0 ) ) + return 0; + + // Need to keep the width/height of the a_frame on the b_frame for titling + if ( mlt_properties_get( a_props, "dest_width" ) == NULL ) + { + mlt_properties_set_int( a_props, "dest_width", *width ); + mlt_properties_set_int( a_props, "dest_height", *height ); + mlt_properties_set_int( b_props, "dest_width", *width ); + mlt_properties_set_int( b_props, "dest_height", *height ); + } + else + { + mlt_properties_set_int( b_props, "dest_width", mlt_properties_get_int( a_props, "dest_width" ) ); + mlt_properties_set_int( b_props, "dest_height", mlt_properties_get_int( a_props, "dest_height" ) ); + } + + // Special case for titling... + if ( mlt_properties_get_int( properties, "titles" ) ) + { + if ( mlt_properties_get( b_props, "rescale.interp" ) == NULL ) + mlt_properties_set( b_props, "rescale.interp", "hyper" ); + width_b = mlt_properties_get_int( a_props, "dest_width" ); + height_b = mlt_properties_get_int( a_props, "dest_height" ); + } + + if ( *image != image_b && ( image_b != NULL || get_b_frame_image( this, b_frame, &image_b, &width_b, &height_b, &result ) == 0 ) ) + { + uint8_t *dest = *image; + uint8_t *src = image_b; + int progressive = + mlt_properties_get_int( a_props, "consumer_deinterlace" ) || + mlt_properties_get_int( properties, "progressive" ); + int field; + + int32_t luma_softness = mlt_properties_get_double( properties, "softness" ) * ( 1 << 16 ); + uint16_t *luma_bitmap = get_luma( properties, width_b, height_b ); + char *operator = mlt_properties_get( properties, "operator" ); + + alpha_b = alpha_b == NULL ? mlt_frame_get_alpha_mask( b_frame ) : alpha_b; + + composite_line_fn line_fn = composite_line_yuv; + + // Replacement and override + if ( operator != NULL ) + { + if ( !strcmp( operator, "or" ) ) + line_fn = composite_line_yuv_or; + if ( !strcmp( operator, "and" ) ) + line_fn = composite_line_yuv_and; + if ( !strcmp( operator, "xor" ) ) + line_fn = composite_line_yuv_xor; + } + + // Allow the user to completely obliterate the alpha channels from both frames + if ( mlt_properties_get( properties, "alpha_a" ) ) + memset( alpha_a, mlt_properties_get_int( properties, "alpha_a" ), *width * *height ); + + if ( mlt_properties_get( properties, "alpha_b" ) ) + memset( alpha_b, mlt_properties_get_int( properties, "alpha_b" ), width_b * height_b ); + + for ( field = 0; field < ( progressive ? 1 : 2 ); field++ ) + { + // Assume lower field (0) first + double field_position = position + field * delta; + + // Do the calculation if we need to + // NB: Locks needed here since the properties are being modified + mlt_service_lock( MLT_TRANSITION_SERVICE( this ) ); + composite_calculate( this, &result, a_frame, field_position ); + mlt_service_unlock( MLT_TRANSITION_SERVICE( this ) ); + + if ( mlt_properties_get_int( properties, "titles" ) ) + { + result.item.w = rint( 0.5 + *width * ( result.item.w / result.nw ) ); + result.nw = result.item.w; + result.item.h = rint( 0.5 + *height * ( result.item.h / result.nh ) ); + result.nh = *height; + result.sw = width_b; + result.sh = height_b; + } + + // Align + alignment_calculate( &result ); + + // Composite the b_frame on the a_frame + composite_yuv( dest, *width, *height, src, width_b, height_b, alpha_b, alpha_a, result, progressive ? -1 : field, luma_bitmap, luma_softness, line_fn ); + } + } + } + else + { + mlt_frame_get_image( a_frame, image, format, width, height, 1 ); + } + + return 0; +} + +/** Composition transition processing. +*/ + +static mlt_frame composite_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +{ + // UGH - this is a TODO - find a more reliable means of obtaining in/out for the always_active case + if ( mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "always_active" ) == 0 ) + { + mlt_frame_push_service_int( a_frame, mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "in" ) ); + mlt_frame_push_service_int( a_frame, mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "out" ) ); + mlt_deque_push_back_double( MLT_FRAME_IMAGE_STACK( a_frame ), position_calculate( this, mlt_frame_get_position( a_frame ) ) ); + } + else + { + mlt_properties props = mlt_properties_get_data( MLT_FRAME_PROPERTIES( b_frame ), "_producer", NULL ); + mlt_frame_push_service_int( a_frame, mlt_properties_get_int( props, "in" ) ); + mlt_frame_push_service_int( a_frame, mlt_properties_get_int( props, "out" ) ); + mlt_deque_push_back_double( MLT_FRAME_IMAGE_STACK( a_frame ), mlt_properties_get_int( props, "_frame" ) - mlt_properties_get_int( props, "in" ) ); + } + + mlt_frame_push_service( a_frame, this ); + mlt_frame_push_frame( a_frame, b_frame ); + mlt_frame_push_get_image( a_frame, transition_get_image ); + return a_frame; +} + +/** Constructor for the filter. +*/ + +mlt_transition transition_composite_init( char *arg ) +{ + mlt_transition this = calloc( sizeof( struct mlt_transition_s ), 1 ); + if ( this != NULL && mlt_transition_init( this, NULL ) == 0 ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + this->process = composite_process; + + // Default starting motion and zoom + mlt_properties_set( properties, "start", arg != NULL ? arg : "0,0:100%x100%" ); + + // Default factory + mlt_properties_set( properties, "factory", "fezzik" ); + + // Use alignment (and hence alpha of b frame) + mlt_properties_set_int( properties, "aligned", 1 ); + + // Inform apps and framework that this is a video only transition + mlt_properties_set_int( properties, "_transition_type", 1 ); + } + return this; +} diff --git a/src/modules/core/transition_composite.h b/src/modules/core/transition_composite.h new file mode 100644 index 0000000..984304c --- /dev/null +++ b/src/modules/core/transition_composite.h @@ -0,0 +1,31 @@ +/* + * transition_composite.h -- compose one image over another using alpha channel + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TRANSITION_COMPOSITE_H_ +#define _TRANSITION_COMPOSITE_H_ + +#include + +extern mlt_transition transition_composite_init( char *arg ); + +// Courtesy functionality - allows regionalised filtering +extern mlt_frame composite_copy_region( mlt_transition, mlt_frame, mlt_position ); + +#endif diff --git a/src/modules/core/transition_luma.c b/src/modules/core/transition_luma.c new file mode 100644 index 0000000..194fba1 --- /dev/null +++ b/src/modules/core/transition_luma.c @@ -0,0 +1,586 @@ +/* + * transition_luma.c -- a generic dissolve/wipe processor + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * Adapted from Kino Plugin Timfx, which is + * Copyright (C) 2002 Timothy M. Shead + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "transition_luma.h" +#include + +#include +#include +#include +#include +#include + +/** Calculate the position for this frame. +*/ + +static float position_calculate( mlt_transition this, mlt_frame frame ) +{ + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + mlt_position out = mlt_transition_get_out( this ); + + // Get the position of the frame + char *name = mlt_properties_get( MLT_TRANSITION_PROPERTIES( this ), "_unique_id" ); + mlt_position position = mlt_properties_get_position( MLT_FRAME_PROPERTIES( frame ), name ); + + // Now do the calcs + return ( float )( position - in ) / ( float )( out - in + 1 ); +} + +/** Calculate the field delta for this frame - position between two frames. +*/ + +static float delta_calculate( mlt_transition this, mlt_frame frame ) +{ + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + mlt_position out = mlt_transition_get_out( this ); + + // Get the position of the frame + mlt_position position = mlt_frame_get_position( frame ); + + // Now do the calcs + float x = ( float )( position - in ) / ( float )( out - in + 1 ); + float y = ( float )( position + 1 - in ) / ( float )( out - in + 1 ); + + return ( y - x ) / 2.0; +} + +static inline int dissolve_yuv( mlt_frame this, mlt_frame that, float weight, int width, int height ) +{ + int ret = 0; + int width_src = width, height_src = height; + mlt_image_format format = mlt_image_yuv422; + uint8_t *p_src, *p_dest; + uint8_t *p, *q; + uint8_t *limit; + uint8_t *alpha_src; + uint8_t *alpha_dst; + + int32_t weigh = weight * ( 1 << 16 ); + int32_t weigh_complement = ( 1 - weight ) * ( 1 << 16 ); + + if ( mlt_properties_get( &this->parent, "distort" ) ) + mlt_properties_set( &that->parent, "distort", mlt_properties_get( &this->parent, "distort" ) ); + mlt_properties_set_int( &that->parent, "consumer_deinterlace", mlt_properties_get_int( &this->parent, "consumer_deinterlace" ) ); + mlt_frame_get_image( this, &p_dest, &format, &width, &height, 1 ); + alpha_dst = mlt_frame_get_alpha_mask( this ); + mlt_frame_get_image( that, &p_src, &format, &width_src, &height_src, 0 ); + alpha_src = mlt_frame_get_alpha_mask( that ); + + // Pick the lesser of two evils ;-) + width_src = width_src > width ? width : width_src; + height_src = height_src > height ? height : height_src; + + p = p_dest; + q = alpha_dst; + limit = p_dest + height_src * width_src * 2; + + while ( p < limit ) + { + *p_dest++ = ( *p_src++ * weigh + *p++ * weigh_complement ) >> 16; + *p_dest++ = ( *p_src++ * weigh + *p++ * weigh_complement ) >> 16; + *alpha_dst++ = ( *alpha_src++ * weigh + *q++ * weigh_complement ) >> 16; + } + + return ret; +} + +// image processing functions + +static inline int32_t smoothstep( int32_t edge1, int32_t edge2, uint32_t a ) +{ + if ( a < edge1 ) + return 0; + + if ( a >= edge2 ) + return 0x10000; + + a = ( ( a - edge1 ) << 16 ) / ( edge2 - edge1 ); + + return ( ( ( a * a ) >> 16 ) * ( ( 3 << 16 ) - ( 2 * a ) ) ) >> 16; +} + +/** powerful stuff + + \param field_order -1 = progressive, 0 = lower field first, 1 = top field first +*/ +static void luma_composite( mlt_frame a_frame, mlt_frame b_frame, int luma_width, int luma_height, + uint16_t *luma_bitmap, float pos, float frame_delta, float softness, int field_order, + int *width, int *height ) +{ + int width_src = *width, height_src = *height; + int width_dest = *width, height_dest = *height; + mlt_image_format format_src = mlt_image_yuv422, format_dest = mlt_image_yuv422; + uint8_t *p_src, *p_dest; + int i, j; + int stride_src; + int stride_dest; + uint16_t weight = 0; + + format_src = mlt_image_yuv422; + format_dest = mlt_image_yuv422; + + if ( mlt_properties_get( &a_frame->parent, "distort" ) ) + mlt_properties_set( &b_frame->parent, "distort", mlt_properties_get( &a_frame->parent, "distort" ) ); + mlt_properties_set_int( &b_frame->parent, "consumer_deinterlace", mlt_properties_get_int( &a_frame->parent, "consumer_deinterlace" ) ); + mlt_frame_get_image( a_frame, &p_dest, &format_dest, &width_dest, &height_dest, 1 ); + mlt_frame_get_image( b_frame, &p_src, &format_src, &width_src, &height_src, 0 ); + + // Pick the lesser of two evils ;-) + width_src = width_src > width_dest ? width_dest : width_src; + height_src = height_src > height_dest ? height_dest : height_src; + + stride_src = width_src * 2; + stride_dest = width_dest * 2; + + // Offset the position based on which field we're looking at ... + int32_t field_pos[ 2 ]; + field_pos[ 0 ] = ( pos + ( ( field_order == 0 ? 1 : 0 ) * frame_delta * 0.5 ) ) * ( 1 << 16 ) * ( 1.0 + softness ); + field_pos[ 1 ] = ( pos + ( ( field_order == 0 ? 0 : 1 ) * frame_delta * 0.5 ) ) * ( 1 << 16 ) * ( 1.0 + softness ); + + register uint8_t *p; + register uint8_t *q; + register uint8_t *o; + uint16_t *l; + + uint32_t value; + + int32_t x_diff = ( luma_width << 16 ) / *width; + int32_t y_diff = ( luma_height << 16 ) / *height; + int32_t x_offset = 0; + int32_t y_offset = 0; + uint8_t *p_row; + uint8_t *q_row; + + int32_t i_softness = softness * ( 1 << 16 ); + + int field_count = field_order < 0 ? 1 : 2; + int field_stride_src = field_count * stride_src; + int field_stride_dest = field_count * stride_dest; + int field = 0; + + // composite using luma map + while ( field < field_count ) + { + p_row = p_src + field * stride_src; + q_row = p_dest + field * stride_dest; + y_offset = field << 16; + i = field; + + while ( i < height_src ) + { + p = p_row; + q = q_row; + o = q; + l = luma_bitmap + ( y_offset >> 16 ) * ( luma_width * field_count ); + x_offset = 0; + j = width_src; + + while( j -- ) + { + weight = l[ x_offset >> 16 ]; + value = smoothstep( weight, i_softness + weight, field_pos[ field ] ); + *o ++ = ( *p ++ * value + *q++ * ( ( 1 << 16 ) - value ) ) >> 16; + *o ++ = ( *p ++ * value + *q++ * ( ( 1 << 16 ) - value ) ) >> 16; + x_offset += x_diff; + } + + y_offset += y_diff; + i += field_count; + p_row += field_stride_src; + q_row += field_stride_dest; + } + + field ++; + } +} + +/** Load the luma map from PGM stream. +*/ + +static void luma_read_pgm( FILE *f, uint16_t **map, int *width, int *height ) +{ + uint8_t *data = NULL; + while (1) + { + char line[128]; + char comment[128]; + int i = 2; + int maxval; + int bpp; + uint16_t *p; + + line[127] = '\0'; + + // get the magic code + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + if ( line[0] != 'P' || line[1] != '5' ) + break; + + // skip white space and see if a new line must be fetched + for ( i = 2; i < 127 && line[i] != '\0' && isspace( line[i] ); i++ ); + if ( ( line[i] == '\0' || line[i] == '#' ) && fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + // get the dimensions + if ( line[0] == 'P' ) + i = sscanf( line, "P5 %d %d %d", width, height, &maxval ); + else + i = sscanf( line, "%d %d %d", width, height, &maxval ); + + // get the height value, if not yet + if ( i < 2 ) + { + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + i = sscanf( line, "%d", height ); + if ( i == 0 ) + break; + else + i = 2; + } + + // get the maximum gray value, if not yet + if ( i < 3 ) + { + if ( fgets( line, 127, f ) == NULL ) + break; + + // skip comments + while ( sscanf( line, " #%s", comment ) > 0 ) + if ( fgets( line, 127, f ) == NULL ) + break; + + i = sscanf( line, "%d", &maxval ); + if ( i == 0 ) + break; + } + + // determine if this is one or two bytes per pixel + bpp = maxval > 255 ? 2 : 1; + + // allocate temporary storage for the raw data + data = mlt_pool_alloc( *width * *height * bpp ); + if ( data == NULL ) + break; + + // read the raw data + if ( fread( data, *width * *height * bpp, 1, f ) != 1 ) + break; + + // allocate the luma bitmap + *map = p = (uint16_t*)mlt_pool_alloc( *width * *height * sizeof( uint16_t ) ); + if ( *map == NULL ) + break; + + // proces the raw data into the luma bitmap + for ( i = 0; i < *width * *height * bpp; i += bpp ) + { + if ( bpp == 1 ) + *p++ = data[ i ] << 8; + else + *p++ = ( data[ i ] << 8 ) + data[ i+1 ]; + } + + break; + } + + if ( data != NULL ) + mlt_pool_release( data ); +} + +/** Generate a luma map from an RGB image. +*/ + +static void luma_read_yuv422( uint8_t *image, uint16_t **map, int width, int height ) +{ + int i; + int size = width * height * 2; + + // allocate the luma bitmap + uint16_t *p = *map = ( uint16_t* )mlt_pool_alloc( width * height * sizeof( uint16_t ) ); + if ( *map == NULL ) + return; + + // proces the image data into the luma bitmap + for ( i = 0; i < size; i += 2 ) + *p++ = ( image[ i ] - 16 ) * 299; // 299 = 65535 / 219 +} + +/** Generate a luma map from a YUV image. +*/ +static void luma_read_rgb24( uint8_t *image, uint16_t **map, int width, int height ) +{ +} + +/** Get the image. +*/ + +static int transition_get_image( mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the b frame from the stack + mlt_frame b_frame = mlt_frame_pop_frame( a_frame ); + + // Get the transition object + mlt_transition transition = mlt_frame_pop_service( a_frame ); + + // Get the properties of the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( transition ); + + // Get the properties of the a frame + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // Get the properties of the b frame + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // This compositer is yuv422 only + *format = mlt_image_yuv422; + + // The cached luma map information + int luma_width = mlt_properties_get_int( properties, "width" ); + int luma_height = mlt_properties_get_int( properties, "height" ); + uint16_t *luma_bitmap = mlt_properties_get_data( properties, "bitmap", NULL ); + + // If the filename property changed, reload the map + char *resource = mlt_properties_get( properties, "resource" ); + + // Correct width/height if not specified + if ( luma_width == 0 || luma_height == 0 ) + { + luma_width = mlt_properties_get_int( a_props, "width" ); + luma_height = mlt_properties_get_int( a_props, "height" ); + } + + if ( luma_bitmap == NULL && resource != NULL ) + { + char temp[ 512 ]; + char *extension = strrchr( resource, '.' ); + + if ( strchr( resource, '%' ) ) + { + FILE *test; + sprintf( temp, "%s/lumas/%s/%s", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ), strchr( resource, '%' ) + 1 ); + test = fopen( temp, "r" ); + if ( test == NULL ) + strcat( temp, ".png" ); + else + fclose( test ); + resource = temp; + extension = strrchr( resource, '.' ); + } + + // See if it is a PGM + if ( extension != NULL && strcmp( extension, ".pgm" ) == 0 ) + { + // Open PGM + FILE *f = fopen( resource, "r" ); + if ( f != NULL ) + { + // Load from PGM + luma_read_pgm( f, &luma_bitmap, &luma_width, &luma_height ); + fclose( f ); + + // Set the transition properties + mlt_properties_set_int( properties, "width", luma_width ); + mlt_properties_set_int( properties, "height", luma_height ); + mlt_properties_set_data( properties, "bitmap", luma_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL ); + } + } + else + { + // Get the factory producer service + char *factory = mlt_properties_get( properties, "factory" ); + + // Create the producer + mlt_producer producer = mlt_factory_producer( factory, resource ); + + // If we have one + if ( producer != NULL ) + { + // Get the producer properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Ensure that we loop + mlt_properties_set( producer_properties, "eof", "loop" ); + + // Now pass all producer. properties on the transition down + mlt_properties_pass( producer_properties, properties, "producer." ); + + // We will get the alpha frame from the producer + mlt_frame luma_frame = NULL; + + // Get the luma frame + if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &luma_frame, 0 ) == 0 ) + { + uint8_t *luma_image = NULL; + mlt_image_format luma_format = mlt_image_yuv422; + + // Get image from the luma producer + mlt_properties_set( MLT_FRAME_PROPERTIES( luma_frame ), "rescale.interp", "nearest" ); + mlt_frame_get_image( luma_frame, &luma_image, &luma_format, &luma_width, &luma_height, 0 ); + + // Generate the luma map + if ( luma_image != NULL && luma_format == mlt_image_yuv422 ) + luma_read_yuv422( luma_image, &luma_bitmap, luma_width, luma_height ); + + else if ( luma_image != NULL && luma_format == mlt_image_rgb24 ) + luma_read_rgb24( luma_image, &luma_bitmap, luma_width, luma_height ); + + // Set the transition properties + mlt_properties_set_int( properties, "width", luma_width ); + mlt_properties_set_int( properties, "height", luma_height ); + mlt_properties_set_data( properties, "bitmap", luma_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL ); + + // Cleanup the luma frame + mlt_frame_close( luma_frame ); + } + + // Cleanup the luma producer + mlt_producer_close( producer ); + } + } + } + + // Arbitrary composite defaults + float mix = position_calculate( transition, a_frame ); + float frame_delta = delta_calculate( transition, a_frame ); + + float luma_softness = mlt_properties_get_double( properties, "softness" ); + int progressive = + mlt_properties_get_int( a_props, "consumer_deinterlace" ) || + mlt_properties_get_int( properties, "progressive" ) || + mlt_properties_get_int( b_props, "luma.progressive" ); + int top_field_first = mlt_properties_get_int( b_props, "top_field_first" ); + int reverse = mlt_properties_get_int( properties, "reverse" ); + int invert = mlt_properties_get_int( properties, "invert" ); + + if ( mlt_properties_get( a_props, "rescale.interp" ) == NULL || !strcmp( mlt_properties_get( a_props, "rescale.interp" ), "none" ) ) + mlt_properties_set( a_props, "rescale.interp", "nearest" ); + + // Since we are the consumer of the b_frame, we must pass along this + // consumer property from the a_frame + if ( mlt_properties_get_double( a_props, "aspect_ratio" ) == 0.0 ) + mlt_properties_set_double( a_props, "aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + if ( mlt_properties_get_double( b_props, "aspect_ratio" ) == 0.0 ) + mlt_properties_set_double( b_props, "aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + mlt_properties_set_double( b_props, "consumer_aspect_ratio", mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ); + + // Honour the reverse here + if ( mix >= 1.0 ) + mix -= floor( mix ); + + mix = reverse || invert ? 1 - mix : mix; + frame_delta *= reverse || invert ? -1.0 : 1.0; + + // Ensure we get scaling on the b_frame + if ( mlt_properties_get( b_props, "rescale.interp" ) == NULL || !strcmp( mlt_properties_get( b_props, "rescale.interp" ), "none" ) ) + mlt_properties_set( b_props, "rescale.interp", "nearest" ); + + if ( mlt_properties_get( properties, "fixed" ) ) + mix = mlt_properties_get_double( properties, "fixed" ); + + + if ( luma_width > 0 && luma_height > 0 && luma_bitmap != NULL ) + // Composite the frames using a luma map + luma_composite( !invert ? a_frame : b_frame, !invert ? b_frame : a_frame, luma_width, luma_height, luma_bitmap, mix, frame_delta, + luma_softness, progressive ? -1 : top_field_first, width, height ); + else + // Dissolve the frames using the time offset for mix value + dissolve_yuv( a_frame, b_frame, mix, *width, *height ); + + + // Extract the a_frame image info + *width = mlt_properties_get_int( !invert ? a_props : b_props, "width" ); + *height = mlt_properties_get_int( !invert ? a_props : b_props, "height" ); + *image = mlt_properties_get_data( !invert ? a_props : b_props, "image", NULL ); + + return 0; +} + + +/** Luma transition processing. +*/ + +static mlt_frame transition_process( mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame ) +{ + // Get a unique name to store the frame position + char *name = mlt_properties_get( MLT_TRANSITION_PROPERTIES( transition ), "_unique_id" ); + + // Assign the current position to the name + mlt_properties_set_position( MLT_FRAME_PROPERTIES( a_frame ), name, mlt_frame_get_position( a_frame ) ); + + // Push the transition on to the frame + mlt_frame_push_service( a_frame, transition ); + + // Push the b_frame on to the stack + mlt_frame_push_frame( a_frame, b_frame ); + + // Push the transition method + mlt_frame_push_get_image( a_frame, transition_get_image ); + + return a_frame; +} + +/** Constructor for the filter. +*/ + +mlt_transition transition_luma_init( char *lumafile ) +{ + mlt_transition transition = mlt_transition_new( ); + if ( transition != NULL ) + { + // Set the methods + transition->process = transition_process; + + // Default factory + mlt_properties_set( MLT_TRANSITION_PROPERTIES( transition ), "factory", "fezzik" ); + + // Set the main property + mlt_properties_set( MLT_TRANSITION_PROPERTIES( transition ), "resource", lumafile ); + + // Inform apps and framework that this is a video only transition + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "_transition_type", 1 ); + + return transition; + } + return NULL; +} diff --git a/src/modules/core/transition_luma.h b/src/modules/core/transition_luma.h new file mode 100644 index 0000000..e01c88c --- /dev/null +++ b/src/modules/core/transition_luma.h @@ -0,0 +1,28 @@ +/* + * transition_luma.h -- a generic dissolve/wipe processor + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TRANSITION_LUMA_H_ +#define _TRANSITION_LUMA_H_ + +#include + +extern mlt_transition transition_luma_init( char *lumafile ); + +#endif diff --git a/src/modules/core/transition_mix.c b/src/modules/core/transition_mix.c new file mode 100644 index 0000000..a24cf73 --- /dev/null +++ b/src/modules/core/transition_mix.c @@ -0,0 +1,166 @@ +/* + * transition_mix.c -- mix two audio streams + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "transition_mix.h" +#include + +#include +#include + + +/** Get the audio. +*/ + +static int transition_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the b frame from the stack + mlt_frame b_frame = mlt_frame_pop_audio( frame ); + + // Get the effect + mlt_transition effect = mlt_frame_pop_audio( frame ); + + // Get the properties of the b frame + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + if ( mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( effect ), "combine" ) == 0 ) + { + double mix_start = 0.5, mix_end = 0.5; + if ( mlt_properties_get( b_props, "audio.previous_mix" ) != NULL ) + mix_start = mlt_properties_get_double( b_props, "audio.previous_mix" ); + if ( mlt_properties_get( b_props, "audio.mix" ) != NULL ) + mix_end = mlt_properties_get_double( b_props, "audio.mix" ); + if ( mlt_properties_get_int( b_props, "audio.reverse" ) ) + { + mix_start = 1 - mix_start; + mix_end = 1 - mix_end; + } + + mlt_frame_mix_audio( frame, b_frame, mix_start, mix_end, buffer, format, frequency, channels, samples ); + } + else + { + mlt_frame_combine_audio( frame, b_frame, buffer, format, frequency, channels, samples ); + } + + return 0; +} + + +/** Mix transition processing. +*/ + +static mlt_frame transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Only if mix is specified, otherwise a producer may set the mix + if ( mlt_properties_get( properties, "start" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_properties props = mlt_properties_get_data( MLT_FRAME_PROPERTIES( b_frame ), "_producer", NULL ); + int always_active = mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "always_active" ); + mlt_position in = !always_active ? mlt_transition_get_in( this ) : mlt_properties_get_int( props, "in" ); + mlt_position out = !always_active ? mlt_transition_get_out( this ) : mlt_properties_get_int( props, "out" ); + int length = mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "length" ); + mlt_position time = !always_active ? mlt_frame_get_position( b_frame ) : mlt_properties_get_int( props, "_frame" ); + double mix = ( double )( time - in ) / ( double )( out - in + 1 ); + + // TODO: Check the logic here - shouldn't we be computing current and next mixing levels in all cases? + if ( length == 0 ) + { + // If there is an end mix level adjust mix to the range + if ( mlt_properties_get( properties, "end" ) != NULL ) + { + double start = mlt_properties_get_double( properties, "start" ); + double end = mlt_properties_get_double( properties, "end" ); + mix = start + ( end - start ) * mix; + } + // A negative means total crossfade (uses position) + else if ( mlt_properties_get_double( properties, "start" ) >= 0 ) + { + // Otherwise, start/constructor is a constant mix level + mix = mlt_properties_get_double( properties, "start" ); + } + + // Finally, set the mix property on the frame + mlt_properties_set_double( b_props, "audio.mix", mix ); + + // Initialise transition previous mix value to prevent an inadvertant jump from 0 + if ( mlt_properties_get( properties, "previous_mix" ) == NULL ) + mlt_properties_set_double( properties, "previous_mix", mlt_properties_get_double( b_props, "audio.mix" ) ); + + // Tell b frame what the previous mix level was + mlt_properties_set_double( b_props, "audio.previous_mix", mlt_properties_get_double( properties, "previous_mix" ) ); + + // Save the current mix level for the next iteration + mlt_properties_set_double( properties, "previous_mix", mlt_properties_get_double( b_props, "audio.mix" ) ); + + mlt_properties_set_double( b_props, "audio.reverse", mlt_properties_get_double( properties, "reverse" ) ); + } + else + { + double level = mlt_properties_get_double( properties, "start" ); + double mix_start = level; + double mix_end = mix_start; + double mix_increment = 1.0 / length; + if ( time - in < length ) + { + mix_start = mix_start * ( ( double )( time - in ) / length ); + mix_end = mix_start + mix_increment; + } + else if ( time > out - length ) + { + mix_end = mix_start * ( ( double )( out - time - in ) / length ); + mix_start = mix_end - mix_increment; + } + + mix_start = mix_start < 0 ? 0 : mix_start > level ? level : mix_start; + mix_end = mix_end < 0 ? 0 : mix_end > level ? level : mix_end; + mlt_properties_set_double( b_props, "audio.previous_mix", mix_start ); + mlt_properties_set_double( b_props, "audio.mix", mix_end ); + } + } + + // Override the get_audio method + mlt_frame_push_audio( a_frame, this ); + mlt_frame_push_audio( a_frame, b_frame ); + mlt_frame_push_audio( a_frame, transition_get_audio ); + + return a_frame; +} + +/** Constructor for the transition. +*/ + +mlt_transition transition_mix_init( char *arg ) +{ + mlt_transition this = calloc( sizeof( struct mlt_transition_s ), 1 ); + if ( this != NULL && mlt_transition_init( this, NULL ) == 0 ) + { + this->process = transition_process; + if ( arg != NULL ) + mlt_properties_set_double( MLT_TRANSITION_PROPERTIES( this ), "start", atof( arg ) ); + // Inform apps and framework that this is an audio only transition + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( this ), "_transition_type", 2 ); + } + return this; +} + diff --git a/src/modules/core/transition_mix.h b/src/modules/core/transition_mix.h new file mode 100644 index 0000000..5c5c9aa --- /dev/null +++ b/src/modules/core/transition_mix.h @@ -0,0 +1,28 @@ +/* + * transition_mix.h -- mix two audio streams + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TRANSITION_MIX_H_ +#define _TRANSITION_MIX_H_ + +#include + +extern mlt_transition transition_mix_init( char *arg ); + +#endif diff --git a/src/modules/core/transition_region.c b/src/modules/core/transition_region.c new file mode 100644 index 0000000..e57ac93 --- /dev/null +++ b/src/modules/core/transition_region.c @@ -0,0 +1,452 @@ +/* + * transition_region.c -- region transition + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "transition_region.h" +#include "transition_composite.h" + +#include + +#include +#include +#include + +static int create_instance( mlt_transition this, char *name, char *value, int count ) +{ + // Return from this function + int error = 0; + + // Duplicate the value + char *type = strdup( value ); + + // Pointer to filter argument + char *arg = type == NULL ? NULL : strchr( type, ':' ); + + // New filter being created + mlt_filter filter = NULL; + + // Cleanup type and arg + if ( arg != NULL ) + *arg ++ = '\0'; + + // Create the filter + filter = mlt_factory_filter( type, arg ); + + // If we have a filter, then initialise and store it + if ( filter != NULL ) + { + // Properties of this + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // String to hold the property name + char id[ 256 ]; + + // String to hold the passdown key + char key[ 256 ]; + + // Construct id + sprintf( id, "_filter_%d", count ); + + // Counstruct key + sprintf( key, "%s.", name ); + + // Just in case, let's assume that the filter here has a composite + //mlt_properties_set( MLT_FILTER_PROPERTIES( filter ), "composite.geometry", "0%,0%:100%x100%" ); + //mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "composite.fill", 1 ); + + // Pass all the key properties on the filter down + mlt_properties_pass( MLT_FILTER_PROPERTIES( filter ), properties, key ); + + // Ensure that filter is assigned + mlt_properties_set_data( properties, id, filter, 0, ( mlt_destructor )mlt_filter_close, NULL ); + } + else + { + // Indicate that an error has occurred + error = 1; + } + + // Cleanup + free( type ); + + // Return error condition + return error; +} + +static uint8_t *filter_get_alpha_mask( mlt_frame this ) +{ + uint8_t *alpha = NULL; + + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the shape frame + mlt_frame shape_frame = mlt_properties_get_data( properties, "shape_frame", NULL ); + + // Get the width and height of the image + int region_width = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "width" ); + int region_height = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "height" ); + uint8_t *image = NULL; + mlt_image_format format = mlt_image_yuv422; + + // Get the shape image to trigger alpha creation + mlt_properties_set_int( MLT_FRAME_PROPERTIES( shape_frame ), "distort", 1 ); + mlt_frame_get_image( shape_frame, &image, &format, ®ion_width, ®ion_height, 0 ); + + alpha = mlt_frame_get_alpha_mask( shape_frame ); + + // Generate from the Y component of the image if no alpha available + if ( alpha == NULL ) + { + int size = region_width * region_height; + uint8_t *p = mlt_pool_alloc( size ); + alpha = p; + while ( size -- ) + { + *p ++ = ( int )( ( ( *image ++ - 16 ) * 299 ) / 255 ); + image ++; + } + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "alpha", alpha, region_width * region_height, mlt_pool_release, NULL ); + } + else + { + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "alpha", alpha, region_width * region_height, NULL, NULL ); + } + + this->get_alpha_mask = NULL; + + return alpha; +} + +/** Do it :-). +*/ + +static int transition_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Error we will return + int error = 0; + + // We will get the 'b frame' from the frame stack + mlt_frame b_frame = mlt_frame_pop_frame( frame ); + + // Get the watermark transition object + mlt_transition this = mlt_frame_pop_service( frame ); + + // Get the properties of the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Get the composite from the transition + mlt_transition composite = mlt_properties_get_data( properties, "composite", NULL ); + + // Look for the first filter + mlt_filter filter = mlt_properties_get_data( properties, "_filter_0", NULL ); + + // Get the unique id of the filter (used to reacquire the producer position) + char *name = mlt_properties_get( properties, "_unique_id" ); + + // Get the original producer position + mlt_position position = mlt_properties_get_position( MLT_FRAME_PROPERTIES( frame ), name ); + + // Create a composite if we don't have one + if ( composite == NULL ) + { + // Create composite via the factory + composite = mlt_factory_transition( "composite", NULL ); + + // If we have one + if ( composite != NULL ) + { + // Get the properties + mlt_properties composite_properties = MLT_TRANSITION_PROPERTIES( composite ); + + // We want to ensure that we don't get a wobble... + //mlt_properties_set_int( composite_properties, "distort", 1 ); + mlt_properties_set_int( composite_properties, "progressive", 1 ); + + // Pass all the composite. properties on the transition down + mlt_properties_pass( composite_properties, properties, "composite." ); + + // Register the composite for reuse/destruction + mlt_properties_set_data( properties, "composite", composite, 0, ( mlt_destructor )mlt_transition_close, NULL ); + } + } + else + { + // Pass all current properties down + mlt_properties composite_properties = MLT_TRANSITION_PROPERTIES( composite ); + mlt_properties_pass( composite_properties, properties, "composite." ); + } + + // Create filters + if ( filter == NULL ) + { + // Loop Variable + int i = 0; + + // Number of filters created + int count = 0; + + // Loop for all properties + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + { + // Get the name of this property + char *name = mlt_properties_get_name( properties, i ); + + // If the name does not contain a . and matches filter + if ( strchr( name, '.' ) == NULL && !strncmp( name, "filter", 6 ) ) + { + // Get the filter constructor + char *value = mlt_properties_get_value( properties, i ); + + // Create an instance + if ( create_instance( this, name, value, count ) == 0 ) + count ++; + } + } + + // Look for the first filter again + filter = mlt_properties_get_data( properties, "_filter_0", NULL ); + } + else + { + // Pass all properties down + mlt_filter temp = NULL; + + // Loop Variable + int i = 0; + + // Number of filters found + int count = 0; + + // Loop for all properties + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + { + // Get the name of this property + char *name = mlt_properties_get_name( properties, i ); + + // If the name does not contain a . and matches filter + if ( strchr( name, '.' ) == NULL && !strncmp( name, "filter", 6 ) ) + { + // Strings to hold the id and pass down key + char id[ 256 ]; + char key[ 256 ]; + + // Construct id and key + sprintf( id, "_filter_%d", count ); + sprintf( key, "%s.", name ); + + // Get the filter + temp = mlt_properties_get_data( properties, id, NULL ); + + if ( temp != NULL ) + { + mlt_properties_pass( MLT_FILTER_PROPERTIES( temp ), properties, key ); + count ++; + } + } + } + } + + // Get the image + error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + // Only continue if we have both filter and composite + if ( composite != NULL ) + { + // Get the resource of this filter (could be a shape [rectangle/circle] or an alpha provider of choice + char *resource = mlt_properties_get( properties, "resource" ); + + // Get the old resource in case it's changed + char *old_resource = mlt_properties_get( properties, "_old_resource" ); + + // String to hold the filter to query on + char id[ 256 ]; + + // Index to hold the count + int i = 0; + + // We will get the 'b frame' from the composite only if it's NULL + if ( b_frame == NULL ) + { + // Copy the region + b_frame = composite_copy_region( composite, frame, position ); + + // Ensure a destructor + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), name, b_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + + // Set the position of the b_frame + mlt_frame_set_position( b_frame, position ); + + // Make sure the filter is in the correct position + while ( filter != NULL ) + { + // Stack this filter + if ( mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "off" ) == 0 ) + mlt_filter_process( filter, b_frame ); + + // Generate the key for the next + sprintf( id, "_filter_%d", ++ i ); + + // Get the next filter + filter = mlt_properties_get_data( properties, id, NULL ); + } + + // Allow filters to be attached to a region filter + filter = mlt_properties_get_data( properties, "_region_filter", NULL ); + if ( filter != NULL ) + mlt_service_apply_filters( MLT_FILTER_SERVICE( filter ), b_frame, 0 ); + + // Hmm - this is probably going to go wrong.... + mlt_frame_set_position( frame, position ); + + // Get the b frame and process with composite if successful + mlt_transition_process( composite, frame, b_frame ); + + // If we have a shape producer copy the alpha mask from the shape frame to the b_frame + if ( strcmp( resource, "rectangle" ) != 0 ) + { + // Get the producer from the transition + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + + // If We have no producer then create one + if ( producer == NULL || ( old_resource != NULL && strcmp( resource, old_resource ) ) ) + { + // Get the factory producer service + char *factory = mlt_properties_get( properties, "factory" ); + + // Store the old resource + mlt_properties_set( properties, "_old_resource", resource ); + + // Special case circle resource + if ( strcmp( resource, "circle" ) == 0 ) + { + // Special case to ensure that fezzik produces a pixbuf with a NULL constructor + resource = "pixbuf"; + + // Specify the svg circle + mlt_properties_set( properties, "producer.resource", "" ); + } + + // Create the producer + producer = mlt_factory_producer( factory, resource ); + + // If we have one + if ( producer != NULL ) + { + // Get the producer properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Ensure that we loop + mlt_properties_set( producer_properties, "eof", "loop" ); + + // Now pass all producer. properties on the transition down + mlt_properties_pass( producer_properties, properties, "producer." ); + + // Register the producer for reuse/destruction + mlt_properties_set_data( properties, "producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + } + + // Now use the shape producer + if ( producer != NULL ) + { + // We will get the alpha frame from the producer + mlt_frame shape_frame = NULL; + + // Make sure the producer is in the correct position + mlt_producer_seek( producer, position ); + + // Get the shape frame + if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &shape_frame, 0 ) == 0 ) + { + // Ensure that the shape frame will be closed + mlt_properties_set_data( MLT_FRAME_PROPERTIES( b_frame ), "shape_frame", shape_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + + // Specify the callback for evaluation + b_frame->get_alpha_mask = filter_get_alpha_mask; + } + } + } + + // Get the image + error = mlt_frame_get_image( frame, image, format, width, height, 0 ); + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( a_frame ); + + // Get a unique name to store the frame position + char *name = mlt_properties_get( MLT_TRANSITION_PROPERTIES( this ), "_unique_id" ); + + // Assign the current position to the name + mlt_properties_set_position( properties, name, mlt_frame_get_position( a_frame ) ); + + // Push the transition on to the frame + mlt_frame_push_service( a_frame, this ); + + // Push the b_frame on to the stack + mlt_frame_push_frame( a_frame, b_frame ); + + // Push the transition method + mlt_frame_push_get_image( a_frame, transition_get_image ); + + // Return the frame + return a_frame; +} + +/** Constructor for the transition. +*/ + +mlt_transition transition_region_init( void *arg ) +{ + // Create a new transition + mlt_transition this = mlt_transition_new( ); + + // Further initialisation + if ( this != NULL ) + { + // Get the properties from the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Assign the transition process method + this->process = transition_process; + + // Default factory + mlt_properties_set( properties, "factory", "fezzik" ); + + // Resource defines the shape of the region + mlt_properties_set( properties, "resource", arg == NULL ? "rectangle" : arg ); + + // Inform apps and framework that this is a video only transition + mlt_properties_set_int( properties, "_transition_type", 1 ); + } + + // Return the transition + return this; +} + diff --git a/src/modules/core/transition_region.h b/src/modules/core/transition_region.h new file mode 100644 index 0000000..540f49b --- /dev/null +++ b/src/modules/core/transition_region.h @@ -0,0 +1,28 @@ +/* + * transition_region.h -- region transition + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TRANSITION_REGION_H_ +#define _TRANSITION_REGION_H_ + +#include + +extern mlt_transition transition_region_init( void *arg ); + +#endif diff --git a/src/modules/data_fx.properties b/src/modules/data_fx.properties new file mode 100644 index 0000000..f7dc2e9 --- /dev/null +++ b/src/modules/data_fx.properties @@ -0,0 +1,250 @@ +# This properties file describes the fx available to the data_send and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +# +# The titles filter definition +# + +titles=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,70%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 + +# +# The top titles filter definition +# + +top-titles=region +.description=Top Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,5%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.halign=centre +.filter[1].composite.titles=1 + +# +# OK - Silly example... +# + +tickertape=region +.description=Tickertape +.properties.markup=filter[1].producer.markup +.type.markup=text +.properties.length[0]=filter[1].composite.out +.composite.geometry=0%,93%:100%x7% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=100%,0%:300%x100%:100;-1=-300%,0%:300%x100%:100 +.filter[1].producer.font=San 32 +.filter[1].composite.titles=1 + +# +# ETV Location +# + +location=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,80:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=-100%,0%:100%x100%:100;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup= +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:0;24=0%,0%:100%x100%:0;49=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=center + +courtesy=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,115:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=-100%,0%:100%x100%:0;12=-100%,0%:100%x100%:0;37=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=ETV Exclusive +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:0;37=0%,0%:100%x100%:0;61=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=right + +exclusive=region +.description=Exclusive +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,115:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=ETV Exclusive +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=right + +file_shot=region +.description=Titles +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=590,160:80x25 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=File Shot +.filter[1].producer.font=San 18 +.filter[1].composite.geometry=0%,0%:100%x100%:15;25=0%,0%:100%x100%:100 +.filter[1].composite.titles=0 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +special=region +.description=Titles +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=465,375:255x35 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=100%,0%:100%x100%:0;49=100%,0%:100%x100%:0;74=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Special +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=100%,0%:100%x100%:0;49=100%,0%:100%x100%:0;74=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +ticker=region +.description=Tickertape +.properties.markup=filter[1].producer.markup +.type.markup=text +.properties.length[0]=filter[1].composite.out +.composite.geometry=0,500:722x75 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Ticker - provided for reference +.filter[1].composite.geometry=0%,0%:100%x100%:100 +.filter[1].composite.titles=0 +.filter[1].producer.font=San 24 +.filter[1].composite.halign=centre +.filter[1].composite.titles=1 +.filter[1].composite.valign=centre + +super=region +.description=Transcription +.properties.0=filter[1].producer.markup +.properties.1=filter[2].producer.markup +.properties.align=filter[1].composite.valign +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.properties.length[2]=filter[2].composite.out +.period=2 +.composite.geometry=0,410:720x90 +.filter[0]=watermark +.filter[0].resource=colour:0xbbbbbb00 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[0].composite.luma=%luma18.pgm +.filter[0].composite.out=25 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup= +.filter[1].producer.font=San 32 +.filter[1].producer.fgcolour=0x6c0101ff +.filter[1].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=top +.filter[2]=watermark +.filter[2].resource=pango: +.filter[2].producer.markup= +.filter[2].producer.font=San 32 +.filter[2].producer.fgcolour=0x6c0101ff +.filter[2].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[2].composite.titles=1 +.filter[2].composite.halign=centre +.filter[2].composite.valign=bottom + +obscure=region +.description=Obscure +.properties.geometry=composite.geometry +.properties.resource=resource +.properties.length[0]=composite.out +.composite.geometry= +.resource=rectangle +.composite.refresh=1 +.filter[0]=obscure +.filter[0].start=0,0:100%x100% + diff --git a/src/modules/disable-avformat b/src/modules/disable-avformat new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/disable-dv b/src/modules/disable-dv new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/disable-jackrack b/src/modules/disable-jackrack new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/disable-mmx b/src/modules/disable-mmx new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/dv/Makefile b/src/modules/dv/Makefile new file mode 100644 index 0000000..28a5d45 --- /dev/null +++ b/src/modules/dv/Makefile @@ -0,0 +1,36 @@ +include ../../../config.mak + +TARGET = ../libmltdv$(LIBSUF) + +OBJS = factory.o \ + producer_libdv.o \ + consumer_libdv.o + +CFLAGS += `pkg-config --cflags libdv` -I../.. + +LDFLAGS += `pkg-config --libs libdv` + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/dv/configure b/src/modules/dv/configure new file mode 100755 index 0000000..41b860a --- /dev/null +++ b/src/modules/dv/configure @@ -0,0 +1,19 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + pkg-config libdv 2> /dev/null + disable_libdv=$? + + if [ "$disable_libdv" = "0" ] + then + echo "libdv libmltdv$LIBSUF" >> ../producers.dat + echo "libdv libmltdv$LIBSUF" >> ../consumers.dat + else + echo "- libdv not found: disabling" + touch ../disable-dv + fi + +fi + diff --git a/src/modules/dv/consumer_libdv.c b/src/modules/dv/consumer_libdv.c new file mode 100644 index 0000000..434752f --- /dev/null +++ b/src/modules/dv/consumer_libdv.c @@ -0,0 +1,449 @@ +/* + * consumer_libdv.c -- a DV encoder based on libdv + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "consumer_libdv.h" +#include "producer_libdv.h" + +// mlt Header files +#include + +// System header files +#include +#include +#include +#include + +// libdv header files +#include + +// Forward references. +static int consumer_start( mlt_consumer this ); +static int consumer_stop( mlt_consumer this ); +static int consumer_is_stopped( mlt_consumer this ); +static int consumer_encode_video( mlt_consumer this, uint8_t *dv_frame, mlt_frame frame ); +static void consumer_encode_audio( mlt_consumer this, uint8_t *dv_frame, mlt_frame frame ); +static void consumer_output( mlt_consumer this, uint8_t *dv_frame, int size, mlt_frame frame ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer this ); + +/** Initialise the dv consumer. +*/ + +mlt_consumer consumer_libdv_init( char *arg ) +{ + // Allocate the consumer + mlt_consumer this = calloc( 1, sizeof( struct mlt_consumer_s ) ); + + // If memory allocated and initialises without error + if ( this != NULL && mlt_consumer_init( this, NULL ) == 0 ) + { + // Get properties from the consumer + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Assign close callback + this->close = consumer_close; + + // Interpret the argument + if ( arg != NULL ) + mlt_properties_set( properties, "target", arg ); + + // Set the encode and output handling method + mlt_properties_set_data( properties, "video", consumer_encode_video, 0, NULL, NULL ); + mlt_properties_set_data( properties, "audio", consumer_encode_audio, 0, NULL, NULL ); + mlt_properties_set_data( properties, "output", consumer_output, 0, NULL, NULL ); + + // Terminate at end of the stream by default + mlt_properties_set_int( properties, "terminate_on_pause", 1 ); + + // Set up start/stop/terminated callbacks + this->start = consumer_start; + this->stop = consumer_stop; + this->is_stopped = consumer_is_stopped; + } + else + { + // Clean up in case of init failure + free( this ); + this = NULL; + } + + // Return this + return this; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, this ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're running + if ( mlt_properties_get_int( properties, "running" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "running", 0 ); + + // Wait for termination + pthread_join( *thread, NULL ); + + // Close the output file :-) - this is obtuse - doesn't matter if output file + // exists or not - the destructor will kick in if it does + mlt_properties_set_data( properties, "output_file", NULL, 0, NULL, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + return !mlt_properties_get_int( properties, "running" ); +} + +/** Get or create a new libdv encoder. +*/ + +static dv_encoder_t *libdv_get_encoder( mlt_consumer this, mlt_frame frame ) +{ + // Get the properties of the consumer + mlt_properties this_properties = MLT_CONSUMER_PROPERTIES( this ); + + // Obtain the dv_encoder + dv_encoder_t *encoder = mlt_properties_get_data( this_properties, "dv_encoder", NULL ); + + // Construct one if we don't have one + if ( encoder == NULL ) + { + // Get the fps of the consumer (for now - should be from frame) + double fps = mlt_properties_get_double( this_properties, "fps" ); + + // Create the encoder + encoder = dv_encoder_new( 0, 0, 0 ); + + // Encoder settings + encoder->isPAL = fps == 25; + encoder->is16x9 = 0; + encoder->vlc_encode_passes = 1; + encoder->static_qno = 0; + encoder->force_dct = DV_DCT_AUTO; + + // Store the encoder on the properties + mlt_properties_set_data( this_properties, "dv_encoder", encoder, 0, ( mlt_destructor )dv_encoder_free, NULL ); + } + + // Return the encoder + return encoder; +} + + +/** The libdv encode video method. +*/ + +static int consumer_encode_video( mlt_consumer this, uint8_t *dv_frame, mlt_frame frame ) +{ + // Obtain the dv_encoder + dv_encoder_t *encoder = libdv_get_encoder( this, frame ); + + // Get the properties of the consumer + mlt_properties this_properties = MLT_CONSUMER_PROPERTIES( this ); + + // This will hold the size of the dv frame + int size = 0; + + // Is the image rendered + int rendered = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "rendered" ); + + // Get width and height + int width = mlt_properties_get_int( this_properties, "width" ); + int height = mlt_properties_get_int( this_properties, "height" ); + + // If we get an encoder, then encode the image + if ( rendered && encoder != NULL ) + { + // Specify desired image properties + mlt_image_format fmt = mlt_image_yuv422; + uint8_t *image = NULL; + + // Get the image + mlt_events_fire( this_properties, "consumer-frame-show", frame, NULL ); + mlt_frame_get_image( frame, &image, &fmt, &width, &height, 0 ); + + // Check that we get what we expected + if ( fmt != mlt_image_yuv422 || + width != mlt_properties_get_int( this_properties, "width" ) || + height != mlt_properties_get_int( this_properties, "height" ) || + image == NULL ) + { + // We should try to recover here + fprintf( stderr, "We have a problem houston...\n" ); + } + else + { + // Calculate the size of the dv frame + size = height == 576 ? FRAME_SIZE_625_50 : FRAME_SIZE_525_60; + } + + // Process the frame + if ( size != 0 ) + { + // Encode the image + dv_encode_full_frame( encoder, &image, e_dv_color_yuv, dv_frame ); + } + } + else if ( encoder != NULL ) + { + // Calculate the size of the dv frame (duplicate of previous) + size = height == 576 ? FRAME_SIZE_625_50 : FRAME_SIZE_525_60; + } + + return size; +} + +/** The libdv encode audio method. +*/ + +static void consumer_encode_audio( mlt_consumer this, uint8_t *dv_frame, mlt_frame frame ) +{ + // Get the properties of the consumer + mlt_properties this_properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the properties of the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the dv_encoder + dv_encoder_t *encoder = libdv_get_encoder( this, frame ); + + // Only continue if we have an encoder + if ( encoder != NULL ) + { + // Get the frame count + int count = mlt_properties_get_int( this_properties, "count" ); + + // Default audio args + mlt_audio_format fmt = mlt_audio_pcm; + int channels = 2; + int frequency = mlt_properties_get_int( this_properties, "frequency" ); + int samples = mlt_sample_calculator( mlt_properties_get_double( this_properties, "fps" ), frequency, count ); + int16_t *pcm = NULL; + + // Get the frame number + time_t start = time( NULL ); + int height = mlt_properties_get_int( this_properties, "height" ); + int is_pal = height == 576; + int is_wide = mlt_properties_get_int( this_properties, "display_aspect_num" ) == 16; + + // Temporary - audio buffer allocation + int16_t *audio_buffers[ 4 ]; + int i = 0; + int j = 0; + for ( i = 0 ; i < 4; i ++ ) + audio_buffers[ i ] = mlt_pool_alloc( 2 * DV_AUDIO_MAX_SAMPLES ); + + // Get the audio + mlt_frame_get_audio( frame, &pcm, &fmt, &frequency, &channels, &samples ); + + // Inform the encoder of the number of audio samples + encoder->samples_this_frame = samples; + + // Fill the audio buffers correctly + if ( mlt_properties_get_double( frame_properties, "_speed" ) == 1.0 ) + { + for ( i = 0; i < samples; i ++ ) + for ( j = 0; j < channels; j++ ) + audio_buffers[ j ][ i ] = *pcm ++; + } + else + { + for ( j = 0; j < channels; j++ ) + memset( audio_buffers[ j ], 0, 2 * DV_AUDIO_MAX_SAMPLES ); + } + + // Encode audio on frame + dv_encode_full_audio( encoder, audio_buffers, channels, frequency, dv_frame ); + + // Specify meta data on the frame + dv_encode_metadata( dv_frame, is_pal, is_wide, &start, count ); + dv_encode_timecode( dv_frame, is_pal, count ); + + // Update properties + mlt_properties_set_int( this_properties, "count", ++ count ); + + // Temporary - free audio buffers + for ( i = 0 ; i < 4; i ++ ) + mlt_pool_release( audio_buffers[ i ] ); + } +} + +/** The libdv output method. +*/ + +static void consumer_output( mlt_consumer this, uint8_t *dv_frame, int size, mlt_frame frame ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + FILE *output = stdout; + char *target = mlt_properties_get( properties, "target" ); + + if ( target != NULL ) + { + output = mlt_properties_get_data( properties, "output_file", NULL ); + if ( output == NULL ) + { + output = fopen( target, "w" ); + if ( output != NULL ) + mlt_properties_set_data( properties, "output_file", output, 0, ( mlt_destructor )fclose, 0 ); + } + } + + if ( output != NULL ) + { + fwrite( dv_frame, size, 1, output ); + fflush( output ); + } + else + { + fprintf( stderr, "Unable to open %s\n", target ); + } +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer this = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the terminate_on_pause property + int top = mlt_properties_get_int( properties, "terminate_on_pause" ); + + // Get the handling methods + int ( *video )( mlt_consumer, uint8_t *, mlt_frame ) = mlt_properties_get_data( properties, "video", NULL ); + int ( *audio )( mlt_consumer, uint8_t *, mlt_frame ) = mlt_properties_get_data( properties, "audio", NULL ); + int ( *output )( mlt_consumer, uint8_t *, int, mlt_frame ) = mlt_properties_get_data( properties, "output", NULL ); + + // Allocate a single PAL frame for encoding + uint8_t *dv_frame = mlt_pool_alloc( FRAME_SIZE_625_50 ); + + // Frame and size + mlt_frame frame = NULL; + int size = 0; + + // Loop while running + while( mlt_properties_get_int( properties, "running" ) ) + { + // Get the frame + frame = mlt_consumer_rt_frame( this ); + + // Check that we have a frame to work with + if ( frame != NULL ) + { + // Terminate on pause + if ( top && mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0 ) + { + mlt_frame_close( frame ); + break; + } + + // Obtain the dv_encoder + if ( libdv_get_encoder( this, frame ) != NULL ) + { + // Encode the image + size = video( this, dv_frame, frame ); + + // Encode the audio + if ( size > 0 ) + audio( this, dv_frame, frame ); + + // Output the frame + output( this, dv_frame, size, frame ); + + // Close the frame + mlt_frame_close( frame ); + } + else + { + fprintf( stderr, "Unable to obtain dv encoder.\n" ); + } + } + } + + // Tidy up + mlt_pool_release( dv_frame ); + + mlt_consumer_stopped( this ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer this ) +{ + // Stop the consumer + mlt_consumer_stop( this ); + + // Close the parent + mlt_consumer_close( this ); + + // Free the memory + free( this ); +} diff --git a/src/modules/dv/consumer_libdv.h b/src/modules/dv/consumer_libdv.h new file mode 100644 index 0000000..b94237a --- /dev/null +++ b/src/modules/dv/consumer_libdv.h @@ -0,0 +1,28 @@ +/* + * producer_libdv.h -- a DV encoder based on libdv + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_LIBDV_H_ +#define _CONSUMER_LIBDV_H_ + +#include + +extern mlt_consumer consumer_libdv_init( char *filename ); + +#endif diff --git a/src/modules/dv/factory.c b/src/modules/dv/factory.c new file mode 100644 index 0000000..86f5c9d --- /dev/null +++ b/src/modules/dv/factory.c @@ -0,0 +1,49 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_libdv.h" +#include "consumer_libdv.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "libdv" ) ) + return producer_libdv_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "libdv" ) ) + return consumer_libdv_init( arg ); + return NULL; +} + diff --git a/src/modules/dv/producer_libdv.c b/src/modules/dv/producer_libdv.c new file mode 100644 index 0000000..4fa0ec8 --- /dev/null +++ b/src/modules/dv/producer_libdv.c @@ -0,0 +1,525 @@ +/* + * producer_libdv.c -- simple libdv test case + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_libdv.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** To conserve resources, we maintain a stack of dv decoders. +*/ + +static pthread_mutex_t decoder_lock = PTHREAD_MUTEX_INITIALIZER; +static mlt_properties dv_decoders = NULL; + +dv_decoder_t *dv_decoder_alloc( ) +{ + // We'll return a dv_decoder + dv_decoder_t *this = NULL; + + // Lock the mutex + pthread_mutex_lock( &decoder_lock ); + + // Create the properties if necessary + if ( dv_decoders == NULL ) + { + // Create the properties + dv_decoders = mlt_properties_new( ); + + // Create the stack + mlt_properties_set_data( dv_decoders, "stack", mlt_deque_init( ), 0, ( mlt_destructor )mlt_deque_close, NULL ); + + // Register the properties for clean up + mlt_factory_register_for_clean_up( dv_decoders, ( mlt_destructor )mlt_properties_close ); + } + + // Now try to obtain a decoder + if ( dv_decoders != NULL ) + { + // Obtain the stack + mlt_deque stack = mlt_properties_get_data( dv_decoders, "stack", NULL ); + + // Pop the top of the stack + this = mlt_deque_pop_back( stack ); + + // Create a new decoder if none available + if ( this == NULL ) + { + // We'll need a unique property ID for this + char label[ 256 ]; + + // Configure the decoder + this = dv_decoder_new( FALSE, FALSE, FALSE ); + this->quality = DV_QUALITY_COLOR | DV_QUALITY_AC_2; + this->audio->arg_audio_emphasis = 2; + dv_set_audio_correction( this, DV_AUDIO_CORRECT_AVERAGE ); + dv_set_error_log( this, NULL ); + + // Register it with the properties to ensure clean up + sprintf( label, "%p", this ); + mlt_properties_set_data( dv_decoders, label, this, 0, ( mlt_destructor )dv_decoder_free, NULL ); + } + } + + // Unlock the mutex + pthread_mutex_unlock( &decoder_lock ); + + return this; +} + +void dv_decoder_return( dv_decoder_t *this ) +{ + // Lock the mutex + pthread_mutex_lock( &decoder_lock ); + + // Now try to return the decoder + if ( dv_decoders != NULL ) + { + // Obtain the stack + mlt_deque stack = mlt_properties_get_data( dv_decoders, "stack", NULL ); + + // Push it back + mlt_deque_push_back( stack, this ); + } + + // Unlock the mutex + pthread_mutex_unlock( &decoder_lock ); +} + + +typedef struct producer_libdv_s *producer_libdv; + +struct producer_libdv_s +{ + struct mlt_producer_s parent; + int fd; + int is_pal; + uint64_t file_size; + int frame_size; + long frames_in_file; + mlt_producer alternative; +}; + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +static int producer_collect_info( producer_libdv this ); + +mlt_producer producer_libdv_init( char *filename ) +{ + producer_libdv this = calloc( sizeof( struct producer_libdv_s ), 1 ); + + if ( filename != NULL && this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + int destroy = 0; + mlt_producer producer = &this->parent; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Set the resource property (required for all producers) + mlt_properties_set( properties, "resource", filename ); + + // Register transport implementation with the producer + producer->close = ( mlt_destructor )producer_close; + + // Register our get_frame implementation with the producer + producer->get_frame = producer_get_frame; + + // If we have mov or dv, then we'll use an alternative producer + if ( strchr( filename, '.' ) != NULL && ( + strncasecmp( strrchr( filename, '.' ), ".avi", 4 ) == 0 || + strncasecmp( strrchr( filename, '.' ), ".mov", 4 ) == 0 ) ) + { + // Load via an alternative mechanism + this->alternative = mlt_factory_producer( "kino", filename ); + + // If it's unavailable, then clean up + if ( this->alternative == NULL ) + destroy = 1; + else + mlt_properties_pass( properties, MLT_PRODUCER_PROPERTIES( this->alternative ), "" ); + this->is_pal = ( ( int ) mlt_producer_get_fps( producer ) ) == 25; + } + else + { + // Open the file if specified + this->fd = open( filename, O_RDONLY ); + + // Collect info + if ( this->fd == -1 || !producer_collect_info( this ) ) + destroy = 1; + } + + // If we couldn't open the file, then destroy it now + if ( destroy ) + { + mlt_producer_close( producer ); + producer = NULL; + } + + // Return the producer + return producer; + } + free( this ); + return NULL; +} + +static int read_frame( int fd, uint8_t* frame_buf, int *isPAL ) +{ + int result = read( fd, frame_buf, FRAME_SIZE_525_60 ) == FRAME_SIZE_525_60; + if ( result ) + { + *isPAL = ( frame_buf[3] & 0x80 ); + + if ( *isPAL ) + { + int diff = FRAME_SIZE_625_50 - FRAME_SIZE_525_60; + result = read( fd, frame_buf + FRAME_SIZE_525_60, diff ) == diff; + } + } + + return result; +} + +static int producer_collect_info( producer_libdv this ) +{ + int valid = 0; + + uint8_t *dv_data = mlt_pool_alloc( FRAME_SIZE_625_50 ); + + if ( dv_data != NULL ) + { + // Read the first frame + valid = read_frame( this->fd, dv_data, &this->is_pal ); + + // If it looks like a valid frame, the get stats + if ( valid ) + { + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( &this->parent ); + + // Get a dv_decoder + dv_decoder_t *dv_decoder = dv_decoder_alloc( ); + + // Determine the file size + struct stat buf; + fstat( this->fd, &buf ); + + // Store the file size + this->file_size = buf.st_size; + + // Determine the frame size + this->frame_size = this->is_pal ? FRAME_SIZE_625_50 : FRAME_SIZE_525_60; + + // Determine the number of frames in the file + this->frames_in_file = this->file_size / this->frame_size; + + // Calculate default in/out points + double fps = this->is_pal ? 25 : 30000.0 / 1001.0; + if ( mlt_producer_get_fps( &this->parent ) == fps ) + { + if ( this->frames_in_file > 0 ) + { + mlt_properties_set_position( properties, "length", this->frames_in_file ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", this->frames_in_file - 1 ); + } + } + else + { + valid = 0; + } + + // Parse the header for meta info + dv_parse_header( dv_decoder, dv_data ); + mlt_properties_set_double( properties, "aspect_ratio", + dv_format_wide( dv_decoder ) ? ( this->is_pal ? 118.0/81.0 : 40.0/33.0 ) : ( this->is_pal ? 59.0/54.0 : 10.0/11.0 ) ); + + // Return the decoder + dv_decoder_return( dv_decoder ); + } + + mlt_pool_release( dv_data ); + } + + return valid; +} + +static int producer_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + int pitches[3] = { 0, 0, 0 }; + uint8_t *pixels[3] = { NULL, NULL, NULL }; + + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get a dv_decoder + dv_decoder_t *decoder = dv_decoder_alloc( ); + + // Get the dv data + uint8_t *dv_data = mlt_properties_get_data( properties, "dv_data", NULL ); + + // Get and set the quality request + char *quality = mlt_frame_pop_service( this ); + + if ( quality != NULL ) + { + if ( strncmp( quality, "fast", 4 ) == 0 ) + decoder->quality = ( DV_QUALITY_COLOR | DV_QUALITY_DC ); + else if ( strncmp( quality, "best", 4 ) == 0 ) + decoder->quality = ( DV_QUALITY_COLOR | DV_QUALITY_AC_2 ); + else + decoder->quality = ( DV_QUALITY_COLOR | DV_QUALITY_AC_1 ); + } + + // Parse the header for meta info + dv_parse_header( decoder, dv_data ); + + // Assign width and height according to the frame + *width = 720; + *height = dv_data[ 3 ] & 0x80 ? 576 : 480; + + // Extract an image of the format requested + if ( *format == mlt_image_yuv422 || *format == mlt_image_yuv420p ) + { + // Allocate an image + uint8_t *image = mlt_pool_alloc( *width * ( *height + 1 ) * 2 ); + + // Pass to properties for clean up + mlt_properties_set_data( properties, "image", image, *width * ( *height + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + + // Decode the image + pitches[ 0 ] = *width * 2; + pixels[ 0 ] = image; + dv_decode_full_frame( decoder, dv_data, e_dv_color_yuv, pixels, pitches ); + + // Assign result + *buffer = image; + *format = mlt_image_yuv422; + } + else + { + // Allocate an image + uint8_t *image = mlt_pool_alloc( *width * ( *height + 1 ) * 3 ); + + // Pass to properties for clean up + mlt_properties_set_data( properties, "image", image, *width * ( *height + 1 ) * 3, ( mlt_destructor )mlt_pool_release, NULL ); + + // Decode the frame + pitches[ 0 ] = 720 * 3; + pixels[ 0 ] = image; + dv_decode_full_frame( decoder, dv_data, e_dv_color_rgb, pixels, pitches ); + + // Assign result + *buffer = image; + *format = mlt_image_rgb24; + } + + // Return the decoder + dv_decoder_return( decoder ); + + return 0; +} + +static int producer_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + int16_t *p; + int i, j; + int16_t *audio_channels[ 4 ]; + + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get a dv_decoder + dv_decoder_t *decoder = dv_decoder_alloc( ); + + // Get the dv data + uint8_t *dv_data = mlt_properties_get_data( properties, "dv_data", NULL ); + + // Parse the header for meta info + dv_parse_header( decoder, dv_data ); + + // Check that we have audio + if ( decoder->audio->num_channels > 0 ) + { + // Obtain required values + *frequency = decoder->audio->frequency; + *samples = decoder->audio->samples_this_frame; + *channels = decoder->audio->num_channels; + + // Create a temporary workspace + for ( i = 0; i < 4; i++ ) + audio_channels[ i ] = mlt_pool_alloc( DV_AUDIO_MAX_SAMPLES * sizeof( int16_t ) ); + + // Create a workspace for the result + *buffer = mlt_pool_alloc( *channels * DV_AUDIO_MAX_SAMPLES * sizeof( int16_t ) ); + + // Pass the allocated audio buffer as a property + mlt_properties_set_data( properties, "audio", *buffer, *channels * DV_AUDIO_MAX_SAMPLES * sizeof( int16_t ), ( mlt_destructor )mlt_pool_release, NULL ); + + // Decode the audio + dv_decode_full_audio( decoder, dv_data, audio_channels ); + + // Interleave the audio + p = *buffer; + for ( i = 0; i < *samples; i++ ) + for ( j = 0; j < *channels; j++ ) + *p++ = audio_channels[ j ][ i ]; + + // Free the temporary work space + for ( i = 0; i < 4; i++ ) + mlt_pool_release( audio_channels[ i ] ); + } + else + { + // No audio available on the frame, so get test audio (silence) + mlt_frame_get_audio( this, buffer, format, frequency, channels, samples ); + } + + // Return the decoder + dv_decoder_return( decoder ); + + return 0; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Access the private data + producer_libdv this = producer->child; + + // Will carry the frame data + uint8_t *data = NULL; + + // Obtain the current frame number + uint64_t position = mlt_producer_frame( producer ); + + if ( this->alternative == NULL ) + { + // Convert timecode to a file position (ensuring that we're on a frame boundary) + uint64_t offset = position * this->frame_size; + + // Allocate space + data = mlt_pool_alloc( FRAME_SIZE_625_50 ); + + // Create an empty frame + *frame = mlt_frame_init( ); + + // Seek and fetch + if ( this->fd != 0 && + lseek( this->fd, offset, SEEK_SET ) == offset && + read_frame( this->fd, data, &this->is_pal ) ) + { + // Pass the dv data + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "dv_data", data, FRAME_SIZE_625_50, ( mlt_destructor )mlt_pool_release, NULL ); + } + else + { + mlt_pool_release( data ); + data = NULL; + } + } + else + { + // Seek + mlt_producer_seek( this->alternative, position ); + + // Fetch + mlt_service_get_frame( MLT_PRODUCER_SERVICE( this->alternative ), frame, 0 ); + + // Verify + if ( *frame != NULL ) + data = mlt_properties_get_data( MLT_FRAME_PROPERTIES( *frame ), "dv_data", NULL ); + } + + if ( data != NULL ) + { + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Get a dv_decoder + dv_decoder_t *dv_decoder = dv_decoder_alloc( ); + + mlt_properties_set_int( properties, "test_image", 0 ); + mlt_properties_set_int( properties, "test_audio", 0 ); + + // Update other info on the frame + mlt_properties_set_int( properties, "width", 720 ); + mlt_properties_set_int( properties, "height", this->is_pal ? 576 : 480 ); + mlt_properties_set_int( properties, "top_field_first", !this->is_pal ? 0 : ( data[ 5 ] & 0x07 ) == 0 ? 0 : 1 ); + + // Parse the header for meta info + dv_parse_header( dv_decoder, data ); + //mlt_properties_set_int( properties, "progressive", dv_is_progressive( dv_decoder ) ); + mlt_properties_set_double( properties, "aspect_ratio", + dv_format_wide( dv_decoder ) ? ( this->is_pal ? 118.0/81.0 : 40.0/33.0 ) : ( this->is_pal ? 59.0/54.0 : 10.0/11.0 ) ); + + + mlt_properties_set_int( properties, "frequency", dv_decoder->audio->frequency ); + mlt_properties_set_int( properties, "channels", dv_decoder->audio->num_channels ); + + // Hmm - register audio callback + mlt_frame_push_audio( *frame, producer_get_audio ); + + // Push the quality string + mlt_frame_push_service( *frame, mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "quality" ) ); + + // Push the get_image method on to the stack + mlt_frame_push_get_image( *frame, producer_get_image ); + + // Return the decoder + dv_decoder_return( dv_decoder ); + } + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + // Obtain this + producer_libdv this = parent->child; + + // Close the file + if ( this->fd > 0 ) + close( this->fd ); + + if ( this->alternative ) + mlt_producer_close( this->alternative ); + + // Close the parent + parent->close = NULL; + mlt_producer_close( parent ); + + // Free the memory + free( this ); +} diff --git a/src/modules/dv/producer_libdv.h b/src/modules/dv/producer_libdv.h new file mode 100644 index 0000000..5b8a50b --- /dev/null +++ b/src/modules/dv/producer_libdv.h @@ -0,0 +1,31 @@ +/* + * producer_libdv.h -- a DV decoder based on libdv + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_LIBDV_H_ +#define _PRODUCER_LIBDV_H_ + +#include + +extern mlt_producer producer_libdv_init( char *filename ); + +#define FRAME_SIZE_525_60 10 * 150 * 80 +#define FRAME_SIZE_625_50 12 * 150 * 80 + +#endif diff --git a/src/modules/effectv/Makefile b/src/modules/effectv/Makefile new file mode 100644 index 0000000..e8d9a8e --- /dev/null +++ b/src/modules/effectv/Makefile @@ -0,0 +1,35 @@ +include ../../../config.mak + +TARGET = ../libmlteffectv$(LIBSUF) + +OBJS = factory.o \ + filter_burn.o \ + image.o \ + utils.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/effectv/configure b/src/modules/effectv/configure new file mode 100755 index 0000000..25f1d82 --- /dev/null +++ b/src/modules/effectv/configure @@ -0,0 +1,19 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +EOF + +cat << EOF >> ../filters.dat +BurningTV libmlteffectv$LIBSUF +EOF + +cat << EOF >> ../transitions.dat +EOF + +cat << EOF >> ../consumers.dat +EOF + +fi diff --git a/src/modules/effectv/factory.c b/src/modules/effectv/factory.c new file mode 100644 index 0000000..952f688 --- /dev/null +++ b/src/modules/effectv/factory.c @@ -0,0 +1,44 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2007 Stephane Fillod + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_burn.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "BurningTV" ) ) + return filter_burn_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/effectv/filter_burn.c b/src/modules/effectv/filter_burn.c new file mode 100644 index 0000000..ff8e017 --- /dev/null +++ b/src/modules/effectv/filter_burn.c @@ -0,0 +1,214 @@ +/* + * filter_burn.c -- burning filter + * Copyright (C) 2007 Stephane Fillod + * + * Filter taken from EffecTV - Realtime Digital Video Effector + * Copyright (C) 2001-2006 FUKUCHI Kentaro + * + * BurningTV - burns incoming objects. + * Copyright (C) 2001-2002 FUKUCHI Kentaro + * + * Fire routine is taken from Frank Jan Sorensen's demo program. + * + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_burn.h" + +#include + +#include +#include +#include +#include +#include "utils.h" + + +#define MaxColor 120 +#define Decay 15 +#define MAGIC_THRESHOLD "50" + +static RGB32 palette[256]; + +/* FIXME: endianess? */ +static void makePalette(void) +{ + int i, r, g, b; + + for(i=0; i> 8)); + i++; + } + i += 2; + } + + mlt_convert_rgb24a_to_yuv422((uint8_t *)dest, *width, *height, *width * sizeof(RGB32), + *image, NULL ); + + mlt_pool_release(dest); + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the frame filter + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_burn_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "foreground", "0" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "threshold", MAGIC_THRESHOLD ); + } + if (!palette[128]) + { + makePalette(); + } + return this; +} + diff --git a/src/modules/effectv/filter_burn.h b/src/modules/effectv/filter_burn.h new file mode 100644 index 0000000..3a2b824 --- /dev/null +++ b/src/modules/effectv/filter_burn.h @@ -0,0 +1,27 @@ +/* + * filter_brun.h -- burning filter + * Copyright (C) 2007 Stephane Fillod + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_BURN_H_ +#define _FILTER_BURN_H_ + +#include + +extern mlt_filter filter_burn_init( char *arg ); + +#endif diff --git a/src/modules/effectv/gpl b/src/modules/effectv/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/effectv/image.c b/src/modules/effectv/image.c new file mode 100644 index 0000000..dbd848f --- /dev/null +++ b/src/modules/effectv/image.c @@ -0,0 +1,307 @@ +/* + * EffecTV - Realtime Digital Video Effector + * Copyright (C) 2001-2006 FUKUCHI Kentaro + * + * image.c: utilities for image processing. + * + */ + +#include +#include +#include "utils.h" + + +/* + * Collection of background subtraction functions + */ + +/* checks only fake-Y value */ +/* In these function Y value is treated as R*2+G*4+B. */ + +int image_set_threshold_y(int threshold) +{ + int y_threshold = threshold * 7; /* fake-Y value is timed by 7 */ + + return y_threshold; +} + +void image_bgset_y(RGB32 *background, const RGB32 *src, int video_area, int y_threshold) +{ + int i; + int R, G, B; + const RGB32 *p; + short *q; + + p = src; + q = (short *)background; + for(i=0; i>(16-1); + G = ((*p)&0xff00)>>(8-2); + B = (*p)&0xff; + *q = (short)(R + G + B); + p++; + q++; + } +} + +void image_bgsubtract_y(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, int y_threshold) +{ + int i; + int R, G, B; + const RGB32 *p; + short *q; + unsigned char *r; + int v; + + p = src; + q = (short *)background; + r = diff; + for(i=0; i>(16-1); + G = ((*p)&0xff00)>>(8-2); + B = (*p)&0xff; + v = (R + G + B) - (int)(*q); + *r = ((v + y_threshold)>>24) | ((y_threshold - v)>>24); + + p++; + q++; + r++; + } + +/* The origin of subtraction function is; + * diff(src, dest) = (abs(src - dest) > threshold) ? 0xff : 0; + * + * This functions is transformed to; + * (threshold > (src - dest) > -threshold) ? 0 : 0xff; + * + * (v + threshold)>>24 is 0xff when v is less than -threshold. + * (v - threshold)>>24 is 0xff when v is less than threshold. + * So, ((v + threshold)>>24) | ((threshold - v)>>24) will become 0xff when + * abs(src - dest) > threshold. + */ +} + +/* Background image is refreshed every frame */ +void image_bgsubtract_update_y(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, int y_threshold) +{ + int i; + int R, G, B; + const RGB32 *p; + short *q; + unsigned char *r; + int v; + + p = src; + q = (short *)background; + r = diff; + for(i=0; i>(16-1); + G = ((*p)&0xff00)>>(8-2); + B = (*p)&0xff; + v = (R + G + B) - (int)(*q); + *q = (short)(R + G + B); + *r = ((v + y_threshold)>>24) | ((y_threshold - v)>>24); + + p++; + q++; + r++; + } +} + +/* checks each RGB value */ + +/* The range of r, g, b are [0..7] */ +RGB32 image_set_threshold_RGB(int r, int g, int b) +{ + unsigned char R, G, B; + RGB32 rgb_threshold; + + R = G = B = 0xff; + R = R<>8); + b = b ^ 0xffffff; + a = a ^ b; + a = a & rgb_threshold; + *r++ = (0 - a)>>24; + } +} + +void image_bgsubtract_update_RGB(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold) +{ + int i; + const RGB32 *p; + RGB32 *q; + unsigned a, b; + unsigned char *r; + + p = src; + q = background; + r = diff; + for(i=0; i>8); + b = b ^ 0xffffff; + a = a ^ b; + a = a & rgb_threshold; + *r++ = (0 - a)>>24; + } +} + +/* noise filter for subtracted image. */ +void image_diff_filter(unsigned char *diff2, const unsigned char *diff, int width, int height) +{ + int x, y; + const unsigned char *src; + unsigned char *dest; + unsigned int count; + unsigned int sum1, sum2, sum3; + + src = diff; + dest = diff2 + width +1; + for(y=1; y>24; + src++; + } + dest += 2; + } +} + +/* Y value filters */ +void image_y_over(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold) +{ + int i; + int R, G, B, v; + unsigned char *p = diff; + + for(i = video_area; i>0; i--) { + R = ((*src)&0xff0000)>>(16-1); + G = ((*src)&0xff00)>>(8-2); + B = (*src)&0xff; + v = y_threshold - (R + G + B); + *p = (unsigned char)(v>>24); + src++; + p++; + } +} + +void image_y_under(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold) +{ + int i; + int R, G, B, v; + unsigned char *p = diff; + + for(i = video_area; i>0; i--) { + R = ((*src)&0xff0000)>>(16-1); + G = ((*src)&0xff00)>>(8-2); + B = (*src)&0xff; + v = (R + G + B) - y_threshold; + *p = (unsigned char)(v>>24); + src++; + p++; + } +} + +/* tiny edge detection */ +void image_edge(unsigned char *diff2, const RGB32 *src, int width, int height, int y_threshold) +{ + int x, y; + unsigned char *p, *q; + int r, g, b; + int ar, ag, ab; + int w; + + p = (unsigned char *)src; + q = diff2; + w = width * sizeof(RGB32); + + for(y=0; y y_threshold) { + *q = 255; + } else { + *q = 0; + } + q++; + p += 4; + } + p += 4; + *q++ = 0; + } + memset(q, 0, width); +} + +/* horizontal flipping */ +void image_hflip(const RGB32 *src, RGB32 *dest, int width, int height) +{ + int x, y; + + src += width - 1; + for(y=0; y +#include "utils.h" + +/* + * HSI color system utilities + */ +static int itrunc(double f) +{ + int i; + + i=(int)f; + if(i<0)i=0; + if(i>255)i=255; + return i; +} + +void HSItoRGB(double H, double S, double I, int *r, int *g, int *b) +{ + double T,Rv,Gv,Bv; + + Rv=1+S*sin(H-2*M_PI/3); + Gv=1+S*sin(H); + Bv=1+S*sin(H+2*M_PI/3); + T=255.999*I/2; + *r=itrunc(Rv*T); + *g=itrunc(Gv*T); + *b=itrunc(Bv*T); +} + +/* + * fastrand - fast fake random number generator + * Warning: The low-order bits of numbers generated by fastrand() + * are bad as random numbers. For example, fastrand()%4 + * generates 1,2,3,0,1,2,3,0... + * You should use high-order bits. + */ +#ifdef __DARWIN__ +static +#endif +unsigned int fastrand_val; + +unsigned int fastrand(void) +{ + return (fastrand_val=fastrand_val*1103515245+12345); +} + +void fastsrand(unsigned int seed) +{ + fastrand_val = seed; +} diff --git a/src/modules/effectv/utils.h b/src/modules/effectv/utils.h new file mode 100644 index 0000000..4461898 --- /dev/null +++ b/src/modules/effectv/utils.h @@ -0,0 +1,48 @@ +/* + * EffecTV - Realtime Digital Video Effector + * Copyright (C) 2001-2006 FUKUCHI Kentaro + * + * utils.h: header file for utils + * + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include + +typedef uint32_t RGB32; + +/* DEFINE's by nullset@dookie.net */ +#define RED(n) ((n>>16) & 0x000000FF) +#define GREEN(n) ((n>>8) & 0x000000FF) +#define BLUE(n) ((n>>0) & 0x000000FF) +#define RGB(r,g,b) ((0<<24) + (r<<16) + (g <<8) + (b)) +#define INTENSITY(n) ( ( (RED(n)+GREEN(n)+BLUE(n))/3)) + +/* utils.c */ +void HSItoRGB(double H, double S, double I, int *r, int *g, int *b); + +#ifndef __DARWIN__ +extern unsigned int fastrand_val; +#define inline_fastrand() (fastrand_val=fastrand_val*1103515245+12345) +#endif +unsigned int fastrand(void); +void fastsrand(unsigned int); + +/* image.c */ +int image_set_threshold_y(int threshold); +void image_bgset_y(RGB32 *background, const RGB32 *src, int video_area, int y_threshold); +void image_bgsubtract_y(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, int y_threshold); +void image_bgsubtract_update_y(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, int y_threshold); +RGB32 image_set_threshold_RGB(int r, int g, int b); +void image_bgset_RGB(RGB32 *background, const RGB32 *src, int video_area); +void image_bgsubtract_RGB(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold); +void image_bgsubtract_update_RGB(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold); +void image_diff_filter(unsigned char *diff2, const unsigned char *diff, int width, int height); +void image_y_over(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold); +void image_y_under(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold); +void image_edge(unsigned char *diff2, const RGB32 *src, int width, int height, int y_threshold); +void image_hflip(const RGB32 *src, RGB32 *dest, int width, int height); + +#endif /* __UTILS_H__ */ diff --git a/src/modules/feeds/Makefile b/src/modules/feeds/Makefile new file mode 100644 index 0000000..ce76aaa --- /dev/null +++ b/src/modules/feeds/Makefile @@ -0,0 +1,15 @@ +include ../../../config.mak + +all: + +depend: + +distclean: + +clean: + +install: all + install -d $(DESTDIR)$(prefix)/share/mlt/modules/feeds/PAL + install -d $(DESTDIR)$(prefix)/share/mlt/modules/feeds/NTSC + install -m 644 PAL/*.* $(DESTDIR)$(prefix)/share/mlt/modules/feeds/PAL + install -m 644 NTSC/*.* $(DESTDIR)$(prefix)/share/mlt/modules/feeds/NTSC diff --git a/src/modules/feeds/NTSC/data_fx.properties b/src/modules/feeds/NTSC/data_fx.properties new file mode 100644 index 0000000..03832aa --- /dev/null +++ b/src/modules/feeds/NTSC/data_fx.properties @@ -0,0 +1,250 @@ +# This properties file describes the fx available to the data_feed and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +# +# The titles filter definition +# + +titles=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,70%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 + +# +# The top titles filter definition +# + +top-titles=region +.description=Top Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,5%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.halign=centre +.filter[1].composite.titles=1 + +# +# OK - Silly example... +# + +tickertape=region +.description=Tickertape +.properties.markup=filter[1].producer.markup +.type.markup=text +.properties.length[0]=filter[1].composite.out +.composite.geometry=0%,93%:100%x7% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=100%,0%:300%x100%:100;-1=-300%,0%:300%x100%:100 +.filter[1].producer.font=San 32 +.filter[1].composite.titles=1 + +# +# ETV Location +# + +location=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,80:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=-100%,0%:100%x100%:100;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup= +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:0;24=0%,0%:100%x100%:0;49=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=center + +courtesy=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,115:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=-100%,0%:100%x100%:0;12=-100%,0%:100%x100%:0;37=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=ETV Exclusive +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:0;37=0%,0%:100%x100%:0;61=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=right + +exclusive=region +.description=Exclusive +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=0,115:230x30 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=ETV Exclusive +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=right + +file_shot=region +.description=Titles +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=590,160:80x25 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=File Shot +.filter[1].producer.font=San 20 +.filter[1].composite.geometry=1%,1%:99%x99%:15;25=1%,1%:99%x99%:100 +.filter[1].composite.titles=0 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +special=region +.description=Titles +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=465,375:255x35 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=100%,0%:100%x100%:0;49=100%,0%:100%x100%:0;74=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Special +.filter[1].producer.font=San 24 +.filter[1].composite.geometry=100%,0%:100%x100%:0;49=100%,0%:100%x100%:0;74=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +ticker=region +.description=Tickertape +.properties.markup=filter[1].producer.markup +.type.markup=text +.properties.length[0]=filter[1].composite.out +.composite.geometry=0,500:722x75 +.filter[0]=watermark +.filter[0].resource=colour:0x6c010100 +.filter[0].composite.geometry=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Ticker - provided for reference +.filter[1].composite.geometry=0%,0%:100%x100%:100 +.filter[1].composite.titles=0 +.filter[1].producer.font=San 24 +.filter[1].composite.halign=centre +.filter[1].composite.titles=1 +.filter[1].composite.valign=centre + +super=region +.description=Transcription +.properties.0=filter[1].producer.markup +.properties.1=filter[2].producer.markup +.properties.align=filter[1].composite.valign +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.properties.length[2]=filter[2].composite.out +.period=2 +.composite.geometry=0,410:720x90 +.filter[0]=watermark +.filter[0].resource=colour:0xbbbbbb00 +.filter[0].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[0].composite.titles=1 +.filter[0].composite.luma=%luma18.pgm +.filter[0].composite.out=25 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup= +.filter[1].producer.font=San 32 +.filter[1].producer.fgcolour=0x6c0101ff +.filter[1].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=top +.filter[2]=watermark +.filter[2].resource=pango: +.filter[2].producer.markup= +.filter[2].producer.font=San 32 +.filter[2].producer.fgcolour=0x6c0101ff +.filter[2].composite.geometry=0%,0%:100%x100%:10;25=0%,0%:100%x100%:100 +.filter[2].composite.titles=1 +.filter[2].composite.halign=centre +.filter[2].composite.valign=bottom + +obscure=region +.description=Obscure +.properties.geometry=composite.geometry +.properties.resource=resource +.properties.length[0]=composite.out +.composite.geometry= +.resource=rectangle +.composite.refresh=1 +.filter[0]=obscure +.filter[0].start=0,0:100%x100% + diff --git a/src/modules/feeds/NTSC/obscure.properties b/src/modules/feeds/NTSC/obscure.properties new file mode 100644 index 0000000..7eba160 --- /dev/null +++ b/src/modules/feeds/NTSC/obscure.properties @@ -0,0 +1,26 @@ +# This properties file describes the fx available to the data_feed and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +obscure=region +.description=Obscure +.properties.geometry=composite.geometry +.properties.resource=resource +.properties.length[0]=composite.out +.composite.geometry= +.resource=rectangle +.composite.refresh=1 +.filter[0]=obscure +.filter[0].start=0,0:100%x100% + diff --git a/src/modules/feeds/PAL/border.properties b/src/modules/feeds/PAL/border.properties new file mode 100644 index 0000000..e02ab35 --- /dev/null +++ b/src/modules/feeds/PAL/border.properties @@ -0,0 +1,22 @@ +border_left=watermark +.description=Border Left +.resource=colour:black +.reverse=1 +.period=2 +.properties.length[0]=composite.out +.composite.geometry=0,0:100%x100%;25=2.5%,17.5%:45%x45% +.composite.halign=c +.composite.valign=c +.composite.fill=1 + +border_right=watermark +.description=Border Right +.resource=colour:black +.reverse=1 +.period=2 +.properties.length[0]=composite.out +.composite.geometry=0,0:100%x100%;25=52.5%,17.5%:45%x45% +.composite.halign=c +.composite.valign=c +.composite.fill=1 + diff --git a/src/modules/feeds/PAL/data_fx.properties b/src/modules/feeds/PAL/data_fx.properties new file mode 100644 index 0000000..e2e483b --- /dev/null +++ b/src/modules/feeds/PAL/data_fx.properties @@ -0,0 +1,76 @@ +# This properties file describes the fx available to the data_send and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +# +# The titles filter definition +# + +titles=region +.description=Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,70%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 + +# +# The top titles filter definition +# + +top-titles=region +.description=Top Titles +.properties.markup=filter[1].producer.markup +.type.markup=text +.period=2 +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=5%,5%:90%x20% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[0].composite.geometry=0%,0%:100%x100%:0;5=0%,0%:100%x100%:40 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=0%,0%:100%x100%:0;8=0%,0%:100%x100%:100 +.filter[1].composite.titles=1 + +# +# OK - Silly example... +# + +tickertape=region +.description=Tickertape +.properties.markup=filter[1].producer.markup +.type.markup=text +.properties.length[0]=filter[1].composite.out +.composite.geometry=0%,93%:100%x7% +.filter[0]=watermark +.filter[0].resource=colour:0x000000 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.markup=Shotcut +.filter[1].composite.geometry=100%,0%:300%x100%:100;-1=-300%,0%:300%x100%:100 +.filter[1].producer.font=San 32 +.filter[1].composite.titles=1 + diff --git a/src/modules/feeds/PAL/etv.properties b/src/modules/feeds/PAL/etv.properties new file mode 100644 index 0000000..07be453 --- /dev/null +++ b/src/modules/feeds/PAL/etv.properties @@ -0,0 +1,186 @@ +# This properties file describes the fx available to the data_feed and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +location=region +.description=Titles +.properties.markup=filter[1].producer.text +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.period=2 +.properties.length[0]=composite.out +.composite.geometry=0,80:230x30:0;12=,:x:100 +.composite.luma=%luma01.pgm +.composite.softness=.3 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text= +.filter[1].producer.font=Sans +.filter[1].producer.size=24 +.filter[1].composite.geometry=0,0:95%x100% +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=center + +courtesy=region +.description=Courtesy +.properties.markup=filter[1].producer.text +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.period=2 +.properties.length[0]=composite.out +.composite.geometry=0,115:230x30:0;12=,:x:100 +.composite.luma=%luma01.pgm +.composite.softness=.3 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text= +.filter[1].producer.font=Sans +.filter[1].producer.size=24 +.filter[1].composite.geometry=0,0:95%x100% +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=centre + +exclusive=region +.description=Exclusive +.properties.markup=filter[1].producer.text +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.period=2 +.properties.length[0]=composite.out +.composite.geometry=-230,115:230x30;12=0 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text=ETV Exclusive +.filter[1].producer.font=Sans +.filter[1].producer.size=24 +.filter[1].producer.weight=700 +.filter[1].composite.geometry=0,0:95%x100% +.filter[1].composite.titles=1 +.filter[1].composite.halign=right +.filter[1].composite.valign=centre + +file_shot=region +.description=Titles +.period=2 +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.properties.length[0]=composite.out +.composite.geometry=590,160:80x25:0;12=,:x:100 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text=File Shot +.filter[1].producer.font=Sans +.filter[1].producer.size=18 +.filter[1].producer.weight=700 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +special=region +.description=Special +.period=2 +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.properties.length[0]=filter[0].composite.out +.properties.length[1]=filter[1].composite.out +.composite.geometry=465,375:255x35 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.geometry=100%,0%:100%x100%:0;12=0%,0%:x:100 +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text=Special +.filter[1].producer.font=Sans +.filter[1].producer.size=24 +.filter[1].producer.weight=700 +.filter[1].composite.geometry=100%,0%:100%x100%:0;12=0%,0%:x:100 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +ticker=region +.description=Tickertape +.properties.markup=filter[1].producer.text +.properties.font=filter[1].producer.font +.properties.size=filter[1].producer.size +.properties.length[0]=filter[1].composite.out +.composite.geometry=0,500:722x75 +.filter[0]=watermark +.filter[0].resource=colour:0x6c0101ff +.filter[0].composite.titles=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text=Ticker - provided for reference +.filter[1].producer.font=Sans +.filter[1].producer.size=24 +.filter[1].producer.weight=700 +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=centre + +super=region +.description=Transcription +.properties.0=filter[1].producer.text +.properties.1=filter[2].producer.text +.properties.align=filter[1].composite.valign +.properties.weight=filter[1].producer.weight +.properties.f0=filter[1].producer.font +.properties.s0=filter[1].producer.size +.properties.f1=filter[2].producer.font +.properties.s1=filter[2].producer.size +.properties.length[0]=composite.out +.period=2 +.composite.geometry=0,410:720x90:0;25=,:x:100 +.composite.luma=%luma01.pgm +.composite.luma_invert=1 +.composite.softness=.3 +.filter[0]=watermark +.filter[0].resource=colour:0xbbbbbbff +.filter[0].composite.geometry=0,0:100%:100%:70 +.filter[0].composite.distort=1 +.filter[1]=watermark +.filter[1].resource=pango: +.filter[1].producer.text= +.filter[1].producer.font=Sans +.filter[1].producer.size=32 +.filter[1].producer.weight=700 +.filter[1].producer.fgcolour=0x6c0101ff +.filter[1].composite.titles=1 +.filter[1].composite.halign=centre +.filter[1].composite.valign=top +.filter[2]=watermark +.filter[2].resource=pango: +.filter[2].producer.text= +.filter[2].producer.font=Sans +.filter[2].producer.size=32 +.filter[2].producer.fgcolour=0x6c0101ff +.filter[2].composite.titles=1 +.filter[2].composite.halign=centre +.filter[2].composite.valign=bottom + diff --git a/src/modules/feeds/PAL/example.properties b/src/modules/feeds/PAL/example.properties new file mode 100644 index 0000000..0509fff --- /dev/null +++ b/src/modules/feeds/PAL/example.properties @@ -0,0 +1,12 @@ +greyscale=greyscale +.description=Greyscale + +sepia=sepia +.description=Sepia + +charcoal=charcoal +.description=Charcoal + +invert=invert +.description=Invert + diff --git a/src/modules/feeds/PAL/obscure.properties b/src/modules/feeds/PAL/obscure.properties new file mode 100644 index 0000000..3917d9a --- /dev/null +++ b/src/modules/feeds/PAL/obscure.properties @@ -0,0 +1,35 @@ +# This properties file describes the fx available to the data_feed and +# data_show filters +# +# Syntax is as follows: +# +# name= +# name.description= +# name.properties.= +# name.=value +# etc +# +# Typically, the is a 'region' and additional filters are +# included as properties using the normal region filter syntax. +# + +obscure0=region +.description=Primary Obscure +.properties.geometry=composite.geometry +.properties.resource=resource +.properties.length[0]=composite.out +.composite.geometry= +.resource=rectangle +.composite.refresh=1 +.filter[0]=obscure + +obscure1=region +.description=Secondary Obscure +.properties.geometry=composite.geometry +.properties.resource=resource +.properties.length[0]=composite.out +.composite.geometry= +.resource=rectangle +.composite.refresh=1 +.filter[0]=obscure + diff --git a/src/modules/feeds/configure b/src/modules/feeds/configure new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/fezzik.dict b/src/modules/fezzik.dict new file mode 100644 index 0000000..e6724ef --- /dev/null +++ b/src/modules/fezzik.dict @@ -0,0 +1,38 @@ +http://*=avformat +.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -m 644 ../fezzik.dict "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -m 644 ../fezzik.ini "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/fezzik/configure b/src/modules/fezzik/configure new file mode 100755 index 0000000..9d26d18 --- /dev/null +++ b/src/modules/fezzik/configure @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +fezzik libmltfezzik$LIBSUF +hold libmltfezzik$LIBSUF +EOF + +fi + diff --git a/src/modules/fezzik/factory.c b/src/modules/fezzik/factory.c new file mode 100644 index 0000000..73949a4 --- /dev/null +++ b/src/modules/fezzik/factory.c @@ -0,0 +1,49 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_fezzik.h" +#include "producer_hold.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "fezzik" ) ) + return producer_fezzik_init( arg ); + if ( !strcmp( id, "hold" ) ) + return producer_hold_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/fezzik/producer_fezzik.c b/src/modules/fezzik/producer_fezzik.c new file mode 100644 index 0000000..81ac239 --- /dev/null +++ b/src/modules/fezzik/producer_fezzik.c @@ -0,0 +1,179 @@ +/* + * producer_fezzik.c -- a normalising filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_fezzik.h" + +#include +#include +#include +#include +#include + +#include + +static mlt_properties dictionary = NULL; +static mlt_properties normalisers = NULL; + +static mlt_producer create_from( char *file, char *services ) +{ + mlt_producer producer = NULL; + char *temp = strdup( services ); + char *service = temp; + do + { + char *p = strchr( service, ',' ); + if ( p != NULL ) + *p ++ = '\0'; + producer = mlt_factory_producer( service, file ); + service = p; + } + while ( producer == NULL && service != NULL ); + free( temp ); + return producer; +} + +static mlt_producer create_producer( char *file ) +{ + mlt_producer result = NULL; + + // 1st Line - check for service:resource handling + if ( strchr( file, ':' ) ) + { + char *temp = strdup( file ); + char *service = temp; + char *resource = strchr( temp, ':' ); + *resource ++ = '\0'; + result = mlt_factory_producer( service, resource ); + free( temp ); + } + + // 2nd Line preferences + if ( result == NULL ) + { + int i = 0; + char *lookup = strdup( file ); + char *p = lookup; + + // We only need to load the dictionary once + if ( dictionary == NULL ) + { + char temp[ 1024 ]; + sprintf( temp, "%s/fezzik.dict", mlt_factory_prefix( ) ); + dictionary = mlt_properties_load( temp ); + mlt_factory_register_for_clean_up( dictionary, ( mlt_destructor )mlt_properties_close ); + } + + // Convert the lookup string to lower case + while ( *p ) + { + *p = tolower( *p ); + p ++; + } + + // Iterate through the dictionary + for ( i = 0; result == NULL && i < mlt_properties_count( dictionary ); i ++ ) + { + char *name = mlt_properties_get_name( dictionary, i ); + if ( fnmatch( name, lookup, 0 ) == 0 ) + result = create_from( file, mlt_properties_get_value( dictionary, i ) ); + } + + free( lookup ); + } + + // Finally, try just loading as service + if ( result == NULL ) + result = mlt_factory_producer( file, NULL ); + + return result; +} + +static void create_filter( mlt_producer producer, char *effect, int *created ) +{ + char *id = strdup( effect ); + char *arg = strchr( id, ':' ); + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_filter filter = mlt_factory_filter( id, arg ); + if ( filter != NULL ) + { + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "_fezzik", 1 ); + mlt_producer_attach( producer, filter ); + mlt_filter_close( filter ); + *created = 1; + } + free( id ); +} + +static void attach_normalisers( mlt_producer producer ) +{ + // Loop variable + int i; + + // Tokeniser + mlt_tokeniser tokeniser = mlt_tokeniser_init( ); + + // We only need to load the normalising properties once + if ( normalisers == NULL ) + { + char temp[ 1024 ]; + sprintf( temp, "%s/fezzik.ini", mlt_factory_prefix( ) ); + normalisers = mlt_properties_load( temp ); + mlt_factory_register_for_clean_up( normalisers, ( mlt_destructor )mlt_properties_close ); + } + + // Apply normalisers + for ( i = 0; i < mlt_properties_count( normalisers ); i ++ ) + { + int j = 0; + int created = 0; + char *value = mlt_properties_get_value( normalisers, i ); + mlt_tokeniser_parse_new( tokeniser, value, "," ); + for ( j = 0; !created && j < mlt_tokeniser_count( tokeniser ); j ++ ) + create_filter( producer, mlt_tokeniser_get_string( tokeniser, j ), &created ); + } + + // Close the tokeniser + mlt_tokeniser_close( tokeniser ); +} + +mlt_producer producer_fezzik_init( char *arg ) +{ + // Create the producer + mlt_producer producer = NULL; + mlt_properties properties = NULL; + + if ( arg != NULL ) + producer = create_producer( arg ); + + if ( producer != NULL ) + properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Attach filters if we have a producer and it isn't already westley'd :-) + if ( producer != NULL && mlt_properties_get( properties, "westley" ) == NULL && mlt_properties_get( properties, "_westley" ) == NULL ) + attach_normalisers( producer ); + + // Now make sure we don't lose our identity + if ( properties != NULL ) + mlt_properties_set_int( properties, "_mlt_service_hidden", 1 ); + + // Return the producer + return producer; +} diff --git a/src/modules/fezzik/producer_fezzik.h b/src/modules/fezzik/producer_fezzik.h new file mode 100644 index 0000000..c43a884 --- /dev/null +++ b/src/modules/fezzik/producer_fezzik.h @@ -0,0 +1,28 @@ +/* + * producer_fezzik.h -- a normalising producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_FEZZIK_H_ +#define _PRODUCER_FEZZIK_H_ + +#include + +extern mlt_producer producer_fezzik_init( char *args ); + +#endif diff --git a/src/modules/fezzik/producer_hold.c b/src/modules/fezzik/producer_hold.c new file mode 100644 index 0000000..a728314 --- /dev/null +++ b/src/modules/fezzik/producer_hold.c @@ -0,0 +1,203 @@ +/* + * producer_hold.c -- frame holding producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_hold.h" + +#include +#include +#include + +#include + +// Forward references +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer this ); + +/** Constructor for the frame holding producer. Basically, all this producer does is + provide a producer wrapper for the requested producer, allows the specifcation of + the frame required and will then repeatedly obtain that frame for each get_frame + and get_image requested. +*/ + +mlt_producer producer_hold_init( char *arg ) +{ + // Construct a new holding producer + mlt_producer this = mlt_producer_new( ); + + // Construct the requested producer via fezzik + mlt_producer producer = mlt_factory_producer( "fezzik", arg ); + + // Initialise the frame holding capabilities + if ( this != NULL && producer != NULL ) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Store the producer + mlt_properties_set_data( properties, "producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + + // Set frame, in, out and length for this producer + mlt_properties_set_position( properties, "frame", 0 ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 25 ); + mlt_properties_set_position( properties, "length", 15000 ); + mlt_properties_set( properties, "resource", arg ); + mlt_properties_set( properties, "method", "onefield" ); + + // Override the get_frame method + this->get_frame = producer_get_frame; + this->close = ( mlt_destructor )producer_close; + } + else + { + // Clean up (not sure which one failed, can't be bothered to find out, so close both) + if ( this ) + mlt_producer_close( this ); + if ( producer ) + mlt_producer_close( producer ); + + // Make sure we return NULL + this = NULL; + } + + // Return this producer + return this; +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the real frame + mlt_frame real_frame = mlt_frame_pop_service( frame ); + + // Get the image from the real frame + int size = 0; + *buffer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( real_frame ), "image", &size ); + *width = mlt_properties_get_int( MLT_FRAME_PROPERTIES( real_frame ), "width" ); + *height = mlt_properties_get_int( MLT_FRAME_PROPERTIES( real_frame ), "height" ); + + // If this is the first time, get it from the producer + if ( *buffer == NULL ) + { + mlt_properties_pass( MLT_FRAME_PROPERTIES( real_frame ), properties, "" ); + + // We'll deinterlace on the downstream deinterlacer + mlt_properties_set_int( MLT_FRAME_PROPERTIES( real_frame ), "consumer_deinterlace", 1 ); + + // We want distorted to ensure we don't hit the resize filter twice + mlt_properties_set_int( MLT_FRAME_PROPERTIES( real_frame ), "distort", 1 ); + + // Get the image + mlt_frame_get_image( real_frame, buffer, format, width, height, writable ); + + // Make sure we get the size + *buffer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( real_frame ), "image", &size ); + } + + mlt_properties_pass( properties, MLT_FRAME_PROPERTIES( real_frame ), "" ); + + // Set the values obtained on the frame + if ( *buffer != NULL ) + { + uint8_t *image = mlt_pool_alloc( size ); + memcpy( image, *buffer, size ); + *buffer = image; + mlt_properties_set_data( properties, "image", *buffer, size, mlt_pool_release, NULL ); + } + else + { + // Pass the current image as is + mlt_properties_set_data( properties, "image", *buffer, size, NULL, NULL ); + } + + // Make sure that no further scaling is done + mlt_properties_set( properties, "rescale.interps", "none" ); + mlt_properties_set( properties, "scale", "off" ); + + // All done + return 0; +} + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Construct a new frame + *frame = mlt_frame_init( ); + + // If we have a frame, then stack the producer itself and the get_image method + if ( *frame != NULL ) + { + // Define the real frame + mlt_frame real_frame = mlt_properties_get_data( properties, "real_frame", NULL ); + + // Obtain real frame if we don't have it + if ( real_frame == NULL ) + { + // Get the producer + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + + // Get the frame position requested + mlt_position position = mlt_properties_get_position( properties, "frame" ); + + // Seek the producer to the correct place + mlt_producer_seek( producer, position ); + + // Get the real frame + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &real_frame, index ); + + // Ensure that the real frame gets wiped eventually + mlt_properties_set_data( properties, "real_frame", real_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + else + { + // Temporary fix - ensure that we aren't seen as a test frame + int8_t *image = mlt_properties_get_data( MLT_FRAME_PROPERTIES( real_frame ), "image", NULL ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "image", image, 0, NULL, NULL ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", 0 ); + } + + // Stack the real frame and method + mlt_frame_push_service( *frame, real_frame ); + mlt_frame_push_service( *frame, producer_get_image ); + + // Ensure that the consumer sees what the real frame has + mlt_properties_pass( MLT_FRAME_PROPERTIES( *frame ), MLT_FRAME_PROPERTIES( real_frame ), "" ); + + mlt_properties_set( MLT_FRAME_PROPERTIES( real_frame ), "deinterlace_method", + mlt_properties_get( properties, "method" ) ); + } + + // Move to the next position + mlt_producer_prepare_next( this ); + + return 0; +} + +static void producer_close( mlt_producer this ) +{ + this->close = NULL; + mlt_producer_close( this ); + free( this ); +} + diff --git a/src/modules/fezzik/producer_hold.h b/src/modules/fezzik/producer_hold.h new file mode 100644 index 0000000..64dc166 --- /dev/null +++ b/src/modules/fezzik/producer_hold.h @@ -0,0 +1,28 @@ +/* + * producer_hold.h -- a frame holding producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_HOLD_H_ +#define _PRODUCER_HOLD_H_ + +#include + +extern mlt_producer producer_hold_init( char *args ); + +#endif diff --git a/src/modules/gtk2/Makefile b/src/modules/gtk2/Makefile new file mode 100644 index 0000000..bb4eb96 --- /dev/null +++ b/src/modules/gtk2/Makefile @@ -0,0 +1,60 @@ +include ../../../config.mak +include config.mak + +TARGET = ../libmltgtk2$(LIBSUF) + +OBJS = factory.o + +ifdef USE_GTK2 +OBJS += consumer_gtk2.o +CFLAGS += `pkg-config gtk+-2.0 --cflags` +LDFLAGS += `pkg-config gtk+-2.0 --libs` +endif + +ifdef USE_PIXBUF +OBJS += producer_pixbuf.o pixops.o filter_rescale.o +CFLAGS += `pkg-config gdk-pixbuf-2.0 --cflags` +LDFLAGS += `pkg-config gdk-pixbuf-2.0 --libs` +endif + +ifdef MMX_FLAGS +ASM_OBJS = have_mmx.o scale_line_22_yuv_mmx.o +endif + +ifdef USE_PANGO +OBJS += producer_pango.o +CFLAGS += `pkg-config pangoft2 --cflags` +LDFLAGS += `pkg-config pangoft2 --libs` +endif + +CFLAGS += -I../.. +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(ASM_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(ASM_OBJS) $(LDFLAGS) + +have_mmx.o: + $(CC) -o $@ -c have_mmx.S + +scale_line_22_yuv_mmx.o: scale_line_22_yuv_mmx.S + $(CC) -o $@ -c scale_line_22_yuv_mmx.S + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(ASM_OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/gtk2/config.h b/src/modules/gtk2/config.h new file mode 100644 index 0000000..c1fcb54 --- /dev/null +++ b/src/modules/gtk2/config.h @@ -0,0 +1,4 @@ + +#define USE_GTK2 +#define USE_PIXBUF +#define USE_PANGO diff --git a/src/modules/gtk2/config.mak b/src/modules/gtk2/config.mak new file mode 100644 index 0000000..e3f3560 --- /dev/null +++ b/src/modules/gtk2/config.mak @@ -0,0 +1,4 @@ + +USE_GTK2=1 +USE_PIXBUF=1 +USE_PANGO=1 diff --git a/src/modules/gtk2/configure b/src/modules/gtk2/configure new file mode 100755 index 0000000..d6c7f3f --- /dev/null +++ b/src/modules/gtk2/configure @@ -0,0 +1,43 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + pkg-config gtk+-2.0 2> /dev/null + disable_gtk2=$? + + pkg-config gdk-pixbuf-2.0 2> /dev/null + disable_pixbuf=$? + + pkg-config pangoft2 2> /dev/null + disable_pango=$? + + if [ "$disable_gtk2" != "0" -a "$disable_pixbuf" != 0 -a "$disable_pango" != "0" ] + then + echo No GTK2 components found - disabling + touch ../disable-gtk2 + exit 0 + fi + + [ "$disable_gtk2" != "0" ] && echo "- gtk2 not found: gtk2 preview disabled" + [ "$disable_pixbuf" != "0" ] && echo "- pixbuf not found: pixbuf loader and rescaler disabled" + [ "$disable_pango" != "0" ] && echo "- pango not found: pango titler disabled" + + echo > config.h + [ "$disable_gtk2" = "0" ] && echo "#define USE_GTK2" >> config.h + [ "$disable_pixbuf" = "0" ] && echo "#define USE_PIXBUF" >> config.h + [ "$disable_pango" = "0" ] && echo "#define USE_PANGO" >> config.h + + echo > config.mak + [ "$disable_gtk2" = "0" ] && echo "USE_GTK2=1" >> config.mak + [ "$disable_pixbuf" = "0" ] && echo "USE_PIXBUF=1" >> config.mak + [ "$disable_pango" = "0" ] && echo "USE_PANGO=1" >> config.mak + + [ "$disable_pixbuf" = "0" ] && echo "pixbuf libmltgtk2$LIBSUF" >> ../producers.dat + [ "$disable_pango" = "0" ] && echo "pango libmltgtk2$LIBSUF" >> ../producers.dat + [ "$disable_pixbuf" = "0" ] && echo "gtkrescale libmltgtk2$LIBSUF" >> ../filters.dat + [ "$disable_gtk2" = "0" ] && echo "gtk2_preview libmltgtk2$LIBSUF" >> ../consumers.dat + + exit 0 +fi + diff --git a/src/modules/gtk2/consumer_gtk2.c b/src/modules/gtk2/consumer_gtk2.c new file mode 100644 index 0000000..20e26b9 --- /dev/null +++ b/src/modules/gtk2/consumer_gtk2.c @@ -0,0 +1,56 @@ +/* + * consumer_gtk2.c -- A consumer for GTK2 apps + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_gtk2.h" + +#include +#include +#include +#include +#include + +mlt_consumer consumer_gtk2_preview_init( GtkWidget *widget ) +{ + // Create an sdl preview consumer + mlt_consumer consumer = NULL; + + // This is a nasty little hack which is required by SDL + if ( widget != NULL ) + { + Window xwin = GDK_WINDOW_XWINDOW( widget->window ); + char windowhack[ 32 ]; + sprintf( windowhack, "%ld", xwin ); + setenv( "SDL_WINDOWID", windowhack, 1 ); + } + + // Create an sdl preview consumer + consumer = mlt_factory_consumer( "sdl_preview", NULL ); + + // Now assign the lock/unlock callbacks + if ( consumer != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + mlt_properties_set_int( properties, "app_locked", 1 ); + mlt_properties_set_data( properties, "app_lock", gdk_threads_enter, 0, NULL, NULL ); + mlt_properties_set_data( properties, "app_unlock", gdk_threads_leave, 0, NULL, NULL ); + } + + return consumer; +} diff --git a/src/modules/gtk2/consumer_gtk2.h b/src/modules/gtk2/consumer_gtk2.h new file mode 100644 index 0000000..58d54ac --- /dev/null +++ b/src/modules/gtk2/consumer_gtk2.h @@ -0,0 +1,29 @@ +/* + * consumer_gtk2.h -- A consumer for GTK2 apps + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_GTK2_PREVIEW_H +#define _CONSUMER_GTK2_PREVIEW_H + +#include +#include + +extern mlt_consumer consumer_gtk2_preview_init( GtkWidget *widget ); + +#endif diff --git a/src/modules/gtk2/factory.c b/src/modules/gtk2/factory.c new file mode 100644 index 0000000..4ad1971 --- /dev/null +++ b/src/modules/gtk2/factory.c @@ -0,0 +1,93 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include + +#ifdef USE_PIXBUF +#include +#include "producer_pixbuf.h" +#include "filter_rescale.h" +#endif + +#ifdef USE_GTK2 +#include "consumer_gtk2.h" +#endif + +#ifdef USE_PANGO +#include "producer_pango.h" +#endif + +static void initialise( ) +{ + static int init = 0; + if ( init == 0 ) + { + init = 1; + g_type_init( ); + } +} + +void *mlt_create_producer( char *id, void *arg ) +{ + initialise( ); + +#ifdef USE_PIXBUF + if ( !strcmp( id, "pixbuf" ) ) + return producer_pixbuf_init( arg ); +#endif + +#ifdef USE_PANGO + if ( !strcmp( id, "pango" ) ) + return producer_pango_init( arg ); +#endif + + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + initialise( ); + +#ifdef USE_PIXBUF + if ( !strcmp( id, "gtkrescale" ) ) + return filter_rescale_init( arg ); +#endif + + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + initialise( ); + +#ifdef USE_GTK2 + if ( !strcmp( id, "gtk2_preview" ) ) + return consumer_gtk2_preview_init( arg ); +#endif + + return NULL; +} + diff --git a/src/modules/gtk2/filter_rescale.c b/src/modules/gtk2/filter_rescale.c new file mode 100644 index 0000000..0e6d8ca --- /dev/null +++ b/src/modules/gtk2/filter_rescale.c @@ -0,0 +1,158 @@ +/* + * filter_rescale.c -- scale the producer video frame size to match the consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_rescale.h" +#include "pixops.h" + +#include +#include + +#include +#include +#include +#include + +static int filter_scale( mlt_frame this, uint8_t **image, mlt_image_format iformat, mlt_image_format oformat, int iwidth, int iheight, int owidth, int oheight ) +{ + // Get the properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the requested interpolation method + char *interps = mlt_properties_get( properties, "rescale.interp" ); + + // Convert to the GTK flag + int interp = PIXOPS_INTERP_BILINEAR; + + if ( strcmp( interps, "nearest" ) == 0 ) + interp = PIXOPS_INTERP_NEAREST; + else if ( strcmp( interps, "tiles" ) == 0 ) + interp = PIXOPS_INTERP_TILES; + else if ( strcmp( interps, "hyper" ) == 0 ) + interp = PIXOPS_INTERP_HYPER; + + // Carry out the rescaling + if ( iformat == mlt_image_yuv422 && oformat == mlt_image_yuv422 ) + { + // Create the output image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + // Calculate strides + int istride = iwidth * 2; + int ostride = owidth * 2; + + yuv422_scale_simple( output, owidth, oheight, ostride, *image, iwidth, iheight, istride, interp ); + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + // Return the output + *image = output; + } + else if ( iformat == mlt_image_rgb24 || iformat == mlt_image_rgb24a ) + { + int bpp = (iformat == mlt_image_rgb24a ? 4 : 3 ); + + // Create the yuv image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + if ( strcmp( interps, "none" ) && ( iwidth != owidth || iheight != oheight ) ) + { + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data( *image, GDK_COLORSPACE_RGB, + ( iformat == mlt_image_rgb24a ), 8, iwidth, iheight, + iwidth * bpp, NULL, NULL ); + + GdkPixbuf *scaled = gdk_pixbuf_scale_simple( pixbuf, owidth, oheight, interp ); + g_object_unref( pixbuf ); + + // Extract YUV422 and alpha + if ( bpp == 4 ) + { + // Allocate the alpha mask + uint8_t *alpha = mlt_pool_alloc( owidth * ( oheight + 1 ) ); + + // Convert the image and extract alpha + mlt_convert_rgb24a_to_yuv422( gdk_pixbuf_get_pixels( scaled ), owidth, oheight, gdk_pixbuf_get_rowstride( scaled ), output, alpha ); + + mlt_properties_set_data( properties, "alpha", alpha, owidth * ( oheight + 1 ), ( mlt_destructor )mlt_pool_release, NULL ); + } + else + { + // No alpha to extract + mlt_convert_rgb24_to_yuv422( gdk_pixbuf_get_pixels( scaled ), owidth, oheight, gdk_pixbuf_get_rowstride( scaled ), output ); + } + g_object_unref( scaled ); + } + else + { + // Extract YUV422 and alpha + if ( bpp == 4 ) + { + // Allocate the alpha mask + uint8_t *alpha = mlt_pool_alloc( owidth * ( oheight + 1 ) ); + + // Convert the image and extract alpha + mlt_convert_rgb24a_to_yuv422( *image, owidth, oheight, owidth * 4, output, alpha ); + + mlt_properties_set_data( properties, "alpha", alpha, owidth * ( oheight + 1 ), ( mlt_destructor )mlt_pool_release, NULL ); + } + else + { + // No alpha to extract + mlt_convert_rgb24_to_yuv422( *image, owidth, oheight, owidth * 3, output ); + } + } + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + *image = output; + } + + return 0; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_rescale_init( char *arg ) +{ + // Create a new scaler + mlt_filter this = mlt_factory_filter( "rescale", arg ); + + // If successful, then initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Set the inerpolation + mlt_properties_set( properties, "interpolation", arg == NULL ? "bilinear" : arg ); + + // Set the method + mlt_properties_set_data( properties, "method", filter_scale, 0, NULL, NULL ); + } + + return this; +} + diff --git a/src/modules/gtk2/filter_rescale.h b/src/modules/gtk2/filter_rescale.h new file mode 100644 index 0000000..1d0e8d7 --- /dev/null +++ b/src/modules/gtk2/filter_rescale.h @@ -0,0 +1,28 @@ +/* + * filter_rescale.h -- scale the producer video frame size to match the consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_RESCALE_H_ +#define _FILTER_RESCALE_H_ + +#include + +extern mlt_filter filter_rescale_init( char *arg ); + +#endif diff --git a/src/modules/gtk2/have_mmx.S b/src/modules/gtk2/have_mmx.S new file mode 100644 index 0000000..4f8f5d8 --- /dev/null +++ b/src/modules/gtk2/have_mmx.S @@ -0,0 +1,53 @@ + .file "have_mmx.S" + .version "01.01" +gcc2_compiled.: +.text + .align 16 + +#if !defined(__MINGW32__) && !defined(__CYGWIN__) + +.globl pixops_have_mmx + .type pixops_have_mmx,@function +pixops_have_mmx: + +#else + +.globl _pixops_have_mmx +_pixops_have_mmx: + +#endif + + push %ebx + +# Check if bit 21 in flags word is writeable + + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + + je .notfound + +# OK, we have CPUID + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz .notfound + + movl $1, %eax + jmp .out + +.notfound: + movl $0, %eax +.out: + popl %ebx + ret + diff --git a/src/modules/gtk2/pixops.c b/src/modules/gtk2/pixops.c new file mode 100644 index 0000000..3920597 --- /dev/null +++ b/src/modules/gtk2/pixops.c @@ -0,0 +1,769 @@ +/* GdkPixbuf library - Scaling and compositing functions + * + * Original: + * Copyright (C) 2000 Red Hat, Inc + * Author: Owen Taylor + * + * Modification for MLT: + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "pixops.h" + +#define SUBSAMPLE_BITS 4 +#define SUBSAMPLE (1 << SUBSAMPLE_BITS) +#define SUBSAMPLE_MASK ((1 << SUBSAMPLE_BITS)-1) +#define SCALE_SHIFT 16 + +typedef struct _PixopsFilter PixopsFilter; +typedef struct _PixopsFilterDimension PixopsFilterDimension; + +struct _PixopsFilterDimension +{ + int n; + double offset; + double *weights; +}; + +struct _PixopsFilter +{ + PixopsFilterDimension x; + PixopsFilterDimension y; + double overall_alpha; +}; + +typedef guchar *( *PixopsLineFunc ) ( int *weights, int n_x, int n_y, + guchar *dest, int dest_x, guchar *dest_end, + guchar **src, + int x_init, int x_step, int src_width ); + +typedef void ( *PixopsPixelFunc ) ( guchar *dest, guint y1, guint cr, guint y2, guint cb ); + + +/* mmx function declarations */ +#ifdef USE_MMX +guchar *pixops_scale_line_22_yuv_mmx ( guint32 weights[ 16 ][ 8 ], guchar *p, guchar *q1, guchar *q2, int x_step, guchar *p_stop, int x_init, int destx ); +int pixops_have_mmx ( void ); +#endif + +static inline int +get_check_shift ( int check_size ) +{ + int check_shift = 0; + g_return_val_if_fail ( check_size >= 0, 4 ); + + while ( !( check_size & 1 ) ) + { + check_shift++; + check_size >>= 1; + } + + return check_shift; +} + +static inline void +pixops_scale_nearest ( guchar *dest_buf, + int render_x0, + int render_y0, + int render_x1, + int render_y1, + int dest_rowstride, + const guchar *src_buf, + int src_width, + int src_height, + int src_rowstride, + double scale_x, + double scale_y ) +{ + register int i, j; + register int x_step = ( 1 << SCALE_SHIFT ) / scale_x; + register int y_step = ( 1 << SCALE_SHIFT ) / scale_y; + register int x, x_scaled; + + for ( i = 0; i < ( render_y1 - render_y0 ); i++ ) + { + const guchar *src = src_buf + ( ( ( i + render_y0 ) * y_step + ( y_step >> 1 ) ) >> SCALE_SHIFT ) * src_rowstride; + guchar *dest = dest_buf + i * dest_rowstride; + x = render_x0 * x_step + ( x_step >> 1 ); + + for ( j = 0; j < ( render_x1 - render_x0 ); j++ ) + { + x_scaled = x >> SCALE_SHIFT; + *dest++ = src[ x_scaled << 1 ]; + *dest++ = src[ ( ( x_scaled >> 1 ) << 2 ) + ( ( j & 1 ) << 1 ) + 1 ]; + x += x_step; + } + } +} + + +static inline guchar * +scale_line ( int *weights, int n_x, int n_y, + guchar *dest, int dest_x, guchar *dest_end, + guchar **src, + int x_init, int x_step, int src_width ) +{ + register int x = x_init; + register int i, j, x_scaled, y_index, uv_index; + + while ( dest < dest_end ) + { + unsigned int y = 0, uv = 0; + int *pixel_weights = weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * n_x * n_y; + + x_scaled = x >> SCALE_SHIFT; + y_index = x_scaled << 1; + uv_index = ( ( x_scaled >> 1 ) << 2 ) + ( ( dest_x & 1 ) << 1 ) + 1; + + for ( i = 0; i < n_y; i++ ) + { + int *line_weights = pixel_weights + n_x * i; + guchar *q = src[ i ]; + + for ( j = 0; j < n_x; j ++ ) + { + unsigned int ta = line_weights[ j ]; + + y += ta * q[ y_index ]; + uv += ta * q[ uv_index ]; + } + } + + *dest++ = ( y + 0xffff ) >> SCALE_SHIFT; + *dest++ = ( uv + 0xffff ) >> SCALE_SHIFT; + + x += x_step; + dest_x++; + } + + return dest; +} + +#ifdef USE_MMX +static inline guchar * +scale_line_22_yuv_mmx_stub ( int *weights, int n_x, int n_y, + guchar *dest, int dest_x, guchar *dest_end, + guchar **src, + int x_init, int x_step, int src_width ) +{ + guint32 mmx_weights[ 16 ][ 8 ]; + int j; + + for ( j = 0; j < 16; j++ ) + { + mmx_weights[ j ][ 0 ] = 0x00010001 * ( weights[ 4 * j ] >> 8 ); + mmx_weights[ j ][ 1 ] = 0x00010001 * ( weights[ 4 * j ] >> 8 ); + mmx_weights[ j ][ 2 ] = 0x00010001 * ( weights[ 4 * j + 1 ] >> 8 ); + mmx_weights[ j ][ 3 ] = 0x00010001 * ( weights[ 4 * j + 1 ] >> 8 ); + mmx_weights[ j ][ 4 ] = 0x00010001 * ( weights[ 4 * j + 2 ] >> 8 ); + mmx_weights[ j ][ 5 ] = 0x00010001 * ( weights[ 4 * j + 2 ] >> 8 ); + mmx_weights[ j ][ 6 ] = 0x00010001 * ( weights[ 4 * j + 3 ] >> 8 ); + mmx_weights[ j ][ 7 ] = 0x00010001 * ( weights[ 4 * j + 3 ] >> 8 ); + } + + return pixops_scale_line_22_yuv_mmx ( mmx_weights, dest, src[ 0 ], src[ 1 ], x_step, dest_end, x_init, dest_x ); +} +#endif /* USE_MMX */ + +static inline guchar * +scale_line_22_yuv ( int *weights, int n_x, int n_y, + guchar *dest, int dest_x, guchar *dest_end, + guchar **src, + int x_init, int x_step, int src_width ) +{ + register int x = x_init; + register guchar *src0 = src[ 0 ]; + register guchar *src1 = src[ 1 ]; + register unsigned int p; + register guchar *q0, *q1; + register int w1, w2, w3, w4; + register int x_scaled, x_aligned, uv_index; + + while ( dest < dest_end ) + { + int *pixel_weights = weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * 4; + + x_scaled = x >> SCALE_SHIFT; + + w1 = pixel_weights[ 0 ]; + w2 = pixel_weights[ 1 ]; + w3 = pixel_weights[ 2 ]; + w4 = pixel_weights[ 3 ]; + + /* process Y */ + q0 = src0 + ( x_scaled << 1 ); + q1 = src1 + ( x_scaled << 1 ); + p = w1 * q0[ 0 ]; + p += w2 * q0[ 2 ]; + p += w3 * q1[ 0 ]; + p += w4 * q1[ 2 ]; + *dest++ = ( p + 0x8000 ) >> SCALE_SHIFT; + + /* process U/V */ + x_aligned = ( ( x_scaled >> 1 ) << 2 ); + uv_index = ( ( dest_x & 1 ) << 1 ) + 1; + + q0 = src0 + x_aligned; + q1 = src1 + x_aligned; + p = w1 * q0[ uv_index ]; + p += w3 * q1[ uv_index ]; + p += w2 * q0[ uv_index ]; + p += w4 * q1[ uv_index ]; + + x += x_step; + dest_x ++; + + *dest++ = ( p + 0x8000 ) >> SCALE_SHIFT; + } + + return dest; +} + + +static inline void +process_pixel ( int *weights, int n_x, int n_y, + guchar *dest, int dest_x, int dest_channels, + guchar **src, int src_channels, + int x_start, int src_width ) +{ + register unsigned int y = 0, uv = 0; + register int i, j; + int uv_index = ( ( dest_x & 1 ) << 1 ) + 1; + + for ( i = 0; i < n_y; i++ ) + { + int *line_weights = weights + n_x * i; + + for ( j = 0; j < n_x; j++ ) + { + unsigned int ta = 0xff * line_weights[ j ]; + + if ( x_start + j < 0 ) + { + y += ta * src[ i ][ 0 ]; + uv += ta * src[ i ][ uv_index ]; + } + else if ( x_start + j < src_width ) + { + y += ta * src[ i ][ ( x_start + j ) << 1 ]; + uv += ta * src[ i ][ ( ( ( x_start + j ) >> 1 ) << 2) + uv_index ]; + } + else + { + y += ta * src[ i ][ ( src_width - 1 ) << 1 ]; + uv += ta * src[ i ][ ( ( ( src_width - 1 ) >> 1 ) << 2) + uv_index ]; + } + } + } + + *dest++ = ( y + 0xffffff ) >> 24; + *dest++ = ( uv + 0xffffff ) >> 24; +} + + +static inline void +correct_total ( int *weights, + int n_x, + int n_y, + int total, + double overall_alpha ) +{ + int correction = ( int ) ( 0.5 + 65536 * overall_alpha ) - total; + int remaining, c, d, i; + + if ( correction != 0 ) + { + remaining = correction; + for ( d = 1, c = correction; c != 0 && remaining != 0; d++, c = correction / d ) + for ( i = n_x * n_y - 1; i >= 0 && c != 0 && remaining != 0; i-- ) + if ( *( weights + i ) + c >= 0 ) + { + *( weights + i ) += c; + remaining -= c; + if ( ( 0 < remaining && remaining < c ) || + ( 0 > remaining && remaining > c ) ) + c = remaining; + } + } +} + + +static inline int * +make_filter_table ( PixopsFilter *filter ) +{ + int i_offset, j_offset; + int n_x = filter->x.n; + int n_y = filter->y.n; + int *weights = g_new ( int, SUBSAMPLE * SUBSAMPLE * n_x * n_y ); + + for ( i_offset = 0; i_offset < SUBSAMPLE; i_offset++ ) + for ( j_offset = 0; j_offset < SUBSAMPLE; j_offset++ ) + { + double weight; + int *pixel_weights = weights + ( ( i_offset * SUBSAMPLE ) + j_offset ) * n_x * n_y; + int total = 0; + int i, j; + + for ( i = 0; i < n_y; i++ ) + for ( j = 0; j < n_x; j++ ) + { + weight = filter->x.weights[ ( j_offset * n_x ) + j ] * + filter->y.weights[ ( i_offset * n_y ) + i ] * + filter->overall_alpha * 65536 + 0.5; + + total += ( int ) weight; + + *( pixel_weights + n_x * i + j ) = weight; + } + + correct_total ( pixel_weights, n_x, n_y, total, filter->overall_alpha ); + } + + return weights; +} + + +static inline void +pixops_process ( guchar *dest_buf, + int render_x0, + int render_y0, + int render_x1, + int render_y1, + int dest_rowstride, + int dest_channels, + gboolean dest_has_alpha, + const guchar *src_buf, + int src_width, + int src_height, + int src_rowstride, + int src_channels, + gboolean src_has_alpha, + double scale_x, + double scale_y, + int check_x, + int check_y, + int check_size, + guint32 color1, + guint32 color2, + PixopsFilter *filter, + PixopsLineFunc line_func ) +{ + int i, j; + int x, y; /* X and Y position in source (fixed_point) */ + + guchar **line_bufs = g_new ( guchar *, filter->y.n ); + int *filter_weights = make_filter_table ( filter ); + + int x_step = ( 1 << SCALE_SHIFT ) / scale_x; /* X step in source (fixed point) */ + int y_step = ( 1 << SCALE_SHIFT ) / scale_y; /* Y step in source (fixed point) */ + + int check_shift = check_size ? get_check_shift ( check_size ) : 0; + + int scaled_x_offset = floor ( filter->x.offset * ( 1 << SCALE_SHIFT ) ); + + /* Compute the index where we run off the end of the source buffer. The furthest + * source pixel we access at index i is: + * + * ((render_x0 + i) * x_step + scaled_x_offset) >> SCALE_SHIFT + filter->x.n - 1 + * + * So, run_end_index is the smallest i for which this pixel is src_width, i.e, for which: + * + * (i + render_x0) * x_step >= ((src_width - filter->x.n + 1) << SCALE_SHIFT) - scaled_x_offset + * + */ +#define MYDIV(a,b) ((a) > 0 ? (a) / (b) : ((a) - (b) + 1) / (b)) /* Division so that -1/5 = -1 */ + + int run_end_x = ( ( ( src_width - filter->x.n + 1 ) << SCALE_SHIFT ) - scaled_x_offset ); + int run_end_index = MYDIV ( run_end_x + x_step - 1, x_step ) - render_x0; + run_end_index = MIN ( run_end_index, render_x1 - render_x0 ); + + y = render_y0 * y_step + floor ( filter->y.offset * ( 1 << SCALE_SHIFT ) ); + for ( i = 0; i < ( render_y1 - render_y0 ); i++ ) + { + int dest_x; + int y_start = y >> SCALE_SHIFT; + int x_start; + int *run_weights = filter_weights + + ( ( y >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * + filter->x.n * filter->y.n * SUBSAMPLE; + guchar *new_outbuf; + guint32 tcolor1, tcolor2; + + guchar *outbuf = dest_buf + dest_rowstride * i; + guchar *outbuf_end = outbuf + dest_channels * ( render_x1 - render_x0 ); + + if ( ( ( i + check_y ) >> check_shift ) & 1 ) + { + tcolor1 = color2; + tcolor2 = color1; + } + else + { + tcolor1 = color1; + tcolor2 = color2; + } + + for ( j = 0; j < filter->y.n; j++ ) + { + if ( y_start < 0 ) + line_bufs[ j ] = ( guchar * ) src_buf; + else if ( y_start < src_height ) + line_bufs[ j ] = ( guchar * ) src_buf + src_rowstride * y_start; + else + line_bufs[ j ] = ( guchar * ) src_buf + src_rowstride * ( src_height - 1 ); + + y_start++; + } + + dest_x = check_x; + x = render_x0 * x_step + scaled_x_offset; + x_start = x >> SCALE_SHIFT; + + while ( x_start < 0 && outbuf < outbuf_end ) + { + process_pixel ( run_weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * ( filter->x.n * filter->y.n ), + filter->x.n, filter->y.n, + outbuf, dest_x, dest_channels, + line_bufs, src_channels, + x >> SCALE_SHIFT, src_width ); + + x += x_step; + x_start = x >> SCALE_SHIFT; + dest_x++; + outbuf += dest_channels; + } + + new_outbuf = ( *line_func ) ( run_weights, filter->x.n, filter->y.n, + outbuf, dest_x, + dest_buf + dest_rowstride * i + run_end_index * dest_channels, + line_bufs, + x, x_step, src_width ); + + dest_x += ( new_outbuf - outbuf ) / dest_channels; + + x = ( dest_x - check_x + render_x0 ) * x_step + scaled_x_offset; + outbuf = new_outbuf; + + while ( outbuf < outbuf_end ) + { + process_pixel ( run_weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * ( filter->x.n * filter->y.n ), + filter->x.n, filter->y.n, + outbuf, dest_x, dest_channels, + line_bufs, src_channels, + x >> SCALE_SHIFT, src_width ); + + x += x_step; + dest_x++; + outbuf += dest_channels; + } + + y += y_step; + } + + g_free ( line_bufs ); + g_free ( filter_weights ); +} + + +/* Compute weights for reconstruction by replication followed by + * sampling with a box filter + */ +static inline void +tile_make_weights ( PixopsFilterDimension *dim, + double scale ) +{ + int n = ceil ( 1 / scale + 1 ); + double *pixel_weights = g_new ( double, SUBSAMPLE * n ); + int offset; + int i; + + dim->n = n; + dim->offset = 0; + dim->weights = pixel_weights; + + for ( offset = 0; offset < SUBSAMPLE; offset++ ) + { + double x = ( double ) offset / SUBSAMPLE; + double a = x + 1 / scale; + + for ( i = 0; i < n; i++ ) + { + if ( i < x ) + { + if ( i + 1 > x ) + * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - x ) * scale; + else + *( pixel_weights++ ) = 0; + } + else + { + if ( a > i ) + * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - i ) * scale; + else + *( pixel_weights++ ) = 0; + } + } + } +} + +/* Compute weights for a filter that, for minification + * is the same as 'tiles', and for magnification, is bilinear + * reconstruction followed by a sampling with a delta function. + */ +static inline void +bilinear_magnify_make_weights ( PixopsFilterDimension *dim, + double scale ) +{ + double * pixel_weights; + int n; + int offset; + int i; + + if ( scale > 1.0 ) /* Linear */ + { + n = 2; + dim->offset = 0.5 * ( 1 / scale - 1 ); + } + else /* Tile */ + { + n = ceil ( 1.0 + 1.0 / scale ); + dim->offset = 0.0; + } + + dim->n = n; + dim->weights = g_new ( double, SUBSAMPLE * n ); + + pixel_weights = dim->weights; + + for ( offset = 0; offset < SUBSAMPLE; offset++ ) + { + double x = ( double ) offset / SUBSAMPLE; + + if ( scale > 1.0 ) /* Linear */ + { + for ( i = 0; i < n; i++ ) + *( pixel_weights++ ) = ( ( ( i == 0 ) ? ( 1 - x ) : x ) / scale ) * scale; + } + else /* Tile */ + { + double a = x + 1 / scale; + + /* x + * ---------|--.-|----|--.-|------- SRC + * ------------|---------|--------- DEST + */ + for ( i = 0; i < n; i++ ) + { + if ( i < x ) + { + if ( i + 1 > x ) + * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - x ) * scale; + else + *( pixel_weights++ ) = 0; + } + else + { + if ( a > i ) + * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - i ) * scale; + else + *( pixel_weights++ ) = 0; + } + } + } + } +} + +/* Computes the integral from b0 to b1 of + * + * f(x) = x; 0 <= x < 1 + * f(x) = 0; otherwise + * + * We combine two of these to compute the convolution of + * a box filter with a triangular spike. + */ +static inline double +linear_box_half ( double b0, double b1 ) +{ + double a0, a1; + double x0, x1; + + a0 = 0.; + a1 = 1.; + + if ( a0 < b0 ) + { + if ( a1 > b0 ) + { + x0 = b0; + x1 = MIN ( a1, b1 ); + } + else + return 0; + } + else + { + if ( b1 > a0 ) + { + x0 = a0; + x1 = MIN ( a1, b1 ); + } + else + return 0; + } + + return 0.5 * ( x1 * x1 - x0 * x0 ); +} + +/* Compute weights for reconstructing with bilinear + * interpolation, then sampling with a box filter + */ +static inline void +bilinear_box_make_weights ( PixopsFilterDimension *dim, + double scale ) +{ + int n = ceil ( 1 / scale + 2.0 ); + double *pixel_weights = g_new ( double, SUBSAMPLE * n ); + double w; + int offset, i; + + dim->offset = -1.0; + dim->n = n; + dim->weights = pixel_weights; + + for ( offset = 0 ; offset < SUBSAMPLE; offset++ ) + { + double x = ( double ) offset / SUBSAMPLE; + double a = x + 1 / scale; + + for ( i = 0; i < n; i++ ) + { + w = linear_box_half ( 0.5 + i - a, 0.5 + i - x ); + w += linear_box_half ( 1.5 + x - i, 1.5 + a - i ); + + *( pixel_weights++ ) = w * scale; + } + } +} + + +static inline void +make_weights ( PixopsFilter *filter, + PixopsInterpType interp_type, + double scale_x, + double scale_y ) +{ + switch ( interp_type ) + { + case PIXOPS_INTERP_NEAREST: + g_assert_not_reached (); + break; + + case PIXOPS_INTERP_TILES: + tile_make_weights ( &filter->x, scale_x ); + tile_make_weights ( &filter->y, scale_y ); + break; + + case PIXOPS_INTERP_BILINEAR: + bilinear_magnify_make_weights ( &filter->x, scale_x ); + bilinear_magnify_make_weights ( &filter->y, scale_y ); + break; + + case PIXOPS_INTERP_HYPER: + bilinear_box_make_weights ( &filter->x, scale_x ); + bilinear_box_make_weights ( &filter->y, scale_y ); + break; + } +} + + +void +yuv422_scale ( guchar *dest_buf, + int render_x0, + int render_y0, + int render_x1, + int render_y1, + int dest_rowstride, + int dest_channels, + gboolean dest_has_alpha, + const guchar *src_buf, + int src_width, + int src_height, + int src_rowstride, + int src_channels, + gboolean src_has_alpha, + double scale_x, + double scale_y, + PixopsInterpType interp_type ) +{ + PixopsFilter filter = { { 0, 0, 0}, { 0, 0, 0 }, 0 }; + PixopsLineFunc line_func; + +#ifdef USE_MMX + gboolean found_mmx = pixops_have_mmx(); +#endif + + //g_return_if_fail ( !( dest_channels == 3 && dest_has_alpha ) ); + //g_return_if_fail ( !( src_channels == 3 && src_has_alpha ) ); + //g_return_if_fail ( !( src_has_alpha && !dest_has_alpha ) ); + + if ( scale_x == 0 || scale_y == 0 ) + return ; + + if ( interp_type == PIXOPS_INTERP_NEAREST ) + { + pixops_scale_nearest ( dest_buf, render_x0, render_y0, render_x1, render_y1, + dest_rowstride, + src_buf, src_width, src_height, src_rowstride, + scale_x, scale_y ); + return; + } + + filter.overall_alpha = 1.0; + make_weights ( &filter, interp_type, scale_x, scale_y ); + + if ( filter.x.n == 2 && filter.y.n == 2 ) + { +#ifdef USE_MMX + if ( found_mmx ) + { + //fprintf( stderr, "rescale: using mmx\n" ); + line_func = scale_line_22_yuv_mmx_stub; + } + else +#endif + + line_func = scale_line_22_yuv; + } + else + line_func = scale_line; + + pixops_process ( dest_buf, render_x0, render_y0, render_x1, render_y1, + dest_rowstride, dest_channels, dest_has_alpha, + src_buf, src_width, src_height, src_rowstride, src_channels, + src_has_alpha, scale_x, scale_y, 0, 0, 0, 0, 0, + &filter, line_func ); + + g_free ( filter.x.weights ); + g_free ( filter.y.weights ); +} + diff --git a/src/modules/gtk2/pixops.h b/src/modules/gtk2/pixops.h new file mode 100644 index 0000000..edfb365 --- /dev/null +++ b/src/modules/gtk2/pixops.h @@ -0,0 +1,72 @@ +/* GdkPixbuf library - Scaling and compositing functions + * + * Original: + * Copyright (C) 2000 Red Hat, Inc + * Author: Owen Taylor + * + * Modification for MLT: + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef PIXOPS_H +#define PIXOPS_H + +#include + +/* Interpolation modes; must match GdkInterpType */ +typedef enum { + PIXOPS_INTERP_NEAREST, + PIXOPS_INTERP_TILES, + PIXOPS_INTERP_BILINEAR, + PIXOPS_INTERP_HYPER +} PixopsInterpType; + +/* Scale src_buf from src_width / src_height by factors scale_x, scale_y + * and composite the portion corresponding to + * render_x, render_y, render_width, render_height in the new + * coordinate system into dest_buf starting at 0, 0 + */ +void yuv422_scale (guchar *dest_buf, + int render_x0, + int render_y0, + int render_x1, + int render_y1, + int dest_rowstride, + int dest_channels, + int dest_has_alpha, + const guchar *src_buf, + int src_width, + int src_height, + int src_rowstride, + int src_channels, + int src_has_alpha, + double scale_x, + double scale_y, + PixopsInterpType interp_type); + +#define yuv422_scale_simple( dest_buf, dest_width, dest_height, dest_rowstride, src_buf, src_width, src_height, src_rowstride, interp_type ) \ + yuv422_scale( (dest_buf), 0, 0, \ + (dest_width), (dest_height), \ + (dest_rowstride), 2, 0, \ + (src_buf), (src_width), (src_height), \ + (src_rowstride), 2, 0, \ + (double) (dest_width) / (src_width), (double) (dest_height) / (src_height), \ + (PixopsInterpType) interp_type ); + +#endif diff --git a/src/modules/gtk2/producer_pango.c b/src/modules/gtk2/producer_pango.c new file mode 100644 index 0000000..b841c95 --- /dev/null +++ b/src/modules/gtk2/producer_pango.c @@ -0,0 +1,699 @@ +/* + * producer_pango.c -- a pango-based titler + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_pango.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t pango_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct producer_pango_s +{ + struct mlt_producer_s parent; + int width; + int height; + uint8_t *image; + uint8_t *alpha; + char *fgcolor; + char *bgcolor; + int align; + int pad; + char *markup; + char *text; + char *font; + int weight; +}; + +// special color type used by internal pango routines +typedef struct +{ + uint8_t r, g, b, a; +} rgba_color; + +// Forward declarations +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); +static void pango_draw_background( GdkPixbuf *pixbuf, rgba_color bg ); +static GdkPixbuf *pango_get_pixbuf( const char *markup, const char *text, const char *font, + rgba_color fg, rgba_color bg, int pad, int align, int weight, int size ); + +/** Return nonzero if the two strings are equal, ignoring case, up to + the first n characters. +*/ +int strncaseeq(const char *s1, const char *s2, size_t n) +{ + for ( ; n > 0; n--) + { + if (tolower(*s1++) != tolower(*s2++)) + return 0; + } + return 1; +} + +/** Parse the alignment property. +*/ + +static int alignment_parse( char* align ) +{ + int ret = pango_align_left; + + if ( align == NULL ); + else if ( isdigit( align[ 0 ] ) ) + ret = atoi( align ); + else if ( align[ 0 ] == 'c' || align[ 0 ] == 'm' ) + ret = pango_align_center; + else if ( align[ 0 ] == 'r' ) + ret = pango_align_right; + + return ret; +} + +static PangoFT2FontMap *fontmap = NULL; + +mlt_producer producer_pango_init( const char *filename ) +{ + producer_pango this = calloc( sizeof( struct producer_pango_s ), 1 ); + if ( this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + mlt_producer producer = &this->parent; + + pthread_mutex_lock( &pango_mutex ); + if ( fontmap == NULL ) + fontmap = (PangoFT2FontMap*) pango_ft2_font_map_new(); + g_type_init(); + pthread_mutex_unlock( &pango_mutex ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + // Get the properties interface + mlt_properties properties = MLT_PRODUCER_PROPERTIES( &this->parent ); + + // Set the default properties + mlt_properties_set( properties, "fgcolour", "0xffffffff" ); + mlt_properties_set( properties, "bgcolour", "0x00000000" ); + mlt_properties_set_int( properties, "align", pango_align_left ); + mlt_properties_set_int( properties, "pad", 0 ); + mlt_properties_set( properties, "text", "" ); + mlt_properties_set( properties, "font", "Sans 48" ); + mlt_properties_set( properties, "encoding", "UTF-8" ); + mlt_properties_set_int( properties, "weight", PANGO_WEIGHT_NORMAL ); + + if ( filename == NULL ) + { + mlt_properties_set( properties, "markup", "" ); + } + else if ( filename[ 0 ] == '+' || strstr( filename, "/+" ) ) + { + char *copy = strdup( filename + 1 ); + char *markup = copy; + if ( strstr( markup, "/+" ) ) + markup = strstr( markup, "/+" ) + 2; + ( *strrchr( markup, '.' ) ) = '\0'; + while ( strchr( markup, '~' ) ) + ( *strchr( markup, '~' ) ) = '\n'; + mlt_properties_set( properties, "resource", ( char * )filename ); + mlt_properties_set( properties, "markup", markup ); + free( copy ); + } + else if ( strstr( filename, ".mpl" ) ) + { + int i = 0; + mlt_properties contents = mlt_properties_load( filename ); + mlt_geometry key_frames = mlt_geometry_init( ); + struct mlt_geometry_item_s item; + mlt_properties_set( properties, "resource", ( char * )filename ); + mlt_properties_set_data( properties, "contents", contents, 0, ( mlt_destructor )mlt_properties_close, NULL ); + mlt_properties_set_data( properties, "key_frames", key_frames, 0, ( mlt_destructor )mlt_geometry_close, NULL ); + + // Make sure we have at least one entry + if ( mlt_properties_get( contents, "0" ) == NULL ) + mlt_properties_set( contents, "0", "" ); + + for ( i = 0; i < mlt_properties_count( contents ); i ++ ) + { + char *name = mlt_properties_get_name( contents, i ); + char *value = mlt_properties_get_value( contents, i ); + while ( value != NULL && strchr( value, '~' ) ) + ( *strchr( value, '~' ) ) = '\n'; + item.frame = atoi( name ); + mlt_geometry_insert( key_frames, &item ); + } + } + else + { + FILE *f = fopen( filename, "r" ); + if ( f != NULL ) + { + char line[81]; + char *markup = NULL; + size_t size = 0; + line[80] = '\0'; + + while ( fgets( line, 80, f ) ) + { + size += strlen( line ) + 1; + if ( markup ) + { + markup = realloc( markup, size ); + strcat( markup, line ); + } + else + { + markup = strdup( line ); + } + } + fclose( f ); + + if ( markup[ strlen( markup ) - 1 ] == '\n' ) + markup[ strlen( markup ) - 1 ] = '\0'; + + mlt_properties_set( properties, "resource", ( char * ) filename ); + mlt_properties_set( properties, "markup", ( char * ) ( markup == NULL ? "" : markup ) ); + free( markup ); + } + else + { + mlt_properties_set( properties, "markup", "" ); + } + } + + return producer; + } + free( this ); + return NULL; +} + +static void set_string( char **string, char *value, char *fallback ) +{ + if ( value != NULL ) + { + free( *string ); + *string = strdup( value ); + } + else if ( *string == NULL && fallback != NULL ) + { + *string = strdup( fallback ); + } + else if ( *string != NULL && fallback == NULL ) + { + free( *string ); + *string = NULL; + } +} + +rgba_color parse_color( char *color ) +{ + rgba_color result = { 0xff, 0xff, 0xff, 0xff }; + + if ( !strncmp( color, "0x", 2 ) ) + { + unsigned int temp = 0; + sscanf( color + 2, "%x", &temp ); + result.r = ( temp >> 24 ) & 0xff; + result.g = ( temp >> 16 ) & 0xff; + result.b = ( temp >> 8 ) & 0xff; + result.a = ( temp ) & 0xff; + } + else if ( !strcmp( color, "red" ) ) + { + result.r = 0xff; + result.g = 0x00; + result.b = 0x00; + } + else if ( !strcmp( color, "green" ) ) + { + result.r = 0x00; + result.g = 0xff; + result.b = 0x00; + } + else if ( !strcmp( color, "blue" ) ) + { + result.r = 0x00; + result.g = 0x00; + result.b = 0xff; + } + else + { + unsigned int temp = 0; + sscanf( color, "%d", &temp ); + result.r = ( temp >> 24 ) & 0xff; + result.g = ( temp >> 16 ) & 0xff; + result.b = ( temp >> 8 ) & 0xff; + result.a = ( temp ) & 0xff; + } + + return result; +} + +/** Convert a string property to UTF-8 +*/ +static int iconv_utf8( mlt_properties properties, char *prop_name, const char* encoding ) +{ + char *text = mlt_properties_get( properties, prop_name ); + int result = -1; + + iconv_t cd = iconv_open( "UTF-8", encoding ); + if ( cd != ( iconv_t )-1 ) + { + char *inbuf_p = text; + size_t inbuf_n = strlen( text ); + size_t outbuf_n = inbuf_n * 6; + char *outbuf = mlt_pool_alloc( outbuf_n ); + char *outbuf_p = outbuf; + + memset( outbuf, 0, outbuf_n ); + + if ( text != NULL && strcmp( text, "" ) && iconv( cd, &inbuf_p, &inbuf_n, &outbuf_p, &outbuf_n ) != -1 ) + mlt_properties_set( properties, prop_name, outbuf ); + else + mlt_properties_set( properties, prop_name, "" ); + + mlt_pool_release( outbuf ); + iconv_close( cd ); + result = 0; + } + return result; +} + +static void refresh_image( mlt_frame frame, int width, int height ) +{ + // Pixbuf + GdkPixbuf *pixbuf = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "pixbuf", NULL ); + + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the producer pango for this frame + producer_pango this = mlt_properties_get_data( properties, "producer_pango", NULL ); + + // Obtain the producer + mlt_producer producer = &this->parent; + + // Obtain the producer properties + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Get producer properties + char *fg = mlt_properties_get( producer_props, "fgcolour" ); + char *bg = mlt_properties_get( producer_props, "bgcolour" ); + int align = alignment_parse( mlt_properties_get( producer_props, "align" ) ); + int pad = mlt_properties_get_int( producer_props, "pad" ); + char *markup = mlt_properties_get( producer_props, "markup" ); + char *text = mlt_properties_get( producer_props, "text" ); + char *font = mlt_properties_get( producer_props, "font" ); + char *encoding = mlt_properties_get( producer_props, "encoding" ); + int weight = mlt_properties_get_int( producer_props, "weight" ); + int size = mlt_properties_get_int( producer_props, "size" ); + int property_changed = 0; + + if ( pixbuf == NULL ) + { + // Check for file support + int position = mlt_properties_get_position( properties, "pango_position" ); + mlt_properties contents = mlt_properties_get_data( producer_props, "contents", NULL ); + mlt_geometry key_frames = mlt_properties_get_data( producer_props, "key_frames", NULL ); + struct mlt_geometry_item_s item; + if ( contents != NULL ) + { + char temp[ 20 ]; + mlt_geometry_prev_key( key_frames, &item, position ); + sprintf( temp, "%d", item.frame ); + markup = mlt_properties_get( contents, temp ); + } + + // See if any properties changed + property_changed = ( align != this->align ); + property_changed = property_changed || ( this->fgcolor == NULL || ( fg && strcmp( fg, this->fgcolor ) ) ); + property_changed = property_changed || ( this->bgcolor == NULL || ( bg && strcmp( bg, this->bgcolor ) ) ); + property_changed = property_changed || ( pad != this->pad ); + property_changed = property_changed || ( markup && this->markup && strcmp( markup, this->markup ) ); + property_changed = property_changed || ( text && this->text && strcmp( text, this->text ) ); + property_changed = property_changed || ( font && this->font && strcmp( font, this->font ) ); + property_changed = property_changed || ( weight != this->weight ); + + // Save the properties for next comparison + this->align = align; + this->pad = pad; + set_string( &this->fgcolor, fg, "0xffffffff" ); + set_string( &this->bgcolor, bg, "0x00000000" ); + set_string( &this->markup, markup, NULL ); + set_string( &this->text, text, NULL ); + set_string( &this->font, font, "Sans 48" ); + this->weight = weight; + } + + if ( pixbuf == NULL && property_changed ) + { + rgba_color fgcolor = parse_color( this->fgcolor ); + rgba_color bgcolor = parse_color( this->bgcolor ); + + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + this->image = NULL; + this->alpha = NULL; + + // Convert from specified encoding to UTF-8 + if ( encoding != NULL && !strncaseeq( encoding, "utf-8", 5 ) && !strncaseeq( encoding, "utf8", 4 ) ) + { + if ( markup != NULL && iconv_utf8( producer_props, "markup", encoding ) != -1 ) + { + markup = mlt_properties_get( producer_props, "markup" ); + set_string( &this->markup, markup, NULL ); + } + if ( text != NULL && iconv_utf8( producer_props, "text", encoding ) != -1 ) + { + text = mlt_properties_get( producer_props, "text" ); + set_string( &this->text, text, NULL ); + } + } + + // Render the title + pixbuf = pango_get_pixbuf( markup, text, font, fgcolor, bgcolor, pad, align, weight, size ); + + if ( pixbuf != NULL ) + { + // Register this pixbuf for destruction and reuse + mlt_properties_set_data( producer_props, "pixbuf", pixbuf, 0, ( mlt_destructor )g_object_unref, NULL ); + g_object_ref( pixbuf ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "pixbuf", pixbuf, 0, ( mlt_destructor )g_object_unref, NULL ); + + mlt_properties_set_int( producer_props, "real_width", gdk_pixbuf_get_width( pixbuf ) ); + mlt_properties_set_int( producer_props, "real_height", gdk_pixbuf_get_height( pixbuf ) ); + + // Store the width/height of the pixbuf temporarily + this->width = gdk_pixbuf_get_width( pixbuf ); + this->height = gdk_pixbuf_get_height( pixbuf ); + } + } + else if ( pixbuf == NULL && ( width > 0 && ( this->image == NULL || width != this->width || height != this->height ) ) ) + { + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + this->image = NULL; + this->alpha = NULL; + + pixbuf = mlt_properties_get_data( producer_props, "pixbuf", NULL ); + } + + // If we have a pixbuf and a valid width + if ( pixbuf && width > 0 ) + { + char *interps = mlt_properties_get( properties, "rescale.interp" ); + int interp = GDK_INTERP_BILINEAR; + + if ( strcmp( interps, "nearest" ) == 0 ) + interp = GDK_INTERP_NEAREST; + else if ( strcmp( interps, "tiles" ) == 0 ) + interp = GDK_INTERP_TILES; + else if ( strcmp( interps, "hyper" ) == 0 ) + interp = GDK_INTERP_HYPER; + +// fprintf( stderr, "SCALING PANGO from %dx%d to %dx%d was %dx%d\n", gdk_pixbuf_get_width( pixbuf ), gdk_pixbuf_get_height( pixbuf ), width, height, this->width, this->height ); + + // Note - the original pixbuf is already safe and ready for destruction + pixbuf = gdk_pixbuf_scale_simple( pixbuf, width, height, interp ); + + // Store width and height + this->width = width; + this->height = height; + + // Allocate/define image + this->image = mlt_pool_alloc( width * ( height + 1 ) * 2 ); + this->alpha = mlt_pool_alloc( this->width * this->height ); + + // Convert the image + mlt_convert_rgb24a_to_yuv422( gdk_pixbuf_get_pixels( pixbuf ), + this->width, this->height, + gdk_pixbuf_get_rowstride( pixbuf ), + this->image, this->alpha ); + + // Finished with pixbuf now + g_object_unref( pixbuf ); + } + + // Set width/height + mlt_properties_set_int( properties, "width", this->width ); + mlt_properties_set_int( properties, "height", this->height ); + mlt_properties_set_int( properties, "real_width", mlt_properties_get_int( producer_props, "real_width" ) ); + mlt_properties_set_int( properties, "real_height", mlt_properties_get_int( producer_props, "real_height" ) ); + + // pass the image data without destructor + mlt_properties_set_data( properties, "image", this->image, this->width * ( this->height + 1 ) * 2, NULL, NULL ); + mlt_properties_set_data( properties, "alpha", this->alpha, this->width * this->height, NULL, NULL ); +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // We need to know the size of the image to clone it + int image_size = 0; + int alpha_size = 0; + + // Alpha channel + uint8_t *alpha = NULL; + + *width = mlt_properties_get_int( properties, "rescale_width" ); + *height = mlt_properties_get_int( properties, "rescale_height" ); + + // Refresh the image + pthread_mutex_lock( &pango_mutex ); + refresh_image( frame, *width, *height ); + + // Get the image + *buffer = mlt_properties_get_data( properties, "image", &image_size ); + alpha = mlt_properties_get_data( properties, "alpha", &alpha_size ); + + // Get width and height + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + + // Always clone here to allow 'animated' text + if ( *buffer != NULL ) + { + // Clone the image and the alpha + uint8_t *image_copy = mlt_pool_alloc( image_size ); + uint8_t *alpha_copy = mlt_pool_alloc( alpha_size ); + + memcpy( image_copy, *buffer, image_size ); + + // Copy or default the alpha + if ( alpha != NULL ) + memcpy( alpha_copy, alpha, alpha_size ); + else + memset( alpha_copy, 255, alpha_size ); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", image_copy, image_size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha_copy, alpha_size, mlt_pool_release, NULL ); + + // We're going to pass the copy on + *buffer = image_copy; + } + else + { + // TODO: Review all cases of invalid images + *buffer = mlt_pool_alloc( 50 * 50 * 2 ); + mlt_properties_set_data( properties, "image", *buffer, image_size, mlt_pool_release, NULL ); + *width = 50; + *height = 50; + } + + pthread_mutex_unlock( &pango_mutex ); + + return 0; +} + +static uint8_t *producer_get_alpha_mask( mlt_frame this ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Return the alpha mask + return mlt_properties_get_data( properties, "alpha", NULL ); +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + producer_pango this = producer->child; + + // Generate a frame + *frame = mlt_frame_init( ); + + // Obtain properties of frame and producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Set the producer on the frame properties + mlt_properties_set_data( properties, "producer_pango", this, 0, NULL, NULL ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + mlt_properties_set_position( properties, "pango_position", mlt_producer_frame( producer ) ); + + // Refresh the pango image + pthread_mutex_lock( &pango_mutex ); + refresh_image( *frame, 0, 0 ); + pthread_mutex_unlock( &pango_mutex ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_double( properties, "aspect_ratio", 1 ); + + // Set alpha call back + ( *frame )->get_alpha_mask = producer_get_alpha_mask; + + // Stack the get image callback + mlt_frame_push_get_image( *frame, producer_get_image ); + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + producer_pango this = parent->child; + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + free( this->fgcolor ); + free( this->bgcolor ); + free( this->markup ); + free( this->text ); + free( this->font ); + parent->close = NULL; + mlt_producer_close( parent ); + free( this ); +} + +static void pango_draw_background( GdkPixbuf *pixbuf, rgba_color bg ) +{ + int ww = gdk_pixbuf_get_width( pixbuf ); + int hh = gdk_pixbuf_get_height( pixbuf ); + uint8_t *p = gdk_pixbuf_get_pixels( pixbuf ); + int i, j; + + for ( j = 0; j < hh; j++ ) + { + for ( i = 0; i < ww; i++ ) + { + *p++ = bg.r; + *p++ = bg.g; + *p++ = bg.b; + *p++ = bg.a; + } + } +} + +static GdkPixbuf *pango_get_pixbuf( const char *markup, const char *text, const char *font, rgba_color fg, rgba_color bg, int pad, int align, int weight, int size ) +{ + PangoContext *context = pango_ft2_font_map_create_context( fontmap ); + PangoLayout *layout = pango_layout_new( context ); + int w, h, x; + int i, j; + GdkPixbuf *pixbuf = NULL; + FT_Bitmap bitmap; + uint8_t *src = NULL; + uint8_t* dest = NULL; + uint8_t *d, *s, a; + int stride; + PangoFontDescription *desc = pango_font_description_from_string( font ); + + pango_ft2_font_map_set_resolution( fontmap, 72, 72 ); + pango_layout_set_width( layout, -1 ); // set wrapping constraints + pango_font_description_set_weight( desc, ( PangoWeight ) weight ); + if ( size != 0 ) + pango_font_description_set_size( desc, PANGO_SCALE * size ); + pango_layout_set_font_description( layout, desc ); +// pango_layout_set_spacing( layout, space ); + pango_layout_set_alignment( layout, ( PangoAlignment ) align ); + if ( markup != NULL && strcmp( markup, "" ) != 0 ) + { + pango_layout_set_markup( layout, markup, strlen( markup ) ); + } + else if ( text != NULL && strcmp( text, "" ) != 0 ) + { + // Replace all ~'s with a line feed (silly convention, but handy) + char *copy = strdup( text ); + while ( strchr( copy, '~' ) ) + ( *strchr( copy, '~' ) ) = '\n'; + pango_layout_set_text( layout, copy, strlen( copy ) ); + free( copy ); + } + else + { + // Pango doesn't like empty strings + pango_layout_set_text( layout, " ", 2 ); + } + pango_layout_get_pixel_size( layout, &w, &h ); + + pixbuf = gdk_pixbuf_new( GDK_COLORSPACE_RGB, TRUE /* has alpha */, 8, w + 2 * pad, h + 2 * pad ); + pango_draw_background( pixbuf, bg ); + + stride = gdk_pixbuf_get_rowstride( pixbuf ); + + bitmap.width = w; + bitmap.pitch = 32 * ( ( w + 31 ) / 31 ); + bitmap.rows = h; + bitmap.buffer = mlt_pool_alloc( h * bitmap.pitch ); + bitmap.num_grays = 256; + bitmap.pixel_mode = ft_pixel_mode_grays; + + memset( bitmap.buffer, 0, h * bitmap.pitch ); + + pango_ft2_render_layout( &bitmap, layout, 0, 0 ); + + src = bitmap.buffer; + x = ( gdk_pixbuf_get_width( pixbuf ) - w - 2 * pad ) * align / 2 + pad; + dest = gdk_pixbuf_get_pixels( pixbuf ) + 4 * x + pad * stride; + j = h; + + while( j -- ) + { + d = dest; + s = src; + i = w; + while( i -- ) + { + a = *s ++; + *d++ = ( a * fg.r + ( 255 - a ) * bg.r ) >> 8; + *d++ = ( a * fg.g + ( 255 - a ) * bg.g ) >> 8; + *d++ = ( a * fg.b + ( 255 - a ) * bg.b ) >> 8; + *d++ = ( a * fg.a + ( 255 - a ) * bg.a ) >> 8; + } + dest += stride; + src += bitmap.pitch; + } + mlt_pool_release( bitmap.buffer ); + pango_font_description_free( desc ); + g_object_unref( layout ); + g_object_unref( context ); + + return pixbuf; +} diff --git a/src/modules/gtk2/producer_pango.h b/src/modules/gtk2/producer_pango.h new file mode 100644 index 0000000..525fb42 --- /dev/null +++ b/src/modules/gtk2/producer_pango.h @@ -0,0 +1,37 @@ +/* + * producer_pango.h -- a pango-based titler + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_PANGO_H_ +#define _PRODUCER_PANGO_H_ + +#include + +typedef struct producer_pango_s *producer_pango; + +typedef enum +{ + pango_align_left = 0, + pango_align_center, + pango_align_right +} pango_align; + +extern mlt_producer producer_pango_init( const char *filename ); + +#endif diff --git a/src/modules/gtk2/producer_pixbuf.c b/src/modules/gtk2/producer_pixbuf.c new file mode 100644 index 0000000..71f71f2 --- /dev/null +++ b/src/modules/gtk2/producer_pixbuf.c @@ -0,0 +1,499 @@ +/* + * producer_pixbuf.c -- raster image loader based upon gdk-pixbuf + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_pixbuf.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; + +typedef struct producer_pixbuf_s *producer_pixbuf; + +struct producer_pixbuf_s +{ + struct mlt_producer_s parent; + + // File name list + mlt_properties filenames; + int count; + int image_idx; + + int width; + int height; + uint8_t *image; + uint8_t *alpha; +}; + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_pixbuf_init( char *filename ) +{ + producer_pixbuf this = calloc( sizeof( struct producer_pixbuf_s ), 1 ); + if ( this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + mlt_producer producer = &this->parent; + + // Get the properties interface + mlt_properties properties = MLT_PRODUCER_PROPERTIES( &this->parent ); + + // Callback registration + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + // Set the default properties + mlt_properties_set( properties, "resource", filename ); + mlt_properties_set_int( properties, "ttl", 25 ); + mlt_properties_set_int( properties, "aspect_ratio", 1 ); + mlt_properties_set_int( properties, "progressive", 1 ); + + return producer; + } + free( this ); + return NULL; +} + +static void refresh_image( mlt_frame frame, int width, int height ) +{ + // Pixbuf + GdkPixbuf *pixbuf = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "pixbuf", NULL ); + GError *error = NULL; + + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the producer for this frame + producer_pixbuf this = mlt_properties_get_data( properties, "producer_pixbuf", NULL ); + + // Obtain the producer + mlt_producer producer = &this->parent; + + // Obtain properties of producer + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Obtain the cache flag and structure + int use_cache = mlt_properties_get_int( producer_props, "cache" ); + mlt_properties cache = mlt_properties_get_data( producer_props, "_cache", NULL ); + int update_cache = 0; + + // Get the time to live for each frame + double ttl = mlt_properties_get_int( producer_props, "ttl" ); + + // Get the original position of this frame + mlt_position position = mlt_properties_get_position( properties, "pixbuf_position" ); + + // Image index + int image_idx = ( int )floor( ( double )position / ttl ) % this->count; + + // Key for the cache + char image_key[ 10 ]; + sprintf( image_key, "%d", image_idx ); + + pthread_mutex_lock( &fastmutex ); + + // Check if the frame is already loaded + if ( use_cache ) + { + if ( cache == NULL ) + { + cache = mlt_properties_new( ); + mlt_properties_set_data( producer_props, "_cache", cache, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + mlt_frame cached = mlt_properties_get_data( cache, image_key, NULL ); + + if ( cached ) + { + this->image_idx = image_idx; + mlt_properties cached_props = MLT_FRAME_PROPERTIES( cached ); + this->width = mlt_properties_get_int( cached_props, "width" ); + this->height = mlt_properties_get_int( cached_props, "height" ); + mlt_properties_set_int( producer_props, "_real_width", mlt_properties_get_int( cached_props, "real_width" ) ); + mlt_properties_set_int( producer_props, "_real_height", mlt_properties_get_int( cached_props, "real_height" ) ); + this->image = mlt_properties_get_data( cached_props, "image", NULL ); + this->alpha = mlt_properties_get_data( cached_props, "alpha", NULL ); + + if ( width != 0 && ( width != this->width || height != this->height ) ) + this->image = NULL; + } + } + + // optimization for subsequent iterations on single picture + if ( width != 0 && this->image != NULL && image_idx == this->image_idx ) + { + if ( width != this->width || height != this->height ) + { + pixbuf = mlt_properties_get_data( producer_props, "_pixbuf", NULL ); + if ( !use_cache ) + { + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + } + this->image = NULL; + this->alpha = NULL; + } + } + else if ( pixbuf == NULL && ( this->image == NULL || image_idx != this->image_idx ) ) + { + if ( !use_cache ) + { + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + } + this->image = NULL; + this->alpha = NULL; + + this->image_idx = image_idx; + pixbuf = gdk_pixbuf_new_from_file( mlt_properties_get_value( this->filenames, image_idx ), &error ); + + if ( pixbuf != NULL ) + { + // Register this pixbuf for destruction and reuse + mlt_events_block( producer_props, NULL ); + mlt_properties_set_data( producer_props, "_pixbuf", pixbuf, 0, ( mlt_destructor )g_object_unref, NULL ); + g_object_ref( pixbuf ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "pixbuf", pixbuf, 0, ( mlt_destructor )g_object_unref, NULL ); + + mlt_properties_set_int( producer_props, "_real_width", gdk_pixbuf_get_width( pixbuf ) ); + mlt_properties_set_int( producer_props, "_real_height", gdk_pixbuf_get_height( pixbuf ) ); + mlt_events_unblock( producer_props, NULL ); + + // Store the width/height of the pixbuf temporarily + this->width = gdk_pixbuf_get_width( pixbuf ); + this->height = gdk_pixbuf_get_height( pixbuf ); + } + } + + // If we have a pixbuf + if ( pixbuf && width > 0 ) + { + char *interps = mlt_properties_get( properties, "rescale.interp" ); + int interp = GDK_INTERP_BILINEAR; + + if ( strcmp( interps, "nearest" ) == 0 ) + interp = GDK_INTERP_NEAREST; + else if ( strcmp( interps, "tiles" ) == 0 ) + interp = GDK_INTERP_TILES; + else if ( strcmp( interps, "hyper" ) == 0 ) + interp = GDK_INTERP_HYPER; + + // Note - the original pixbuf is already safe and ready for destruction + pixbuf = gdk_pixbuf_scale_simple( pixbuf, width, height, interp ); + + // Store width and height + this->width = width; + this->height = height; + + // Allocate/define image + this->image = mlt_pool_alloc( width * ( height + 1 ) * 2 ); + + // Extract YUV422 and alpha + if ( gdk_pixbuf_get_has_alpha( pixbuf ) ) + { + // Allocate the alpha mask + this->alpha = mlt_pool_alloc( this->width * this->height ); + + // Convert the image + mlt_convert_rgb24a_to_yuv422( gdk_pixbuf_get_pixels( pixbuf ), + this->width, this->height, + gdk_pixbuf_get_rowstride( pixbuf ), + this->image, this->alpha ); + } + else + { + // No alpha to extract + mlt_convert_rgb24_to_yuv422( gdk_pixbuf_get_pixels( pixbuf ), + this->width, this->height, + gdk_pixbuf_get_rowstride( pixbuf ), + this->image ); + } + + // Finished with pixbuf now + g_object_unref( pixbuf ); + + // Ensure we update the cache when we need to + update_cache = use_cache; + } + + // Set width/height of frame + mlt_properties_set_int( properties, "width", this->width ); + mlt_properties_set_int( properties, "height", this->height ); + mlt_properties_set_int( properties, "real_width", mlt_properties_get_int( producer_props, "_real_width" ) ); + mlt_properties_set_int( properties, "real_height", mlt_properties_get_int( producer_props, "_real_height" ) ); + + // pass the image data without destructor + mlt_properties_set_data( properties, "image", this->image, this->width * ( this->height + 1 ) * 2, NULL, NULL ); + mlt_properties_set_data( properties, "alpha", this->alpha, this->width * this->height, NULL, NULL ); + + if ( update_cache ) + { + mlt_frame cached = mlt_frame_init( ); + mlt_properties cached_props = MLT_FRAME_PROPERTIES( cached ); + mlt_properties_set_int( cached_props, "width", this->width ); + mlt_properties_set_int( cached_props, "height", this->height ); + mlt_properties_set_int( cached_props, "real_width", mlt_properties_get_int( producer_props, "_real_width" ) ); + mlt_properties_set_int( cached_props, "real_height", mlt_properties_get_int( producer_props, "_real_height" ) ); + mlt_properties_set_data( cached_props, "image", this->image, this->width * ( this->height + 1 ) * 2, mlt_pool_release, NULL ); + mlt_properties_set_data( cached_props, "alpha", this->alpha, this->width * this->height, mlt_pool_release, NULL ); + mlt_properties_set_data( cache, image_key, cached, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + + pthread_mutex_unlock( &fastmutex ); +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // We need to know the size of the image to clone it + int image_size = 0; + int alpha_size = 0; + + // Alpha channel + uint8_t *alpha = NULL; + + *width = mlt_properties_get_int( properties, "rescale_width" ); + *height = mlt_properties_get_int( properties, "rescale_height" ); + + // Refresh the image + refresh_image( frame, *width, *height ); + + // Get the image + *buffer = mlt_properties_get_data( properties, "image", &image_size ); + alpha = mlt_properties_get_data( properties, "alpha", &alpha_size ); + + // Get width and height (may have changed during the refresh) + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + + // NB: Cloning is necessary with this producer (due to processing of images ahead of use) + // The fault is not in the design of mlt, but in the implementation of the pixbuf producer... + if ( *buffer != NULL ) + { + if ( *format == mlt_image_yuv422 || *format == mlt_image_yuv420p ) + { + // Clone the image and the alpha + uint8_t *image_copy = mlt_pool_alloc( image_size ); + uint8_t *alpha_copy = mlt_pool_alloc( alpha_size ); + + memcpy( image_copy, *buffer, image_size ); + + // Copy or default the alpha + if ( alpha != NULL ) + memcpy( alpha_copy, alpha, alpha_size ); + else + memset( alpha_copy, 255, alpha_size ); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", image_copy, image_size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha_copy, alpha_size, mlt_pool_release, NULL ); + + // We're going to pass the copy on + *buffer = image_copy; + } + else if ( *format == mlt_image_rgb24a ) + { + // Clone the image and the alpha + image_size = *width * ( *height + 1 ) * 4; + alpha_size = *width * ( *height + 1 ); + uint8_t *image_copy = mlt_pool_alloc( image_size ); + uint8_t *alpha_copy = mlt_pool_alloc( alpha_size ); + + mlt_convert_yuv422_to_rgb24a(*buffer, image_copy, (*width)*(*height)); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", image_copy, image_size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha_copy, alpha_size, mlt_pool_release, NULL ); + + // We're going to pass the copy on + *buffer = image_copy; + } + + } + else + { + // TODO: Review all cases of invalid images + *buffer = mlt_pool_alloc( 50 * 50 * 2 ); + mlt_properties_set_data( properties, "image", *buffer, image_size, mlt_pool_release, NULL ); + *width = 50; + *height = 50; + } + + return 0; +} + +static uint8_t *producer_get_alpha_mask( mlt_frame this ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Return the alpha mask + return mlt_properties_get_data( properties, "alpha", NULL ); +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Get the real structure for this producer + producer_pixbuf this = producer->child; + + // Fetch the producers properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + if ( this->filenames == NULL && mlt_properties_get( producer_properties, "resource" ) != NULL ) + { + char *filename = mlt_properties_get( producer_properties, "resource" ); + this->filenames = mlt_properties_new( ); + + // Read xml string + if ( strstr( filename, " -1 ) + { + // Write the svg into the temp file + ssize_t remaining_bytes; + char *xml = filename; + + // Strip leading crap + while ( xml[0] != '<' ) + xml++; + + remaining_bytes = strlen( xml ); + while ( remaining_bytes > 0 ) + remaining_bytes -= write( fd, xml + strlen( xml ) - remaining_bytes, remaining_bytes ); + close( fd ); + + mlt_properties_set( this->filenames, "0", fullname ); + + // Teehe - when the producer closes, delete the temp file and the space allo + mlt_properties_set_data( producer_properties, "__temporary_file__", fullname, 0, ( mlt_destructor )unlink, NULL ); + } + } + // Obtain filenames + else if ( strchr( filename, '%' ) != NULL ) + { + // handle picture sequences + int i = mlt_properties_get_int( producer_properties, "begin" ); + int gap = 0; + char full[1024]; + int keyvalue = 0; + char key[ 50 ]; + + while ( gap < 100 ) + { + struct stat buf; + snprintf( full, 1023, filename, i ++ ); + if ( stat( full, &buf ) == 0 ) + { + sprintf( key, "%d", keyvalue ++ ); + mlt_properties_set( this->filenames, "0", full ); + gap = 0; + } + else + { + gap ++; + } + } + } + else if ( strstr( filename, "/.all." ) != NULL ) + { + char wildcard[ 1024 ]; + char *dir_name = strdup( filename ); + char *extension = strrchr( dir_name, '.' ); + + *( strstr( dir_name, "/.all." ) + 1 ) = '\0'; + sprintf( wildcard, "*%s", extension ); + + mlt_properties_dir_list( this->filenames, dir_name, wildcard, 1 ); + + free( dir_name ); + } + else + { + mlt_properties_set( this->filenames, "0", filename ); + } + + this->count = mlt_properties_count( this->filenames ); + } + + // Generate a frame + *frame = mlt_frame_init( ); + + if ( *frame != NULL && this->count > 0 ) + { + // Obtain properties of frame and producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Set the producer on the frame properties + mlt_properties_set_data( properties, "producer_pixbuf", this, 0, NULL, NULL ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Ensure that we have a way to obtain the position in the get_image + mlt_properties_set_position( properties, "pixbuf_position", mlt_producer_position( producer ) ); + + // Refresh the image + refresh_image( *frame, 0, 0 ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", mlt_properties_get_int( producer_properties, "progressive" ) ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_properties, "aspect_ratio" ) ); + + // Set alpha call back + ( *frame )->get_alpha_mask = producer_get_alpha_mask; + + // Push the get_image method + mlt_frame_push_get_image( *frame, producer_get_image ); + } + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + producer_pixbuf this = parent->child; + if ( !mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( parent ), "cache" ) ) + { + mlt_pool_release( this->image ); + mlt_pool_release( this->alpha ); + } + parent->close = NULL; + mlt_producer_close( parent ); + mlt_properties_close( this->filenames ); + free( this ); +} diff --git a/src/modules/gtk2/producer_pixbuf.h b/src/modules/gtk2/producer_pixbuf.h new file mode 100644 index 0000000..77efcfa --- /dev/null +++ b/src/modules/gtk2/producer_pixbuf.h @@ -0,0 +1,28 @@ +/* + * producer_pixbuf.h -- raster image loader based upon gdk-pixbuf + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PRODUCER_PIXBUF_H_ +#define _PRODUCER_PIXBUF_H_ + +#include + +extern mlt_producer producer_pixbuf_init( char *filename ); + +#endif diff --git a/src/modules/gtk2/scale_line_22_yuv_mmx.S b/src/modules/gtk2/scale_line_22_yuv_mmx.S new file mode 100644 index 0000000..7ada6e9 --- /dev/null +++ b/src/modules/gtk2/scale_line_22_yuv_mmx.S @@ -0,0 +1,227 @@ +/* + * scale_line_22_yuv_mmx.S -- scale line in YUY2 format + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + .file "scale_line_22_yuv_mmx.S" + .version "01.01" + +.extern printf + +gcc2_compiled.: +.data +MSG: .ascii "scale_line_22_yuv_mmx: %d %d\n" + +.text + .align 16 + +#if !defined(__MINGW32__) && !defined(__CYGWIN__) + +.globl pixops_scale_line_22_yuv_mmx + .type pixops_scale_line_22_yuv_mmx,@function +pixops_scale_line_22_yuv_mmx: + +#else + +.globl _pixops_scale_line_22_yuv_mmx +_pixops_scale_line_22_yuv_mmx: + +#endif +/* + * Arguments + * + * weights: 8(%ebp) + * p (dest): 12(%ebp) %esi + * q1 (src0): 16(%ebp) + * q2 (src1): 20(%ebp) + * xstep: 24(%ebp) + * p_end: 28(%ebp) + * xinit: 32(%ebp) + * dest_x: 36(%ebp) + * + */ + +/* + * Function call entry + */ + pushl %ebp + movl %esp,%ebp + subl $28,%esp + pushl %edi + pushl %esi + pushl %ebx +/* Locals: + * int x %ebx + * int x_scaled -24(%ebp) + * int dest_x 36(%ebp) + */ + +/* + * Setup + */ +/* Initialize variables */ + movl 36(%ebp),%eax # destx + movl %eax,36(%ebp) + movl 32(%ebp),%ebx # x + movl 12(%ebp),%esi # dest + + cmpl 28(%ebp),%esi # dest == dest_end ? + jnb .out + +/* For the body of this loop, %mm0, %mm1, %mm2, %mm3 hold the 4 adjoining + * points we are interpolating between, as: + * + * 00VV00Y200UU00Y1 + */ + + pxor %mm4, %mm4 +/* + * Load next component values into mm1 (src0) and mm3 (src1) + */ + movl %ebx, %eax # x_scaled + sarl $15, %eax + andl $0xfffffffe, %eax + movl %eax, %edx # x_aligned + andl $0xfffffffc, %edx + + movl 16(%ebp), %edi # get src0 + movl (%edi,%eax), %ecx # get y + andl $0x00ff00ff, %ecx # mask off y + movl (%edi,%edx), %eax # get uv + andl $0xff00ff00, %eax # mask off uv + orl %eax, %ecx # composite y, uv + movd %ecx, %mm1 # move to mmx1 + punpcklbw %mm4, %mm1 + + movl 20(%ebp), %edi # get src1 + movl (%edi,%edx), %ecx # get y + andl $0x00ff00ff, %ecx # mask off y + movl (%edi,%edx), %eax # get uv + andl $0xff00ff00, %eax # mask off uv + orl %eax, %ecx # composite y, uv + movd %ecx, %mm3 # move to mmx3 + punpcklbw %mm4, %mm3 + + jmp .newx + + .p2align 4,,7 +.loop: + +/* short *pixel_weights = weights + ((x >> (SCALE_SHIFT - SUBSAMPLE_BITS)) & SUBSAMPLE_MASK) * n_x * n_y + * 16 4 0xf 2 2 + */ + movl 8(%ebp), %edi # get weights pointer + movl %ebx, %eax + andl $0xf000, %eax + shrl $7, %eax + +/* At this point, %edi holds weights. Load the 4 weights into + * %mm4,%mm5,%mm6,%mm7, multiply and accumulate. + */ + movq (%edi,%eax), %mm4 + pmullw %mm0, %mm4 + movq 8(%edi,%eax), %mm5 + pmullw %mm1, %mm5 + movq 16(%edi,%eax), %mm6 + pmullw %mm2,%mm6 + movq 24(%edi,%eax), %mm7 + pmullw %mm3,%mm7 + + paddw %mm4, %mm5 + paddw %mm6, %mm7 + paddw %mm5, %mm7 + +/* %mm7 holds the accumulated sum. Compute (C + 0x80) / 256 + */ + pxor %mm4, %mm4 + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm4, %mm6 + paddw %mm6, %mm7 + psrlw $8, %mm7 + +/* Pack into %eax and store result + */ + packuswb %mm7, %mm7 + movd %mm7, %eax + + movb %al, (%esi) # *dest = y + + movl 36(%ebp), %ecx # get dest_x + andl $1, %ecx # select u or v + sall $1, %ecx # determine offset + addl $1, %ecx # relative to x_aligned + sall $3, %ecx # offset * 8 bits/byte + + movd %mm7, %eax + shrl %cl, %eax + movb %al, 1(%esi) # *dest = uv + + addl $2, %esi # dest += 2 + cmpl %esi,28(%ebp) # if dest == dest_end + je .out # then exit + + addl $1, 36(%ebp) # dest_x++ + +.newx: + + addl 24(%ebp), %ebx # x += x_step +/* + * Load current component values into mm0 (src0) and mm2 (src1) + */ + movq %mm1, %mm0 + movq %mm3, %mm2 + +/* + * Load next component values into mm1 (src0) and mm3 (src1) + */ + movl %ebx, %eax # x_scaled + sarl $15, %eax + andl $0xfffffffe, %eax + movl %eax, %edx # x_aligned + andl $0xfffffffc, %edx + + movl 16(%ebp), %edi # get src0 + movl (%edi,%eax), %ecx # get y + andl $0x00ff00ff, %ecx # mask off y + movl (%edi,%edx), %eax # get uv + andl $0xff00ff00, %eax # mask off uv + orl %eax, %ecx # composite y, uv + movd %ecx, %mm1 # move to mmx1 + punpcklbw %mm4, %mm1 + + movl 20(%ebp), %edi # get src1 + movl (%edi,%edx), %ecx # get y + andl $0x00ff00ff, %ecx # mask off y + movl (%edi,%edx), %eax # get uv + andl $0xff00ff00, %eax # mask off uv + orl %eax, %ecx # composite y, uv + movd %ecx, %mm3 # move to mmx3 + punpcklbw %mm4, %mm3 + + jmp .loop + +.out: + movl %esi,%eax + emms + leal -40(%ebp),%esp + popl %ebx + popl %esi + popl %edi + movl %ebp,%esp + popl %ebp + ret diff --git a/src/modules/inigo/Makefile b/src/modules/inigo/Makefile new file mode 100644 index 0000000..8766b84 --- /dev/null +++ b/src/modules/inigo/Makefile @@ -0,0 +1,33 @@ +include ../../../config.mak + +TARGET = ../libmltinigo$(LIBSUF) + +OBJS = factory.o \ + producer_inigo.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/inigo/configure b/src/modules/inigo/configure new file mode 100755 index 0000000..5296827 --- /dev/null +++ b/src/modules/inigo/configure @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +inigo libmltinigo$LIBSUF +inigo_file libmltinigo$LIBSUF +EOF + +fi + diff --git a/src/modules/inigo/factory.c b/src/modules/inigo/factory.c new file mode 100644 index 0000000..009216b --- /dev/null +++ b/src/modules/inigo/factory.c @@ -0,0 +1,48 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_inigo.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "inigo_file" ) ) + return producer_inigo_file_init( arg ); + if ( !strcmp( id, "inigo" ) ) + return producer_inigo_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/inigo/producer_inigo.c b/src/modules/inigo/producer_inigo.c new file mode 100644 index 0000000..63b2ec8 --- /dev/null +++ b/src/modules/inigo/producer_inigo.c @@ -0,0 +1,442 @@ +/* + * producer_inigo.c -- simple inigo test case + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_inigo.h" + +#include +#include +#include + +#include + +mlt_producer producer_inigo_file_init( char *file ) +{ + FILE *input = fopen( file, "r" ); + char **args = calloc( sizeof( char * ), 1000 ); + int count = 0; + char temp[ 2048 ]; + + if ( input != NULL ) + { + while( fgets( temp, 2048, input ) ) + { + temp[ strlen( temp ) - 1 ] = '\0'; + if ( strcmp( temp, "" ) ) + args[ count ++ ] = strdup( temp ); + } + } + + mlt_producer result = producer_inigo_init( args ); + + if ( result != NULL ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( result ); + mlt_properties_set( properties, "resource", file ); + } + + while( count -- ) + free( args[ count ] ); + free( args ); + + return result; +} + +static void track_service( mlt_field field, void *service, mlt_destructor destructor ) +{ + mlt_properties properties = mlt_field_properties( field ); + int registered = mlt_properties_get_int( properties, "registered" ); + char *key = mlt_properties_get( properties, "registered" ); + mlt_properties_set_data( properties, key, service, 0, destructor, NULL ); + mlt_properties_set_int( properties, "registered", ++ registered ); +} + +static mlt_producer create_producer( mlt_field field, char *file ) +{ + mlt_producer result = mlt_factory_producer( "fezzik", file ); + + if ( result != NULL ) + track_service( field, result, ( mlt_destructor )mlt_producer_close ); + + return result; +} + +static mlt_filter create_attach( mlt_field field, char *id, int track ) +{ + char *temp = strdup( id ); + char *arg = strchr( temp, ':' ); + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_filter filter = mlt_factory_filter( temp, arg ); + if ( filter != NULL ) + track_service( field, filter, ( mlt_destructor )mlt_filter_close ); + free( temp ); + return filter; +} + +static mlt_filter create_filter( mlt_field field, char *id, int track ) +{ + char *temp = strdup( id ); + char *arg = strchr( temp, ':' ); + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_filter filter = mlt_factory_filter( temp, arg ); + if ( filter != NULL ) + { + mlt_field_plant_filter( field, filter, track ); + track_service( field, filter, ( mlt_destructor )mlt_filter_close ); + } + free( temp ); + return filter; +} + +static mlt_transition create_transition( mlt_field field, char *id, int track ) +{ + char *arg = strchr( id, ':' ); + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_transition transition = mlt_factory_transition( id, arg ); + if ( transition != NULL ) + { + mlt_field_plant_transition( field, transition, track, track + 1 ); + track_service( field, transition, ( mlt_destructor )mlt_transition_close ); + } + return transition; +} + +mlt_producer producer_inigo_init( char **argv ) +{ + int i; + int track = 0; + mlt_producer producer = NULL; + mlt_tractor mix = NULL; + mlt_playlist playlist = mlt_playlist_init( ); + mlt_properties group = mlt_properties_new( ); + mlt_tractor tractor = mlt_tractor_new( ); + mlt_properties properties = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_field field = mlt_tractor_field( tractor ); + mlt_properties field_properties = mlt_field_properties( field ); + mlt_multitrack multitrack = mlt_tractor_multitrack( tractor ); + char *title = NULL; + + // Assistance for template construction (allows -track usage to specify the first track) + mlt_properties_set_int( MLT_PLAYLIST_PROPERTIES( playlist ), "_inigo_first", 1 ); + + // We need to track the number of registered filters + mlt_properties_set_int( field_properties, "registered", 0 ); + + // Parse the arguments + for ( i = 0; argv[ i ] != NULL; i ++ ) + { + if ( !strcmp( argv[ i ], "-group" ) ) + { + if ( mlt_properties_count( group ) != 0 ) + { + mlt_properties_close( group ); + group = mlt_properties_new( ); + } + if ( group != NULL ) + properties = group; + } + else if ( !strcmp( argv[ i ], "-attach" ) || + !strcmp( argv[ i ], "-attach-cut" ) || + !strcmp( argv[ i ], "-attach-track" ) || + !strcmp( argv[ i ], "-attach-clip" ) ) + { + int type = !strcmp( argv[ i ], "-attach" ) ? 0 : + !strcmp( argv[ i ], "-attach-cut" ) ? 1 : + !strcmp( argv[ i ], "-attach-track" ) ? 2 : 3; + mlt_filter filter = create_attach( field, argv[ ++ i ], track ); + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + { + mlt_playlist_clip_info info; + mlt_playlist_append( playlist, producer ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + } + + if ( type == 1 || type == 2 ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + } + + if ( filter != NULL && mlt_playlist_count( playlist ) > 0 ) + { + if ( type == 0 ) + mlt_service_attach( ( mlt_service )properties, filter ); + else if ( type == 1 ) + mlt_service_attach( ( mlt_service )producer, filter ); + else if ( type == 2 ) + mlt_service_attach( ( mlt_service )playlist, filter ); + else if ( type == 3 ) + mlt_service_attach( ( mlt_service )mlt_producer_cut_parent( producer ), filter ); + + properties = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_inherit( properties, group ); + } + else if ( filter != NULL ) + { + mlt_service_attach( ( mlt_service )playlist, filter ); + properties = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_inherit( properties, group ); + } + } + else if ( !strcmp( argv[ i ], "-repeat" ) ) + { + int repeat = atoi( argv[ ++ i ] ); + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) > 0 ) + { + mlt_playlist_clip_info info; + mlt_playlist_repeat_clip( playlist, mlt_playlist_count( playlist ) - 1, repeat ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + } + else if ( !strcmp( argv[ i ], "-split" ) ) + { + int split = atoi( argv[ ++ i ] ); + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) > 0 ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + split = split < 0 ? info.frame_out + split : split; + mlt_playlist_split( playlist, mlt_playlist_count( playlist ) - 1, split ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + } + else if ( !strcmp( argv[ i ], "-swap" ) ) + { + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) >= 2 ) + { + mlt_playlist_clip_info info; + mlt_playlist_move( playlist, mlt_playlist_count( playlist ) - 2, mlt_playlist_count( playlist ) - 1 ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + } + else if ( !strcmp( argv[ i ], "-join" ) ) + { + int clips = atoi( argv[ ++ i ] ); + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) > 0 ) + { + mlt_playlist_clip_info info; + int clip = clips <= 0 ? 0 : mlt_playlist_count( playlist ) - clips - 1; + if ( clip < 0 ) clip = 0; + if ( clip >= mlt_playlist_count( playlist ) ) clip = mlt_playlist_count( playlist ) - 2; + if ( clips < 0 ) clips = mlt_playlist_count( playlist ) - 1; + mlt_playlist_join( playlist, clip, clips, 0 ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + } + else if ( !strcmp( argv[ i ], "-remove" ) ) + { + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) > 0 ) + { + mlt_playlist_clip_info info; + mlt_playlist_remove( playlist, mlt_playlist_count( playlist ) - 1 ); + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + producer = info.cut; + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + } + else if ( !strcmp( argv[ i ], "-mix" ) ) + { + int length = atoi( argv[ ++ i ] ); + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( mlt_playlist_count( playlist ) >= 2 ) + { + if ( mlt_playlist_mix( playlist, mlt_playlist_count( playlist ) - 2, length, NULL ) == 0 ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 1 ); + if ( mlt_properties_get_data( ( mlt_properties )info.producer, "mlt_mix", NULL ) == NULL ) + mlt_playlist_get_clip_info( playlist, &info, mlt_playlist_count( playlist ) - 2 ); + mix = ( mlt_tractor )mlt_properties_get_data( ( mlt_properties )info.producer, "mlt_mix", NULL ); + properties = NULL; + } + else + { + fprintf( stderr, "Mix failed?\n" ); + } + } + else + { + fprintf( stderr, "Invalid position for a mix...\n" ); + } + } + else if ( !strcmp( argv[ i ], "-mixer" ) ) + { + if ( mix != NULL ) + { + char *id = strdup( argv[ ++ i ] ); + char *arg = strchr( id, ':' ); + mlt_field field = mlt_tractor_field( mix ); + mlt_transition transition = NULL; + if ( arg != NULL ) + *arg ++ = '\0'; + transition = mlt_factory_transition( id, arg ); + if ( transition != NULL ) + { + properties = MLT_TRANSITION_PROPERTIES( transition ); + mlt_properties_inherit( properties, group ); + mlt_field_plant_transition( field, transition, 0, 1 ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", mlt_producer_get_out( ( mlt_producer )mix ) ); + mlt_transition_close( transition ); + } + free( id ); + } + else + { + fprintf( stderr, "Invalid mixer...\n" ); + } + } + else if ( !strcmp( argv[ i ], "-filter" ) ) + { + mlt_filter filter = create_filter( field, argv[ ++ i ], track ); + if ( filter != NULL ) + { + properties = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_inherit( properties, group ); + } + } + else if ( !strcmp( argv[ i ], "-transition" ) ) + { + mlt_transition transition = create_transition( field, argv[ ++ i ], track - 1 ); + if ( transition != NULL ) + { + properties = MLT_TRANSITION_PROPERTIES( transition ); + mlt_properties_inherit( properties, group ); + } + } + else if ( !strcmp( argv[ i ], "-blank" ) ) + { + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + mlt_playlist_blank( playlist, atof( argv[ ++ i ] ) ); + } + else if ( !strcmp( argv[ i ], "-track" ) || + !strcmp( argv[ i ], "-null-track" ) || + !strcmp( argv[ i ], "-video-track" ) || + !strcmp( argv[ i ], "-audio-track" ) || + !strcmp( argv[ i ], "-hide-track" ) || + !strcmp( argv[ i ], "-hide-video" ) || + !strcmp( argv[ i ], "-hide-audio" ) ) + { + if ( producer != NULL && !mlt_producer_is_cut( producer ) ) + mlt_playlist_append( playlist, producer ); + producer = NULL; + if ( !mlt_properties_get_int( MLT_PLAYLIST_PROPERTIES( playlist ), "_inigo_first" ) || + mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( playlist ) ) > 0 ) + { + mlt_multitrack_connect( multitrack, MLT_PLAYLIST_PRODUCER( playlist ), track ++ ); + track_service( field, playlist, ( mlt_destructor )mlt_playlist_close ); + playlist = mlt_playlist_init( ); + } + if ( playlist != NULL ) + { + properties = MLT_PLAYLIST_PROPERTIES( playlist ); + if ( !strcmp( argv[ i ], "-null-track" ) || !strcmp( argv[ i ], "-hide-track" ) ) + mlt_properties_set_int( properties, "hide", 3 ); + else if ( !strcmp( argv[ i ], "-audio-track" ) || !strcmp( argv[ i ], "-hide-video" ) ) + mlt_properties_set_int( properties, "hide", 1 ); + else if ( !strcmp( argv[ i ], "-video-track" ) || !strcmp( argv[ i ], "-hide-audio" ) ) + mlt_properties_set_int( properties, "hide", 2 ); + } + } + else if ( strchr( argv[ i ], '=' ) && strstr( argv[ i ], " 0 ) + mlt_multitrack_connect( multitrack, MLT_PLAYLIST_PRODUCER( playlist ), track ); + + mlt_producer prod = MLT_TRACTOR_PRODUCER( tractor ); + mlt_producer_optimise( prod ); + mlt_properties props = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_properties_set_data( props, "group", group, 0, ( mlt_destructor )mlt_properties_close, NULL ); + mlt_properties_set_position( props, "length", mlt_producer_get_out( MLT_MULTITRACK_PRODUCER( multitrack ) ) + 1 ); + mlt_producer_set_in_and_out( prod, 0, mlt_producer_get_out( MLT_MULTITRACK_PRODUCER( multitrack ) ) ); + if ( title != NULL ) + mlt_properties_set( props, "title", strchr( title, '/' ) ? strrchr( title, '/' ) + 1 : title ); + + return prod; +} diff --git a/src/modules/inigo/producer_inigo.h b/src/modules/inigo/producer_inigo.h new file mode 100644 index 0000000..3b802d7 --- /dev/null +++ b/src/modules/inigo/producer_inigo.h @@ -0,0 +1,29 @@ +/* + * producer_inigo.h -- simple inigo test case + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_INIGO_H_ +#define _PRODUCER_INIGO_H_ + +#include + +extern mlt_producer producer_inigo_file_init( char *args ); +extern mlt_producer producer_inigo_init( char **args ); + +#endif diff --git a/src/modules/jackrack/Makefile b/src/modules/jackrack/Makefile new file mode 100644 index 0000000..9c9994c --- /dev/null +++ b/src/modules/jackrack/Makefile @@ -0,0 +1,47 @@ +include ../../../config.mak + +TARGET = ../libmltjackrack$(LIBSUF) + +OBJS = factory.o \ + jack_rack.o \ + lock_free_fifo.o \ + plugin.o \ + plugin_desc.o \ + plugin_mgr.o \ + plugin_settings.o \ + process.o \ + filter_jackrack.o \ + filter_ladspa.o + +CFLAGS += -I../.. `pkg-config --cflags jack` +CFLAGS += `xml2-config --cflags` +CFLAGS += `pkg-config glib-2.0 --cflags` + +LDFLAGS += `pkg-config --libs jack` +LDFLAGS += `xml2-config --libs` +LDFLAGS += `pkg-config glib-2.0 --libs` + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/jackrack/configure b/src/modules/jackrack/configure new file mode 100755 index 0000000..01c8cdd --- /dev/null +++ b/src/modules/jackrack/configure @@ -0,0 +1,31 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + pkg-config jack + disable_jack=$? + + which xml2-config > /dev/null 2>&1 + disable_xml2=$? + + disable_ladspa=1 + ladspa_prefix=`which listplugins 2> /dev/null` + if [ "$ladspa_prefix" != "" ] + then + ladspa_prefix=`dirname "$ladspa_prefix"` + disable_ladspa=`[ -f "$ladspa_prefix/include/ladspa.h" ] && echo 1 || echo 0` + fi + + if [ "$disable_jack" = "0" -a "$disable_xml2" = "0" -a "$disable_ladspa" = "0" ] + then + echo "jackrack libmltjackrack$LIBSUF" >> ../filters.dat + echo "ladspa libmltjackrack$LIBSUF" >> ../filters.dat + else + [ "$disable_jack" = "1" ] && echo "- jackrack not found: disabling" + [ "$disable_xml2" = "1" ] && echo "- xml2 not found: disabling jackrack" + [ "$disable_ladspa" = "1" ] && echo "- ladspa not found; disabling" + touch ../disable-jackrack + fi + +fi diff --git a/src/modules/jackrack/factory.c b/src/modules/jackrack/factory.c new file mode 100644 index 0000000..4a00b49 --- /dev/null +++ b/src/modules/jackrack/factory.c @@ -0,0 +1,48 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_jackrack.h" +#include "filter_ladspa.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "jackrack" ) ) + return filter_jackrack_init( arg ); + else if ( !strcmp( id, "ladspa" ) ) + return filter_ladspa_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/jackrack/filter_jackrack.c b/src/modules/jackrack/filter_jackrack.c new file mode 100644 index 0000000..53774a4 --- /dev/null +++ b/src/modules/jackrack/filter_jackrack.c @@ -0,0 +1,373 @@ +/* + * filter_jackrack.c -- filter audio through Jack and/or LADSPA plugins + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_jackrack.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "jack_rack.h" + +#define BUFFER_LEN 204800 * 3 + +static void initialise_jack_ports( mlt_properties properties ) +{ + int i; + char mlt_name[20], rack_name[30]; + jack_port_t **port = NULL; + jack_client_t *jack_client = mlt_properties_get_data( properties, "jack_client", NULL ); + jack_nframes_t jack_buffer_size = jack_get_buffer_size( jack_client ); + + // Propogate these for the Jack processing callback + int channels = mlt_properties_get_int( properties, "channels" ); + + // Start JackRack + if ( mlt_properties_get( properties, "src" ) ) + { + snprintf( rack_name, sizeof( rack_name ), "jackrack%d", getpid() ); + jack_rack_t *jackrack = jack_rack_new( rack_name, mlt_properties_get_int( properties, "channels" ) ); + jack_rack_open_file( jackrack, mlt_properties_get( properties, "src" ) ); + + mlt_properties_set_data( properties, "jackrack", jackrack, 0, NULL, NULL ); + mlt_properties_set( properties, "_rack_client_name", rack_name ); + } + + // Allocate buffers and ports + jack_ringbuffer_t **output_buffers = mlt_pool_alloc( sizeof( jack_ringbuffer_t *) * channels ); + jack_ringbuffer_t **input_buffers = mlt_pool_alloc( sizeof( jack_ringbuffer_t *) * channels ); + jack_port_t **jack_output_ports = mlt_pool_alloc( sizeof(jack_port_t *) * channels ); + jack_port_t **jack_input_ports = mlt_pool_alloc( sizeof(jack_port_t *) * channels ); + float **jack_output_buffers = mlt_pool_alloc( sizeof(float *) * jack_buffer_size ); + float **jack_input_buffers = mlt_pool_alloc( sizeof(float *) * jack_buffer_size ); + + // Set properties - released inside filter_close + mlt_properties_set_data( properties, "output_buffers", output_buffers, sizeof( jack_ringbuffer_t *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "input_buffers", input_buffers, sizeof( jack_ringbuffer_t *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "jack_output_ports", jack_output_ports, sizeof( jack_port_t *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "jack_input_ports", jack_input_ports, sizeof( jack_port_t *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "jack_output_buffers", jack_output_buffers, sizeof( float *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "jack_input_buffers", jack_input_buffers, sizeof( float *) * channels, NULL, NULL ); + + // Start Jack processing - required before registering ports + jack_activate( jack_client ); + + // Register Jack ports + for ( i = 0; i < channels; i++ ) + { + int in; + + output_buffers[i] = jack_ringbuffer_create( BUFFER_LEN * sizeof(float) ); + input_buffers[i] = jack_ringbuffer_create( BUFFER_LEN * sizeof(float) ); + snprintf( mlt_name, sizeof( mlt_name ), "obuf%d", i ); + mlt_properties_set_data( properties, mlt_name, output_buffers[i], BUFFER_LEN * sizeof(float), NULL, NULL ); + snprintf( mlt_name, sizeof( mlt_name ), "ibuf%d", i ); + mlt_properties_set_data( properties, mlt_name, input_buffers[i], BUFFER_LEN * sizeof(float), NULL, NULL ); + + for ( in = 0; in < 2; in++ ) + { + snprintf( mlt_name, sizeof( mlt_name ), "%s_%d", in ? "in" : "out", i + 1); + port = ( in ? &jack_input_ports[i] : &jack_output_ports[i] ); + + *port = jack_port_register( jack_client, mlt_name, JACK_DEFAULT_AUDIO_TYPE, + ( in ? JackPortIsInput : JackPortIsOutput ) | JackPortIsTerminal, 0 ); + } + } + + // Establish connections + for ( i = 0; i < channels; i++ ) + { + int in; + for ( in = 0; in < 2; in++ ) + { + port = ( in ? &jack_input_ports[i] : &jack_output_ports[i] ); + snprintf( mlt_name, sizeof( mlt_name ), "%s", jack_port_name( *port ) ); + + snprintf( rack_name, sizeof( rack_name ), "%s_%d", in ? "in" : "out", i + 1 ); + if ( mlt_properties_get( properties, "_rack_client_name" ) ) + snprintf( rack_name, sizeof( rack_name ), "%s:%s_%d", mlt_properties_get( properties, "_rack_client_name" ), in ? "out" : "in", i + 1); + else if ( mlt_properties_get( properties, rack_name ) ) + snprintf( rack_name, sizeof( rack_name ), "%s", mlt_properties_get( properties, rack_name ) ); + else + snprintf( rack_name, sizeof( rack_name ), "%s:%s_%d", mlt_properties_get( properties, "_client_name" ), in ? "out" : "in", i + 1); + + if ( in ) + { + fprintf( stderr, "jack connect %s to %s\n", rack_name, mlt_name ); + jack_connect( jack_client, rack_name, mlt_name ); + } + else + { + fprintf( stderr, "jack connect %s to %s\n", mlt_name, rack_name ); + jack_connect( jack_client, mlt_name, rack_name ); + } + } + } +} + +static int jack_process (jack_nframes_t frames, void * data) +{ + mlt_filter filter = (mlt_filter) data; + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + int channels = mlt_properties_get_int( properties, "channels" ); + int frame_size = mlt_properties_get_int( properties, "_samples" ) * sizeof(float); + int sync = mlt_properties_get_int( properties, "_sync" ); + int err = 0; + int i; + static int total_size = 0; + + jack_ringbuffer_t **output_buffers = mlt_properties_get_data( properties, "output_buffers", NULL ); + if ( output_buffers == NULL ) + return 0; + jack_ringbuffer_t **input_buffers = mlt_properties_get_data( properties, "input_buffers", NULL ); + jack_port_t **jack_output_ports = mlt_properties_get_data( properties, "jack_output_ports", NULL ); + jack_port_t **jack_input_ports = mlt_properties_get_data( properties, "jack_input_ports", NULL ); + float **jack_output_buffers = mlt_properties_get_data( properties, "jack_output_buffers", NULL ); + float **jack_input_buffers = mlt_properties_get_data( properties, "jack_input_buffers", NULL ); + pthread_mutex_t *output_lock = mlt_properties_get_data( properties, "output_lock", NULL ); + pthread_cond_t *output_ready = mlt_properties_get_data( properties, "output_ready", NULL ); + + for ( i = 0; i < channels; i++ ) + { + size_t jack_size = ( frames * sizeof(float) ); + size_t ring_size; + + // Send audio through out port + jack_output_buffers[i] = jack_port_get_buffer( jack_output_ports[i], frames ); + if ( ! jack_output_buffers[i] ) + { + fprintf( stderr, "%s: no jack buffer for output port %d\n", __FUNCTION__, i ); + err = 1; + break; + } + ring_size = jack_ringbuffer_read_space( output_buffers[i] ); + jack_ringbuffer_read( output_buffers[i], ( char * )jack_output_buffers[i], ring_size < jack_size ? ring_size : jack_size ); + + // Return audio through in port + jack_input_buffers[i] = jack_port_get_buffer( jack_input_ports[i], frames ); + if ( ! jack_input_buffers[i] ) + { + fprintf( stderr, "%s: no jack buffer for input port %d\n", __FUNCTION__, i ); + err = 1; + break; + } + + // Do not start returning audio until we have sent first mlt frame + if ( sync && i == 0 && frame_size > 0 ) + total_size += ring_size; + //fprintf(stderr, "sync %d frame_size %d ring_size %d jack_size %d\n", sync, frame_size, ring_size, jack_size ); + + if ( ! sync || ( frame_size > 0 && total_size >= frame_size ) ) + { + ring_size = jack_ringbuffer_write_space( input_buffers[i] ); + jack_ringbuffer_write( input_buffers[i], ( char * )jack_input_buffers[i], ring_size < jack_size ? ring_size : jack_size ); + + if ( sync ) + { + // Tell mlt that audio is available + pthread_mutex_lock( output_lock); + pthread_cond_signal( output_ready ); + pthread_mutex_unlock( output_lock); + + // Clear sync phase + mlt_properties_set_int( properties, "_sync", 0 ); + } + } + } + + return err; +} + + +/** Get the audio. +*/ + +static int jackrack_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + int jack_frequency = mlt_properties_get_int( filter_properties, "_sample_rate" ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, &jack_frequency, channels, samples ); + + // TODO: Deal with sample rate differences + if ( *frequency != jack_frequency ) + fprintf( stderr, "mismatching frequencies in filter jackrack\n" ); + *frequency = jack_frequency; + + // Initialise Jack ports and connections if needed + if ( mlt_properties_get_int( filter_properties, "_samples" ) == 0 ) + mlt_properties_set_int( filter_properties, "_samples", *samples ); + + // Get the filter-specific properties + jack_ringbuffer_t **output_buffers = mlt_properties_get_data( filter_properties, "output_buffers", NULL ); + jack_ringbuffer_t **input_buffers = mlt_properties_get_data( filter_properties, "input_buffers", NULL ); +// pthread_mutex_t *output_lock = mlt_properties_get_data( filter_properties, "output_lock", NULL ); +// pthread_cond_t *output_ready = mlt_properties_get_data( filter_properties, "output_ready", NULL ); + + // Process the audio + int16_t *q = *buffer; + float sample[ 2 ][ 10000 ]; + int i, j; +// struct timespec tm = { 0, 0 }; + + // Convert to floats and write into output ringbuffer + if ( jack_ringbuffer_write_space( output_buffers[0] ) >= ( *samples * sizeof(float) ) ) + { + for ( i = 0; i < *samples; i++ ) + for ( j = 0; j < *channels; j++ ) + sample[ j ][ i ] = ( float )( *q ++ ) / 32768.0; + + for ( j = 0; j < *channels; j++ ) + jack_ringbuffer_write( output_buffers[j], ( char * )sample[ j ], *samples * sizeof(float) ); + } + + // Synchronization phase - wait for signal from Jack process + while ( jack_ringbuffer_read_space( input_buffers[ *channels - 1 ] ) < ( *samples * sizeof(float) ) ) ; + //pthread_cond_wait( output_ready, output_lock ); + + // Read from input ringbuffer and convert from floats + if ( jack_ringbuffer_read_space( input_buffers[0] ) >= ( *samples * sizeof(float) ) ) + { + // Initialise to silence, but repeat last frame if available in case of + // buffer underrun + for ( j = 0; j < *channels; j++ ) + jack_ringbuffer_read( input_buffers[j], ( char * )sample[ j ], *samples * sizeof(float) ); + + q = *buffer; + for ( i = 0; i < *samples; i++ ) + for ( j = 0; j < *channels; j++ ) + { + if ( sample[ j ][ i ] > 1.0 ) + sample[ j ][ i ] = 1.0; + else if ( sample[ j ][ i ] < -1.0 ) + sample[ j ][ i ] = -1.0; + + if ( sample[ j ][ i ] > 0 ) + *q ++ = 32767 * sample[ j ][ i ]; + else + *q ++ = 32768 * sample[ j ][ i ]; + } + } + + return 0; +} + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + mlt_frame_push_audio( frame, this ); + mlt_frame_push_audio( frame, jackrack_get_audio ); + + if ( mlt_properties_get_int( properties, "_sync" ) ) + initialise_jack_ports( properties ); + } + + return frame; +} + + +static void filter_close( mlt_filter this ) +{ + int i; + char mlt_name[20]; + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + jack_client_t *jack_client = mlt_properties_get_data( properties, "jack_client", NULL ); + + jack_deactivate( jack_client ); + jack_client_close( jack_client ); + for ( i = 0; i < mlt_properties_get_int( properties, "channels" ); i++ ) + { + snprintf( mlt_name, sizeof( mlt_name ), "obuf%d", i ); + jack_ringbuffer_free( mlt_properties_get_data( properties, mlt_name, NULL ) ); + snprintf( mlt_name, sizeof( mlt_name ), "ibuf%d", i ); + jack_ringbuffer_free( mlt_properties_get_data( properties, mlt_name, NULL ) ); + } + mlt_pool_release( mlt_properties_get_data( properties, "output_buffers", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "input_buffers", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "jack_output_ports", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "jack_input_ports", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "jack_output_buffers", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "jack_input_buffers", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "output_lock", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "output_ready", NULL ) ); + + jack_rack_t *jackrack = mlt_properties_get_data( properties, "jackrack", NULL ); + jack_rack_destroy( jackrack ); + + this->parent.close = NULL; + mlt_service_close( &this->parent ); +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_jackrack_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + char name[14]; + + snprintf( name, sizeof( name ), "mlt%d", getpid() ); + jack_client_t *jack_client = jack_client_new( name ); + if ( jack_client ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + pthread_mutex_t *output_lock = mlt_pool_alloc( sizeof( pthread_mutex_t ) ); + pthread_cond_t *output_ready = mlt_pool_alloc( sizeof( pthread_cond_t ) ); + + jack_set_process_callback( jack_client, jack_process, this ); + //TODO: jack_on_shutdown( jack_client, jack_shutdown_cb, this ); + this->process = filter_process; + this->close = filter_close; + pthread_mutex_init( output_lock, NULL ); + pthread_cond_init( output_ready, NULL ); + + mlt_properties_set( properties, "src", arg ); + mlt_properties_set( properties, "_client_name", name ); + mlt_properties_set_data( properties, "jack_client", jack_client, 0, NULL, NULL ); + mlt_properties_set_int( properties, "_sample_rate", jack_get_sample_rate( jack_client ) ); + mlt_properties_set_data( properties, "output_lock", output_lock, 0, NULL, NULL ); + mlt_properties_set_data( properties, "output_ready", output_ready, 0, NULL, NULL ); + mlt_properties_set_int( properties, "_sync", 1 ); + mlt_properties_set_int( properties, "channels", 2 ); + } + } + return this; +} diff --git a/src/modules/jackrack/filter_jackrack.h b/src/modules/jackrack/filter_jackrack.h new file mode 100644 index 0000000..064f69d --- /dev/null +++ b/src/modules/jackrack/filter_jackrack.h @@ -0,0 +1,28 @@ +/* + * filter_jackrack.h -- filter audio through Jack and/or LADSPA plugins + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_JACKRACK_H_ +#define _FILTER_JACKRACK_H_ + +#include + +extern mlt_filter filter_jackrack_init( char *arg ); + +#endif diff --git a/src/modules/jackrack/filter_ladspa.c b/src/modules/jackrack/filter_ladspa.c new file mode 100644 index 0000000..dfe3734 --- /dev/null +++ b/src/modules/jackrack/filter_ladspa.c @@ -0,0 +1,191 @@ +/* + * filter_ladspa.c -- filter audio through LADSPA plugins + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_ladspa.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "jack_rack.h" + +#define BUFFER_LEN 10000 + +static void initialise_jack_rack( mlt_properties properties, int channels ) +{ + int i; + char mlt_name[20]; + + // Propogate these for the Jack processing callback + mlt_properties_set_int( properties, "channels", channels ); + + // Start JackRack + if ( mlt_properties_get( properties, "src" ) ) + { + // Create JackRack without Jack client name so that it only uses LADSPA + jack_rack_t *jackrack = jack_rack_new( NULL, channels ); + mlt_properties_set_data( properties, "jackrack", jackrack, 0, NULL, NULL ); + jack_rack_open_file( jackrack, mlt_properties_get( properties, "src" ) ); + } + + // Allocate buffers + LADSPA_Data **input_buffers = mlt_pool_alloc( sizeof( LADSPA_Data ) * channels ); + LADSPA_Data **output_buffers = mlt_pool_alloc( sizeof( LADSPA_Data ) * channels ); + + // Set properties - released inside filter_close + mlt_properties_set_data( properties, "input_buffers", input_buffers, sizeof( LADSPA_Data *) * channels, NULL, NULL ); + mlt_properties_set_data( properties, "output_buffers", output_buffers, sizeof( LADSPA_Data *) * channels, NULL, NULL ); + + // Register Jack ports + for ( i = 0; i < channels; i++ ) + { + input_buffers[i] = mlt_pool_alloc( BUFFER_LEN * sizeof( LADSPA_Data ) ); + snprintf( mlt_name, sizeof( mlt_name ), "ibuf%d", i ); + mlt_properties_set_data( properties, mlt_name, input_buffers[i], BUFFER_LEN * sizeof( LADSPA_Data ), NULL, NULL ); + + output_buffers[i] = mlt_pool_alloc( BUFFER_LEN * sizeof( LADSPA_Data ) ); + snprintf( mlt_name, sizeof( mlt_name ), "obuf%d", i ); + mlt_properties_set_data( properties, mlt_name, output_buffers[i], BUFFER_LEN * sizeof( LADSPA_Data ), NULL, NULL ); + } +} + + +/** Get the audio. +*/ + +static int ladspa_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + // Initialise LADSPA if needed + jack_rack_t *jackrack = mlt_properties_get_data( filter_properties, "jackrack", NULL ); + if ( jackrack == NULL ) + { + sample_rate = *frequency; + initialise_jack_rack( filter_properties, *channels ); + jackrack = mlt_properties_get_data( filter_properties, "jackrack", NULL ); + } + + // Get the filter-specific properties + LADSPA_Data **input_buffers = mlt_properties_get_data( filter_properties, "input_buffers", NULL ); + LADSPA_Data **output_buffers = mlt_properties_get_data( filter_properties, "output_buffers", NULL ); + + // Process the audio + int16_t *q = *buffer; + int i, j; + + // Convert to floats and write into output ringbuffer + for ( i = 0; i < *samples; i++ ) + for ( j = 0; j < *channels; j++ ) + input_buffers[ j ][ i ] = ( float )( *q ++ ) / 32768.0; + + // Do LADSPA processing + if ( jackrack && process_ladspa( jackrack->procinfo, *samples, input_buffers, output_buffers) == 0 ) + { + // Read from output buffer and convert from floats + q = *buffer; + for ( i = 0; i < *samples; i++ ) + for ( j = 0; j < *channels; j++ ) + { + if ( output_buffers[ j ][ i ] > 1.0 ) + output_buffers[ j ][ i ] = 1.0; + else if ( output_buffers[ j ][ i ] < -1.0 ) + output_buffers[ j ][ i ] = -1.0; + + if ( output_buffers[ j ][ i ] > 0 ) + *q ++ = 32767 * output_buffers[ j ][ i ]; + else + *q ++ = 32768 * output_buffers[ j ][ i ]; + } + } + + return 0; +} + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + { + mlt_frame_push_audio( frame, this ); + mlt_frame_push_audio( frame, ladspa_get_audio ); + } + + return frame; +} + + +static void filter_close( mlt_filter this ) +{ + int i; + char mlt_name[20]; + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + if ( mlt_properties_get_data( properties, "jackrack", NULL ) != NULL ) + { + for ( i = 0; i < mlt_properties_get_int( properties, "channels" ); i++ ) + { + snprintf( mlt_name, sizeof( mlt_name ), "obuf%d", i ); + mlt_pool_release( mlt_properties_get_data( properties, mlt_name, NULL ) ); + snprintf( mlt_name, sizeof( mlt_name ), "ibuf%d", i ); + mlt_pool_release( mlt_properties_get_data( properties, mlt_name, NULL ) ); + } + mlt_pool_release( mlt_properties_get_data( properties, "output_buffers", NULL ) ); + mlt_pool_release( mlt_properties_get_data( properties, "input_buffers", NULL ) ); + + jack_rack_t *jackrack = mlt_properties_get_data( properties, "jackrack", NULL ); + jack_rack_destroy( jackrack ); + } + this->parent.close = NULL; + mlt_service_close( &this->parent ); +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_ladspa_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + this->process = filter_process; + this->close = filter_close; + + mlt_properties_set( properties, "src", arg ); + } + return this; +} diff --git a/src/modules/jackrack/filter_ladspa.h b/src/modules/jackrack/filter_ladspa.h new file mode 100644 index 0000000..86ea371 --- /dev/null +++ b/src/modules/jackrack/filter_ladspa.h @@ -0,0 +1,28 @@ +/* + * filter_ladspa.h -- filter audio through LADSPA plugins + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_LADSPA_H_ +#define _FILTER_LADSPA_H_ + +#include + +extern mlt_filter filter_ladspa_init( char *arg ); + +#endif diff --git a/src/modules/jackrack/gpl b/src/modules/jackrack/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/jackrack/jack_rack.c b/src/modules/jackrack/jack_rack.c new file mode 100644 index 0000000..2d66f95 --- /dev/null +++ b/src/modules/jackrack/jack_rack.c @@ -0,0 +1,359 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "jack_rack.h" +#include "lock_free_fifo.h" +#include "plugin_settings.h" + +#ifndef _ +#define _(x) x +#endif +#define _x (xmlChar*) +#define _s (char*) + +jack_rack_t * +jack_rack_new (const char * client_name, unsigned long channels) +{ + jack_rack_t *rack; + + rack = g_malloc (sizeof (jack_rack_t)); + rack->saved_plugins = NULL; + rack->channels = channels; + rack->procinfo = process_info_new (client_name, channels, FALSE, FALSE); + if (!rack->procinfo) { + g_free (rack); + return NULL; + } + rack->plugin_mgr = plugin_mgr_new (); + plugin_mgr_set_plugins (rack->plugin_mgr, channels); + + return rack; +} + + +void +jack_rack_destroy (jack_rack_t * jack_rack) +{ + process_quit (jack_rack->procinfo); + plugin_mgr_destroy (jack_rack->plugin_mgr); + process_info_destroy (jack_rack->procinfo); + g_slist_free (jack_rack->saved_plugins); + g_free (jack_rack); +} + +plugin_t * +jack_rack_instantiate_plugin (jack_rack_t * jack_rack, plugin_desc_t * desc) +{ + plugin_t * plugin; + + /* check whether or not the plugin is RT capable and confirm with the user if it isn't */ + if (!LADSPA_IS_HARD_RT_CAPABLE(desc->properties)) { + fprintf (stderr, "Plugin not RT capable. The plugin '%s' does not describe itself as being capable of real-time operation. You may experience drop outs or jack may even kick us out if you use it.\n", + desc->name); + } + + /* create the plugin */ + plugin = plugin_new (desc, jack_rack); + + if (!plugin) { + fprintf (stderr, "Error loading file plugin '%s' from file '%s'\n", + desc->name, desc->object_file); + } + + return plugin; +} + + +void +jack_rack_add_saved_plugin (jack_rack_t * jack_rack, saved_plugin_t * saved_plugin) +{ + plugin_t * plugin = jack_rack_instantiate_plugin (jack_rack, saved_plugin->settings->desc); + if (!plugin) + return; + jack_rack->saved_plugins = g_slist_append (jack_rack->saved_plugins, saved_plugin); + process_add_plugin (jack_rack->procinfo, plugin); + jack_rack_add_plugin (jack_rack, plugin); +} + + +void +jack_rack_add_plugin (jack_rack_t * jack_rack, plugin_t * plugin) +{ + saved_plugin_t * saved_plugin = NULL; + GSList * list; + unsigned long control, channel; + LADSPA_Data value; + guint copy; + + /* see if there's any saved settings that match the plugin id */ + for (list = jack_rack->saved_plugins; list; list = g_slist_next (list)) + { + saved_plugin = list->data; + + if (saved_plugin->settings->desc->id == plugin->desc->id) + { + /* process the settings! */ + jack_rack->saved_plugins = g_slist_remove (jack_rack->saved_plugins, saved_plugin); + break; + } + saved_plugin = NULL; + } + + /* initialize plugin parameters */ + plugin->enabled = settings_get_enabled (saved_plugin->settings); + plugin->wet_dry_enabled = settings_get_wet_dry_enabled (saved_plugin->settings); + + for (control = 0; control < saved_plugin->settings->desc->control_port_count; control++) + for (copy = 0; copy < plugin->copies; copy++) + { + value = settings_get_control_value (saved_plugin->settings, copy, control); + plugin->holders[copy].control_memory[control] = value; +//printf("setting control value %s (%d) = %f\n", saved_plugin->settings->desc->port_names[control], copy, value); +// lff_write (plugin->holders[copy].ui_control_fifos + control, &value); + } + if (plugin->wet_dry_enabled) + for (channel = 0; channel < jack_rack->channels; channel++) + { + value = settings_get_wet_dry_value (saved_plugin->settings, channel); + plugin->wet_dry_values[channel] = value; +//printf("setting wet/dry value %d = %f\n", channel, value); +// lff_write (plugin->wet_dry_fifos + channel, &value); + } +} + + +static void +saved_rack_parse_plugin (jack_rack_t * jack_rack, saved_rack_t * saved_rack, saved_plugin_t * saved_plugin, + const char * filename, xmlNodePtr plugin) +{ + plugin_desc_t * desc; + settings_t * settings = NULL; + xmlNodePtr node; + xmlNodePtr sub_node; + xmlChar *content; + unsigned long num; + unsigned long control = 0; + + for (node = plugin->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("id")) == 0) + { + content = xmlNodeGetContent (node); + num = strtoul (_s(content), NULL, 10); + xmlFree (content); + + desc = plugin_mgr_get_any_desc (jack_rack->plugin_mgr, num); + if (!desc) + { + fprintf (stderr, _("The file '%s' contains an unknown plugin with ID '%ld'; skipping\n"), filename, num); + return; + } + + settings = settings_new (desc, saved_rack->channels, saved_rack->sample_rate); + } + else if (xmlStrcmp (node->name, _x("enabled")) == 0) + { + content = xmlNodeGetContent (node); + settings_set_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_enabled")) == 0) + { + content = xmlNodeGetContent (node); + settings_set_wet_dry_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_locked")) == 0) + { + content = xmlNodeGetContent (node); + settings_set_wet_dry_locked (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_values")) == 0) + { + unsigned long channel = 0; + + for (sub_node = node->children; sub_node; sub_node = sub_node->next) + { + if (xmlStrcmp (sub_node->name, _x("value")) == 0) + { + content = xmlNodeGetContent (sub_node); + settings_set_wet_dry_value (settings, channel, strtod (_s(content), NULL)); + xmlFree (content); + + channel++; + } + } + } + else if (xmlStrcmp (node->name, _x("lockall")) == 0) + { + content = xmlNodeGetContent (node); + settings_set_lock_all (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("controlrow")) == 0) + { + gint copy = 0; + + for (sub_node = node->children; sub_node; sub_node = sub_node->next) + { + if (xmlStrcmp (sub_node->name, _x("lock")) == 0) + { + content = xmlNodeGetContent (sub_node); + settings_set_lock (settings, control, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (sub_node->name, _x("value")) == 0) + { + content = xmlNodeGetContent (sub_node); + settings_set_control_value (settings, copy, control, strtod (_s(content), NULL)); + xmlFree (content); + copy++; + } + } + + control++; + } + } + + if (settings) + saved_plugin->settings = settings; +} + +static void +saved_rack_parse_jackrack (jack_rack_t * jack_rack, saved_rack_t * saved_rack, const char * filename, xmlNodePtr jackrack) +{ + xmlNodePtr node; + xmlChar *content; + saved_plugin_t * saved_plugin; + + for (node = jackrack->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("channels")) == 0) + { + content = xmlNodeGetContent (node); + saved_rack->channels = strtoul (_s(content), NULL, 10); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("samplerate")) == 0) + { + content = xmlNodeGetContent (node); + saved_rack->sample_rate = strtoul (_s(content), NULL, 10); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("plugin")) == 0) + { + saved_plugin = g_malloc0 (sizeof (saved_plugin_t)); + saved_rack->plugins = g_slist_append (saved_rack->plugins, saved_plugin); + saved_rack_parse_plugin (jack_rack, saved_rack, saved_plugin, filename, node); + } + } +} + +static saved_rack_t * +saved_rack_new (jack_rack_t * jack_rack, const char * filename, xmlDocPtr doc) +{ + xmlNodePtr node; + saved_rack_t *saved_rack; + + /* create the saved rack */ + saved_rack = g_malloc (sizeof (saved_rack_t)); + saved_rack->plugins = NULL; + saved_rack->sample_rate = 48000; + saved_rack->channels = 2; + + for (node = doc->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("jackrack")) == 0) + saved_rack_parse_jackrack (jack_rack, saved_rack, filename, node); + } + + return saved_rack; +} + +static void +saved_rack_destroy (saved_rack_t * saved_rack) +{ + GSList * list; + + for (list = saved_rack->plugins; list; list = g_slist_next (list)) + settings_destroy (((saved_plugin_t *) list->data)->settings); + g_slist_free (saved_rack->plugins); + g_free (saved_rack); +} + + +int +jack_rack_open_file (jack_rack_t * jack_rack, const char * filename) +{ + xmlDocPtr doc; + saved_rack_t * saved_rack; + GSList * list; + saved_plugin_t * saved_plugin; + + doc = xmlParseFile (filename); + if (!doc) + { + fprintf (stderr, _("Could not parse file '%s'\n"), filename); + return 1; + } + + if (xmlStrcmp ( ((xmlDtdPtr)doc->children)->name, _x("jackrack")) != 0) + { + fprintf (stderr, _("The file '%s' is not a JACK Rack settings file\n"), filename); + return 1; + } + + saved_rack = saved_rack_new (jack_rack, filename, doc); + xmlFreeDoc (doc); + + if (!saved_rack) + return 1; + + for (list = saved_rack->plugins; list; list = g_slist_next (list)) + { + saved_plugin = list->data; + + settings_set_sample_rate (saved_plugin->settings, sample_rate); + + jack_rack_add_saved_plugin (jack_rack, saved_plugin); + } + + saved_rack_destroy (saved_rack); + + return 0; +} + + +/* EOF */ diff --git a/src/modules/jackrack/jack_rack.h b/src/modules/jackrack/jack_rack.h new file mode 100644 index 0000000..7936a8b --- /dev/null +++ b/src/modules/jackrack/jack_rack.h @@ -0,0 +1,72 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JR_JACK_RACK_H__ +#define __JR_JACK_RACK_H__ + +#include +#include + +#include "plugin.h" +#include "plugin_mgr.h" +#include "plugin_settings.h" +#include "process.h" + +typedef struct _saved_plugin saved_plugin_t; + +struct _saved_plugin +{ + settings_t *settings; +}; + +typedef struct _saved_rack saved_rack_t; + +struct _saved_rack +{ + unsigned long channels; + jack_nframes_t sample_rate; + GSList * plugins; +}; + +typedef struct _jack_rack jack_rack_t; + +struct _jack_rack +{ + plugin_mgr_t * plugin_mgr; + process_info_t * procinfo; + unsigned long channels; + GSList * saved_plugins; +}; + +jack_rack_t * jack_rack_new (const char * client_name, unsigned long channels); +void jack_rack_destroy (jack_rack_t * jack_rack); + +int jack_rack_open_file (jack_rack_t * jack_rack, const char * filename); +void jack_rack_add_plugin (jack_rack_t * jack_rack, plugin_t * plugin); +void jack_rack_add_saved_plugin (jack_rack_t * jack_rack, struct _saved_plugin * saved_plugin); + +plugin_t * jack_rack_instantiate_plugin (jack_rack_t * jack_rack, plugin_desc_t * desc); + +#endif /* __JR_JACK_RACK_H__ */ diff --git a/src/modules/jackrack/lock_free_fifo.c b/src/modules/jackrack/lock_free_fifo.c new file mode 100644 index 0000000..177f6b4 --- /dev/null +++ b/src/modules/jackrack/lock_free_fifo.c @@ -0,0 +1,117 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include + +#include "lock_free_fifo.h" + +/** initialise a lock free fifo */ + +void +lff_init (lff_t * lff, unsigned int size, size_t object_size) +{ + lff->size = size; + lff->object_size = object_size; + lff->read_index = 0; + lff->write_index = 0; + lff->data = g_malloc (object_size * size); +} + +lff_t * +lff_new (unsigned int size, size_t object_size) +{ + lff_t * lff; + + lff = g_malloc (sizeof (lff_t)); + + lff_init (lff, size, object_size); + + return lff; +} + +void +lff_free (lff_t * lff) +{ + g_free (lff->data); +} + +void +lff_destroy (lff_t * lff) +{ + lff_free (lff); + g_free (lff); +} + +/** read an element from the fifo into data. +returns 0 on success, non-zero if there were no elements to read */ +int lff_read (lff_t * lff, void * data) { + if (lff->read_index == lff->write_index) { + return -1; + } else { + memcpy (data, ((char *)lff->data) + (lff->read_index * lff->object_size), + lff->object_size); + lff->read_index++; + if (lff->read_index >= lff->size) { + lff->read_index = 0; + } + return 0; + } +} + +/** write an element from data to the fifo. +returns 0 on success, non-zero if there was no space */ +int lff_write (lff_t * lff, void * data) { + static unsigned int ri; + + /* got to read read_index only once for safety */ + ri = lff->read_index; + + /* lots of logic for when we're allowed to write to the fifo which basically + boils down to "don't write if we're one element behind the read index" */ + if ((ri > lff->write_index && ri - lff->write_index > 1) || + (lff->write_index >= ri && lff->write_index != lff->size - 1) || + (lff->write_index >= ri && lff->write_index == lff->size - 1 && ri != 0)) { + +/* if ((ri > lff->write_index && ri - lff->write_index > 1) || + (lff->write_index >= ri && (lff->write_index != lff->size - 1 || ri != 0))) { */ + + memcpy (((char *)lff->data) + (lff->write_index * lff->object_size), + data, lff->object_size); + + /* FIXME: is this safe? */ + lff->write_index++; + if (lff->write_index >= lff->size) { + lff->write_index = 0; + } + + return 0; + } else { + return -1; + } +} diff --git a/src/modules/jackrack/lock_free_fifo.h b/src/modules/jackrack/lock_free_fifo.h new file mode 100644 index 0000000..29abd3c --- /dev/null +++ b/src/modules/jackrack/lock_free_fifo.h @@ -0,0 +1,53 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JLH_LOCK_FREE_FIFO_H__ +#define __JLH_LOCK_FREE_FIFO_H__ + +/** lock free fifo ring buffer structure */ +typedef struct lock_free_fifo { + /** Size of the ringbuffer (in elements) */ + unsigned int size; + /** the memory containing the ringbuffer */ + void * data; + /** the size of an element */ + size_t object_size; + /** the current position of the reader */ + unsigned int read_index; + /** the current position of the writer */ + unsigned int write_index; +} lff_t; + +void lff_init (lff_t * lff, unsigned int size, size_t object_size); +void lff_free (lff_t * lff); + +lff_t * lff_new (unsigned int size, size_t object_size); +void lff_destroy (lff_t * lock_free_fifo); + +int lff_read (lff_t * lock_free_fifo, void * data); +int lff_write (lff_t * lock_free_fifo, void * data); + + +#endif /* __JLH_LOCK_FREE_FIFO_H__ */ diff --git a/src/modules/jackrack/plugin.c b/src/modules/jackrack/plugin.c new file mode 100644 index 0000000..6a483a8 --- /dev/null +++ b/src/modules/jackrack/plugin.c @@ -0,0 +1,596 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include + +#include "plugin.h" +#include "jack_rack.h" +#include "process.h" + +#define CONTROL_FIFO_SIZE 128 + + + +/* swap over the jack ports in two plugins */ +static void +plugin_swap_aux_ports (plugin_t * plugin, plugin_t * other) +{ + guint copy; + jack_port_t ** aux_ports_tmp; + + for (copy = 0; copy < plugin->copies; copy++) + { + aux_ports_tmp = other->holders[copy].aux_ports; + other->holders[copy].aux_ports = plugin->holders[copy].aux_ports; + plugin->holders[copy].aux_ports = aux_ports_tmp; + } +} + +/** connect up the ladspa instance's input buffers to the previous + plugin's audio memory. make sure to check that plugin->prev + exists. */ +void +plugin_connect_input_ports (plugin_t * plugin, LADSPA_Data ** inputs) +{ + gint copy; + unsigned long channel; + unsigned long rack_channel; + + if (!plugin || !inputs) + return; + + rack_channel = 0; + for (copy = 0; copy < plugin->copies; copy++) + { + for (channel = 0; channel < plugin->desc->channels; channel++) + { + plugin->descriptor-> + connect_port (plugin->holders[copy].instance, + plugin->desc->audio_input_port_indicies[channel], + inputs[rack_channel]); + rack_channel++; + } + } + + plugin->audio_input_memory = inputs; +} + +/** connect up a plugin's output ports to its own audio_output_memory output memory */ +void +plugin_connect_output_ports (plugin_t * plugin) +{ + gint copy; + unsigned long channel; + unsigned long rack_channel = 0; + + if (!plugin) + return; + + + for (copy = 0; copy < plugin->copies; copy++) + { + for (channel = 0; channel < plugin->desc->channels; channel++) + { + plugin->descriptor-> + connect_port (plugin->holders[copy].instance, + plugin->desc->audio_output_port_indicies[channel], + plugin->audio_output_memory[rack_channel]); + rack_channel++; + } + } +} + +void +process_add_plugin (process_info_t * procinfo, plugin_t * plugin) +{ + + /* sort out list pointers */ + plugin->next = NULL; + plugin->prev = procinfo->chain_end; + + if (procinfo->chain_end) + procinfo->chain_end->next = plugin; + else + procinfo->chain = plugin; + + procinfo->chain_end = plugin; + +} + + +/** remove a plugin from the chain */ +plugin_t * +process_remove_plugin (process_info_t * procinfo, plugin_t *plugin) +{ + /* sort out chain pointers */ + if (plugin->prev) + plugin->prev->next = plugin->next; + else + procinfo->chain = plugin->next; + + if (plugin->next) + plugin->next->prev = plugin->prev; + else + procinfo->chain_end = plugin->prev; + + /* sort out the aux ports */ + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + plugin_t * other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } + + return plugin; +} + +/** enable/disable a plugin */ +void +process_ablise_plugin (process_info_t * procinfo, plugin_t *plugin, gboolean enable) +{ + plugin->enabled = enable; +} + +/** enable/disable a plugin */ +void +process_ablise_plugin_wet_dry (process_info_t * procinfo, plugin_t *plugin, gboolean enable) +{ + plugin->wet_dry_enabled = enable; +} + +/** move a plugin up or down one place in the chain */ +void +process_move_plugin (process_info_t * procinfo, plugin_t *plugin, gint up) +{ + /* other plugins in the chain */ + plugin_t *pp = NULL, *p, *n, *nn = NULL; + + /* note that we should never recieve an illogical move request + ie, there will always be at least 1 plugin before for an up + request or 1 plugin after for a down request */ + + /* these are pointers to the plugins surrounding the specified one: + { pp, p, plugin, n, nn } which makes things much clearer than + tptr, tptr2 etc */ + p = plugin->prev; + if (p) pp = p->prev; + n = plugin->next; + if (n) nn = n->next; + + if (up) + { + if (!p) + return; + + if (pp) + pp->next = plugin; + else + procinfo->chain = plugin; + + p->next = n; + p->prev = plugin; + + plugin->prev = pp; + plugin->next = p; + + if (n) + n->prev = p; + else + procinfo->chain_end = p; + + } + else + { + if (!n) + return; + + if (p) + p->next = n; + else + procinfo->chain = n; + + n->prev = p; + n->next = plugin; + + plugin->prev = n; + plugin->next = nn; + + if (nn) + nn->prev = plugin; + else + procinfo->chain_end = plugin; + } + + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + plugin_t * other; + other = up ? plugin->next : plugin->prev; + + /* swap around the jack ports */ + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } +} + +/** exchange an existing plugin for a newly created one */ +plugin_t * +process_change_plugin (process_info_t * procinfo, + plugin_t *plugin, plugin_t * new_plugin) +{ + new_plugin->next = plugin->next; + new_plugin->prev = plugin->prev; + + if (plugin->prev) + plugin->prev->next = new_plugin; + else + procinfo->chain = new_plugin; + + if (plugin->next) + plugin->next->prev = new_plugin; + else + procinfo->chain_end = new_plugin; + + /* sort out the aux ports */ + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + plugin_t * other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } + + return plugin; +} + + +/****************************************** + ************* non RT stuff *************** + ******************************************/ + + +static int +plugin_open_plugin (plugin_desc_t * desc, + void ** dl_handle_ptr, + const LADSPA_Descriptor ** descriptor_ptr) +{ + void * dl_handle; + const char * dlerr; + LADSPA_Descriptor_Function get_descriptor; + + /* open the object file */ + dl_handle = dlopen (desc->object_file, RTLD_NOW|RTLD_GLOBAL); + if (!dl_handle) + { + fprintf (stderr, "%s: error opening shared object file '%s': %s\n", + __FUNCTION__, desc->object_file, dlerror()); + return 1; + } + + + /* get the get_descriptor function */ + dlerror (); /* clear the error report */ + + get_descriptor = (LADSPA_Descriptor_Function) + dlsym (dl_handle, "ladspa_descriptor"); + + dlerr = dlerror(); + if (dlerr) + { + fprintf (stderr, "%s: error finding descriptor symbol in object file '%s': %s\n", + __FUNCTION__, desc->object_file, dlerr); + dlclose (dl_handle); + return 1; + } + + *descriptor_ptr = get_descriptor (desc->index); + *dl_handle_ptr = dl_handle; + + return 0; +} + +static int +plugin_instantiate (const LADSPA_Descriptor * descriptor, + unsigned long plugin_index, + gint copies, + LADSPA_Handle * instances) +{ + gint i; + + for (i = 0; i < copies; i++) + { + instances[i] = descriptor->instantiate (descriptor, sample_rate); + + if (!instances[i]) + { + unsigned long d; + + for (d = 0; d < i; d++) + descriptor->cleanup (instances[d]); + + return 1; + } + } + + return 0; +} + +static void +plugin_create_aux_ports (plugin_t * plugin, guint copy, jack_rack_t * jack_rack) +{ + plugin_desc_t * desc; +// plugin_slot_t * slot; + unsigned long aux_channel = 1; + unsigned long plugin_index = 1; + unsigned long i; + char port_name[64]; + char * plugin_name; + char * ptr; +// GList * list; + ladspa_holder_t * holder; + + desc = plugin->desc; + holder = plugin->holders + copy; + + holder->aux_ports = g_malloc (sizeof (jack_port_t *) * desc->aux_channels); + + /* make the plugin name jack worthy */ + ptr = plugin_name = g_strndup (plugin->desc->name, 7); + while (*ptr != '\0') + { + if (*ptr == ' ') + *ptr = '_'; + else + *ptr = tolower (*ptr); + + ptr++; + } + +/* + for (list = jack_rack->slots; list; list = g_list_next (list)) + { + slot = (plugin_slot_t *) list->data; + + if (slot->plugin->desc->id == plugin->desc->id) + plugin_index++; + } +*/ + + for (i = 0; i < desc->aux_channels; i++, aux_channel++) + { + sprintf (port_name, "%s_%ld-%d_%c%ld", + plugin_name, + plugin_index, + copy + 1, + desc->aux_are_input ? 'i' : 'o', + aux_channel); + + holder->aux_ports[i] = + jack_port_register (jack_rack->procinfo->jack_client, + port_name, + JACK_DEFAULT_AUDIO_TYPE, + desc->aux_are_input ? JackPortIsInput : JackPortIsOutput, + 0); + + if (!holder->aux_ports[i]) + { + fprintf (stderr, "Could not register jack port '%s'; aborting\n", port_name); + abort (); + } + } + + g_free (plugin_name); +} + +static LADSPA_Data unused_control_port_output; + +static void +plugin_init_holder (plugin_t * plugin, + guint copy, + LADSPA_Handle instance, + jack_rack_t * jack_rack) +{ + unsigned long i; + plugin_desc_t * desc; + ladspa_holder_t * holder; + + desc = plugin->desc; + holder = plugin->holders + copy; + + holder->instance = instance; + + if (desc->control_port_count > 0) + { + holder->ui_control_fifos = g_malloc (sizeof (lff_t) * desc->control_port_count); + holder->control_memory = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + } + else + { + holder->ui_control_fifos = NULL; + holder->control_memory = NULL; + } + + for (i = 0; i < desc->control_port_count; i++) + { + lff_init (holder->ui_control_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); + holder->control_memory[i] = + plugin_desc_get_default_control_value (desc, desc->control_port_indicies[i], sample_rate); + + plugin->descriptor-> + connect_port (instance, desc->control_port_indicies[i], holder->control_memory + i); + } + + for (i = 0; i < desc->port_count; i++) + { + if (!LADSPA_IS_PORT_CONTROL (desc->port_descriptors[i])) + continue; + + if (LADSPA_IS_PORT_OUTPUT (desc->port_descriptors[i])) + plugin->descriptor-> connect_port (instance, i, &unused_control_port_output); + } + + if (jack_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) + plugin_create_aux_ports (plugin, copy, jack_rack); + + if (plugin->descriptor->activate) + plugin->descriptor->activate (instance); +} + + +plugin_t * +plugin_new (plugin_desc_t * desc, jack_rack_t * jack_rack) +{ + void * dl_handle; + const LADSPA_Descriptor * descriptor; + LADSPA_Handle * instances; + gint copies; + unsigned long i; + int err; + plugin_t * plugin; + + /* open the plugin */ + err = plugin_open_plugin (desc, &dl_handle, &descriptor); + if (err) + return NULL; + + /* create the instances */ + copies = plugin_desc_get_copies (desc, jack_rack->channels); + instances = g_malloc (sizeof (LADSPA_Handle) * copies); + + err = plugin_instantiate (descriptor, desc->index, copies, instances); + if (err) + { + g_free (instances); + dlclose (dl_handle); + return NULL; + } + + + plugin = g_malloc (sizeof (plugin_t)); + + plugin->descriptor = descriptor; + plugin->dl_handle = dl_handle; + plugin->desc = desc; + plugin->copies = copies; + plugin->enabled = FALSE; + plugin->next = NULL; + plugin->prev = NULL; + plugin->wet_dry_enabled = FALSE; + plugin->jack_rack = jack_rack; + + /* create audio memory and wet/dry stuff */ + plugin->audio_output_memory = g_malloc (sizeof (LADSPA_Data *) * jack_rack->channels); + plugin->wet_dry_fifos = g_malloc (sizeof (lff_t) * jack_rack->channels); + plugin->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * jack_rack->channels); + + for (i = 0; i < jack_rack->channels; i++) + { + plugin->audio_output_memory[i] = g_malloc (sizeof (LADSPA_Data) * buffer_size); + lff_init (plugin->wet_dry_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); + plugin->wet_dry_values[i] = 1.0; + } + + /* create holders and fill them out */ + plugin->holders = g_malloc (sizeof (ladspa_holder_t) * copies); + for (i = 0; i < copies; i++) + plugin_init_holder (plugin, i, instances[i], jack_rack); + + return plugin; +} + + +void +plugin_destroy (plugin_t * plugin) +{ + unsigned long i, j; + int err; + + /* destroy holders */ + for (i = 0; i < plugin->copies; i++) + { + if (plugin->descriptor->deactivate) + plugin->descriptor->deactivate (plugin->holders[i].instance); + +/* if (plugin->descriptor->cleanup) + plugin->descriptor->cleanup (plugin->holders[i].instance); */ + + if (plugin->desc->control_port_count > 0) + { + for (j = 0; j < plugin->desc->control_port_count; j++) + { + lff_free (plugin->holders[i].ui_control_fifos + j); + } + g_free (plugin->holders[i].ui_control_fifos); + g_free (plugin->holders[i].control_memory); + } + + /* aux ports */ + if (plugin->jack_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) + { + for (j = 0; j < plugin->desc->aux_channels; j++) + { + err = jack_port_unregister (plugin->jack_rack->procinfo->jack_client, + plugin->holders[i].aux_ports[j]); + + if (err) + fprintf (stderr, "%s: could not unregister jack port\n", __FUNCTION__); + } + + g_free (plugin->holders[i].aux_ports); + } + } + + g_free (plugin->holders); + + for (i = 0; i < plugin->jack_rack->channels; i++) + { + g_free (plugin->audio_output_memory[i]); + lff_free (plugin->wet_dry_fifos + i); + } + + g_free (plugin->audio_output_memory); + g_free (plugin->wet_dry_fifos); + g_free (plugin->wet_dry_values); + + err = dlclose (plugin->dl_handle); + if (err) + { + fprintf (stderr, "%s: error closing shared object '%s': %s\n", + __FUNCTION__, plugin->desc->object_file, dlerror ()); + } + + g_free (plugin); +} + + +/* EOF */ diff --git a/src/modules/jackrack/plugin.h b/src/modules/jackrack/plugin.h new file mode 100644 index 0000000..576b803 --- /dev/null +++ b/src/modules/jackrack/plugin.h @@ -0,0 +1,88 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JR_PLUGIN_H__ +#define __JR_PLUGIN_H__ + +#include +#include +#include + +#include "process.h" +#include "plugin_desc.h" + +typedef struct _ladspa_holder ladspa_holder_t; +typedef struct _plugin plugin_t; + +struct _ladspa_holder +{ + LADSPA_Handle instance; + lff_t * ui_control_fifos; + LADSPA_Data * control_memory; + + jack_port_t ** aux_ports; +}; + +struct _plugin +{ + plugin_desc_t * desc; + gint enabled; + + gint copies; + ladspa_holder_t * holders; + LADSPA_Data ** audio_input_memory; + LADSPA_Data ** audio_output_memory; + + gboolean wet_dry_enabled; + /* 1.0 = all wet, 0.0 = all dry, 0.5 = 50% wet/50% dry */ + LADSPA_Data * wet_dry_values; + lff_t * wet_dry_fifos; + + plugin_t * next; + plugin_t * prev; + + const LADSPA_Descriptor * descriptor; + void * dl_handle; + struct _jack_rack * jack_rack; + +}; + +void process_add_plugin (process_info_t *, plugin_t *plugin); +plugin_t * process_remove_plugin (process_info_t *, plugin_t *plugin); +void process_ablise_plugin (process_info_t *, plugin_t *plugin, gboolean able); +void process_ablise_plugin_wet_dry (process_info_t *, plugin_t *plugin, gboolean enable); +void process_move_plugin (process_info_t *, plugin_t *plugin, gint up); +plugin_t * process_change_plugin (process_info_t *, plugin_t *plugin, plugin_t * new_plugin); + +struct _jack_rack; +struct _ui; + +plugin_t * plugin_new (plugin_desc_t * plugin_desc, struct _jack_rack * jack_rack); +void plugin_destroy (plugin_t * plugin); + +void plugin_connect_input_ports (plugin_t * plugin, LADSPA_Data ** inputs); +void plugin_connect_output_ports (plugin_t * plugin); + +#endif /* __JR_PLUGIN_H__ */ diff --git a/src/modules/jackrack/plugin_desc.c b/src/modules/jackrack/plugin_desc.c new file mode 100644 index 0000000..b325989 --- /dev/null +++ b/src/modules/jackrack/plugin_desc.c @@ -0,0 +1,417 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "plugin_desc.h" +#include "plugin.h" + +#define set_string_property(property, value) \ + \ + if (property) \ + g_free (property); \ + \ + if (value) \ + (property) = g_strdup (value); \ + else \ + (property) = NULL; + + +void +plugin_desc_set_ports (plugin_desc_t * pd, + unsigned long port_count, + const LADSPA_PortDescriptor * port_descriptors, + const LADSPA_PortRangeHint * port_range_hints, + const char * const * port_names); + + + +static void +plugin_desc_init (plugin_desc_t * pd) +{ + pd->object_file = NULL; + pd->id = 0; + pd->name = NULL; + pd->properties = 0; + pd->channels = 0; + pd->port_count = 0; + pd->port_descriptors = NULL; + pd->port_range_hints = NULL; + pd->audio_input_port_indicies = NULL; + pd->audio_output_port_indicies = NULL; + pd->audio_aux_port_indicies = NULL; + pd->control_port_count = 0; + pd->control_port_indicies = NULL; + pd->aux_channels = 0; + pd->aux_are_input = TRUE; +} + +static void +plugin_desc_free_ports (plugin_desc_t * pd) +{ + if (pd->port_count) + { + g_free (pd->port_descriptors); + g_free (pd->port_range_hints); + pd->port_descriptors = NULL; + pd->port_range_hints = NULL; + pd->port_count = 0; + } +} + +static void +plugin_desc_free (plugin_desc_t * pd) +{ + plugin_desc_set_object_file (pd, NULL); + plugin_desc_set_name (pd, NULL); + plugin_desc_free_ports (pd); +} + +plugin_desc_t * +plugin_desc_new () +{ + plugin_desc_t * pd; + pd = g_malloc (sizeof (plugin_desc_t)); + plugin_desc_init (pd); + return pd; +} + +plugin_desc_t * +plugin_desc_new_with_descriptor (const char * object_file, + unsigned long index, + const LADSPA_Descriptor * descriptor) +{ + plugin_desc_t * pd; + pd = plugin_desc_new (); + + plugin_desc_set_object_file (pd, object_file); + plugin_desc_set_index (pd, index); + plugin_desc_set_id (pd, descriptor->UniqueID); + plugin_desc_set_name (pd, descriptor->Name); + plugin_desc_set_properties (pd, descriptor->Properties); + plugin_desc_set_ports (pd, + descriptor->PortCount, + descriptor->PortDescriptors, + descriptor->PortRangeHints, + descriptor->PortNames); + + pd->rt = LADSPA_IS_HARD_RT_CAPABLE(pd->properties) ? TRUE : FALSE; + + return pd; +} + +void +plugin_desc_destroy (plugin_desc_t * pd) +{ + plugin_desc_free (pd); + g_free (pd); +} + +void +plugin_desc_set_object_file (plugin_desc_t * pd, const char * object_file) +{ + set_string_property (pd->object_file, object_file); +} + +void +plugin_desc_set_index (plugin_desc_t * pd, unsigned long index) +{ + pd->index = index; +} + + +void +plugin_desc_set_id (plugin_desc_t * pd, unsigned long id) +{ + pd->id = id; +} + +void +plugin_desc_set_name (plugin_desc_t * pd, const char * name) +{ + set_string_property (pd->name, name); +} + +void +plugin_desc_set_properties (plugin_desc_t * pd, LADSPA_Properties properties) +{ + pd->properties = properties; +} + +static void +plugin_desc_add_audio_port_index (unsigned long ** indicies, + unsigned long * current_port_count, + unsigned long index) +{ + (*current_port_count)++; + + if (*current_port_count == 0) + *indicies = g_malloc (sizeof (unsigned long) * *current_port_count); + else + *indicies = g_realloc (*indicies, sizeof (unsigned long) * *current_port_count); + + (*indicies)[*current_port_count - 1] = index; +} + +static void +plugin_desc_set_port_counts (plugin_desc_t * pd) +{ + unsigned long i; + unsigned long icount = 0; + unsigned long ocount = 0; + + for (i = 0; i < pd->port_count; i++) + { + if (LADSPA_IS_PORT_AUDIO (pd->port_descriptors[i])) + { + if (LADSPA_IS_PORT_INPUT (pd->port_descriptors[i])) + plugin_desc_add_audio_port_index (&pd->audio_input_port_indicies, &icount, i); + else + plugin_desc_add_audio_port_index (&pd->audio_output_port_indicies, &ocount, i); + } + else + { + if (LADSPA_IS_PORT_OUTPUT (pd->port_descriptors[i])) + continue; + + pd->control_port_count++; + if (pd->control_port_count == 0) + pd->control_port_indicies = g_malloc (sizeof (unsigned long) * pd->control_port_count); + else + pd->control_port_indicies = g_realloc (pd->control_port_indicies, + sizeof (unsigned long) * pd->control_port_count); + + pd->control_port_indicies[pd->control_port_count - 1] = i; + } + } + + if (icount == ocount) + pd->channels = icount; + else + { /* deal with auxilliary ports */ + unsigned long ** port_indicies; + unsigned long port_count; + unsigned long i, j; + + if (icount > ocount) + { + pd->channels = ocount; + pd->aux_channels = icount - ocount; + pd->aux_are_input = TRUE; + port_indicies = &pd->audio_input_port_indicies; + port_count = icount; + } + else + { + pd->channels = icount; + pd->aux_channels = ocount - icount; + pd->aux_are_input = FALSE; + port_indicies = &pd->audio_output_port_indicies; + port_count = ocount; + } + + /* allocate indicies */ + pd->audio_aux_port_indicies = g_malloc (sizeof (unsigned long) * pd->aux_channels); + + /* copy indicies */ + for (i = pd->channels, j = 0; i < port_count; i++, j++) + pd->audio_aux_port_indicies[j] = (*port_indicies)[i]; + + /* shrink the main indicies to only have channels indicies */ + *port_indicies = g_realloc (*port_indicies, sizeof (unsigned long) * pd->channels); + } +} + +void +plugin_desc_set_ports (plugin_desc_t * pd, + unsigned long port_count, + const LADSPA_PortDescriptor * port_descriptors, + const LADSPA_PortRangeHint * port_range_hints, + const char * const * port_names) +{ + unsigned long i; + + plugin_desc_free_ports (pd); + + if (!port_count) + return; + + pd->port_count = port_count; + pd->port_descriptors = g_malloc (sizeof (LADSPA_PortDescriptor) * port_count); + pd->port_range_hints = g_malloc (sizeof (LADSPA_PortRangeHint) * port_count); + pd->port_names = g_malloc (sizeof (char *) * port_count); + + memcpy (pd->port_descriptors, port_descriptors, sizeof (LADSPA_PortDescriptor) * port_count); + memcpy (pd->port_range_hints, port_range_hints, sizeof (LADSPA_PortRangeHint) * port_count); + + for (i = 0; i < port_count; i++) + pd->port_names[i] = g_strdup (port_names[i]); + + plugin_desc_set_port_counts (pd); +} + + +LADSPA_Data +plugin_desc_get_default_control_value (plugin_desc_t * pd, unsigned long port_index, guint32 sample_rate) +{ + LADSPA_Data upper, lower; + LADSPA_PortRangeHintDescriptor hint_descriptor; + + hint_descriptor = pd->port_range_hints[port_index].HintDescriptor; + + /* set upper and lower, possibly adjusted to the sample rate */ + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + upper = pd->port_range_hints[port_index].UpperBound * (LADSPA_Data) sample_rate; + lower = pd->port_range_hints[port_index].LowerBound * (LADSPA_Data) sample_rate; + } else { + upper = pd->port_range_hints[port_index].UpperBound; + lower = pd->port_range_hints[port_index].LowerBound; + } + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) + { + if (lower < FLT_EPSILON) + lower = FLT_EPSILON; + } + + + if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) { + + if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) { + + return lower; + + } else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) { + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + return exp(log(lower) * 0.75 + log(upper) * 0.25); + } else { + return lower * 0.75 + upper * 0.25; + } + + } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) { + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + return exp(log(lower) * 0.5 + log(upper) * 0.5); + } else { + return lower * 0.5 + upper * 0.5; + } + + } else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) { + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + return exp(log(lower) * 0.25 + log(upper) * 0.75); + } else { + return lower * 0.25 + upper * 0.75; + } + + } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) { + + return upper; + + } else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) { + + return 0.0; + + } else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) { + + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + return (LADSPA_Data) sample_rate; + } else { + return 1.0; + } + + } else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) { + + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + return 100.0 * (LADSPA_Data) sample_rate; + } else { + return 100.0; + } + + } else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) { + + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + return 440.0 * (LADSPA_Data) sample_rate; + } else { + return 440.0; + } + + } + + } else { /* try and find a reasonable default */ + + if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { + return lower; + } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { + return upper; + } + } + + return 0.0; +} + +LADSPA_Data +plugin_desc_change_control_value (plugin_desc_t * pd, + unsigned long control_index, + LADSPA_Data value, + guint32 old_sample_rate, + guint32 new_sample_rate) +{ + + if (LADSPA_IS_HINT_SAMPLE_RATE (pd->port_range_hints[control_index].HintDescriptor)) + { + LADSPA_Data old_sr, new_sr; + + old_sr = (LADSPA_Data) old_sample_rate; + new_sr = (LADSPA_Data) new_sample_rate; + + value /= old_sr; + value *= new_sr; + } + + return value; +} + +gint +plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels) +{ + gint copies = 1; + + if (pd->channels > rack_channels) + return 0; + + while (pd->channels * copies < rack_channels) + copies++; + + if (pd->channels * copies > rack_channels) + return 0; + + return copies; +} + +/* EOF */ diff --git a/src/modules/jackrack/plugin_desc.h b/src/modules/jackrack/plugin_desc.h new file mode 100644 index 0000000..d6b5ca3 --- /dev/null +++ b/src/modules/jackrack/plugin_desc.h @@ -0,0 +1,81 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JR_PLUGIN_DESC_H__ +#define __JR_PLUGIN_DESC_H__ + +#include +#include + +typedef struct _plugin_desc plugin_desc_t; + +struct _plugin_desc +{ + char * object_file; + unsigned long index; + unsigned long id; + char * name; + LADSPA_Properties properties; + gboolean rt; + + unsigned long channels; + + gboolean aux_are_input; + unsigned long aux_channels; + + unsigned long port_count; + LADSPA_PortDescriptor * port_descriptors; + LADSPA_PortRangeHint * port_range_hints; + char ** port_names; + + unsigned long * audio_input_port_indicies; + unsigned long * audio_output_port_indicies; + + unsigned long * audio_aux_port_indicies; + + unsigned long control_port_count; + unsigned long * control_port_indicies; +}; + +plugin_desc_t * plugin_desc_new (); +plugin_desc_t * plugin_desc_new_with_descriptor (const char * object_file, + unsigned long index, + const LADSPA_Descriptor * descriptor); +void plugin_desc_destroy (); + +void plugin_desc_set_object_file (plugin_desc_t * pd, const char * object_file); +void plugin_desc_set_index (plugin_desc_t * pd, unsigned long index); +void plugin_desc_set_id (plugin_desc_t * pd, unsigned long id); +void plugin_desc_set_name (plugin_desc_t * pd, const char * name); +void plugin_desc_set_properties (plugin_desc_t * pd, LADSPA_Properties properties); + +struct _plugin * plugin_desc_instantiate (plugin_desc_t * pd); + +LADSPA_Data plugin_desc_get_default_control_value (plugin_desc_t * pd, unsigned long port_index, guint32 sample_rate); +LADSPA_Data plugin_desc_change_control_value (plugin_desc_t *, unsigned long, LADSPA_Data, guint32, guint32); + +gint plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels); + +#endif /* __JR_PLUGIN_DESC_H__ */ diff --git a/src/modules/jackrack/plugin_mgr.c b/src/modules/jackrack/plugin_mgr.c new file mode 100644 index 0000000..37d7510 --- /dev/null +++ b/src/modules/jackrack/plugin_mgr.c @@ -0,0 +1,321 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plugin_mgr.h" +#include "plugin_desc.h" + + +static gboolean +plugin_is_valid (const LADSPA_Descriptor * descriptor) +{ + unsigned long i; + unsigned long icount = 0; + unsigned long ocount = 0; + + for (i = 0; i < descriptor->PortCount; i++) + { + if (!LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[i])) + continue; + + if (LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[i])) + icount++; + else + ocount++; + } + + if (icount == 0 || ocount == 0) + return FALSE; + + return TRUE; +} + +static void +plugin_mgr_get_object_file_plugins (plugin_mgr_t * plugin_mgr, const char * filename) +{ + const char * dlerr; + void * dl_handle; + LADSPA_Descriptor_Function get_descriptor; + const LADSPA_Descriptor * descriptor; + unsigned long plugin_index; + plugin_desc_t * desc, * other_desc = NULL; + GSList * list; + gboolean exists; + int err; + + /* open the object file */ + dl_handle = dlopen (filename, RTLD_NOW|RTLD_GLOBAL); + if (!dl_handle) + { + fprintf (stderr, "%s: error opening shared object file '%s': %s\n", + __FUNCTION__, filename, dlerror()); + return; + } + + + /* get the get_descriptor function */ + dlerror (); /* clear the error report */ + + get_descriptor = (LADSPA_Descriptor_Function) + dlsym (dl_handle, "ladspa_descriptor"); + + dlerr = dlerror(); + if (dlerr) { + fprintf (stderr, "%s: error finding ladspa_descriptor symbol in object file '%s': %s\n", + __FUNCTION__, filename, dlerr); + dlclose (dl_handle); + return; + } + + plugin_index = 0; + while ( (descriptor = get_descriptor (plugin_index)) ) + { + if (!plugin_is_valid (descriptor)) + { + plugin_index++; + continue; + } + + + /* check it doesn't already exist */ + exists = FALSE; + for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) + { + other_desc = (plugin_desc_t *) list->data; + + if (other_desc->id == descriptor->UniqueID) + { + exists = TRUE; + break; + } + } + + if (exists) + { + printf ("Plugin %ld exists in both '%s' and '%s'; using version in '%s'\n", + descriptor->UniqueID, other_desc->object_file, filename, other_desc->object_file); + plugin_index++; + continue; + } + + + desc = plugin_desc_new_with_descriptor (filename, plugin_index, descriptor); + plugin_mgr->all_plugins = g_slist_append (plugin_mgr->all_plugins, desc); + plugin_index++; + plugin_mgr->plugin_count++; + + /* print in the splash screen */ + /* printf ("Loaded plugin '%s'\n", desc->name); */ + } + + err = dlclose (dl_handle); + if (err) + { + fprintf (stderr, "%s: error closing object file '%s': %s\n", + __FUNCTION__, filename, dlerror ()); + } +} + +static void +plugin_mgr_get_dir_plugins (plugin_mgr_t * plugin_mgr, const char * dir) +{ + DIR * dir_stream; + struct dirent * dir_entry; + char * file_name; + int err; + size_t dirlen; + + dir_stream = opendir (dir); + if (!dir_stream) + { +/* fprintf (stderr, "%s: error opening directory '%s': %s\n", + __FUNCTION__, dir, strerror (errno)); */ + return; + } + + dirlen = strlen (dir); + + while ( (dir_entry = readdir (dir_stream)) ) + { + struct stat info; + + if (strcmp (dir_entry->d_name, ".") == 0 || + strcmp (dir_entry->d_name, "..") == 0) + continue; + + file_name = g_malloc (dirlen + 1 + strlen (dir_entry->d_name) + 1); + + strcpy (file_name, dir); + if (file_name[dirlen - 1] == '/') + strcpy (file_name + dirlen, dir_entry->d_name); + else + { + file_name[dirlen] = '/'; + strcpy (file_name + dirlen + 1, dir_entry->d_name); + } + + stat (file_name, &info); + if (S_ISDIR (info.st_mode)) + plugin_mgr_get_dir_plugins (plugin_mgr, file_name); + else + plugin_mgr_get_object_file_plugins (plugin_mgr, file_name); + + g_free (file_name); + } + + err = closedir (dir_stream); + if (err) + fprintf (stderr, "%s: error closing directory '%s': %s\n", + __FUNCTION__, dir, strerror (errno)); +} + +static void +plugin_mgr_get_path_plugins (plugin_mgr_t * plugin_mgr) +{ + char * ladspa_path, * dir; + + ladspa_path = g_strdup (getenv ("LADSPA_PATH")); + if (!ladspa_path) + ladspa_path = g_strdup ("/usr/local/lib/ladspa:/usr/lib/ladspa"); + + dir = strtok (ladspa_path, ":"); + do + plugin_mgr_get_dir_plugins (plugin_mgr, dir); + while ((dir = strtok (NULL, ":"))); + + g_free (ladspa_path); +} + +static gint +plugin_mgr_sort (gconstpointer a, gconstpointer b) +{ + const plugin_desc_t * da; + const plugin_desc_t * db; + da = (const plugin_desc_t *) a; + db = (const plugin_desc_t *) b; + + return strcasecmp (da->name, db->name); +} + +plugin_mgr_t * +plugin_mgr_new () +{ + plugin_mgr_t * pm; + + pm = g_malloc (sizeof (plugin_mgr_t)); + pm->all_plugins = NULL; + pm->plugins = NULL; + pm->plugin_count = 0; + + plugin_mgr_get_path_plugins (pm); + + if (!pm->all_plugins) + { + fprintf (stderr, "No LADSPA plugins were found!\n\nCheck your LADSPA_PATH environment variable.\n"); + abort (); + } + + pm->all_plugins = g_slist_sort (pm->all_plugins, plugin_mgr_sort); + + return pm; +} + +void +plugin_mgr_destroy (plugin_mgr_t * plugin_mgr) +{ + GSList * list; + + for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) + plugin_desc_destroy ((plugin_desc_t *) list->data); + + g_slist_free (plugin_mgr->plugins); + g_slist_free (plugin_mgr->all_plugins); + free (plugin_mgr); +} + + +void +plugin_mgr_set_plugins (plugin_mgr_t * plugin_mgr, unsigned long rack_channels) +{ + GSList * list; + plugin_desc_t * desc; + + /* clear the current plugins */ + g_slist_free (plugin_mgr->plugins); + plugin_mgr->plugins = NULL; + + for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) + { + desc = (plugin_desc_t *) list->data; + + if (plugin_desc_get_copies (desc, rack_channels) != 0) + plugin_mgr->plugins = g_slist_append (plugin_mgr->plugins, desc); + } +} + +static plugin_desc_t * +plugin_mgr_find_desc (plugin_mgr_t * plugin_mgr, GSList * plugins, unsigned long id) +{ + GSList * list; + plugin_desc_t * desc; + + for (list = plugins; list; list = g_slist_next (list)) + { + desc = (plugin_desc_t *) list->data; + + if (desc->id == id) + return desc; + } + + return NULL; +} + +plugin_desc_t * +plugin_mgr_get_desc (plugin_mgr_t * plugin_mgr, unsigned long id) +{ + return plugin_mgr_find_desc (plugin_mgr, plugin_mgr->plugins, id); +} + +plugin_desc_t * +plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id) +{ + return plugin_mgr_find_desc (plugin_mgr, plugin_mgr->all_plugins, id); +} + + +/* EOF */ diff --git a/src/modules/jackrack/plugin_mgr.h b/src/modules/jackrack/plugin_mgr.h new file mode 100644 index 0000000..bc70237 --- /dev/null +++ b/src/modules/jackrack/plugin_mgr.h @@ -0,0 +1,53 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JR_PLUGIN_MANAGER_H__ +#define __JR_PLUGIN_MANAGER_H__ + +#include + +#include "plugin_desc.h" + +typedef struct _plugin_mgr plugin_mgr_t; + +struct _plugin_mgr +{ + GSList * all_plugins; + + GSList * plugins; + unsigned long plugin_count; +}; + +struct _ui; + +plugin_mgr_t * plugin_mgr_new (); +void plugin_mgr_destroy (plugin_mgr_t * plugin_mgr); + +void plugin_mgr_set_plugins (plugin_mgr_t * plugin_mgr, unsigned long rack_channels); + +plugin_desc_t * plugin_mgr_get_desc (plugin_mgr_t * plugin_mgr, unsigned long id); +plugin_desc_t * plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id); + +#endif /* __JR_PLUGIN_MANAGER_H__ */ diff --git a/src/modules/jackrack/plugin_settings.c b/src/modules/jackrack/plugin_settings.c new file mode 100644 index 0000000..7284b80 --- /dev/null +++ b/src/modules/jackrack/plugin_settings.c @@ -0,0 +1,395 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define _GNU_SOURCE + +#include + +#include "plugin_settings.h" + + +static void +settings_set_to_default (settings_t * settings, guint32 sample_rate) +{ + unsigned long control; + guint copy; + LADSPA_Data value; + + for (control = 0; control < settings->desc->control_port_count; control++) + { + value = plugin_desc_get_default_control_value (settings->desc, control, sample_rate); + + for (copy = 0; copy < settings->copies; copy++) + { + settings->control_values[copy][control] = value; + } + + settings->locks[control] = TRUE; + } +} + +settings_t * +settings_new (plugin_desc_t * desc, unsigned long channels, guint32 sample_rate) +{ + settings_t * settings; + unsigned long channel; + guint copies; + + settings = g_malloc (sizeof (settings_t)); + copies = plugin_desc_get_copies (desc, channels); + + settings->sample_rate = sample_rate; + settings->desc = desc; + settings->copies = copies; + settings->channels = channels; + settings->lock_all = TRUE; + settings->enabled = FALSE; + settings->locks = NULL; + settings->control_values = NULL; + settings->wet_dry_enabled = FALSE; + settings->wet_dry_locked = TRUE; + + /* control settings */ + if (desc->control_port_count > 0) + { + guint copy; + + settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); + + settings->control_values = g_malloc (sizeof (LADSPA_Data *) * copies); + for (copy = 0; copy < copies; copy++) + { + settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + } + + settings_set_to_default (settings, sample_rate); + } + + /* wet/dry settings */ + settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * channels); + for (channel = 0; channel < channels; channel++) + settings->wet_dry_values[channel] = 1.0; + + return settings; +} + +settings_t * +settings_dup (settings_t * other) +{ + settings_t * settings; + plugin_desc_t * desc; + unsigned long channel; + + settings = g_malloc (sizeof (settings_t)); + + settings->sample_rate = other->sample_rate; + settings->desc = other->desc; + settings->copies = settings_get_copies (other); + settings->channels = settings_get_channels (other); + settings->wet_dry_enabled = settings_get_wet_dry_enabled (other); + settings->wet_dry_locked = settings_get_wet_dry_locked (other); + settings->lock_all = settings_get_lock_all (other); + settings->enabled = settings_get_enabled (other); + settings->locks = NULL; + settings->control_values = NULL; + + desc = other->desc; + + if (desc->control_port_count > 0) + { + guint copy; + unsigned long control; + + settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); + for (control = 0; control < desc->control_port_count; control++) + settings_set_lock (settings, control, settings_get_lock (other, control)); + + settings->control_values = g_malloc (sizeof (LADSPA_Data *) * settings->copies); + for (copy = 0; copy < settings->copies; copy++) + { + settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + + for (control = 0; control < desc->control_port_count; control++) + { + settings->control_values[copy][control] = settings_get_control_value (other, copy, control); + } + } + } + + settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * settings->channels); + for (channel = 0; channel < settings->channels; channel++) + settings->wet_dry_values[channel] = settings_get_wet_dry_value (other, channel); + + return settings; +} + +void +settings_destroy (settings_t * settings) +{ + if (settings->desc->control_port_count > 0) + { + guint i; + for (i = 0; i < settings->copies; i++) + g_free (settings->control_values[i]); + + g_free (settings->control_values); + g_free (settings->locks); + } + + g_free (settings->wet_dry_values); + + g_free (settings); +} + +static void +settings_set_copies (settings_t * settings, guint copies) +{ + guint copy; + guint last_copy; + unsigned long control; + + if (copies <= settings->copies) + return; + + last_copy = settings->copies - 1; + + settings->control_values = g_realloc (settings->control_values, + sizeof (LADSPA_Data *) * copies); + + /* copy over the last settings to the new copies */ + for (copy = settings->copies; copy < copies; copy++) + { + for (control = 0; control < settings->desc->control_port_count; control++) + { + settings->control_values[copy][control] = + settings->control_values[last_copy][control]; + } + } + + settings->copies = copies; +} + +static void +settings_set_channels (settings_t * settings, unsigned long channels) +{ + unsigned long channel; + LADSPA_Data last_value; + + if (channels <= settings->channels) + return; + + settings->wet_dry_values = g_realloc (settings->wet_dry_values, sizeof (LADSPA_Data) * channels); + + last_value = settings->wet_dry_values[settings->channels - 1]; + + for (channel = settings->channels; channel < channels; channel++) + settings->wet_dry_values[channel] = last_value; + + settings->channels = channels; +} + +void +settings_set_sample_rate (settings_t * settings, guint32 sample_rate) +{ + LADSPA_Data old_sample_rate; + LADSPA_Data new_sample_rate; + + g_return_if_fail (settings != NULL); + + if (settings->sample_rate == sample_rate) + return; + + if (settings->desc->control_port_count > 0) + { + unsigned long control; + guint copy; + + new_sample_rate = (LADSPA_Data) sample_rate; + old_sample_rate = (LADSPA_Data) settings->sample_rate; + + for (control = 0; control < settings->desc->control_port_count; control++) + { + for (copy = 0; copy < settings->copies; copy++) + { + if (LADSPA_IS_HINT_SAMPLE_RATE (settings->desc->port_range_hints[control].HintDescriptor)) + { + settings->control_values[copy][control] = + (settings->control_values[copy][control] / old_sample_rate) * new_sample_rate; + } + } + } + } + + settings->sample_rate = sample_rate; +} + +void +settings_set_control_value (settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value) +{ + g_return_if_fail (settings != NULL); + g_return_if_fail (control_index < settings->desc->control_port_count); + + if (copy >= settings->copies) + settings_set_copies (settings, copy + 1); + + settings->control_values[copy][control_index] = value; +} + +void +settings_set_lock (settings_t * settings, unsigned long control_index, gboolean locked) +{ + g_return_if_fail (settings != NULL); + g_return_if_fail (control_index < settings->desc->control_port_count); + + settings->locks[control_index] = locked; +} + +void +settings_set_lock_all (settings_t * settings, gboolean lock_all) +{ + g_return_if_fail (settings != NULL); + + settings->lock_all = lock_all; +} + +void +settings_set_enabled (settings_t * settings, gboolean enabled) +{ + g_return_if_fail (settings != NULL); + + settings->enabled = enabled; +} + +void +settings_set_wet_dry_enabled (settings_t * settings, gboolean enabled) +{ + g_return_if_fail (settings != NULL); + + settings->wet_dry_enabled = enabled; +} + +void +settings_set_wet_dry_locked (settings_t * settings, gboolean locked) +{ + g_return_if_fail (settings != NULL); + + settings->wet_dry_locked = locked; +} + +void +settings_set_wet_dry_value (settings_t * settings, unsigned long channel, LADSPA_Data value) +{ + g_return_if_fail (settings != NULL); + + if (channel >= settings->channels) + settings_set_channels (settings, channel + 1); + + settings->wet_dry_values[channel] = value; +} + + +LADSPA_Data +settings_get_control_value (settings_t * settings, guint copy, unsigned long control_index) +{ + g_return_val_if_fail (settings != NULL, NAN); + g_return_val_if_fail (control_index < settings->desc->control_port_count, NAN); + + if (copy >= settings->copies) + settings_set_copies (settings, copy - 1); + + return settings->control_values[copy][control_index]; +} + +gboolean +settings_get_lock (const settings_t * settings, unsigned long control_index) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->locks[control_index]; +} + +gboolean +settings_get_lock_all (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->lock_all; +} + +gboolean +settings_get_enabled (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->enabled; +} + +guint +settings_get_copies (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, 0); + + return settings->copies; +} + + +unsigned long +settings_get_channels (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, 0); + + return settings->channels; +} + +gboolean +settings_get_wet_dry_enabled (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->wet_dry_enabled; +} + +gboolean +settings_get_wet_dry_locked (const settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->wet_dry_locked; +} + +LADSPA_Data +settings_get_wet_dry_value (settings_t * settings, unsigned long channel) +{ + g_return_val_if_fail (settings != NULL, NAN); + + if (channel >= settings->channels) + settings_set_channels (settings, channel + 1); + + return settings->wet_dry_values[channel]; +} + + + +/* EOF */ diff --git a/src/modules/jackrack/plugin_settings.h b/src/modules/jackrack/plugin_settings.h new file mode 100644 index 0000000..852333b --- /dev/null +++ b/src/modules/jackrack/plugin_settings.h @@ -0,0 +1,76 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JR_PLUGIN_SETTINGS_H__ +#define __JR_PLUGIN_SETTINGS_H__ + +#include +#include + +#include "plugin_mgr.h" +#include "plugin_desc.h" + +typedef struct _settings settings_t; + +struct _settings +{ + guint32 sample_rate; + plugin_desc_t * desc; + guint copies; + LADSPA_Data ** control_values; + gboolean * locks; + gboolean lock_all; + gboolean enabled; + unsigned long channels; + gboolean wet_dry_enabled; + gboolean wet_dry_locked; + LADSPA_Data * wet_dry_values; +}; + +settings_t * settings_new (plugin_desc_t * desc, unsigned long channels, guint32 sample_rate); +settings_t * settings_dup (settings_t * settings); +void settings_destroy (settings_t * settings); + +void settings_set_control_value (settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value); +void settings_set_lock (settings_t * settings, unsigned long control_index, gboolean locked); +void settings_set_lock_all (settings_t * settings, gboolean lock_all); +void settings_set_enabled (settings_t * settings, gboolean enabled); +void settings_set_wet_dry_enabled (settings_t * settings, gboolean enabled); +void settings_set_wet_dry_locked (settings_t * settings, gboolean locked); +void settings_set_wet_dry_value (settings_t * settings, unsigned long channel, LADSPA_Data value); + +LADSPA_Data settings_get_control_value (settings_t * settings, guint copy, unsigned long control_index); +gboolean settings_get_lock (const settings_t * settings, unsigned long control_index); +gboolean settings_get_lock_all (const settings_t * settings); +gboolean settings_get_enabled (const settings_t * settings); +guint settings_get_copies (const settings_t * settings); +unsigned long settings_get_channels (const settings_t * settings); +gboolean settings_get_wet_dry_enabled (const settings_t * settings); +gboolean settings_get_wet_dry_locked (const settings_t * settings); +LADSPA_Data settings_get_wet_dry_value (settings_t * settings, unsigned long channel); + +void settings_set_sample_rate (settings_t * settings, guint32 sample_rate); + +#endif /* __JR_PLUGIN_SETTINGS_H__ */ diff --git a/src/modules/jackrack/process.c b/src/modules/jackrack/process.c new file mode 100644 index 0000000..efd497f --- /dev/null +++ b/src/modules/jackrack/process.c @@ -0,0 +1,600 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "process.h" +#include "lock_free_fifo.h" +#include "plugin.h" +#include "jack_rack.h" + +#ifndef _ +#define _(x) x +#endif + +#define USEC_PER_SEC 1000000 +#define MSEC_PER_SEC 1000 +#define TIME_RUN_SKIP_COUNT 5 +#define MAX_BUFFER_SIZE 4096 + +jack_nframes_t sample_rate; +jack_nframes_t buffer_size; + +static void +jack_shutdown_cb (void * data) +{ + process_info_t * procinfo = data; + + procinfo->quit = TRUE; +} + +/** process messages for plugins' control ports */ +void process_control_port_messages (process_info_t * procinfo) { + plugin_t * plugin; + unsigned long control; + unsigned long channel; + gint copy; + + if (!procinfo->chain) return; + + for (plugin = procinfo->chain; plugin; plugin = plugin->next) + { + if (plugin->desc->control_port_count > 0) + for (control = 0; control < plugin->desc->control_port_count; control++) + for (copy = 0; copy < plugin->copies; copy++) + { + while (lff_read (plugin->holders[copy].ui_control_fifos + control, + plugin->holders[copy].control_memory + control) == 0); + } + + if (plugin->wet_dry_enabled) + for (channel = 0; channel < procinfo->channels; channel++) + { + while (lff_read (plugin->wet_dry_fifos + channel, + plugin->wet_dry_values + channel) == 0); + } + } +} + +int get_jack_buffers (process_info_t * procinfo, jack_nframes_t frames) { + unsigned long channel; + + for (channel = 0; channel < procinfo->channels; channel++) + { + procinfo->jack_input_buffers[channel] = jack_port_get_buffer (procinfo->jack_input_ports[channel], frames); + if (!procinfo->jack_input_buffers[channel]) + { + fprintf (stderr, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); + return 1; + } + + procinfo->jack_output_buffers[channel] = jack_port_get_buffer (procinfo->jack_output_ports[channel], frames); + if (!procinfo->jack_output_buffers[channel]) + { + fprintf (stderr, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); + return 1; + } + } + + return 0; +} + +plugin_t * +get_first_enabled_plugin (process_info_t * procinfo) +{ + plugin_t * first_enabled; + + if (!procinfo->chain) return NULL; + + for (first_enabled = procinfo->chain; + first_enabled; + first_enabled = first_enabled->next) + { + if (first_enabled->enabled) return first_enabled; + } + + return NULL; +} + +plugin_t * +get_last_enabled_plugin (process_info_t * procinfo) +{ + plugin_t * last_enabled; + + if (!procinfo->chain) return NULL; + + for (last_enabled = procinfo->chain_end; + last_enabled; + last_enabled = last_enabled->prev) + { + if (last_enabled->enabled) return last_enabled; + } + + return NULL; +} + +void +connect_chain (process_info_t * procinfo, jack_nframes_t frames) +{ + plugin_t * first_enabled, * last_enabled, * plugin; + gint copy; + unsigned long channel; + if (!procinfo->chain) return; + + first_enabled = get_first_enabled_plugin (procinfo); + if (!first_enabled) return; + + last_enabled = get_last_enabled_plugin (procinfo); + + /* sort out the aux ports */ + plugin = first_enabled; + do + { + if (plugin->desc->aux_channels > 0 && plugin->enabled) + { + if (procinfo->jack_client) + { + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + plugin->descriptor-> + connect_port (plugin->holders[copy].instance, + plugin->desc->audio_aux_port_indicies[channel], + jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames)); + } + else + { + for (copy = 0; copy < frames; copy++) + procinfo->silent_buffer[copy] = 0.0; + + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + plugin->descriptor-> + connect_port (plugin->holders[copy].instance, + plugin->desc->audio_aux_port_indicies[channel], + procinfo->silent_buffer); + } + } + } + while ( (plugin != last_enabled) && (plugin = plugin->next) ); + + /* ensure that all the of the enabled plugins are connected to their memory */ + plugin_connect_output_ports (first_enabled); + if (first_enabled != last_enabled) + { + plugin_connect_input_ports (last_enabled, last_enabled->prev->audio_output_memory); + for (plugin = first_enabled->next; plugin; plugin = plugin->next) + { + if (plugin->enabled) + { + plugin_connect_input_ports (plugin, plugin->prev->audio_output_memory); + plugin_connect_output_ports (plugin); + } + } + } + + /* input buffers for first plugin */ + plugin_connect_input_ports (first_enabled, procinfo->jack_input_buffers); +} + +void +process_chain (process_info_t * procinfo, jack_nframes_t frames) +{ + plugin_t * first_enabled; + plugin_t * last_enabled = NULL; + plugin_t * plugin; + unsigned long channel; + unsigned long i; + + if (procinfo->jack_client) + { + LADSPA_Data zero_signal[frames]; + guint copy; + + /* set the zero signal to zero */ + for (channel = 0; channel < frames; channel++) + zero_signal[channel] = 0.0; + + /* possibly set aux output channels to zero if they're not enabled */ + for (plugin = procinfo->chain; plugin; plugin = plugin->next) + if (!plugin->enabled && + plugin->desc->aux_channels > 0 && + !plugin->desc->aux_are_input) + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + memcpy (jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames), + zero_signal, sizeof (LADSPA_Data) * frames); + } + + first_enabled = get_first_enabled_plugin (procinfo); + + /* no chain; just copy input to output */ + if (!procinfo->chain || !first_enabled) + { + unsigned long channel; + for (channel = 0; channel < procinfo->channels; channel++) + { + memcpy (procinfo->jack_output_buffers[channel], + procinfo->jack_input_buffers[channel], + sizeof(LADSPA_Data) * frames); + } + return; + } + + /* all past here is guaranteed to have at least 1 enabled plugin */ + + last_enabled = get_last_enabled_plugin (procinfo); + + for (plugin = first_enabled; + plugin; + plugin = plugin->next) + { + if (plugin->enabled) + { + for (i = 0; i < plugin->copies; i++) + plugin->descriptor->run (plugin->holders[i].instance, frames); + + if (plugin->wet_dry_enabled) + for (channel = 0; channel < procinfo->channels; channel++) + for (i = 0; i < frames; i++) + { + plugin->audio_output_memory[channel][i] *= plugin->wet_dry_values[channel]; + plugin->audio_output_memory[channel][i] += plugin->audio_input_memory[channel][i] * (1.0 - plugin->wet_dry_values[channel]); + } + + if (plugin == last_enabled) + break; + } + else + { + + /* copy the data through */ + for (i = 0; i < procinfo->channels; i++) + memcpy (plugin->audio_output_memory[i], + plugin->prev->audio_output_memory[i], + sizeof(LADSPA_Data) * frames); + } + } + + /* copy the last enabled data to the jack ports */ + for (i = 0; i < procinfo->channels; i++) + memcpy (procinfo->jack_output_buffers[i], + last_enabled->audio_output_memory[i], + sizeof(LADSPA_Data) * frames); + +} + +int process_ladspa (process_info_t * procinfo, jack_nframes_t frames, + LADSPA_Data ** inputs, LADSPA_Data ** outputs) { + unsigned long channel; + + if (!procinfo) + { + fprintf (stderr, "%s: no process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->quit == TRUE) + return 1; + + process_control_port_messages (procinfo); + + for (channel = 0; channel < procinfo->channels; channel++) + { + procinfo->jack_input_buffers[channel] = inputs[channel]; + if (!procinfo->jack_input_buffers[channel]) + { + fprintf (stderr, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); + return 1; + } + + procinfo->jack_output_buffers[channel] = outputs[channel]; + if (!procinfo->jack_output_buffers[channel]) + { + fprintf (stderr, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); + return 1; + } + } + + connect_chain (procinfo, frames); + + process_chain (procinfo, frames); + + return 0; +} + +int process_jack (jack_nframes_t frames, void * data) { + int err; + process_info_t * procinfo; + + procinfo = (process_info_t *) data; + + if (!procinfo) + { + fprintf (stderr, "%s: no process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->port_count == 0) + return 0; + + if (procinfo->quit == TRUE) + return 1; + + process_control_port_messages (procinfo); + + err = get_jack_buffers (procinfo, frames); + if (err) + { + fprintf(stderr, "%s: failed to get jack ports, not processing\n", __FUNCTION__); + return 0; + } + + connect_chain (procinfo, frames); + + process_chain (procinfo, frames); + + return 0; +} + + + +/******************************************* + ************** non RT stuff *************** + *******************************************/ + +static int +process_info_connect_jack (process_info_t * procinfo) +{ + printf (_("Connecting to JACK server with client name '%s'\n"), procinfo->jack_client_name); + + procinfo->jack_client = jack_client_new (procinfo->jack_client_name); + + if (!procinfo->jack_client) + { + fprintf (stderr, "%s: could not create jack client; is the jackd server running?\n", __FUNCTION__); + return 1; + } + + printf (_("Connected to JACK server\n")); + + jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); + jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); + + return 0; +} + +static void +process_info_connect_port (process_info_t * procinfo, + gshort in, + unsigned long port_index, + const char * port_name) +{ + const char ** jack_ports; + unsigned long jack_port_index; + int err; + char * full_port_name; + + jack_ports = jack_get_ports (procinfo->jack_client, NULL, NULL, + JackPortIsPhysical | (in ? JackPortIsOutput : JackPortIsInput)); + + if (!jack_ports) + return; + + for (jack_port_index = 0; + jack_ports[jack_port_index] && jack_port_index <= port_index; + jack_port_index++) + { + if (jack_port_index != port_index) + continue; + + full_port_name = g_strdup_printf ("%s:%s", procinfo->jack_client_name, port_name); + + printf (_("Connecting ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); + + err = jack_connect (procinfo->jack_client, + in ? jack_ports[jack_port_index] : full_port_name, + in ? full_port_name : jack_ports[jack_port_index]); + + if (err) + fprintf (stderr, "%s: error connecting ports '%s' and '%s'\n", + __FUNCTION__, full_port_name, jack_ports[jack_port_index]); + else + printf (_("Connected ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); + + free (full_port_name); + } + + free (jack_ports); +} + +int +process_info_set_port_count (process_info_t * procinfo, + unsigned long port_count, gboolean connect_inputs, gboolean connect_outputs) +{ + unsigned long i; + char * port_name; + jack_port_t ** port_ptr; + gshort in; + + if (procinfo->port_count >= port_count) + return -1; + + if (procinfo->port_count == 0) + { + procinfo->jack_input_ports = g_malloc (sizeof (jack_port_t *) * port_count); + procinfo->jack_output_ports = g_malloc (sizeof (jack_port_t *) * port_count); + + procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); + procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); + } + else + { + procinfo->jack_input_ports = g_realloc (procinfo->jack_input_ports, sizeof (jack_port_t *) * port_count); + procinfo->jack_output_ports = g_realloc (procinfo->jack_output_ports, sizeof (jack_port_t *) * port_count); + + procinfo->jack_input_buffers = g_realloc (procinfo->jack_input_buffers, sizeof (LADSPA_Data *) * port_count); + procinfo->jack_output_buffers = g_realloc (procinfo->jack_output_buffers, sizeof (LADSPA_Data *) * port_count); + } + + for (i = procinfo->port_count; i < port_count; i++) + { + for (in = 0; in < 2; in++) + { + port_name = g_strdup_printf ("%s_%ld", in ? "in" : "out", i + 1); + + //printf (_("Creating %s port %s\n"), in ? "input" : "output", port_name); + + port_ptr = (in ? &procinfo->jack_input_ports[i] + : &procinfo->jack_output_ports[i]); + + *port_ptr = jack_port_register (procinfo->jack_client, + port_name, + JACK_DEFAULT_AUDIO_TYPE, + in ? JackPortIsInput : JackPortIsOutput, + 0); + + if (!*port_ptr) + { + fprintf (stderr, "%s: could not register port '%s'; aborting\n", + __FUNCTION__, port_name); + return 1; + } + + //printf (_("Created %s port %s\n"), in ? "input" : "output", port_name); + + if ((in && connect_inputs) || (!in && connect_outputs)) + process_info_connect_port (procinfo, in, i, port_name); + + g_free (port_name); + } + } + + procinfo->port_count = port_count; + + return 0; +} + +void +process_info_set_channels (process_info_t * procinfo, + unsigned long channels, gboolean connect_inputs, gboolean connect_outputs) +{ + process_info_set_port_count (procinfo, channels, connect_inputs, connect_outputs); + procinfo->channels = channels; +} + +process_info_t * +process_info_new (const char * client_name, unsigned long rack_channels, + gboolean connect_inputs, gboolean connect_outputs) +{ + process_info_t * procinfo; + char * jack_client_name; + int err; + + procinfo = g_malloc (sizeof (process_info_t)); + + procinfo->chain = NULL; + procinfo->chain_end = NULL; + procinfo->jack_client = NULL; + procinfo->port_count = 0; + procinfo->jack_input_ports = NULL; + procinfo->jack_output_ports = NULL; + procinfo->channels = rack_channels; + procinfo->quit = FALSE; + + if ( client_name == NULL ) + { + sample_rate = 48000; // should be set externally before calling process_ladspa + buffer_size = MAX_BUFFER_SIZE; + procinfo->silent_buffer = g_malloc (sizeof (LADSPA_Data) * buffer_size ); + procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); + procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); + + return procinfo; + } + + /* sort out the client name */ + procinfo->jack_client_name = jack_client_name = strdup (client_name); + for (err = 0; jack_client_name[err] != '\0'; err++) + { + if (jack_client_name[err] == ' ') + jack_client_name[err] = '_'; + else if (!isalnum (jack_client_name[err])) + { /* shift all the chars up one (to remove the non-alphanumeric char) */ + int i; + for (i = err; jack_client_name[i] != '\0'; i++) + jack_client_name[i] = jack_client_name[i + 1]; + } + else if (isupper (jack_client_name[err])) + jack_client_name[err] = tolower (jack_client_name[err]); + } + + err = process_info_connect_jack (procinfo); + if (err) + { +/* g_free (procinfo); */ + return NULL; +/* abort (); */ + } + + sample_rate = jack_get_sample_rate (procinfo->jack_client); + buffer_size = jack_get_sample_rate (procinfo->jack_client); + + jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); + jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); + + jack_activate (procinfo->jack_client); + + err = process_info_set_port_count (procinfo, rack_channels, connect_inputs, connect_outputs); + if (err) + return NULL; + + return procinfo; +} + +void +process_info_destroy (process_info_t * procinfo) { + if (procinfo->jack_client) + { + jack_deactivate (procinfo->jack_client); + jack_client_close (procinfo->jack_client); + } + g_free (procinfo->silent_buffer); + g_free (procinfo->jack_input_ports); + g_free (procinfo->jack_output_ports); + g_free (procinfo->jack_input_buffers); + g_free (procinfo->jack_output_buffers); + g_free (procinfo); +} + +void process_quit (process_info_t * procinfo) { + procinfo->quit = TRUE; +} diff --git a/src/modules/jackrack/process.h b/src/modules/jackrack/process.h new file mode 100644 index 0000000..8b0c365 --- /dev/null +++ b/src/modules/jackrack/process.h @@ -0,0 +1,76 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JLH_PROCESS_H__ +#define __JLH_PROCESS_H__ + +#include +#include +#include + +#include "lock_free_fifo.h" + +typedef struct _process_info process_info_t; + +/** this is what gets passed to the process() callback and contains all + the data the process callback will need */ +struct _process_info { + + /** the plugin instance chain */ + struct _plugin * chain; + struct _plugin * chain_end; + + jack_client_t * jack_client; + unsigned long port_count; + jack_port_t ** jack_input_ports; + jack_port_t ** jack_output_ports; + + unsigned long channels; + LADSPA_Data ** jack_input_buffers; + LADSPA_Data ** jack_output_buffers; + LADSPA_Data * silent_buffer; + + char * jack_client_name; + int quit; +}; + +extern jack_nframes_t sample_rate; +extern jack_nframes_t buffer_size; + +process_info_t * process_info_new (const char * client_name, + unsigned long rack_channels, gboolean connect_inputs, gboolean connect_outputs); +void process_info_destroy (process_info_t * procinfo); + +void process_info_set_channels (process_info_t * procinfo, + unsigned long channels, gboolean connect_inputs, gboolean connect_outputs); + +int process_ladspa (process_info_t * procinfo, jack_nframes_t frames, + LADSPA_Data ** inputs, LADSPA_Data ** outputs); + +int process_jack (jack_nframes_t frames, void * data); + +void process_quit (process_info_t * procinfo); + +#endif /* __JLH_PROCESS_H__ */ diff --git a/src/modules/kdenlive/Makefile b/src/modules/kdenlive/Makefile new file mode 100644 index 0000000..87d35a4 --- /dev/null +++ b/src/modules/kdenlive/Makefile @@ -0,0 +1,37 @@ +include ../../../config.mak + +TARGET = ../libmltkdenlive$(LIBSUF) + +OBJS = factory.o \ + filter_boxblur.o \ + filter_wave.o \ + producer_framebuffer.o + +CFLAGS += -I../.. + +LDFLAGS += -lm + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/kdenlive/configure b/src/modules/kdenlive/configure new file mode 100755 index 0000000..792b6f2 --- /dev/null +++ b/src/modules/kdenlive/configure @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + echo "boxblur libmltkdenlive$LIBSUF" >> ../filters.dat + echo "wave libmltkdenlive$LIBSUF" >> ../filters.dat + echo "framebuffer libmltkdenlive$LIBSUF" >> ../producers.dat +fi diff --git a/src/modules/kdenlive/factory.c b/src/modules/kdenlive/factory.c new file mode 100644 index 0000000..084814c --- /dev/null +++ b/src/modules/kdenlive/factory.c @@ -0,0 +1,50 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2007 Jean-Baptiste Mardelle + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_framebuffer.h" +#include "filter_boxblur.h" +#include "filter_wave.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "framebuffer" ) ) + return producer_framebuffer_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "boxblur" ) ) + return filter_boxblur_init( arg ); + if ( !strcmp( id, "wave" ) ) + return filter_wave_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/kdenlive/filter_boxblur.c b/src/modules/kdenlive/filter_boxblur.c new file mode 100644 index 0000000..3c49add --- /dev/null +++ b/src/modules/kdenlive/filter_boxblur.c @@ -0,0 +1,233 @@ +/* + * filter_boxblur.c -- blur filter + * Author: Leny Grisel + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_boxblur.h" + +#include + +#include +#include +#include + + +static void PreCompute(uint8_t *yuv, int32_t *rgb, unsigned int width, unsigned int height) +{ + register int x, y, z; + register int uneven = width % 2; + int w = (width - uneven ) / 2; + int yy, uu, vv; + int r, g, b; + int32_t pts[3]; + for (y=0; y0) pts[z]+=rgb[-3]; + if (y>0) pts[z]+=rgb[-(width*3)]; + if (x>0 && y>0) pts[z]-=rgb[-((width+1)*3)]; + *rgb++=pts[z]; + } + + yy = yuv[2]; + YUV2RGB(yy, uu, vv, r, g, b); + pts[0] = r; + pts[1] = g; + pts[2] = b; + for (z = 0; z < 3; z++) + { + pts[z]+=rgb[-3]; + if (y>0) + { + pts[z]+=rgb[-(width*3)]; + pts[z]-=rgb[-((width+1)*3)]; + } + *rgb++=pts[z]; + } + yuv += 4; + } + if (uneven) + { + uu = yuv[1]; + vv = yuv[3]; + yy = yuv[0]; + YUV2RGB(yy, uu, vv, r, g, b); + pts[0] = r; + pts[1] = g; + pts[2] = b; + for (z = 0; z < 3; z++) + { + pts[z]+=rgb[-3]; + if (y>0) + { + pts[z]+=rgb[-(width*3)]; + pts[z]-=rgb[-((width+1)*3)]; + } + *rgb++=pts[z]; + } + yuv += 2; + } + } +} + +static int32_t GetRGB(int32_t *rgb, unsigned int w, unsigned int h, unsigned int x, int offsetx, unsigned int y, int offsety, unsigned int z) +{ + int xtheo = x * 2 + offsetx; + int ytheo = y + offsety; + if (xtheo < 0) xtheo = 0; else if (xtheo >= w) xtheo = w - 1; + if (ytheo < 0) ytheo = 0; else if (ytheo >= h) ytheo = h - 1; + return rgb[3*(xtheo+ytheo*w)+z]; +} + +static int32_t GetRGB2(int32_t *rgb, unsigned int w, unsigned int h, unsigned int x, int offsetx, unsigned int y, int offsety, unsigned int z) +{ + int xtheo = x * 2 + 1 + offsetx; + int ytheo = y + offsety; + if (xtheo < 0) xtheo = 0; else if (xtheo >= w) xtheo = w - 1; + if (ytheo < 0) ytheo = 0; else if (ytheo >= h) ytheo = h - 1; + return rgb[3*(xtheo+ytheo*w)+z]; +} + +static void DoBoxBlur(uint8_t *yuv, int32_t *rgb, unsigned int width, unsigned int height, unsigned int boxw, unsigned int boxh) +{ + register int x, y; + int32_t r, g, b; + register int uneven = width % 2; + register int y0, y1, u0, u1, v0, v1; + int w = (width - uneven ) / 2; + float mul = 1.f / ((boxw*2) * (boxh*2)); + + for (y = 0; y < height; y++) + { + for (x = 0; x < w; x++) + { + r = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 0) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 0) - GetRGB(rgb, width, height, x, -boxw, y, + boxh, 0) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 0); + g = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 1) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 1) - GetRGB(rgb, width, height, x, -boxw, y, +boxh, 1) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 1); + b = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 2) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 2) - GetRGB(rgb, width, height, x, -boxw, y, +boxh, 2) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 2); + r = (int32_t) (r * mul); + g = (int32_t) (g * mul); + b = (int32_t) (b * mul); + RGB2YUV (r, g, b, y0, u0, v0); + + r = GetRGB2(rgb, width, height, x, +boxw, y, +boxh, 0) + GetRGB2(rgb, width, height, x, -boxw, y, -boxh, 0) - GetRGB2(rgb, width, height, x, -boxw, y, +boxh, 0) - GetRGB2(rgb, width, height, x, +boxw, y, -boxh, 0); + g = GetRGB2(rgb, width, height, x, +boxw, y, +boxh, 1) + GetRGB2(rgb, width, height, x, -boxw, y, -boxh, 1) - GetRGB2(rgb, width, height, x, -boxw, y, +boxh, 1) - GetRGB2(rgb, width, height, x, +boxw, y, -boxh, 1); + b = GetRGB2(rgb, width, height, x, +boxw, y, +boxh, 2) + GetRGB2(rgb, width, height, x, -boxw, y, -boxh, 2) - GetRGB2(rgb, width, height, x, -boxw, y, +boxh, 2) - GetRGB2(rgb, width, height, x, +boxw, y, -boxh, 2); + r = (int32_t) (r * mul); + g = (int32_t) (g * mul); + b = (int32_t) (b * mul); + RGB2YUV (r, g, b, y1, u1, v1); + *yuv++ = y0; + *yuv++ = (u0+u1) >> 1; + *yuv++ = y1; + *yuv++ = (v0+v1) >> 1; + } + if (uneven) + { + r = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 0) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 0) - GetRGB(rgb, width, height, x, -boxw, y, +boxh, 0) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 0); + g = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 1) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 1) - GetRGB(rgb, width, height, x, -boxw, y, +boxh, 1) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 1); + b = GetRGB(rgb, width, height, x, +boxw, y, +boxh, 2) + GetRGB(rgb, width, height, x, -boxw, y, -boxh, 2) - GetRGB(rgb, width, height, x, -boxw, y, +boxh, 2) - GetRGB(rgb, width, height, x, +boxw, y, -boxh, 2); + r = (int32_t) (r * mul); + g = (int32_t) (g * mul); + b = (int32_t) (b * mul); + RGB2YUV (r, g, b, y0, u0, v0); + *yuv++ = mul * y0; + *yuv++ = mul * u0; + } + } +} + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + short hori = mlt_properties_get_int(MLT_FRAME_PROPERTIES( this ), "hori" ); + short vert = mlt_properties_get_int(MLT_FRAME_PROPERTIES( this ), "vert" ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + double factor = mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "boxblur" ); + if (factor != 0) { + int h = *height + 1; + int32_t *rgb = mlt_pool_alloc (3 * *width * h * sizeof(int32_t)); + PreCompute (*image, rgb, *width, h); + DoBoxBlur (*image, rgb, *width, h, (int) factor*hori, (int) factor*vert); + mlt_pool_release (rgb); + } + } + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the starting blur level + double blur = (double) mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "start" ); + short hori = mlt_properties_get_int(MLT_FILTER_PROPERTIES( this ), "hori" ); + short vert = mlt_properties_get_int(MLT_FILTER_PROPERTIES( this ), "vert" ); + + // If there is an end adjust gain to the range + if ( mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "end" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + mlt_position time = mlt_frame_get_position( frame ); + double position = (double) ( time - in ) / ( out - in + 1.0 ); + double end = (double) mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "end" ); + blur += ( end - blur ) * position; + } + + // Push the frame filter + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "boxblur", blur ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "hori", hori ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "vert", vert ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_boxblur_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "start", arg == NULL ? "10" : arg); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "hori", arg == NULL ? "1" : arg); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "vert", arg == NULL ? "1" : arg); + } + return this; +} + + + diff --git a/src/modules/kdenlive/filter_boxblur.h b/src/modules/kdenlive/filter_boxblur.h new file mode 100644 index 0000000..c3af689 --- /dev/null +++ b/src/modules/kdenlive/filter_boxblur.h @@ -0,0 +1,27 @@ +/* + * filter_boxblur.h -- box blur filter + * Author: Leny Grisel + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_BOXBLUR_H_ +#define _FILTER_BOXBLUR_H_ + +#include + +extern mlt_filter filter_boxblur_init( char *arg ); + +#endif diff --git a/src/modules/kdenlive/filter_wave.c b/src/modules/kdenlive/filter_wave.c new file mode 100644 index 0000000..1ef017b --- /dev/null +++ b/src/modules/kdenlive/filter_wave.c @@ -0,0 +1,139 @@ +/* + * wave.c -- wave filter + * Author: Leny Grisel + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_wave.h" + +#include + +#include +#include +#include +#include + +// this is a utility function used by DoWave below +static uint8_t getPoint(uint8_t *src, int w, int h, int x, int y, int z) +{ + if (x<0) x+=-((-x)%w)+w; else if (x>=w) x=x%w; + if (y<0) y+=-((-y)%h)+h; else if (y>=h) y=y%h; + return src[(x+y*w)*4+z]; +} + +// the main meat of the algorithm lies here +static void DoWave(uint8_t *src, int src_w, int src_h, uint8_t *dst, mlt_position position, int speed, int factor, int deformX, int deformY) +{ + register int x, y; + int decalY, decalX, z; + float amplitude, phase, pulsation; + register int uneven = src_w % 2; + int w = (src_w - uneven ) / 2; + amplitude = factor; + pulsation = 0.5 / factor; // smaller means bigger period + phase = position * pulsation * speed / 10; // smaller means longer + for (y=0;yprocess = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "start", arg == NULL ? "10" : arg); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "speed", arg == NULL ? "5" : arg); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "deformX", arg == NULL ? "1" : arg); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "deformY", arg == NULL ? "1" : arg); + } + return this; +} + + + diff --git a/src/modules/kdenlive/filter_wave.h b/src/modules/kdenlive/filter_wave.h new file mode 100644 index 0000000..eec113b --- /dev/null +++ b/src/modules/kdenlive/filter_wave.h @@ -0,0 +1,27 @@ +/* + * filter_wave.h -- wave filter + * Author: Leny Grisel + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_WAVE_H_ +#define _FILTER_WAVE_H_ + +#include + +extern mlt_filter filter_wave_init( char *arg ); + +#endif diff --git a/src/modules/kdenlive/producer_framebuffer.c b/src/modules/kdenlive/producer_framebuffer.c new file mode 100644 index 0000000..3c5e494 --- /dev/null +++ b/src/modules/kdenlive/producer_framebuffer.c @@ -0,0 +1,280 @@ +/* + * producer_framebuffer.c -- create subspeed frames + * Copyright (C) 2007 Jean-Baptiste Mardelle + * Author: Jean-Baptiste Mardelle, based on the code of motion_est by Zachary Drew + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_framebuffer.h" +#include + +#include +#include +#include +#include +#include +#include + +// Forward references. +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); + +/** Image stack(able) method +*/ + +static int framebuffer_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + + // Get the filter object and properties + mlt_producer producer = mlt_frame_pop_service( this ); + mlt_frame first_frame = mlt_frame_pop_service( this ); + + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Frame properties objects + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( this ); + mlt_properties first_frame_properties = MLT_FRAME_PROPERTIES( first_frame ); + + *width = mlt_properties_get_int( frame_properties, "width" ); + *height = mlt_properties_get_int( frame_properties, "height" ); + + int size; + switch ( *format ) + { + case mlt_image_yuv420p: + size = *width * 3 * ( *height + 1 ) / 2; + break; + case mlt_image_rgb24: + size = *width * ( *height + 1 ) * 3; + break; + default: + *format = mlt_image_yuv422; + size = *width * ( *height + 1 ) * 2; + break; + } + + uint8_t *output = mlt_properties_get_data( producer_properties, "output_buffer", NULL ); + + if( output == NULL ) + { + output = mlt_pool_alloc( size ); + + // Let someone else clean up + mlt_properties_set_data( producer_properties, "output_buffer", output, size, mlt_pool_release, NULL ); + } + + uint8_t *first_image = mlt_properties_get_data( first_frame_properties, "image", NULL ); + + // which frames are buffered? + + int error = 0; + + if( first_image == NULL ) + { + error = mlt_frame_get_image( first_frame, &first_image, format, width, height, writable ); + + if( error != 0 ) { + fprintf(stderr, "first_image == NULL get image died\n"); + return error; + } + } + + // Start with a base image + memcpy( output, first_image, size ); + + *image = output; + mlt_properties_set_data( frame_properties, "image", output, size, NULL, NULL ); + + // Make sure that no further scaling is done + mlt_properties_set( frame_properties, "rescale.interps", "none" ); + mlt_properties_set( frame_properties, "scale", "off" ); + + mlt_frame_close( first_frame ); + + return 0; +} + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Construct a new frame + *frame = mlt_frame_init( ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); + + if( frame != NULL ) + { + mlt_frame first_frame = mlt_properties_get_data( properties, "first_frame", NULL ); + + mlt_position first_position = (first_frame != NULL) ? mlt_frame_get_position( first_frame ) : -1; + + // Get the real producer + mlt_producer real_producer = mlt_properties_get_data( properties, "producer", NULL ); + + // get properties + int strobe = mlt_properties_get_int( properties, "strobe"); + int freeze = mlt_properties_get_int( properties, "freeze"); + int freeze_after = mlt_properties_get_int( properties, "freeze_after"); + int freeze_before = mlt_properties_get_int( properties, "freeze_before"); + + mlt_position need_first; + + if (!freeze || freeze_after || freeze_before) { + double prod_speed = mlt_properties_get_double( properties, "_speed"); + double actual_position = prod_speed * (double) mlt_producer_position( this ); + + if (mlt_properties_get_int( properties, "reverse")) actual_position = mlt_producer_get_playtime(this) - actual_position; + + if (strobe < 2) + { + need_first = floor( actual_position ); + } + else + { + // Strobe effect wanted, calculate frame position + need_first = floor( actual_position ); + need_first -= need_first%strobe; + } + if (freeze) + { + if (freeze_after && need_first > freeze) need_first = freeze; + else if (freeze_before && need_first < freeze) need_first = freeze; + } + } + else need_first = freeze; + + if( need_first != first_position ) + { + mlt_frame_close( first_frame ); + first_position = -1; + first_frame = NULL; + } + + if( first_frame == NULL ) + { + // Seek the producer to the correct place + mlt_producer_seek( real_producer, need_first ); + + // Get the frame + mlt_service_get_frame( MLT_PRODUCER_SERVICE( real_producer ), &first_frame, index ); + + double ratio = mlt_properties_get_double( MLT_PRODUCER_PROPERTIES( real_producer ), "aspect_ratio" ) * (double) mlt_properties_get_int( MLT_FRAME_PROPERTIES( first_frame ), "width" ) / (double) mlt_properties_get_int( MLT_FRAME_PROPERTIES( first_frame ), "height" ) / ( (double) mlt_properties_get_int(MLT_FRAME_PROPERTIES( *frame ), "width" ) / (double) mlt_properties_get_int( MLT_FRAME_PROPERTIES( *frame ), "height" )); + + mlt_properties_set_double( properties, "ratio_fix", ratio ); + } + + // Make sure things are in their place + mlt_properties_set_data( properties, "first_frame", first_frame, 0, NULL, NULL ); + + // Stack the producer and producer's get image + mlt_frame_push_service( *frame, first_frame ); + mlt_properties_inc_ref( MLT_FRAME_PROPERTIES( first_frame ) ); + + mlt_frame_push_service( *frame, this ); + mlt_frame_push_service( *frame, framebuffer_get_image ); + + + // Give the returned frame temporal identity + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + mlt_properties_set_double( MLT_FRAME_PROPERTIES(*frame), "aspect_ratio", mlt_properties_get_double( properties, "ratio_fix" )); + } + + return 0; +} + + +mlt_producer producer_framebuffer_init( char *arg ) +{ + + mlt_producer this = NULL; + this = calloc( 1, sizeof( struct mlt_producer_s ) ); + mlt_producer_init( this, NULL ); + + // Wrap fezzik + mlt_producer real_producer; + + // Check if a speed was specified. + /** + + * Speed must be appended to the filename with ':'. To play your video at 50%: + inigo framebuffer:my_video.mpg:0.5 + + * Stroboscope effect can be obtained by adding a stobe=x parameter, where + x is the number of frames that will be ignored. + + * You can play the movie backwards by adding reverse=1 + + * You can freeze the clip at a determined position by adding freeze=frame_pos + add freeze_after=1 to freeze only paste position or freeze_before to freeze before it + + **/ + + double speed; + + int count; + char *props = strdup( arg ); + char *ptr = props; + count = strcspn( ptr, ":" ); + ptr[count] = '\0'; + + real_producer = mlt_factory_producer( "fezzik", props ); + + ptr += count + 1; + ptr += strspn( ptr, ":" ); + count = strcspn( ptr, ":" ); + ptr[count] = '\0'; + speed = atof(ptr); + free( props ); + + if (speed == 0.0) speed = 1.0; + + + if ( this != NULL && real_producer != NULL) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fezzik normalised it for us already + mlt_properties_set_int( properties, "fezzik_normalised", 1); + + // Store the producer and fitler + mlt_properties_set_data( properties, "producer", real_producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + + // Grab some stuff from the real_producer + mlt_properties_pass_list( properties, MLT_PRODUCER_PROPERTIES( real_producer ), "length,resource,width,height" ); + + + if ( speed != 1.0 ) + { + double real_length = (double) mlt_producer_get_length( real_producer ); + mlt_properties_set_position( properties, "length", real_length / speed ); + } + + // Since we control the seeking, prevent it from seeking on its own + mlt_producer_set_speed( real_producer, 0 ); + mlt_producer_set_speed( this, speed ); + + // Override the get_frame method + this->get_frame = producer_get_frame; + } + else + { + if ( this ) + mlt_producer_close( this ); + if ( real_producer ) + mlt_producer_close( real_producer ); + + this = NULL; + } + return this; +} diff --git a/src/modules/kdenlive/producer_framebuffer.h b/src/modules/kdenlive/producer_framebuffer.h new file mode 100644 index 0000000..3dc6da8 --- /dev/null +++ b/src/modules/kdenlive/producer_framebuffer.h @@ -0,0 +1,27 @@ +/* + * producer_framebuffer.h -- slowmotion and reverse playing + * Copyright (C) 2007 Jean-Baptiste Mardelle + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_FRAMEBUFFER_H_ +#define _PRODUCER_FRAMEBUFFER_H_ + +#include + +extern mlt_producer producer_framebuffer_init( char *arg ); + +#endif diff --git a/src/modules/kino/Makefile b/src/modules/kino/Makefile new file mode 100644 index 0000000..fa00d9d --- /dev/null +++ b/src/modules/kino/Makefile @@ -0,0 +1,45 @@ +include ../../../config.mak +include config.mak + +TARGET=../libmltkino.so + +OBJS=factory.o producer_kino.o +CPPOBJS=kino_wrapper.o avi.o error.o filehandler.o riff.o +CFLAGS+=-I../../ +LDFLAGS+=-L../../framework -lmlt -lstdc++ +CXXFLAGS+=$(CFLAGS) -Wno-deprecated + +ifdef HAVE_LIBQUICKTIME +CFLAGS+=`pkg-config --cflags libquicktime` +CXXFLAGS+=`pkg-config --cflags libquicktime` +LDFLAGS+=`pkg-config --libs libquicktime` +endif + +ifdef HAVE_LIBDV +CFLAGS += `pkg-config --cflags libdv` +LDFLAGS += `pkg-config --libs libdv` +endif + + +SRCS := $(OBJS:.o=.c) $(CPPOBJS:.o=.cc) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(CPPOBJS) + $(CC) -shared -o $@ $(OBJS) $(CPPOBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend config.h config.mak + +clean: + rm -f $(OBJS) $(TARGET) $(CPPOBJS) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/kino/avi.cc b/src/modules/kino/avi.cc new file mode 100644 index 0000000..2ebe3f2 --- /dev/null +++ b/src/modules/kino/avi.cc @@ -0,0 +1,1860 @@ +/* +* avi.cc library for AVI file format i/o +* Copyright (C) 2000 - 2002 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Tag: $Name$ +* +* Change log: +* +* $Log$ +* Revision 1.3 2005/07/25 07:21:39 lilo_booter +* + fixes for opendml dv avi +* +* Revision 1.2 2005/06/21 20:59:39 lilo_booter +* src/framework/mlt_consumer.c src/framework/mlt_consumer.h +* + Added a general profile handling for size, aspect ratio and display ratio +* +* src/framework/mlt_producer.c +* + Correction to aspect ratio properties +* +* src/inigo/inigo.c +* + Minimalist support for sdl_preview (still not very good) +* +* src/modules/avformat/consumer_avformat.c +* + Takes consumer profile into account +* +* src/modules/core/filter_resize.c +* + Corrections for synthesised producers and aspect ratio (inherits from consumer) +* +* src/modules/core/producer_colour.c +* src/modules/core/producer_noise.c +* src/modules/gtk2/producer_pango.c +* + Ensures that resize picks up consumer aspect ratio +* +* src/modules/dv/consumer_libdv.c +* + Honour wide screen output +* +* src/modules/gtk2/producer_pixbuf.c +* + Correction for 1:1 aspect ratio +* +* src/modules/kino/Makefile +* src/modules/kino/avi.cc +* src/modules/kino/avi.h +* src/modules/kino/configure +* src/modules/kino/filehandler.cc +* + Attempt to allow mov dv files to provide audio +* +* src/modules/sdl/consumer_sdl.c +* src/modules/sdl/consumer_sdl_preview.c +* src/modules/sdl/consumer_sdl_still.c +* + Takes consumer profile into account +* +* Revision 1.1 2005/04/15 14:28:26 lilo_booter +* Initial version +* +* Revision 1.28 2005/04/01 23:43:10 ddennedy +* apply endian fixes from Daniel Kobras +* +* Revision 1.27 2004/10/11 01:37:11 ddennedy +* mutex safety locks in RIFF and AVI classes, type 2 AVI optimization, mencoder export script +* +* Revision 1.26 2004/01/05 03:43:11 ddennedy +* metadata editing, deinterlace options, bugfixes and cleanups +* +* Revision 1.25 2003/11/25 23:00:52 ddennedy +* cleanup and a few bugfixes +* +* Revision 1.24 2003/11/12 13:01:56 ddennedy +* disable JUNK chunks in MOVI list, FileHandler max file size zero = infinity +* +* Revision 1.23 2003/11/10 01:02:51 ddennedy +* bugfix: return error on AVI directory entries with size <0 +* +* Revision 1.22 2003/10/28 18:52:32 ddennedy +* fix prefs dialog crash, improve WAV import +* +* Revision 1.21 2003/10/21 16:34:32 ddennedy +* GNOME2 port phase 1: initial checkin +* +* Revision 1.19.2.9 2003/08/26 20:39:00 ddennedy +* relocate mutex unlock and add assert includes +* +* Revision 1.19.2.8 2003/07/24 14:13:57 ddennedy +* support for distinct audio stream in type2 AVI and Quicktime; support for more DV FOURCCs +* +* Revision 1.19.2.7 2003/06/10 23:53:35 ddennedy +* Daniel Kobras' WriteFrame error handling and automatic OpenDML, bugfixes in scene list updates, export AV/C Record +* +* Revision 1.19.2.6 2003/03/05 15:02:12 ddennedy +* yet anther AV/C bugfix, yet another AVI improvement +* +* Revision 1.19.2.5 2003/02/20 21:59:55 ddennedy +* bugfixes to capture and AVI +* +* Revision 1.19.2.4 2003/01/13 05:15:31 ddennedy +* added More Info panel and supporting methods +* +* Revision 1.19.2.3 2002/12/31 22:40:49 ddennedy +* bugfix recent versions Quicktime4Linux build options, extend dvsd fourcc check on AVI to the BITMAPINFOHEADER for compatibility with mencoder +* +* Revision 1.19.2.2 2002/11/25 04:48:30 ddennedy +* bugfix to report errors when loading files +* +* Revision 1.19.2.1 2002/11/24 23:36:55 ddennedy +* bugfix in AVI writing +* +* Revision 1.19 2002/10/08 12:08:01 ddennedy +* more sane frame count, greater potential compatibility +* +* Revision 1.18 2002/10/08 08:33:02 ddennedy +* fix number of frames for small dv2 +* +* Revision 1.17 2002/10/08 07:46:41 ddennedy +* AVI bugfixes, compatibility, optimization, warn bad file in capture and export dv file, allow no mplex +* +* Revision 1.15 2002/06/10 10:39:51 ddennedy +* minor fixes for large files +* +* Revision 1.14 2002/05/17 08:04:24 ddennedy +* revert const-ness of Frame references in Frame, FileHandler, and AVI classes +* +* Revision 1.13 2002/05/15 04:39:35 ddennedy +* bugfixes to dv2 AVI write, audio export, Xv init +* +* Revision 1.12 2002/04/29 05:09:21 ddennedy +* raw dv file support, Frame::ExtractAudio uses libdv, audioScrub prefs +* +* Revision 1.11 2002/04/15 19:12:32 schirmacher +* removed debugging code causing performance losses and crashes with dv2 files +* +* Revision 1.10 2002/04/09 06:53:42 ddennedy +* cleanup, new libdv 0.9.5, large AVI, dnd storyboard +* +* Revision 1.8 2002/03/25 21:34:25 arne +* Support for large (64 bit) files mostly completed +* +* Revision 1.7 2002/03/10 21:28:29 arne +* release 1.1b1, 64 bit support for type 1 avis +* +* Revision 1.6 2002/03/10 13:29:41 arne +* more changes for 64 bit access +* +* Revision 1.5 2002/03/09 17:59:28 arne +* moved index routines to AVIFile +* +* Revision 1.4 2002/03/09 10:26:26 arne +* improved constructors and assignment operator +* +* Revision 1.3 2002/03/09 08:55:57 arne +* moved a few variables to AVIFile +* +* Revision 1.2 2002/03/04 19:22:43 arne +* updated to latest Kino avi code +* +* Revision 1.1.1.1 2002/03/03 19:08:08 arne +* import of version 1.01 +* +*/ + +#include "config.h" + +// C++ includes + +#include +#include +#include + +using std::cout; +using std::hex; +using std::dec; +using std::setw; +using std::setfill; +using std::endl; + +// C includes + +#include +#include +#include +#include +#include + +// local includes + +#include "error.h" +#include "riff.h" +#include "avi.h" + +#define PADDING_SIZE (512) +#define PADDING_1GB (0x40000000) +#define IX00_INDEX_SIZE (4028) + +#define AVIF_HASINDEX 0x00000010 +#define AVIF_MUSTUSEINDEX 0x00000020 +#define AVIF_TRUSTCKTYPE 0x00000800 +#define AVIF_ISINTERLEAVED 0x00000100 +#define AVIF_WASCAPTUREFILE 0x00010000 +#define AVIF_COPYRIGHTED 0x00020000 + + +//static char g_zeroes[ PADDING_SIZE ]; + +/** The constructor + + \todo mainHdr not initialized + \todo add checking for NULL pointers + +*/ + +AVIFile::AVIFile() : RIFFFile(), + idx1( NULL ), file_list( -1 ), riff_list( -1 ), + hdrl_list( -1 ), avih_chunk( -1 ), movi_list( -1 ), junk_chunk( -1 ), idx1_chunk( -1 ), + index_type( -1 ), current_ix00( -1 ), odml_list( -1 ), dmlh_chunk( -1 ), isUpdateIdx1( true ) +{ + // cerr << "0x" << hex << (long)this << dec << " AVIFile::AVIFile() : RIFFFile(), ..." << endl; + + for ( int i = 0; i < 2; ++i ) + { + indx[ i ] = new AVISuperIndex; + memset( indx[ i ], 0, sizeof( AVISuperIndex ) ); + ix[ i ] = new AVIStdIndex; + memset( ix[ i ], 0, sizeof( AVIStdIndex ) ); + indx_chunk[ i ] = -1; + ix_chunk[ i ] = -1; + strl_list[ i ] = -1; + strh_chunk[ i ] = -1; + strf_chunk[ i ] = -1; + } + idx1 = new AVISimpleIndex; + memset( idx1, 0, sizeof( AVISimpleIndex ) ); +} + + +/** The copy constructor + + \todo add checking for NULL pointers + +*/ + +AVIFile::AVIFile( const AVIFile& avi ) : RIFFFile( avi ) +{ + // cerr << "0x" << hex << (long)this << dec << " 0x" << hex << (long)&avi << dec << " AVIFile::AVIFile(const AVIFile& avi) : RIFFFile(avi)" << endl; + + mainHdr = avi.mainHdr; + idx1 = new AVISimpleIndex; + *idx1 = *avi.idx1; + file_list = avi.file_list; + riff_list = avi.riff_list; + hdrl_list = avi.hdrl_list; + avih_chunk = avi.avih_chunk; + movi_list = avi.movi_list; + junk_chunk = avi.junk_chunk; + idx1_chunk = avi.idx1_chunk; + + for ( int i = 0; i < 2; ++i ) + { + indx[ i ] = new AVISuperIndex; + *indx[ i ] = *avi.indx[ i ]; + ix[ i ] = new AVIStdIndex; + *ix[ i ] = *avi.ix[ i ]; + indx_chunk[ i ] = avi.indx_chunk[ i ]; + ix_chunk[ i ] = avi.ix_chunk[ i ]; + strl_list[ i ] = avi.strl_list[ i ]; + strh_chunk[ i ] = avi.strh_chunk[ i ]; + strf_chunk[ i ] = avi.strf_chunk[ i ]; + } + + index_type = avi.index_type; + current_ix00 = avi.current_ix00; + + for ( int i = 0; i < 62; ++i ) + dmlh[ i ] = avi.dmlh[ i ]; + + isUpdateIdx1 = avi.isUpdateIdx1; + +} + + +/** The assignment operator + +*/ + +AVIFile& AVIFile::operator=( const AVIFile& avi ) +{ + // cerr << "0x" << hex << (long)this << dec << " 0x" << hex << (long)&avi << dec << " AVIFile& AVIFile::operator=(const AVIFile& avi)" << endl; + + if ( this != &avi ) + { + RIFFFile::operator=( avi ); + mainHdr = avi.mainHdr; + *idx1 = *avi.idx1; + file_list = avi.file_list; + riff_list = avi.riff_list; + hdrl_list = avi.hdrl_list; + avih_chunk = avi.avih_chunk; + movi_list = avi.movi_list; + junk_chunk = avi.junk_chunk; + idx1_chunk = avi.idx1_chunk; + + for ( int i = 0; i < 2; ++i ) + { + *indx[ i ] = *avi.indx[ i ]; + *ix[ i ] = *avi.ix[ i ]; + indx_chunk[ i ] = avi.indx_chunk[ i ]; + ix_chunk[ i ] = avi.ix_chunk[ i ]; + strl_list[ i ] = avi.strl_list[ i ]; + strh_chunk[ i ] = avi.strh_chunk[ i ]; + strf_chunk[ i ] = avi.strf_chunk[ i ]; + } + + index_type = avi.index_type; + current_ix00 = avi.current_ix00; + + for ( int i = 0; i < 62; ++i ) + dmlh[ i ] = avi.dmlh[ i ]; + + isUpdateIdx1 = avi.isUpdateIdx1; + } + return *this; +} + + +/** The destructor + +*/ + +AVIFile::~AVIFile() +{ + // cerr << "0x" << hex << (long)this << dec << " AVIFile::~AVIFile()" << endl; + + for ( int i = 0; i < 2; ++i ) + { + delete ix[ i ]; + delete indx[ i ]; + } + delete idx1; +} + +/** Initialize the AVI structure to its initial state, either for PAL or NTSC format + + Initialize the AVIFile attributes: mainHdr, indx, ix00, idx1 + + \todo consolidate AVIFile::Init, AVI1File::Init, AVI2File::Init. They are somewhat redundant. + \param format pass AVI_PAL or AVI_NTSC + \param sampleFrequency the sample frequency of the audio content + \param indexType pass AVI_SMALL_INDEX or AVI_LARGE_INDEX + +*/ + +void AVIFile::Init( int format, int sampleFrequency, int indexType ) +{ + int i, j; + + assert( ( format == AVI_PAL ) || ( format == AVI_NTSC ) ); + + index_type = indexType; + + switch ( format ) + { + case AVI_PAL: + mainHdr.dwMicroSecPerFrame = 40000; + mainHdr.dwSuggestedBufferSize = 144008; + break; + + case AVI_NTSC: + mainHdr.dwMicroSecPerFrame = 33366; + mainHdr.dwSuggestedBufferSize = 120008; + break; + + default: /* no default allowed */ + assert( 0 ); + break; + } + + /* Initialize the 'avih' chunk */ + + mainHdr.dwMaxBytesPerSec = 3600000 + sampleFrequency * 4; + mainHdr.dwPaddingGranularity = PADDING_SIZE; + mainHdr.dwFlags = AVIF_TRUSTCKTYPE; + if ( indexType & AVI_SMALL_INDEX ) + mainHdr.dwFlags |= AVIF_HASINDEX; + mainHdr.dwTotalFrames = 0; + mainHdr.dwInitialFrames = 0; + mainHdr.dwStreams = 1; + mainHdr.dwWidth = 0; + mainHdr.dwHeight = 0; + mainHdr.dwReserved[ 0 ] = 0; + mainHdr.dwReserved[ 1 ] = 0; + mainHdr.dwReserved[ 2 ] = 0; + mainHdr.dwReserved[ 3 ] = 0; + + /* Initialize the 'idx1' chunk */ + + for ( int i = 0; i < 8000; ++i ) + { + idx1->aIndex[ i ].dwChunkId = 0; + idx1->aIndex[ i ].dwFlags = 0; + idx1->aIndex[ i ].dwOffset = 0; + idx1->aIndex[ i ].dwSize = 0; + } + idx1->nEntriesInUse = 0; + + /* Initialize the 'indx' chunk */ + + for ( i = 0; i < 2; ++i ) + { + indx[ i ] ->wLongsPerEntry = 4; + indx[ i ] ->bIndexSubType = 0; + indx[ i ] ->bIndexType = KINO_AVI_INDEX_OF_INDEXES; + indx[ i ] ->nEntriesInUse = 0; + indx[ i ] ->dwReserved[ 0 ] = 0; + indx[ i ] ->dwReserved[ 1 ] = 0; + indx[ i ] ->dwReserved[ 2 ] = 0; + for ( j = 0; j < 2014; ++j ) + { + indx[ i ] ->aIndex[ j ].qwOffset = 0; + indx[ i ] ->aIndex[ j ].dwSize = 0; + indx[ i ] ->aIndex[ j ].dwDuration = 0; + } + } + + /* The ix00 and ix01 chunk will be added dynamically in avi_write_frame + as needed */ + + /* Initialize the 'dmlh' chunk. I have no clue what this means + though */ + + for ( i = 0; i < 62; ++i ) + dmlh[ i ] = 0; + //dmlh[0] = -1; /* frame count + 1? */ + +} + + +/** Find position and size of a given frame in the file + + Depending on which index is available, search one of them to + find position and frame size + + \todo the size parameter is redundant. All frames have the same size, which is also in the mainHdr. + \todo all index related operations should be isolated + \param offset the file offset to the start of the frame + \param size the size of the frame + \param frameNum the number of the frame we wish to find + \return 0 if the frame could be found, -1 otherwise +*/ + +int AVIFile::GetDVFrameInfo( off_t &offset, int &size, int frameNum ) +{ + switch ( index_type ) + { + case AVI_LARGE_INDEX: + + /* find relevant index in indx0 */ + + int i; + + for ( i = 0; frameNum >= indx[ 0 ] ->aIndex[ i ].dwDuration; frameNum -= indx[ 0 ] ->aIndex[ i ].dwDuration, ++i ) + ; + + if ( i != current_ix00 ) + { + fail_if( lseek( fd, indx[ 0 ] ->aIndex[ i ].qwOffset + RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, ix[ 0 ], indx[ 0 ] ->aIndex[ i ].dwSize - RIFF_HEADERSIZE ) ); + current_ix00 = i; + } + + if ( frameNum < ix[ 0 ] ->nEntriesInUse ) + { + offset = ix[ 0 ] ->qwBaseOffset + ix[ 0 ] ->aIndex[ frameNum ].dwOffset; + size = ix[ 0 ] ->aIndex[ frameNum ].dwSize; + return 0; + } + else + return -1; + break; + + case AVI_SMALL_INDEX: + int index = -1; + int frameNumIndex = 0; + for ( int i = 0; i < idx1->nEntriesInUse; ++i ) + { + FOURCC chunkID1 = make_fourcc( "00dc" ); + FOURCC chunkID2 = make_fourcc( "00db" ); + if ( idx1->aIndex[ i ].dwChunkId == chunkID1 || + idx1->aIndex[ i ].dwChunkId == chunkID2 ) + { + if ( frameNumIndex == frameNum ) + { + index = i; + break; + } + ++frameNumIndex; + } + } + if ( index != -1 ) + { + // compatibility check for broken dvgrab dv2 format + if ( idx1->aIndex[ 0 ].dwOffset > GetDirectoryEntry( movi_list ).offset ) + { + offset = idx1->aIndex[ index ].dwOffset + RIFF_HEADERSIZE; + } + else + { + // new, correct dv2 format + offset = idx1->aIndex[ index ].dwOffset + RIFF_HEADERSIZE + GetDirectoryEntry( movi_list ).offset; + } + size = idx1->aIndex[ index ].dwSize; + return 0; + } + else + return -1; + break; + } + return -1; +} + +/** Find position and size of a given frame in the file + + Depending on which index is available, search one of them to + find position and frame size + + \todo the size parameter is redundant. All frames have the same size, which is also in the mainHdr. + \todo all index related operations should be isolated + \param offset the file offset to the start of the frame + \param size the size of the frame + \param frameNum the number of the frame we wish to find + \param chunkID the ID of the type of chunk we want + \return 0 if the frame could be found, -1 otherwise +*/ + +int AVIFile::GetFrameInfo( off_t &offset, int &size, int frameNum, FOURCC chunkID ) +{ + if ( index_type & AVI_LARGE_INDEX ) + { + int i; + + for ( i = 0; frameNum >= indx[ 0 ] ->aIndex[ i ].dwDuration; frameNum -= indx[ 0 ] ->aIndex[ i ].dwDuration, ++i ) + ; + + if ( i != current_ix00 ) + { + fail_if( lseek( fd, indx[ 0 ] ->aIndex[ i ].qwOffset + RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, ix[ 0 ], indx[ 0 ] ->aIndex[ i ].dwSize - RIFF_HEADERSIZE ) ); + current_ix00 = i; + } + + if ( frameNum < ix[ 0 ] ->nEntriesInUse ) + { + if ( ( FOURCC ) ix[ 0 ] ->dwChunkId == chunkID ) + { + offset = ix[ 0 ] ->qwBaseOffset + ix[ 0 ] ->aIndex[ frameNum ].dwOffset; + size = ix[ 0 ] ->aIndex[ frameNum ].dwSize; + return 0; + } + } + } + if ( index_type & AVI_SMALL_INDEX ) + { + int index = -1; + int frameNumIndex = 0; + for ( int i = 0; i < idx1->nEntriesInUse; ++i ) + { + if ( idx1->aIndex[ i ].dwChunkId == chunkID ) + { + if ( frameNumIndex == frameNum ) + { + index = i; + break; + } + ++frameNumIndex; + } + } + if ( index != -1 ) + { + // compatibility check for broken dvgrab dv2 format + if ( idx1->aIndex[ 0 ].dwOffset > GetDirectoryEntry( movi_list ).offset ) + { + offset = idx1->aIndex[ index ].dwOffset + RIFF_HEADERSIZE; + } + else + { + // new, correct dv2 format + offset = idx1->aIndex[ index ].dwOffset + RIFF_HEADERSIZE + GetDirectoryEntry( movi_list ).offset; + } + size = idx1->aIndex[ index ].dwSize; + return 0; + } + } + return -1; +} + +/** Read in a frame + + \todo we actually don't need the frame here, we could use just a void pointer + \param frame a reference to the frame object that will receive the frame data + \param frameNum the frame number to read + \return 0 if the frame could be read, -1 otherwise +*/ + +int AVIFile::GetDVFrame( uint8_t *data, int frameNum ) +{ + off_t offset; + int size; + + if ( GetDVFrameInfo( offset, size, frameNum ) != 0 || size < 0 ) + return -1; + pthread_mutex_lock( &file_mutex ); + fail_if( lseek( fd, offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, data, size ) ); + pthread_mutex_unlock( &file_mutex ); + + return 0; +} + +/** Read in a frame + + \param data a pointer to the audio buffer + \param frameNum the frame number to read + \param chunkID the ID of the type of chunk we want + \return the size the of the frame data, 0 if could not be read +*/ + +int AVIFile::getFrame( void *data, int frameNum, FOURCC chunkID ) +{ + off_t offset; + int size; + + if ( GetFrameInfo( offset, size, frameNum, chunkID ) != 0 ) + return 0; + fail_if( lseek( fd, offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, data, size ) ); + + return size; +} + +int AVIFile::GetTotalFrames() const +{ + return mainHdr.dwTotalFrames; +} + + +/** prints out a directory entry in text form + + Every subclass of RIFFFile is supposed to override this function + and to implement it for the entry types it knows about. For all + other entry types it should call its parent::PrintDirectoryData. + + \todo use 64 bit routines + \param entry the entry to print +*/ + +void AVIFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const +{ + static FOURCC lastStreamType = make_fourcc( " " ); + + if ( entry.type == make_fourcc( "avih" ) ) + { + + int i; + MainAVIHeader main_avi_header; + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &main_avi_header, sizeof( MainAVIHeader ) ) ); + + cout << " dwMicroSecPerFrame: " << ( int ) main_avi_header.dwMicroSecPerFrame << endl + << " dwMaxBytesPerSec: " << ( int ) main_avi_header.dwMaxBytesPerSec << endl + << " dwPaddingGranularity: " << ( int ) main_avi_header.dwPaddingGranularity << endl + << " dwFlags: " << ( int ) main_avi_header.dwFlags << endl + << " dwTotalFrames: " << ( int ) main_avi_header.dwTotalFrames << endl + << " dwInitialFrames: " << ( int ) main_avi_header.dwInitialFrames << endl + << " dwStreams: " << ( int ) main_avi_header.dwStreams << endl + << " dwSuggestedBufferSize: " << ( int ) main_avi_header.dwSuggestedBufferSize << endl + << " dwWidth: " << ( int ) main_avi_header.dwWidth << endl + << " dwHeight: " << ( int ) main_avi_header.dwHeight << endl; + for ( i = 0; i < 4; ++i ) + cout << " dwReserved[" << i << "]: " << ( int ) main_avi_header.dwReserved[ i ] << endl; + + } + else if ( entry.type == make_fourcc( "strh" ) ) + { + + AVIStreamHeader avi_stream_header; + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &avi_stream_header, sizeof( AVIStreamHeader ) ) ); + + lastStreamType = avi_stream_header.fccType; + + cout << " fccType: '" + << ((char *)&avi_stream_header.fccType)[0] + << ((char *)&avi_stream_header.fccType)[1] + << ((char *)&avi_stream_header.fccType)[2] + << ((char *)&avi_stream_header.fccType)[3] + << '\'' << endl + << " fccHandler: '" + << ((char *)&avi_stream_header.fccHandler)[0] + << ((char *)&avi_stream_header.fccHandler)[1] + << ((char *)&avi_stream_header.fccHandler)[2] + << ((char *)&avi_stream_header.fccHandler)[3] + << '\'' << endl + << " dwFlags: " << ( int ) avi_stream_header.dwFlags << endl + << " wPriority: " << ( int ) avi_stream_header.wPriority << endl + << " wLanguage: " << ( int ) avi_stream_header.wLanguage << endl + << " dwInitialFrames: " << ( int ) avi_stream_header.dwInitialFrames << endl + << " dwScale: " << ( int ) avi_stream_header.dwScale << endl + << " dwRate: " << ( int ) avi_stream_header.dwRate << endl + << " dwLength: " << ( int ) avi_stream_header.dwLength << endl + << " dwQuality: " << ( int ) avi_stream_header.dwQuality << endl + << " dwSampleSize: " << ( int ) avi_stream_header.dwSampleSize << endl; + + } + else if ( entry.type == make_fourcc( "indx" ) ) + { + + int i; + AVISuperIndex avi_super_index; + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &avi_super_index, sizeof( AVISuperIndex ) ) ); + + cout << " wLongsPerEntry: " << ( int ) avi_super_index.wLongsPerEntry + << endl + << " bIndexSubType: " << ( int ) avi_super_index.bIndexSubType << endl + << " bIndexType: " << ( int ) avi_super_index.bIndexType << endl + << " nEntriesInUse: " << ( int ) avi_super_index.nEntriesInUse << endl + << " dwChunkId: '" + << ((char *)&avi_super_index.dwChunkId)[0] + << ((char *)&avi_super_index.dwChunkId)[1] + << ((char *)&avi_super_index.dwChunkId)[2] + << ((char *)&avi_super_index.dwChunkId)[3] + << '\'' << endl + << " dwReserved[0]: " << ( int ) avi_super_index.dwReserved[ 0 ] << endl + << " dwReserved[1]: " << ( int ) avi_super_index.dwReserved[ 1 ] << endl + << " dwReserved[2]: " << ( int ) avi_super_index.dwReserved[ 2 ] << endl; + for ( i = 0; i < avi_super_index.nEntriesInUse; ++i ) + { + cout << ' ' << setw( 4 ) << setfill( ' ' ) << i + << ": qwOffset : 0x" << setw( 12 ) << setfill( '0' ) << hex << avi_super_index.aIndex[ i ].qwOffset << endl + << " dwSize : 0x" << setw( 8 ) << avi_super_index.aIndex[ i ].dwSize << endl + << " dwDuration : " << dec << avi_super_index.aIndex[ i ].dwDuration << endl; + } + } + else if ( entry.type == make_fourcc( "strf" ) ) + { + if ( lastStreamType == make_fourcc( "auds" ) ) + { + WAVEFORMATEX waveformatex; + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &waveformatex, sizeof( WAVEFORMATEX ) ) ); + cout << " waveformatex.wFormatTag : " << waveformatex.wFormatTag << endl; + cout << " waveformatex.nChannels : " << waveformatex.nChannels << endl; + cout << " waveformatex.nSamplesPerSec : " << waveformatex.nSamplesPerSec << endl; + cout << " waveformatex.nAvgBytesPerSec: " << waveformatex.nAvgBytesPerSec << endl; + cout << " waveformatex.nBlockAlign : " << waveformatex.nBlockAlign << endl; + cout << " waveformatex.wBitsPerSample : " << waveformatex.wBitsPerSample << endl; + cout << " waveformatex.cbSize : " << waveformatex.cbSize << endl; + } + else if ( lastStreamType == make_fourcc( "vids" ) ) + { + BITMAPINFOHEADER bitmapinfo; + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &bitmapinfo, sizeof( BITMAPINFOHEADER ) ) ); + cout << " bitmapinfo.biSize : " << bitmapinfo.biSize << endl; + cout << " bitmapinfo.biWidth : " << bitmapinfo.biWidth << endl; + cout << " bitmapinfo.biHeight : " << bitmapinfo.biHeight << endl; + cout << " bitmapinfo.biPlanes : " << bitmapinfo.biPlanes << endl; + cout << " bitmapinfo.biBitCount : " << bitmapinfo.biBitCount << endl; + cout << " bitmapinfo.biCompression : " << bitmapinfo.biCompression << endl; + cout << " bitmapinfo.biSizeImage : " << bitmapinfo.biSizeImage << endl; + cout << " bitmapinfo.biXPelsPerMeter: " << bitmapinfo.biXPelsPerMeter << endl; + cout << " bitmapinfo.biYPelsPerMeter: " << bitmapinfo.biYPelsPerMeter << endl; + cout << " bitmapinfo.biClrUsed : " << bitmapinfo.biClrUsed << endl; + cout << " bitmapinfo.biClrImportant : " << bitmapinfo.biClrImportant << endl; + } + else if ( lastStreamType == make_fourcc( "iavs" ) ) + { + DVINFO dvinfo; + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &dvinfo, sizeof( DVINFO ) ) ); + cout << " dvinfo.dwDVAAuxSrc : 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVAAuxSrc << endl; + cout << " dvinfo.dwDVAAuxCtl : 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVAAuxCtl << endl; + cout << " dvinfo.dwDVAAuxSrc1: 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVAAuxSrc1 << endl; + cout << " dvinfo.dwDVAAuxCtl1: 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVAAuxCtl1 << endl; + cout << " dvinfo.dwDVVAuxSrc : 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVVAuxSrc << endl; + cout << " dvinfo.dwDVVAuxCtl : 0x" << setw( 8 ) << setfill( '0' ) << hex << dvinfo.dwDVVAuxCtl << endl; + } + } + + /* This is the Standard Index. It is an array of offsets and + sizes relative to some start offset. */ + + else if ( ( entry.type == make_fourcc( "ix00" ) ) || ( entry.type == make_fourcc( "ix01" ) ) ) + { + + int i; + AVIStdIndex avi_std_index; + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, &avi_std_index, sizeof( AVIStdIndex ) ) ); + + cout << " wLongsPerEntry: " << ( int ) avi_std_index.wLongsPerEntry + << endl + << " bIndexSubType: " << ( int ) avi_std_index.bIndexSubType << endl + << " bIndexType: " << ( int ) avi_std_index.bIndexType << endl + << " nEntriesInUse: " << ( int ) avi_std_index.nEntriesInUse << endl + << " dwChunkId: '" + << ((char *)&avi_std_index.dwChunkId)[0] + << ((char *)&avi_std_index.dwChunkId)[1] + << ((char *)&avi_std_index.dwChunkId)[2] + << ((char *)&avi_std_index.dwChunkId)[3] + << '\'' << endl + << " qwBaseOffset: 0x" << setw( 12 ) << hex << avi_std_index.qwBaseOffset << endl + << " dwReserved: " << dec << ( int ) avi_std_index.dwReserved << endl; + for ( i = 0; i < avi_std_index.nEntriesInUse; ++i ) + { + cout << ' ' << setw( 4 ) << setfill( ' ' ) << i + << ": dwOffset : 0x" << setw( 8 ) << setfill( '0' ) << hex << avi_std_index.aIndex[ i ].dwOffset + << " (0x" << setw( 12 ) << avi_std_index.qwBaseOffset + avi_std_index.aIndex[ i ].dwOffset << ')' << endl + << " dwSize : 0x" << setw( 8 ) << avi_std_index.aIndex[ i ].dwSize << dec << endl; + } + + } + else if ( entry.type == make_fourcc( "idx1" ) ) + { + + int i; + int numEntries = entry.length / sizeof( int ) / 4; + DWORD *idx1 = new DWORD[ numEntries * 4 ]; + // FOURCC movi_list = FindDirectoryEntry(make_fourcc("movi")); + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, idx1, entry.length ) ); + + for ( i = 0; i < numEntries; ++i ) + { + + cout << ' ' << setw( 4 ) << setfill( ' ' ) << i << setfill( '0' ) << ": dwChunkId : '" + << ((char *)&idx1[ i * 4 + 0 ])[0] + << ((char *)&idx1[ i * 4 + 0 ])[1] + << ((char *)&idx1[ i * 4 + 0 ])[2] + << ((char *)&idx1[ i * 4 + 0 ])[3] + << '\'' << endl + << " dwType : 0x" << setw( 8 ) << hex << idx1[ i * 4 + 1 ] << endl + << " dwOffset : 0x" << setw( 8 ) << idx1[ i * 4 + 2 ] << endl + // << " (0x" << setw(8) << idx1[i * 4 + 2] + GetDirectoryEntry(movi_list).offset << ')' << endl + << " dwSize : 0x" << setw( 8 ) << idx1[ i * 4 + 3 ] << dec << endl; + } + + delete[] idx1; + } + else if ( entry.type == make_fourcc( "dmlh" ) ) + { + int i; + int numEntries = entry.length / sizeof( int ); + DWORD *dmlh = new DWORD[ numEntries ]; + + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, dmlh, entry.length ) ); + + for ( i = 0; i < numEntries; ++i ) + { + cout << ' ' << setw( 4 ) << setfill( ' ' ) << i << setfill( '0' ) << ": " + << " dwTotalFrames: 0x" << setw( 8 ) << hex << dmlh[ i ] + << " (" << dec << dmlh[ i ] << ")" << endl; + } + delete[] dmlh; + } +} + + +/** If this is not a movi list, read its contents + +*/ + +void AVIFile::ParseList( int parent ) +{ + FOURCC type; + FOURCC name; + DWORD length; + int list; + off_t pos; + off_t listEnd; + + /* Read in the chunk header (type and length). */ + fail_neg( read( fd, &type, sizeof( type ) ) ); + fail_neg( read( fd, &length, sizeof( length ) ) ); + if ( length & 1 ) + length++; + + /* The contents of the list starts here. Obtain its offset. The list + name (4 bytes) is already part of the contents). */ + + pos = lseek( fd, 0, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + fail_neg( read( fd, &name, sizeof( name ) ) ); + + /* if we encounter a movi list, do not read it. It takes too much time + and we don't need it anyway. */ + + if ( name != make_fourcc( "movi" ) ) + { + // if (1) { + + /* Add an entry for this list. */ + list = AddDirectoryEntry( type, name, sizeof( name ), parent ); + + /* Read in any chunks contained in this list. This list is the + parent for all chunks it contains. */ + + listEnd = pos + length; + while ( pos < listEnd ) + { + ParseChunk( list ); + pos = lseek( fd, 0, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + } + } + else + { + /* Add an entry for this list. */ + + movi_list = AddDirectoryEntry( type, name, length, parent ); + + pos = lseek( fd, length - 4, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + } +} + + +void AVIFile::ParseRIFF() +{ + RIFFFile::ParseRIFF(); + + avih_chunk = FindDirectoryEntry( make_fourcc( "avih" ) ); + if ( avih_chunk != -1 ) + ReadChunk( avih_chunk, ( void* ) & mainHdr, sizeof( MainAVIHeader ) ); +} + + +void AVIFile::ReadIndex() +{ + indx_chunk[ 0 ] = FindDirectoryEntry( make_fourcc( "indx" ) ); + if ( indx_chunk[ 0 ] != -1 ) + { + ReadChunk( indx_chunk[ 0 ], ( void* ) indx[ 0 ], sizeof( AVISuperIndex ) ); + index_type = AVI_LARGE_INDEX; + + /* recalc number of frames from each index */ + mainHdr.dwTotalFrames = 0; + for ( int i = 0; + i < indx[ 0 ] ->nEntriesInUse; + mainHdr.dwTotalFrames += indx[ 0 ] ->aIndex[ i++ ].dwDuration ) + ; + return ; + } + idx1_chunk = FindDirectoryEntry( make_fourcc( "idx1" ) ); + if ( idx1_chunk != -1 ) + { + ReadChunk( idx1_chunk, ( void* ) idx1, sizeof( AVISuperIndex ) ); + idx1->nEntriesInUse = GetDirectoryEntry( idx1_chunk ).length / 16; + index_type = AVI_SMALL_INDEX; + + /* recalc number of frames from the simple index */ + int frameNumIndex = 0; + FOURCC chunkID1 = make_fourcc( "00dc" ); + FOURCC chunkID2 = make_fourcc( "00db" ); + for ( int i = 0; i < idx1->nEntriesInUse; ++i ) + { + if ( idx1->aIndex[ i ].dwChunkId == chunkID1 || + idx1->aIndex[ i ].dwChunkId == chunkID2 ) + { + ++frameNumIndex; + } + } + mainHdr.dwTotalFrames = frameNumIndex; + return ; + } +} + + +void AVIFile::FlushIndx( int stream ) +{ + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + int i; + + /* Write out the previous index. When this function is + entered for the first time, there is no index to + write. Note: this may be an expensive operation + because of a time consuming seek to the former file + position. */ + + if ( ix_chunk[ stream ] != -1 ) + WriteChunk( ix_chunk[ stream ], ix[ stream ] ); + + /* make a new ix chunk. */ + + if ( stream == 0 ) + type = make_fourcc( "ix00" ); + else + type = make_fourcc( "ix01" ); + ix_chunk[ stream ] = AddDirectoryEntry( type, 0, sizeof( AVIStdIndex ), movi_list ); + GetDirectoryEntry( ix_chunk[ stream ], type, name, length, offset, parent ); + + /* fill out all required fields. The offsets in the + array are relative to qwBaseOffset, so fill in the + offset to the next free location in the file + there. */ + + ix[ stream ] ->wLongsPerEntry = 2; + ix[ stream ] ->bIndexSubType = 0; + ix[ stream ] ->bIndexType = KINO_AVI_INDEX_OF_CHUNKS; + ix[ stream ] ->nEntriesInUse = 0; + ix[ stream ] ->dwChunkId = indx[ stream ] ->dwChunkId; + ix[ stream ] ->qwBaseOffset = offset + length; + ix[ stream ] ->dwReserved = 0; + + for ( i = 0; i < IX00_INDEX_SIZE; ++i ) + { + ix[ stream ] ->aIndex[ i ].dwOffset = 0; + ix[ stream ] ->aIndex[ i ].dwSize = 0; + } + + /* add a reference to this new index in our super + index. */ + + i = indx[ stream ] ->nEntriesInUse++; + indx[ stream ] ->aIndex[ i ].qwOffset = offset - RIFF_HEADERSIZE; + indx[ stream ] ->aIndex[ i ].dwSize = length + RIFF_HEADERSIZE; + indx[ stream ] ->aIndex[ i ].dwDuration = 0; +} + + +void AVIFile::UpdateIndx( int stream, int chunk, int duration ) +{ + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + int i; + + /* update the appropiate entry in the super index. It reflects + the number of frames in the referenced index. */ + + i = indx[ stream ] ->nEntriesInUse - 1; + indx[ stream ] ->aIndex[ i ].dwDuration += duration; + + /* update the standard index. Calculate the file position of + the new frame. */ + + GetDirectoryEntry( chunk, type, name, length, offset, parent ); + + indx[ stream ] ->dwChunkId = type; + i = ix[ stream ] ->nEntriesInUse++; + ix[ stream ] ->aIndex[ i ].dwOffset = offset - ix[ stream ] ->qwBaseOffset; + ix[ stream ] ->aIndex[ i ].dwSize = length; +} + + +void AVIFile::UpdateIdx1( int chunk, int flags ) +{ + if ( idx1->nEntriesInUse < 20000 ) + { + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + + GetDirectoryEntry( chunk, type, name, length, offset, parent ); + + idx1->aIndex[ idx1->nEntriesInUse ].dwChunkId = type; + idx1->aIndex[ idx1->nEntriesInUse ].dwFlags = flags; + idx1->aIndex[ idx1->nEntriesInUse ].dwOffset = offset - GetDirectoryEntry( movi_list ).offset - RIFF_HEADERSIZE; + idx1->aIndex[ idx1->nEntriesInUse ].dwSize = length; + idx1->nEntriesInUse++; + } +} + +bool AVIFile::verifyStreamFormat( FOURCC type ) +{ + int i, j = 0; + AVIStreamHeader avi_stream_header; + BITMAPINFOHEADER bih; + FOURCC strh = make_fourcc( "strh" ); + FOURCC strf = make_fourcc( "strf" ); + + while ( ( i = FindDirectoryEntry( strh, j++ ) ) != -1 ) + { + ReadChunk( i, ( void* ) & avi_stream_header, sizeof( AVIStreamHeader ) ); + if ( avi_stream_header.fccHandler == type ) + return true; + } + j = 0; + while ( ( i = FindDirectoryEntry( strf, j++ ) ) != -1 ) + { + ReadChunk( i, ( void* ) & bih, sizeof( BITMAPINFOHEADER ) ); + if ( ( FOURCC ) bih.biCompression == type ) + return true; + } + + return false; +} + +bool AVIFile::verifyStream( FOURCC type ) +{ + int i, j = 0; + AVIStreamHeader avi_stream_header; + FOURCC strh = make_fourcc( "strh" ); + + while ( ( i = FindDirectoryEntry( strh, j++ ) ) != -1 ) + { + ReadChunk( i, ( void* ) & avi_stream_header, sizeof( AVIStreamHeader ) ); + if ( avi_stream_header.fccType == type ) + return true; + } + return false; +} + +bool AVIFile::isOpenDML( void ) +{ + int i, j = 0; + FOURCC dmlh = make_fourcc( "dmlh" ); + + while ( ( i = FindDirectoryEntry( dmlh, j++ ) ) != -1 ) + { + return true; + } + return false; +} + +AVI1File::AVI1File() : AVIFile() +{} + + +AVI1File::~AVI1File() +{} + + +/* Initialize the AVI structure to its initial state, either for PAL + or NTSC format */ + +void AVI1File::Init( int format, int sampleFrequency, int indexType ) +{ + int num_blocks; + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + + assert( ( format == AVI_PAL ) || ( format == AVI_NTSC ) ); + + AVIFile::Init( format, sampleFrequency, indexType ); + + switch ( format ) + { + case AVI_PAL: + mainHdr.dwWidth = 720; + mainHdr.dwHeight = 576; + + streamHdr[ 0 ].dwScale = 1; + streamHdr[ 0 ].dwRate = 25; + streamHdr[ 0 ].dwSuggestedBufferSize = 144008; + + /* initialize the 'strf' chunk */ + + /* Meaning of the DV stream format chunk per Microsoft + dwDVAAuxSrc + Specifies the Audio Auxiliary Data Source Pack for the first audio block + (first 5 DV DIF sequences for 525-60 systems or 6 DV DIF sequences for 625-50 systems) of + a frame. A DIF sequence is a data block that contains 150 DIF blocks. A DIF block consists + of 80 bytes. The Audio Auxiliary Data Source Pack is defined in section D.7.1 of Part 2, + Annex D, "The Pack Header Table and Contents of Packs" of the Specification of + Consumer-use Digital VCRs. + dwDVAAuxCtl + Specifies the Audio Auxiliary Data Source Control Pack for the first audio block of a + frame. The Audio Auxiliary Data Control Pack is defined in section D.7.2 of Part 2, + Annex D, "The Pack Header Table and Contents of Packs" of the Specification of + Consumer-use Digital VCRs. + dwDVAAuxSrc1 + Specifies the Audio Auxiliary Data Source Pack for the second audio block + (second 5 DV DIF sequences for 525-60 systems or 6 DV DIF sequences for 625-50 systems) of a frame. + dwDVAAuxCtl1 + Specifies the Audio Auxiliary Data Source Control Pack for the second audio block of a frame. + dwDVVAuxSrc + Specifies the Video Auxiliary Data Source Pack as defined in section D.8.1 of Part 2, + Annex D, "The Pack Header Table and Contents of Packs" of the Specification of + Consumer-use Digital VCRs. + dwDVVAuxCtl + Specifies the Video Auxiliary Data Source Control Pack as defined in section D.8.2 of Part 2, + Annex D, "The Pack Header Table and Contents of Packs" of the Specification of + Consumer-use Digital VCRs. + dwDVReserved[2] + Reserved. Set this array to zero. + */ + + dvinfo.dwDVAAuxSrc = 0xd1e030d0; + dvinfo.dwDVAAuxCtl = 0xffa0cf3f; + dvinfo.dwDVAAuxSrc1 = 0xd1e03fd0; + dvinfo.dwDVAAuxCtl1 = 0xffa0cf3f; + dvinfo.dwDVVAuxSrc = 0xff20ffff; + dvinfo.dwDVVAuxCtl = 0xfffdc83f; + dvinfo.dwDVReserved[ 0 ] = 0; + dvinfo.dwDVReserved[ 1 ] = 0; + break; + + case AVI_NTSC: + mainHdr.dwWidth = 720; + mainHdr.dwHeight = 480; + + streamHdr[ 0 ].dwScale = 1001; + streamHdr[ 0 ].dwRate = 30000; + streamHdr[ 0 ].dwSuggestedBufferSize = 120008; + + /* initialize the 'strf' chunk */ + dvinfo.dwDVAAuxSrc = 0xc0c000c0; + dvinfo.dwDVAAuxCtl = 0xffa0cf3f; + dvinfo.dwDVAAuxSrc1 = 0xc0c001c0; + dvinfo.dwDVAAuxCtl1 = 0xffa0cf3f; + dvinfo.dwDVVAuxSrc = 0xff80ffff; + dvinfo.dwDVVAuxCtl = 0xfffcc83f; + dvinfo.dwDVReserved[ 0 ] = 0; + dvinfo.dwDVReserved[ 1 ] = 0; + break; + + default: /* no default allowed */ + assert( 0 ); + break; + } + + indx[ 0 ] ->dwChunkId = make_fourcc( "00__" ); + + /* Initialize the 'strh' chunk */ + + streamHdr[ 0 ].fccType = make_fourcc( "iavs" ); + streamHdr[ 0 ].fccHandler = make_fourcc( "dvsd" ); + streamHdr[ 0 ].dwFlags = 0; + streamHdr[ 0 ].wPriority = 0; + streamHdr[ 0 ].wLanguage = 0; + streamHdr[ 0 ].dwInitialFrames = 0; + streamHdr[ 0 ].dwStart = 0; + streamHdr[ 0 ].dwLength = 0; + streamHdr[ 0 ].dwQuality = 0; + streamHdr[ 0 ].dwSampleSize = 0; + streamHdr[ 0 ].rcFrame.top = 0; + streamHdr[ 0 ].rcFrame.bottom = 0; + streamHdr[ 0 ].rcFrame.left = 0; + streamHdr[ 0 ].rcFrame.right = 0; + + /* This is a simple directory structure setup. For details see the + "OpenDML AVI File Format Extensions" document. + + An AVI file contains basically two types of objects, a + "chunk" and a "list" object. The list object contains any + number of chunks. Since a list is also a chunk, it is + possible to create a hierarchical "list of lists" + structure. + + Every AVI file starts with a "RIFF" object, which is a list + of several other required objects. The actual DV data is + contained in a "movi" list, each frame is in its own chunk. + + Old AVI files (pre OpenDML V. 1.02) contain only one RIFF + chunk of less than 1 GByte size per file. The current + format which allow for almost arbitrary sizes can contain + several RIFF chunks of less than 1 GByte size. Old software + however would only deal with the first RIFF chunk. + + Note that the first entry (FILE) isn´t actually part + of the AVI file. I use this (pseudo-) directory entry to + keep track of the RIFF chunks and their positions in the + AVI file. + */ + + /* Create the container directory entry */ + + file_list = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT ); + + /* Create a basic directory structure. Only chunks defined from here on will be written to the AVI file. */ + + riff_list = AddDirectoryEntry( make_fourcc( "RIFF" ), make_fourcc( "AVI " ), RIFF_LISTSIZE, file_list ); + hdrl_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "hdrl" ), RIFF_LISTSIZE, riff_list ); + avih_chunk = AddDirectoryEntry( make_fourcc( "avih" ), 0, sizeof( MainAVIHeader ), hdrl_list ); + strl_list[ 0 ] = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "strl" ), RIFF_LISTSIZE, hdrl_list ); + strh_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "strh" ), 0, sizeof( AVIStreamHeader ), strl_list[ 0 ] ); + strf_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "strf" ), 0, sizeof( dvinfo ), strl_list[ 0 ] ); + if ( index_type & AVI_LARGE_INDEX ) + indx_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "indx" ), 0, sizeof( AVISuperIndex ), strl_list[ 0 ] ); + + odml_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "odml" ), RIFF_LISTSIZE, hdrl_list ); + dmlh_chunk = AddDirectoryEntry( make_fourcc( "dmlh" ), 0, 0x00f8, odml_list ); + + /* align movi list to block */ + GetDirectoryEntry( hdrl_list, type, name, length, offset, parent ); + num_blocks = length / PADDING_SIZE + 1; + length = num_blocks * PADDING_SIZE - length - 5 * RIFF_HEADERSIZE; // why 5? + junk_chunk = AddDirectoryEntry( make_fourcc( "JUNK" ), 0, length, riff_list ); + + movi_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "movi" ), RIFF_LISTSIZE, riff_list ); + + /* The ix00 chunk will be added dynamically to the movi_list in avi_write_frame + as needed */ + + ix_chunk[ 0 ] = -1; +} + + +/* Write a DV video frame. This is somewhat complex... */ + +#if 0 +bool AVI1File::WriteFrame( const Frame &frame ) +{ + int frame_chunk; + int junk_chunk; + int num_blocks; + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + + /* exit if no large index and 1GB reached */ + if ( !( index_type & AVI_LARGE_INDEX ) && isUpdateIdx1 == false ) + return false; + + /* Check if we need a new ix00 Standard Index. It has a + capacity of IX00_INDEX_SIZE frames. Whenever we exceed that + number, we need a new index. The new ix00 chunk is also + part of the movi list. */ + + if ( ( index_type & AVI_LARGE_INDEX ) && ( ( ( streamHdr[ 0 ].dwLength - 0 ) % IX00_INDEX_SIZE ) == 0 ) ) + FlushIndx( 0 ); + + /* Write the DV frame data. + + Make a new 00__ chunk for the new frame, write out the + frame. */ + + frame_chunk = AddDirectoryEntry( make_fourcc( "00__" ), 0, frame.GetFrameSize(), movi_list ); + if ( ( index_type & AVI_LARGE_INDEX ) && ( streamHdr[ 0 ].dwLength % IX00_INDEX_SIZE ) == 0 ) + { + GetDirectoryEntry( frame_chunk, type, name, length, offset, parent ); + ix[ 0 ] ->qwBaseOffset = offset - RIFF_HEADERSIZE; + } + WriteChunk( frame_chunk, frame.data ); + // num_blocks = (frame.GetFrameSize() + RIFF_HEADERSIZE) / PADDING_SIZE + 1; + // length = num_blocks * PADDING_SIZE - frame.GetFrameSize() - 2 * RIFF_HEADERSIZE; + // junk_chunk = AddDirectoryEntry(make_fourcc("JUNK"), 0, length, movi_list); + // WriteChunk(junk_chunk, g_zeroes); + + if ( index_type & AVI_LARGE_INDEX ) + UpdateIndx( 0, frame_chunk, 1 ); + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + UpdateIdx1( frame_chunk, 0x10 ); + + /* update some variables with the new frame count. */ + + if ( isUpdateIdx1 ) + ++mainHdr.dwTotalFrames; + ++streamHdr[ 0 ].dwLength; + ++dmlh[ 0 ]; + + /* Find out if the current riff list is close to 1 GByte in + size. If so, start a new (extended) RIFF. The only allowed + item in the new RIFF chunk is a movi list (with video + frames and indexes as usual). */ + + GetDirectoryEntry( riff_list, type, name, length, offset, parent ); + if ( length > 0x3f000000 ) + { + /* write idx1 only once and before end of first GB */ + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + { + int idx1_chunk = AddDirectoryEntry( make_fourcc( "idx1" ), 0, idx1->nEntriesInUse * 16, riff_list ); + WriteChunk( idx1_chunk, ( void* ) idx1 ); + } + isUpdateIdx1 = false; + + if ( index_type & AVI_LARGE_INDEX ) + { + /* pad out to 1GB */ + //GetDirectoryEntry(riff_list, type, name, length, offset, parent); + //junk_chunk = AddDirectoryEntry(make_fourcc("JUNK"), 0, PADDING_1GB - length - 5 * RIFF_HEADERSIZE, riff_list); + //WriteChunk(junk_chunk, g_zeroes); + + /* padding for alignment */ + GetDirectoryEntry( riff_list, type, name, length, offset, parent ); + num_blocks = ( length + 4 * RIFF_HEADERSIZE ) / PADDING_SIZE + 1; + length = ( num_blocks * PADDING_SIZE ) - length - 4 * RIFF_HEADERSIZE - 2 * RIFF_LISTSIZE; + if ( length > 0 ) + { + junk_chunk = AddDirectoryEntry( make_fourcc( "JUNK" ), 0, length, riff_list ); + WriteChunk( junk_chunk, g_zeroes ); + } + + riff_list = AddDirectoryEntry( make_fourcc( "RIFF" ), make_fourcc( "AVIX" ), RIFF_LISTSIZE, file_list ); + movi_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "movi" ), RIFF_LISTSIZE, riff_list ); + } + } + return true; +} +#endif + +void AVI1File::WriteRIFF() +{ + + WriteChunk( avih_chunk, ( void* ) & mainHdr ); + WriteChunk( strh_chunk[ 0 ], ( void* ) & streamHdr[ 0 ] ); + WriteChunk( strf_chunk[ 0 ], ( void* ) & dvinfo ); + WriteChunk( dmlh_chunk, ( void* ) & dmlh ); + + if ( index_type & AVI_LARGE_INDEX ) + { + WriteChunk( indx_chunk[ 0 ], ( void* ) indx[ 0 ] ); + WriteChunk( ix_chunk[ 0 ], ( void* ) ix[ 0 ] ); + } + + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + { + int idx1_chunk = AddDirectoryEntry( make_fourcc( "idx1" ), 0, idx1->nEntriesInUse * 16, riff_list ); + WriteChunk( idx1_chunk, ( void* ) idx1 ); + } + + RIFFFile::WriteRIFF(); +} + + +AVI2File::AVI2File() : AVIFile() +{} + + +AVI2File::~AVI2File() +{} + + +/* Initialize the AVI structure to its initial state, either for PAL + or NTSC format */ + +void AVI2File::Init( int format, int sampleFrequency, int indexType ) +{ + int num_blocks; + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + + assert( ( format == AVI_PAL ) || ( format == AVI_NTSC ) ); + + AVIFile::Init( format, sampleFrequency, indexType ); + + switch ( format ) + { + + case AVI_PAL: + mainHdr.dwStreams = 2; + mainHdr.dwWidth = 720; + mainHdr.dwHeight = 576; + + /* Initialize the 'strh' chunk */ + + streamHdr[ 0 ].fccType = make_fourcc( "vids" ); + streamHdr[ 0 ].fccHandler = make_fourcc( "dvsd" ); + streamHdr[ 0 ].dwFlags = 0; + streamHdr[ 0 ].wPriority = 0; + streamHdr[ 0 ].wLanguage = 0; + streamHdr[ 0 ].dwInitialFrames = 0; + streamHdr[ 0 ].dwScale = 1; + streamHdr[ 0 ].dwRate = 25; + streamHdr[ 0 ].dwStart = 0; + streamHdr[ 0 ].dwLength = 0; + streamHdr[ 0 ].dwSuggestedBufferSize = 144008; + streamHdr[ 0 ].dwQuality = -1; + streamHdr[ 0 ].dwSampleSize = 0; + streamHdr[ 0 ].rcFrame.top = 0; + streamHdr[ 0 ].rcFrame.bottom = 0; + streamHdr[ 0 ].rcFrame.left = 0; + streamHdr[ 0 ].rcFrame.right = 0; + + bitmapinfo.biSize = sizeof( bitmapinfo ); + bitmapinfo.biWidth = 720; + bitmapinfo.biHeight = 576; + bitmapinfo.biPlanes = 1; + bitmapinfo.biBitCount = 24; + bitmapinfo.biCompression = make_fourcc( "dvsd" ); + bitmapinfo.biSizeImage = 144000; + bitmapinfo.biXPelsPerMeter = 0; + bitmapinfo.biYPelsPerMeter = 0; + bitmapinfo.biClrUsed = 0; + bitmapinfo.biClrImportant = 0; + + streamHdr[ 1 ].fccType = make_fourcc( "auds" ); + streamHdr[ 1 ].fccHandler = 0; + streamHdr[ 1 ].dwFlags = 0; + streamHdr[ 1 ].wPriority = 0; + streamHdr[ 1 ].wLanguage = 0; + streamHdr[ 1 ].dwInitialFrames = 0; + streamHdr[ 1 ].dwScale = 2 * 2; + streamHdr[ 1 ].dwRate = sampleFrequency * 2 * 2; + streamHdr[ 1 ].dwStart = 0; + streamHdr[ 1 ].dwLength = 0; + streamHdr[ 1 ].dwSuggestedBufferSize = 8192; + streamHdr[ 1 ].dwQuality = -1; + streamHdr[ 1 ].dwSampleSize = 2 * 2; + streamHdr[ 1 ].rcFrame.top = 0; + streamHdr[ 1 ].rcFrame.bottom = 0; + streamHdr[ 1 ].rcFrame.left = 0; + streamHdr[ 1 ].rcFrame.right = 0; + + break; + + case AVI_NTSC: + mainHdr.dwTotalFrames = 0; + mainHdr.dwStreams = 2; + mainHdr.dwWidth = 720; + mainHdr.dwHeight = 480; + + /* Initialize the 'strh' chunk */ + + streamHdr[ 0 ].fccType = make_fourcc( "vids" ); + streamHdr[ 0 ].fccHandler = make_fourcc( "dvsd" ); + streamHdr[ 0 ].dwFlags = 0; + streamHdr[ 0 ].wPriority = 0; + streamHdr[ 0 ].wLanguage = 0; + streamHdr[ 0 ].dwInitialFrames = 0; + streamHdr[ 0 ].dwScale = 1001; + streamHdr[ 0 ].dwRate = 30000; + streamHdr[ 0 ].dwStart = 0; + streamHdr[ 0 ].dwLength = 0; + streamHdr[ 0 ].dwSuggestedBufferSize = 120008; + streamHdr[ 0 ].dwQuality = -1; + streamHdr[ 0 ].dwSampleSize = 0; + streamHdr[ 0 ].rcFrame.top = 0; + streamHdr[ 0 ].rcFrame.bottom = 0; + streamHdr[ 0 ].rcFrame.left = 0; + streamHdr[ 0 ].rcFrame.right = 0; + + bitmapinfo.biSize = sizeof( bitmapinfo ); + bitmapinfo.biWidth = 720; + bitmapinfo.biHeight = 480; + bitmapinfo.biPlanes = 1; + bitmapinfo.biBitCount = 24; + bitmapinfo.biCompression = make_fourcc( "dvsd" ); + bitmapinfo.biSizeImage = 120000; + bitmapinfo.biXPelsPerMeter = 0; + bitmapinfo.biYPelsPerMeter = 0; + bitmapinfo.biClrUsed = 0; + bitmapinfo.biClrImportant = 0; + + streamHdr[ 1 ].fccType = make_fourcc( "auds" ); + streamHdr[ 1 ].fccHandler = 0; + streamHdr[ 1 ].dwFlags = 0; + streamHdr[ 1 ].wPriority = 0; + streamHdr[ 1 ].wLanguage = 0; + streamHdr[ 1 ].dwInitialFrames = 1; + streamHdr[ 1 ].dwScale = 2 * 2; + streamHdr[ 1 ].dwRate = sampleFrequency * 2 * 2; + streamHdr[ 1 ].dwStart = 0; + streamHdr[ 1 ].dwLength = 0; + streamHdr[ 1 ].dwSuggestedBufferSize = 8192; + streamHdr[ 1 ].dwQuality = 0; + streamHdr[ 1 ].dwSampleSize = 2 * 2; + streamHdr[ 1 ].rcFrame.top = 0; + streamHdr[ 1 ].rcFrame.bottom = 0; + streamHdr[ 1 ].rcFrame.left = 0; + streamHdr[ 1 ].rcFrame.right = 0; + + break; + } + waveformatex.wFormatTag = 1; + waveformatex.nChannels = 2; + waveformatex.nSamplesPerSec = sampleFrequency; + waveformatex.nAvgBytesPerSec = sampleFrequency * 2 * 2; + waveformatex.nBlockAlign = 4; + waveformatex.wBitsPerSample = 16; + waveformatex.cbSize = 0; + + file_list = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT ); + + /* Create a basic directory structure. Only chunks defined from here on will be written to the AVI file. */ + + riff_list = AddDirectoryEntry( make_fourcc( "RIFF" ), make_fourcc( "AVI " ), RIFF_LISTSIZE, file_list ); + hdrl_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "hdrl" ), RIFF_LISTSIZE, riff_list ); + avih_chunk = AddDirectoryEntry( make_fourcc( "avih" ), 0, sizeof( MainAVIHeader ), hdrl_list ); + + strl_list[ 0 ] = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "strl" ), RIFF_LISTSIZE, hdrl_list ); + strh_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "strh" ), 0, sizeof( AVIStreamHeader ), strl_list[ 0 ] ); + strf_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "strf" ), 0, sizeof( BITMAPINFOHEADER ), strl_list[ 0 ] ); + if ( index_type & AVI_LARGE_INDEX ) + { + indx_chunk[ 0 ] = AddDirectoryEntry( make_fourcc( "indx" ), 0, sizeof( AVISuperIndex ), strl_list[ 0 ] ); + ix_chunk[ 0 ] = -1; + indx[ 0 ] ->dwChunkId = make_fourcc( "00dc" ); + } + + strl_list[ 1 ] = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "strl" ), RIFF_LISTSIZE, hdrl_list ); + strh_chunk[ 1 ] = AddDirectoryEntry( make_fourcc( "strh" ), 0, sizeof( AVIStreamHeader ), strl_list[ 1 ] ); + strf_chunk[ 1 ] = AddDirectoryEntry( make_fourcc( "strf" ), 0, sizeof( WAVEFORMATEX ) - 2, strl_list[ 1 ] ); + junk_chunk = AddDirectoryEntry( make_fourcc( "JUNK" ), 0, 2, strl_list[ 1 ] ); + if ( index_type & AVI_LARGE_INDEX ) + { + indx_chunk[ 1 ] = AddDirectoryEntry( make_fourcc( "indx" ), 0, sizeof( AVISuperIndex ), strl_list[ 1 ] ); + ix_chunk[ 1 ] = -1; + indx[ 1 ] ->dwChunkId = make_fourcc( "01wb" ); + + odml_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "odml" ), RIFF_LISTSIZE, hdrl_list ); + dmlh_chunk = AddDirectoryEntry( make_fourcc( "dmlh" ), 0, 0x00f8, odml_list ); + } + + /* align movi list to block */ + GetDirectoryEntry( hdrl_list, type, name, length, offset, parent ); + num_blocks = length / PADDING_SIZE + 1; + length = num_blocks * PADDING_SIZE - length - 5 * RIFF_HEADERSIZE; // why 5 headers? + junk_chunk = AddDirectoryEntry( make_fourcc( "JUNK" ), 0, length, riff_list ); + + movi_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "movi" ), RIFF_LISTSIZE, riff_list ); + + idx1->aIndex[ idx1->nEntriesInUse ].dwChunkId = make_fourcc( "7Fxx" ); + idx1->aIndex[ idx1->nEntriesInUse ].dwFlags = 0; + idx1->aIndex[ idx1->nEntriesInUse ].dwOffset = 0; + idx1->aIndex[ idx1->nEntriesInUse ].dwSize = 0; + idx1->nEntriesInUse++; +} + + +void AVI2File::WriteRIFF() +{ + WriteChunk( avih_chunk, ( void* ) & mainHdr ); + WriteChunk( strh_chunk[ 0 ], ( void* ) & streamHdr[ 0 ] ); + WriteChunk( strf_chunk[ 0 ], ( void* ) & bitmapinfo ); + if ( index_type & AVI_LARGE_INDEX ) + { + WriteChunk( dmlh_chunk, ( void* ) & dmlh ); + WriteChunk( indx_chunk[ 0 ], ( void* ) indx[ 0 ] ); + WriteChunk( ix_chunk[ 0 ], ( void* ) ix[ 0 ] ); + } + WriteChunk( strh_chunk[ 1 ], ( void* ) & streamHdr[ 1 ] ); + WriteChunk( strf_chunk[ 1 ], ( void* ) & waveformatex ); + if ( index_type & AVI_LARGE_INDEX ) + { + WriteChunk( indx_chunk[ 1 ], ( void* ) indx[ 1 ] ); + WriteChunk( ix_chunk[ 1 ], ( void* ) ix[ 1 ] ); + } + + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + { + int idx1_chunk = AddDirectoryEntry( make_fourcc( "idx1" ), 0, idx1->nEntriesInUse * 16, riff_list ); + WriteChunk( idx1_chunk, ( void* ) idx1 ); + } + RIFFFile::WriteRIFF(); +} + + +/** Write a DV video frame + + \param frame the frame to write +*/ + +#if 0 +bool AVI2File::WriteFrame( const Frame &frame ) +{ + int audio_chunk; + int frame_chunk; + int junk_chunk; + char soundbuf[ 20000 ]; + int audio_size; + int num_blocks; + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + + /* exit if no large index and 1GB reached */ + if ( !( index_type & AVI_LARGE_INDEX ) && isUpdateIdx1 == false ) + return false; + + /* Check if we need a new ix00 Standard Index. It has a + capacity of IX00_INDEX_SIZE frames. Whenever we exceed that + number, we need a new index. The new ix00 chunk is also + part of the movi list. */ + + if ( ( index_type & AVI_LARGE_INDEX ) && ( ( ( streamHdr[ 0 ].dwLength - 0 ) % IX00_INDEX_SIZE ) == 0 ) ) + { + FlushIndx( 0 ); + FlushIndx( 1 ); + } + + /* Write audio data if we have it */ + + audio_size = frame.ExtractAudio( soundbuf ); + if ( audio_size > 0 ) + { + audio_chunk = AddDirectoryEntry( make_fourcc( "01wb" ), 0, audio_size, movi_list ); + if ( ( index_type & AVI_LARGE_INDEX ) && ( streamHdr[ 0 ].dwLength % IX00_INDEX_SIZE ) == 0 ) + { + GetDirectoryEntry( audio_chunk, type, name, length, offset, parent ); + ix[ 1 ] ->qwBaseOffset = offset - RIFF_HEADERSIZE; + } + WriteChunk( audio_chunk, soundbuf ); + // num_blocks = (audio_size + RIFF_HEADERSIZE) / PADDING_SIZE + 1; + // length = num_blocks * PADDING_SIZE - audio_size - 2 * RIFF_HEADERSIZE; + // junk_chunk = AddDirectoryEntry(make_fourcc("JUNK"), 0, length, movi_list); + // WriteChunk(junk_chunk, g_zeroes); + if ( index_type & AVI_LARGE_INDEX ) + UpdateIndx( 1, audio_chunk, audio_size / waveformatex.nChannels / 2 ); + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + UpdateIdx1( audio_chunk, 0x00 ); + streamHdr[ 1 ].dwLength += audio_size / waveformatex.nChannels / 2; + + } + + /* Write video data */ + + frame_chunk = AddDirectoryEntry( make_fourcc( "00dc" ), 0, frame.GetFrameSize(), movi_list ); + if ( ( index_type & AVI_LARGE_INDEX ) && ( streamHdr[ 0 ].dwLength % IX00_INDEX_SIZE ) == 0 ) + { + GetDirectoryEntry( frame_chunk, type, name, length, offset, parent ); + ix[ 0 ] ->qwBaseOffset = offset - RIFF_HEADERSIZE; + } + WriteChunk( frame_chunk, frame.data ); + // num_blocks = (frame.GetFrameSize() + RIFF_HEADERSIZE) / PADDING_SIZE + 1; + // length = num_blocks * PADDING_SIZE - frame.GetFrameSize() - 2 * RIFF_HEADERSIZE; + // junk_chunk = AddDirectoryEntry(make_fourcc("JUNK"), 0, length, movi_list); + // WriteChunk(junk_chunk, g_zeroes); + if ( index_type & AVI_LARGE_INDEX ) + UpdateIndx( 0, frame_chunk, 1 ); + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + UpdateIdx1( frame_chunk, 0x10 ); + + /* update some variables with the new frame count. */ + + if ( isUpdateIdx1 ) + ++mainHdr.dwTotalFrames; + ++streamHdr[ 0 ].dwLength; + ++dmlh[ 0 ]; + + /* Find out if the current riff list is close to 1 GByte in + size. If so, start a new (extended) RIFF. The only allowed + item in the new RIFF chunk is a movi list (with video + frames and indexes as usual). */ + + GetDirectoryEntry( riff_list, type, name, length, offset, parent ); + if ( length > 0x3f000000 ) + { + + /* write idx1 only once and before end of first GB */ + if ( ( index_type & AVI_SMALL_INDEX ) && isUpdateIdx1 ) + { + int idx1_chunk = AddDirectoryEntry( make_fourcc( "idx1" ), 0, idx1->nEntriesInUse * 16, riff_list ); + WriteChunk( idx1_chunk, ( void* ) idx1 ); + } + isUpdateIdx1 = false; + + if ( index_type & AVI_LARGE_INDEX ) + { + /* padding for alignment */ + GetDirectoryEntry( riff_list, type, name, length, offset, parent ); + num_blocks = ( length + 4 * RIFF_HEADERSIZE ) / PADDING_SIZE + 1; + length = ( num_blocks * PADDING_SIZE ) - length - 4 * RIFF_HEADERSIZE - 2 * RIFF_LISTSIZE; + if ( length > 0 ) + { + junk_chunk = AddDirectoryEntry( make_fourcc( "JUNK" ), 0, length, riff_list ); + WriteChunk( junk_chunk, g_zeroes ); + } + + riff_list = AddDirectoryEntry( make_fourcc( "RIFF" ), make_fourcc( "AVIX" ), RIFF_LISTSIZE, file_list ); + movi_list = AddDirectoryEntry( make_fourcc( "LIST" ), make_fourcc( "movi" ), RIFF_LISTSIZE, riff_list ); + } + } + return true; +} +#endif + +void AVI1File::setDVINFO( DVINFO &info ) +{ + // do not do this until debugged audio against DirectShow + return ; + + dvinfo.dwDVAAuxSrc = info.dwDVAAuxSrc; + dvinfo.dwDVAAuxCtl = info.dwDVAAuxCtl; + dvinfo.dwDVAAuxSrc1 = info.dwDVAAuxSrc1; + dvinfo.dwDVAAuxCtl1 = info.dwDVAAuxCtl1; + dvinfo.dwDVVAuxSrc = info.dwDVVAuxSrc; + dvinfo.dwDVVAuxCtl = info.dwDVVAuxCtl; +} + + +void AVI2File::setDVINFO( DVINFO &info ) +{} + +void AVIFile::setFccHandler( FOURCC type, FOURCC handler ) +{ + for ( int i = 0; i < mainHdr.dwStreams; i++ ) + { + if ( streamHdr[ i ].fccType == type ) + { + int k, j = 0; + FOURCC strf = make_fourcc( "strf" ); + BITMAPINFOHEADER bih; + + streamHdr[ i ].fccHandler = handler; + + while ( ( k = FindDirectoryEntry( strf, j++ ) ) != -1 ) + { + ReadChunk( k, ( void* ) & bih, sizeof( BITMAPINFOHEADER ) ); + bih.biCompression = handler; + } + } + } +} + +bool AVIFile::getStreamFormat( void* data, FOURCC type ) +{ + int i, j = 0; + FOURCC strh = make_fourcc( "strh" ); + FOURCC strf = make_fourcc( "strf" ); + AVIStreamHeader avi_stream_header; + bool result = false; + + while ( ( result == false ) && ( i = FindDirectoryEntry( strh, j++ ) ) != -1 ) + { + ReadChunk( i, ( void* ) & avi_stream_header, sizeof( AVIStreamHeader ) ); + if ( avi_stream_header.fccType == type ) + { + FOURCC chunkID; + int size; + + pthread_mutex_lock( &file_mutex ); + fail_neg( read( fd, &chunkID, sizeof( FOURCC ) ) ); + if ( chunkID == strf ) + { + fail_neg( read( fd, &size, sizeof( int ) ) ); + fail_neg( read( fd, data, size ) ); + result = true; + } + pthread_mutex_unlock( &file_mutex ); + } + } + return result; +} diff --git a/src/modules/kino/avi.h b/src/modules/kino/avi.h new file mode 100644 index 0000000..820d196 --- /dev/null +++ b/src/modules/kino/avi.h @@ -0,0 +1,447 @@ +/* +* avi.h library for AVI file format i/o +* Copyright (C) 2000 - 2002 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Tag: $Name$ +* +* Change log: +* +* $Log$ +* Revision 1.4 2005/07/25 07:21:39 lilo_booter +* + fixes for opendml dv avi +* +* Revision 1.3 2005/06/21 20:59:39 lilo_booter +* src/framework/mlt_consumer.c src/framework/mlt_consumer.h +* + Added a general profile handling for size, aspect ratio and display ratio +* +* src/framework/mlt_producer.c +* + Correction to aspect ratio properties +* +* src/inigo/inigo.c +* + Minimalist support for sdl_preview (still not very good) +* +* src/modules/avformat/consumer_avformat.c +* + Takes consumer profile into account +* +* src/modules/core/filter_resize.c +* + Corrections for synthesised producers and aspect ratio (inherits from consumer) +* +* src/modules/core/producer_colour.c +* src/modules/core/producer_noise.c +* src/modules/gtk2/producer_pango.c +* + Ensures that resize picks up consumer aspect ratio +* +* src/modules/dv/consumer_libdv.c +* + Honour wide screen output +* +* src/modules/gtk2/producer_pixbuf.c +* + Correction for 1:1 aspect ratio +* +* src/modules/kino/Makefile +* src/modules/kino/avi.cc +* src/modules/kino/avi.h +* src/modules/kino/configure +* src/modules/kino/filehandler.cc +* + Attempt to allow mov dv files to provide audio +* +* src/modules/sdl/consumer_sdl.c +* src/modules/sdl/consumer_sdl_preview.c +* src/modules/sdl/consumer_sdl_still.c +* + Takes consumer profile into account +* +* Revision 1.2 2005/04/15 14:37:03 lilo_booter +* Minor correction +* +* Revision 1.1 2005/04/15 14:28:26 lilo_booter +* Initial version +* +* Revision 1.16 2005/04/01 23:43:10 ddennedy +* apply endian fixes from Daniel Kobras +* +* Revision 1.15 2004/10/11 01:37:11 ddennedy +* mutex safety locks in RIFF and AVI classes, type 2 AVI optimization, mencoder export script +* +* Revision 1.14 2003/11/25 23:00:52 ddennedy +* cleanup and a few bugfixes +* +* Revision 1.13 2003/10/21 16:34:32 ddennedy +* GNOME2 port phase 1: initial checkin +* +* Revision 1.11.2.5 2003/07/24 14:13:57 ddennedy +* support for distinct audio stream in type2 AVI and Quicktime; support for more DV FOURCCs +* +* Revision 1.11.2.4 2003/06/10 23:53:36 ddennedy +* Daniel Kobras' WriteFrame error handling and automatic OpenDML, bugfixes in scene list updates, export AV/C Record +* +* Revision 1.11.2.3 2003/02/20 21:59:57 ddennedy +* bugfixes to capture and AVI +* +* Revision 1.11.2.2 2003/01/13 05:15:31 ddennedy +* added More Info panel and supporting methods +* +* Revision 1.11.2.1 2002/11/25 04:48:31 ddennedy +* bugfix to report errors when loading files +* +* Revision 1.11 2002/10/08 07:46:41 ddennedy +* AVI bugfixes, compatibility, optimization, warn bad file in capture and export dv file, allow no mplex +* +* Revision 1.10 2002/05/17 08:04:25 ddennedy +* revert const-ness of Frame references in Frame, FileHandler, and AVI classes +* +* Revision 1.9 2002/05/15 04:39:35 ddennedy +* bugfixes to dv2 AVI write, audio export, Xv init +* +* Revision 1.8 2002/04/29 05:09:22 ddennedy +* raw dv file support, Frame::ExtractAudio uses libdv, audioScrub prefs +* +* Revision 1.7 2002/04/09 06:53:42 ddennedy +* cleanup, new libdv 0.9.5, large AVI, dnd storyboard +* +* Revision 1.7 2002/03/25 21:34:25 arne +* Support for large (64 bit) files mostly completed +* +* Revision 1.6 2002/03/10 13:29:41 arne +* more changes for 64 bit access +* +* Revision 1.5 2002/03/09 17:59:28 arne +* moved index routines to AVIFile +* +* Revision 1.4 2002/03/09 10:26:26 arne +* improved constructors and assignment operator +* +* Revision 1.3 2002/03/09 08:55:57 arne +* moved a few variables to AVIFile +* +* Revision 1.2 2002/03/04 19:22:43 arne +* updated to latest Kino avi code +* +* Revision 1.1.1.1 2002/03/03 19:08:08 arne +* import of version 1.01 +* +*/ + +/** Common AVI declarations + + Some of this comes from the public domain AVI specification, which + explains the microsoft-style definitions. + + \file avi.h +*/ + +#ifndef _AVI_H +#define _AVI_H 1 + +#include +#include "riff.h" + +#define PACKED(x) __attribute__((packed)) x + +#define AVI_SMALL_INDEX (0x01) +#define AVI_LARGE_INDEX (0x02) +#define KINO_AVI_INDEX_OF_INDEXES (0x00) +#define KINO_AVI_INDEX_OF_CHUNKS (0x01) +#define AVI_INDEX_2FIELD (0x01) + +enum { AVI_PAL, AVI_NTSC, AVI_AUDIO_48KHZ, AVI_AUDIO_44KHZ, AVI_AUDIO_32KHZ }; + +/** Declarations of the main AVI file header + + The contents of this struct goes into the 'avih' chunk. */ + +typedef struct +{ + /// frame display rate (or 0L) + DWORD dwMicroSecPerFrame; + + /// max. transfer rate + DWORD dwMaxBytesPerSec; + + /// pad to multiples of this size, normally 2K + DWORD dwPaddingGranularity; + + /// the ever-present flags + DWORD dwFlags; + + /// # frames in file + DWORD dwTotalFrames; + DWORD dwInitialFrames; + DWORD dwStreams; + DWORD dwSuggestedBufferSize; + + DWORD dwWidth; + DWORD dwHeight; + + DWORD dwReserved[ 4 ]; +} +PACKED(MainAVIHeader); + +typedef struct +{ + WORD top, bottom, left, right; +} +PACKED(RECT); + +/** Declaration of a stream header + + The contents of this struct goes into the 'strh' header. */ + +typedef struct +{ + FOURCC fccType; + FOURCC fccHandler; + DWORD dwFlags; /* Contains AVITF_* flags */ + WORD wPriority; + WORD wLanguage; + DWORD dwInitialFrames; + DWORD dwScale; + DWORD dwRate; /* dwRate / dwScale == samples/second */ + DWORD dwStart; + DWORD dwLength; /* In units above... */ + DWORD dwSuggestedBufferSize; + DWORD dwQuality; + DWORD dwSampleSize; + RECT rcFrame; +} +PACKED(AVIStreamHeader); + +typedef struct +{ + DWORD dwDVAAuxSrc; + DWORD dwDVAAuxCtl; + DWORD dwDVAAuxSrc1; + DWORD dwDVAAuxCtl1; + DWORD dwDVVAuxSrc; + DWORD dwDVVAuxCtl; + DWORD dwDVReserved[ 2 ]; +} +PACKED(DVINFO); + +typedef struct +{ + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; + char dummy[ 1040 ]; +} +PACKED(BITMAPINFOHEADER); + +typedef struct +{ + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + WORD wBitsPerSample; + WORD cbSize; + WORD dummy; +} +PACKED(WAVEFORMATEX); + +typedef struct +{ + WORD wLongsPerEntry; + BYTE bIndexSubType; + BYTE bIndexType; + DWORD nEntriesInUse; + FOURCC dwChunkId; + DWORD dwReserved[ 3 ]; + struct avisuperindex_entry + { + QUADWORD qwOffset; + DWORD dwSize; + DWORD dwDuration; + } + aIndex[ 3198 ]; +} +PACKED(AVISuperIndex); + +typedef struct +{ + WORD wLongsPerEntry; + BYTE bIndexSubType; + BYTE bIndexType; + DWORD nEntriesInUse; + FOURCC dwChunkId; + QUADWORD qwBaseOffset; + DWORD dwReserved; + struct avifieldindex_entry + { + DWORD dwOffset; + DWORD dwSize; + } + aIndex[ 17895 ]; +} +PACKED(AVIStdIndex); + +typedef struct +{ + struct avisimpleindex_entry + { + FOURCC dwChunkId; + DWORD dwFlags; + DWORD dwOffset; + DWORD dwSize; + } + aIndex[ 20000 ]; + DWORD nEntriesInUse; +} +PACKED(AVISimpleIndex); + +typedef struct +{ + DWORD dirEntryType; + DWORD dirEntryName; + DWORD dirEntryLength; + size_t dirEntryOffset; + int dirEntryWrittenFlag; + int dirEntryParentList; +} +AviDirEntry; + + +/** base class for all AVI type files + + It contains methods and members which are the same in all AVI type files regardless of the particular compression, number + of streams etc. + + The AVIFile class also contains methods for handling several indexes to the video frame content. */ + +class AVIFile : public RIFFFile +{ +public: + AVIFile(); + AVIFile( const AVIFile& ); + virtual ~AVIFile(); + virtual AVIFile& operator=( const AVIFile& ); + + virtual void Init( int format, int sampleFrequency, int indexType ); + virtual int GetDVFrameInfo( off_t &offset, int &size, int frameNum ); + virtual int GetFrameInfo( off_t &offset, int &size, int frameNum, FOURCC chunkID ); + virtual int GetDVFrame( uint8_t *data, int frameNum ); + virtual int getFrame( void *data, int frameNum, FOURCC chunkID ); + virtual int GetTotalFrames() const; + virtual void PrintDirectoryEntryData( const RIFFDirEntry &entry ) const; + //virtual bool WriteFrame( const Frame &frame ) { return false; } + virtual void ParseList( int parent ); + virtual void ParseRIFF( void ); + virtual void ReadIndex( void ); + virtual void WriteRIFF( void ) + { } + virtual void FlushIndx( int stream ); + virtual void UpdateIndx( int stream, int chunk, int duration ); + virtual void UpdateIdx1( int chunk, int flags ); + virtual bool verifyStreamFormat( FOURCC type ); + virtual bool verifyStream( FOURCC type ); + virtual bool isOpenDML( void ); + virtual void setDVINFO( DVINFO& ) + { } + virtual void setFccHandler( FOURCC type, FOURCC handler ); + virtual bool getStreamFormat( void* data, FOURCC type ); + +protected: + MainAVIHeader mainHdr; + AVISimpleIndex *idx1; + int file_list; + int riff_list; + int hdrl_list; + int avih_chunk; + int movi_list; + int junk_chunk; + int idx1_chunk; + + AVIStreamHeader streamHdr[ 2 ]; + AVISuperIndex *indx[ 2 ]; + AVIStdIndex *ix[ 2 ]; + int indx_chunk[ 2 ]; + int ix_chunk[ 2 ]; + int strl_list[ 2 ]; + int strh_chunk[ 2 ]; + int strf_chunk[ 2 ]; + + int index_type; + int current_ix00; + + DWORD dmlh[ 62 ]; + int odml_list; + int dmlh_chunk; + bool isUpdateIdx1; + +}; + + +/** writing Type 1 DV AVIs + +*/ + +class AVI1File : public AVIFile +{ +public: + AVI1File(); + virtual ~AVI1File(); + + virtual void Init( int format, int sampleFrequency, int indexType ); + //virtual bool WriteFrame( const Frame &frame ); + virtual void WriteRIFF( void ); + virtual void setDVINFO( DVINFO& ); + +private: + DVINFO dvinfo; + + AVI1File( const AVI1File& ); + AVI1File& operator=( const AVI1File& ); +}; + + +/** writing Type 2 (separate audio data) DV AVIs + +This file type contains both audio and video tracks. It is therefore more compatible +to certain Windows programs, which expect any AVI having both audio and video tracks. +The video tracks contain the raw DV data (as in type 1) and the extracted audio tracks. + +Note that because the DV data contains audio information anyway, this means duplication +of data and a slight increase of file size. + +*/ + +class AVI2File : public AVIFile +{ +public: + AVI2File(); + virtual ~AVI2File(); + + virtual void Init( int format, int sampleFrequency, int indexType ); + //virtual bool WriteFrame( const Frame &frame ); + virtual void WriteRIFF( void ); + virtual void setDVINFO( DVINFO& ); + +private: + BITMAPINFOHEADER bitmapinfo; + WAVEFORMATEX waveformatex; + + AVI2File( const AVI2File& ); + AVI2File& operator=( const AVI2File& ); +}; +#endif diff --git a/src/modules/kino/configure b/src/modules/kino/configure new file mode 100755 index 0000000..17c00f3 --- /dev/null +++ b/src/modules/kino/configure @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + # Entirely optional... + lqt-config --prefix > /dev/null 2>&1 + lqt_disabled=$? + + pkg-config libdv 2> /dev/null + libdv_disabled=$? + + echo > config.h + [ "$lqt_disabled" = "0" ] && echo "#define HAVE_LIBQUICKTIME" >> config.h + [ "$libdv_disabled" = "0" ] && echo "#define HAVE_LIBDV" >> config.h + echo > config.mak + [ "$lqt_disabled" = "0" ] && echo "HAVE_LIBQUICKTIME=1" >> config.mak + [ "$libdv_disabled" = "0" ] && echo "HAVE_LIBDV=1" >> config.mak + + [ "$lqt_disabled" != "0" ] && echo "- libquicktime not found: only enabling dv avi support" + [ "$libdv_disabled" != 0 -a "$lqt_disabled" = "0" ] && echo "- libdv not found: mov dv may not have audio" + + echo "kino libmltkino$LIBSUF" >> ../producers.dat +fi + diff --git a/src/modules/kino/endian_types.h b/src/modules/kino/endian_types.h new file mode 100644 index 0000000..a307032 --- /dev/null +++ b/src/modules/kino/endian_types.h @@ -0,0 +1,265 @@ +/* + * + * Quick hack to handle endianness and word length issues. + * Defines _le, _be, and _ne variants to standard ISO types + * like int32_t, that are stored in little-endian, big-endian, + * and native-endian byteorder in memory, respectively. + * Caveat: int32_le_t and friends cannot be used in vararg + * functions like printf() without an explicit cast. + * + * Copyright (c) 2003-2005 Daniel Kobras + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _ENDIAN_TYPES_H +#define _ENDIAN_TYPES_H + +/* Needed for BYTE_ORDER and BIG/LITTLE_ENDIAN macros. */ +#ifndef _BSD_SOURCE +# define _BSD_SOURCE +# include +# undef _BSD_SOURCE +#else +# include +#endif + +#include +#include + +static inline int8_t bswap(const int8_t& x) +{ + return x; +} + +static inline u_int8_t bswap(const u_int8_t& x) +{ + return x; +} + +static inline int16_t bswap(const int16_t& x) +{ + return bswap_16(x); +} + +static inline u_int16_t bswap(const u_int16_t& x) +{ + return bswap_16(x); +} + +static inline int32_t bswap(const int32_t& x) +{ + return bswap_32(x); +} + +static inline u_int32_t bswap(const u_int32_t& x) +{ + return bswap_32(x); +} + +static inline int64_t bswap(const int64_t& x) +{ + return bswap_64(x); +} + +static inline u_int64_t bswap(const u_int64_t& x) +{ + return bswap_64(x); +} + +#define le_to_cpu cpu_to_le +#define be_to_cpu cpu_to_be + +template static inline T cpu_to_le(const T& x) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + return x; +#else + return bswap(x); +#endif +} + +template static inline T cpu_to_be(const T& x) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + return bswap(x); +#else + return x; +#endif +} + +template class le_t { + T m; + T read() const { + return le_to_cpu(m); + }; + void write(const T& n) { + m = cpu_to_le(n); + }; +public: + le_t(void) { + m = 0; + }; + le_t(const T& o) { + write(o); + }; + operator T() const { + return read(); + }; + le_t operator++() { + write(read() + 1); + return *this; + }; + le_t operator++(int) { + write(read() + 1); + return *this; + }; + le_t operator--() { + write(read() - 1); + return *this; + }; + le_t operator--(int) { + write(read() - 1); + return *this; + }; + le_t& operator+=(const T& t) { + write(read() + t); + return *this; + }; + le_t& operator-=(const T& t) { + write(read() - t); + return *this; + }; + le_t& operator&=(const le_t& t) { + m &= t.m; + return *this; + }; + le_t& operator|=(const le_t& t) { + m |= t.m; + return *this; + }; +} __attribute__((packed)); + +/* Just copy-and-pasted from le_t. Too lazy to do it right. */ + +template class be_t { + T m; + T read() const { + return be_to_cpu(m); + }; + void write(const T& n) { + m = cpu_to_be(n); + }; +public: + be_t(void) { + m = 0; + }; + be_t(const T& o) { + write(o); + }; + operator T() const { + return read(); + }; + be_t operator++() { + write(read() + 1); + return *this; + }; + be_t operator++(int) { + write(read() + 1); + return *this; + }; + be_t operator--() { + write(read() - 1); + return *this; + }; + be_t operator--(int) { + write(read() - 1); + return *this; + }; + be_t& operator+=(const T& t) { + write(read() + t); + return *this; + }; + be_t& operator-=(const T& t) { + write(read() - t); + return *this; + }; + be_t& operator&=(const be_t& t) { + m &= t.m; + return *this; + }; + be_t& operator|=(const be_t& t) { + m |= t.m; + return *this; + }; +} __attribute__((packed)); + +/* Define types of native endianness similar to the little and big endian + * versions below. Not really necessary but useful occasionally to emphasize + * endianness of data. + */ + +typedef int8_t int8_ne_t; +typedef int16_t int16_ne_t; +typedef int32_t int32_ne_t; +typedef int64_t int64_ne_t; +typedef u_int8_t u_int8_ne_t; +typedef u_int16_t u_int16_ne_t; +typedef u_int32_t u_int32_ne_t; +typedef u_int64_t u_int64_ne_t; + + +/* The classes work on their native endianness as well, but obviously + * introduce some overhead. Use the faster typedefs to native types + * therefore, unless you're debugging. + */ + +#if BYTE_ORDER == LITTLE_ENDIAN +typedef int8_ne_t int8_le_t; +typedef int16_ne_t int16_le_t; +typedef int32_ne_t int32_le_t; +typedef int64_ne_t int64_le_t; +typedef u_int8_ne_t u_int8_le_t; +typedef u_int16_ne_t u_int16_le_t; +typedef u_int32_ne_t u_int32_le_t; +typedef u_int64_ne_t u_int64_le_t; +typedef int8_t int8_be_t; +typedef be_t int16_be_t; +typedef be_t int32_be_t; +typedef be_t int64_be_t; +typedef u_int8_t u_int8_be_t; +typedef be_t u_int16_be_t; +typedef be_t u_int32_be_t; +typedef be_t u_int64_be_t; +#else +typedef int8_ne_t int8_be_t; +typedef int16_ne_t int16_be_t; +typedef int32_ne_t int32_be_t; +typedef int64_ne_t int64_be_t; +typedef u_int8_ne_t u_int8_be_t; +typedef u_int16_ne_t u_int16_be_t; +typedef u_int32_ne_t u_int32_be_t; +typedef u_int64_ne_t u_int64_be_t; +typedef int8_t int8_le_t; +typedef le_t int16_le_t; +typedef le_t int32_le_t; +typedef le_t int64_le_t; +typedef u_int8_t u_int8_le_t; +typedef le_t u_int16_le_t; +typedef le_t u_int32_le_t; +typedef le_t u_int64_le_t; +#endif + +#endif diff --git a/src/modules/kino/error.cc b/src/modules/kino/error.cc new file mode 100644 index 0000000..2d82148 --- /dev/null +++ b/src/modules/kino/error.cc @@ -0,0 +1,103 @@ +/* +* error.cc Error handling +* Copyright (C) 2000 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +// C++ includes + +#include +#include +#include +#include + +using std::ostringstream; +using std::string; +using std::endl; +using std::ends; +using std::cerr; + +// C includes + +#include +#include + +// local includes + +#include "error.h" + +void real_fail_neg( int eval, const char *eval_str, const char *func, const char *file, int line ) +{ + if ( eval < 0 ) + { + string exc; + ostringstream sb; + + sb << file << ":" << line << ": In function \"" << func << "\": \"" << eval_str << "\" evaluated to " << eval; + if ( errno != 0 ) + sb << endl << file << ":" << line << ": errno: " << errno << " (" << strerror( errno ) << ")"; + sb << ends; + exc = sb.str(); + cerr << exc << endl; + throw exc; + } +} + + +/** error handler for NULL result codes + + Whenever this is called with a NULL argument, it will throw an + exception. Typically used with functions like malloc() and new(). + +*/ + +void real_fail_null( const void *eval, const char *eval_str, const char *func, const char *file, int line ) +{ + if ( eval == NULL ) + { + + string exc; + ostringstream sb; + + sb << file << ":" << line << ": In function \"" << func << "\": " << eval_str << " is NULL" << ends; + exc = sb.str(); + cerr << exc << endl; + throw exc; + } +} + + +void real_fail_if( bool eval, const char *eval_str, const char *func, const char *file, int line ) +{ + if ( eval == true ) + { + + string exc; + ostringstream sb; + + sb << file << ":" << line << ": In function \"" << func << "\": condition \"" << eval_str << "\" is true"; + if ( errno != 0 ) + sb << endl << file << ":" << line << ": errno: " << errno << " (" << strerror( errno ) << ")"; + sb << ends; + exc = sb.str(); + cerr << exc << endl; + throw exc; + } +} diff --git a/src/modules/kino/error.h b/src/modules/kino/error.h new file mode 100644 index 0000000..63f5323 --- /dev/null +++ b/src/modules/kino/error.h @@ -0,0 +1,51 @@ +/* +* error.h Error handling +* Copyright (C) 2000 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef _ERROR_H +#define _ERROR_H 1 + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* + * Should check for gcc/g++ and version > 2.6 I suppose + */ +#ifndef __ASSERT_FUNCTION +# define __ASSERT_FUNCTION __PRETTY_FUNCTION__ +#endif + +#define fail_neg(eval) real_fail_neg (eval, #eval, __ASSERT_FUNCTION, __FILE__, __LINE__) +#define fail_null(eval) real_fail_null (eval, #eval, __ASSERT_FUNCTION, __FILE__, __LINE__) +#define fail_if(eval) real_fail_if (eval, #eval, __ASSERT_FUNCTION, __FILE__, __LINE__) + + void real_fail_neg ( int eval, const char * eval_str, const char * func, const char * file, int line ); + void real_fail_null ( const void * eval, const char * eval_str, const char * func, const char * file, int line ); + void real_fail_if ( bool eval, const char * eval_str, const char * func, const char * file, int line ); + + extern void sigpipe_clear( ); + extern int sigpipe_get( ); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/modules/kino/factory.c b/src/modules/kino/factory.c new file mode 100644 index 0000000..40b78ea --- /dev/null +++ b/src/modules/kino/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "producer_kino.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "kino" ) ) + return producer_kino_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/kino/filehandler.cc b/src/modules/kino/filehandler.cc new file mode 100644 index 0000000..d26760c --- /dev/null +++ b/src/modules/kino/filehandler.cc @@ -0,0 +1,940 @@ +/* +* filehandler.cc -- saving DV data into different file formats +* Copyright (C) 2000 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "config.h" + +extern "C" { +#include +} + +#include +#include +#include +#include + +using std::cerr; +using std::endl; +using std::ostringstream; +using std::setw; +using std::setfill; + +#include +#include +#include +#include +#include +#include +#include +#include + +// libdv header files +#ifdef HAVE_LIBDV +#include +#endif + +#include "filehandler.h" +#include "error.h" +#include "riff.h" +#include "avi.h" + +FileTracker *FileTracker::instance = NULL; + +FileTracker::FileTracker( ) : mode( CAPTURE_MOVIE_APPEND ) +{ + cerr << ">> Constructing File Capture tracker" << endl; +} + +FileTracker::~FileTracker( ) +{ + cerr << ">> Destroying File Capture tracker" << endl; +} + +FileTracker &FileTracker::GetInstance( ) +{ + if ( instance == NULL ) + instance = new FileTracker(); + + return *instance; +} + +void FileTracker::SetMode( FileCaptureMode mode ) +{ + this->mode = mode; +} + +FileCaptureMode FileTracker::GetMode( ) +{ + return this->mode; +} + +char *FileTracker::Get( int index ) +{ + return list[ index ]; +} + +void FileTracker::Add( const char *file ) +{ + if ( this->mode != CAPTURE_IGNORE ) + { + cerr << ">>>> Registering " << file << " with the tracker" << endl; + list.push_back( strdup( file ) ); + } +} + +unsigned int FileTracker::Size( ) +{ + return list.size(); +} + +void FileTracker::Clear( ) +{ + while ( Size() > 0 ) + { + free( list[ Size() - 1 ] ); + list.pop_back( ); + } + this->mode = CAPTURE_MOVIE_APPEND; +} + +FileHandler::FileHandler() : done( false ), autoSplit( false ), maxFrameCount( 999999 ), + framesWritten( 0 ), filename( "" ) +{ + /* empty body */ +} + + +FileHandler::~FileHandler() +{ + /* empty body */ +} + + +bool FileHandler::GetAutoSplit() const +{ + return autoSplit; +} + + +bool FileHandler::GetTimeStamp() const +{ + return timeStamp; +} + + +string FileHandler::GetBaseName() const +{ + return base; +} + + +string FileHandler::GetExtension() const +{ + return extension; +} + + +int FileHandler::GetMaxFrameCount() const +{ + return maxFrameCount; +} + +off_t FileHandler::GetMaxFileSize() const +{ + return maxFileSize; +} + +string FileHandler::GetFilename() const +{ + return filename; +} + + +void FileHandler::SetAutoSplit( bool flag ) +{ + autoSplit = flag; +} + + +void FileHandler::SetTimeStamp( bool flag ) +{ + timeStamp = flag; +} + + +void FileHandler::SetBaseName( const string& s ) +{ + base = s; +} + + +void FileHandler::SetMaxFrameCount( int count ) +{ + assert( count >= 0 ); + maxFrameCount = count; +} + + +void FileHandler::SetEveryNthFrame( int every ) +{ + assert ( every > 0 ); + + everyNthFrame = every; +} + + +void FileHandler::SetMaxFileSize( off_t size ) +{ + assert ( size >= 0 ); + maxFileSize = size; +} + + +#if 0 +void FileHandler::SetSampleFrame( const Frame& sample ) +{ + /* empty body */ +} +#endif + +bool FileHandler::Done() +{ + return done; +} + +#if 0 +bool FileHandler::WriteFrame( const Frame& frame ) +{ + static TimeCode prevTimeCode; + TimeCode timeCode; + + /* If the user wants autosplit, start a new file if a + new recording is detected. */ + prevTimeCode.sec = -1; + frame.GetTimeCode( timeCode ); + int time_diff = timeCode.sec - prevTimeCode.sec; + bool discontinuity = prevTimeCode.sec != -1 && ( time_diff > 1 || ( time_diff < 0 && time_diff > -59 ) ); + if ( FileIsOpen() && GetAutoSplit() == true && ( frame.IsNewRecording() || discontinuity ) ) + { + Close(); + } + + if ( FileIsOpen() == false ) + { + + string filename; + static int counter = 0; + + if ( GetTimeStamp() == true ) + { + ostringstream sb, sb2; + struct tm date; + string recDate; + + if ( ! frame.GetRecordingDate( date ) ) + { + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + localtime_r( static_cast< const time_t * >( &tv.tv_sec ), &date ); + } + sb << setfill( '0' ) + << setw( 4 ) << date.tm_year + 1900 << '.' + << setw( 2 ) << date.tm_mon + 1 << '.' + << setw( 2 ) << date.tm_mday << '_' + << setw( 2 ) << date.tm_hour << '-' + << setw( 2 ) << date.tm_min << '-' + << setw( 2 ) << date.tm_sec; + recDate = sb.str(); + sb2 << GetBaseName() << recDate << GetExtension(); + filename = sb2.str(); + cerr << ">>> Trying " << filename << endl; + } + else + { + struct stat stats; + do + { + ostringstream sb; + sb << GetBaseName() << setfill( '0' ) << setw( 3 ) << ++ counter << GetExtension(); + filename = sb.str(); + cerr << ">>> Trying " << filename << endl; + } + while ( stat( filename.c_str(), &stats ) == 0 ); + } + + SetSampleFrame( frame ); + if ( Create( filename ) == false ) + { + cerr << ">>> Error creating file!" << endl; + return false; + } + framesWritten = 0; + framesToSkip = 0; + } + + /* write frame */ + + if ( framesToSkip == 0 ) + { + if ( 0 > Write( frame ) ) + { + cerr << ">>> Error writing frame!" << endl; + return false; + } + framesToSkip = everyNthFrame; + ++framesWritten; + } + framesToSkip--; + + /* If the frame count is exceeded, close the current file. + If the autosplit flag is set, a new file will be created in the next iteration. + If the flag is not set, we are done. */ + + if ( ( GetMaxFrameCount() > 0 ) && + ( framesWritten >= GetMaxFrameCount() ) ) + { + Close(); + done = !GetAutoSplit(); + } + + /* If the file size could be exceeded by another frame, close the current file. + If the autosplit flag is set, a new file will be created on the next iteration. + If the flag is not set, we are done. */ + /* not exact, but should be good enough to prevent going over. */ + if ( FileIsOpen() ) + { + AudioInfo info; + frame.GetAudioInfo( info ); + if ( ( GetFileSize() > 0 ) && ( GetMaxFileSize() > 0 ) && + ( GetFileSize() + frame.GetFrameSize() + info.samples * 4 + 12 ) + >= GetMaxFileSize() ) + { // 12 = sizeof chunk metadata + Close(); + done = !GetAutoSplit(); + } + } + prevTimeCode.sec = timeCode.sec; + return true; +} +#endif + +RawHandler::RawHandler() : fd( -1 ) +{ + extension = ".dv"; +} + + +RawHandler::~RawHandler() +{ + Close(); +} + + +bool RawHandler::FileIsOpen() +{ + return fd != -1; +} + + +bool RawHandler::Create( const string& filename ) +{ + fd = open( filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_NONBLOCK, 0644 ); + if ( fd != -1 ) + { + FileTracker::GetInstance().Add( filename.c_str() ); + this->filename = filename; + } + return ( fd != -1 ); +} + + +#if 0 +int RawHandler::Write( const Frame& frame ) +{ + int result = write( fd, frame.data, frame.GetFrameSize() ); + return result; +} +#endif + +int RawHandler::Close() +{ + if ( fd != -1 ) + { + close( fd ); + fd = -1; + } + return 0; +} + + +off_t RawHandler::GetFileSize() +{ + struct stat file_status; + fstat( fd, &file_status ); + return file_status.st_size; +} + +int RawHandler::GetTotalFrames() +{ + return GetFileSize() / ( 480 * numBlocks ); +} + + +bool RawHandler::Open( const char *s ) +{ + unsigned char data[ 4 ]; + assert( fd == -1 ); + fd = open( s, O_RDONLY | O_NONBLOCK ); + if ( fd < 0 ) + return false; + if ( read( fd, data, 4 ) < 0 ) + return false; + lseek( fd, 0, SEEK_SET ); + numBlocks = ( ( data[ 3 ] & 0x80 ) == 0 ) ? 250 : 300; + filename = s; + return true; + +} + +int RawHandler::GetFrame( uint8_t *data, int frameNum ) +{ + assert( fd != -1 ); + int size = 480 * numBlocks; + if ( frameNum < 0 ) + return -1; + off_t offset = ( ( off_t ) frameNum * ( off_t ) size ); + fail_if( lseek( fd, offset, SEEK_SET ) == ( off_t ) - 1 ); + if ( read( fd, data, size ) > 0 ) + return 0; + else + return -1; +} + + +/***************************************************************************/ + + +AVIHandler::AVIHandler( int format ) : avi( NULL ), aviFormat( format ), isOpenDML( false ), + fccHandler( make_fourcc( "dvsd" ) ), channels( 2 ), isFullyInitialized( false ), + audioBuffer( NULL ) +{ + extension = ".avi"; + for ( int c = 0; c < 4; c++ ) + audioChannels[ c ] = NULL; +} + + +AVIHandler::~AVIHandler() +{ + if ( audioBuffer != NULL ) + { + delete audioBuffer; + audioBuffer = NULL; + } + for ( int c = 0; c < 4; c++ ) + { + if ( audioChannels[ c ] != NULL ) + { + delete audioChannels[ c ]; + audioChannels[ c ] = NULL; + } + } + + delete avi; +} + +#if 0 +void AVIHandler::SetSampleFrame( const Frame& sample ) +{ + Pack pack; + sample.GetAudioInfo( audioInfo ); + sample.GetVideoInfo( videoInfo ); + + sample.GetAAUXPack( 0x50, pack ); + dvinfo.dwDVAAuxSrc = *( DWORD* ) ( pack.data + 1 ); + sample.GetAAUXPack( 0x51, pack ); + dvinfo.dwDVAAuxCtl = *( DWORD* ) ( pack.data + 1 ); + + sample.GetAAUXPack( 0x52, pack ); + dvinfo.dwDVAAuxSrc1 = *( DWORD* ) ( pack.data + 1 ); + sample.GetAAUXPack( 0x53, pack ); + dvinfo.dwDVAAuxCtl1 = *( DWORD* ) ( pack.data + 1 ); + + sample.GetVAUXPack( 0x60, pack ); + dvinfo.dwDVVAuxSrc = *( DWORD* ) ( pack.data + 1 ); + sample.GetVAUXPack( 0x61, pack ); + dvinfo.dwDVVAuxCtl = *( DWORD* ) ( pack.data + 1 ); + +#ifdef WITH_LIBDV + + if ( sample.decoder->std == e_dv_std_smpte_314m ) + fccHandler = make_fourcc( "dv25" ); +#endif +} +#endif + +bool AVIHandler::FileIsOpen() +{ + return avi != NULL; +} + + +bool AVIHandler::Create( const string& filename ) +{ + assert( avi == NULL ); + + switch ( aviFormat ) + { + + case AVI_DV1_FORMAT: + fail_null( avi = new AVI1File ); + if ( avi->Create( filename.c_str() ) == false ) + return false; + //avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency, AVI_LARGE_INDEX ); + break; + + case AVI_DV2_FORMAT: + fail_null( avi = new AVI2File ); + if ( avi->Create( filename.c_str() ) == false ) + return false; + //if ( GetOpenDML() ) + //avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency, + //( AVI_SMALL_INDEX | AVI_LARGE_INDEX ) ); + //else + //avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency, + //( AVI_SMALL_INDEX ) ); + break; + + default: + assert( aviFormat == AVI_DV1_FORMAT || aviFormat == AVI_DV2_FORMAT ); + } + + avi->setDVINFO( dvinfo ); + avi->setFccHandler( make_fourcc( "iavs" ), fccHandler ); + avi->setFccHandler( make_fourcc( "vids" ), fccHandler ); + this->filename = filename; + FileTracker::GetInstance().Add( filename.c_str() ); + return ( avi != NULL ); +} + +#if 0 +int AVIHandler::Write( const Frame& frame ) +{ + assert( avi != NULL ); + try + { + return avi->WriteFrame( frame ) ? 0 : -1; + } + catch (...) + { + return -1; + } +} +#endif + +int AVIHandler::Close() +{ + if ( avi != NULL ) + { + avi->WriteRIFF(); + delete avi; + avi = NULL; + } + if ( audioBuffer != NULL ) + { + delete audioBuffer; + audioBuffer = NULL; + } + for ( int c = 0; c < 4; c++ ) + { + if ( audioChannels[ c ] != NULL ) + { + delete audioChannels[ c ]; + audioChannels[ c ] = NULL; + } + } + isFullyInitialized = false; + return 0; +} + +off_t AVIHandler::GetFileSize() +{ + return avi->GetFileSize(); +} + +int AVIHandler::GetTotalFrames() +{ + return avi->GetTotalFrames(); +} + + +bool AVIHandler::Open( const char *s ) +{ + assert( avi == NULL ); + fail_null( avi = new AVI1File ); + if ( avi->Open( s ) ) + { + avi->ParseRIFF(); + if ( ! ( + avi->verifyStreamFormat( make_fourcc( "dvsd" ) ) || + avi->verifyStreamFormat( make_fourcc( "DVSD" ) ) || + avi->verifyStreamFormat( make_fourcc( "dvcs" ) ) || + avi->verifyStreamFormat( make_fourcc( "DVCS" ) ) || + avi->verifyStreamFormat( make_fourcc( "dvcp" ) ) || + avi->verifyStreamFormat( make_fourcc( "DVCP" ) ) || + avi->verifyStreamFormat( make_fourcc( "CDVC" ) ) || + avi->verifyStreamFormat( make_fourcc( "cdvc" ) ) || + avi->verifyStreamFormat( make_fourcc( "DV25" ) ) || + avi->verifyStreamFormat( make_fourcc( "dv25" ) ) ) ) + return false; + avi->ReadIndex(); + if ( avi->verifyStream( make_fourcc( "auds" ) ) ) + aviFormat = AVI_DV2_FORMAT; + else + aviFormat = AVI_DV1_FORMAT; + isOpenDML = avi->isOpenDML(); + filename = s; + return true; + } + else + return false; + +} + +int AVIHandler::GetFrame( uint8_t *data, int frameNum ) +{ + int result = avi->GetDVFrame( data, frameNum ); +#if 0 + if ( result == 0 ) + { + /* get the audio from the audio stream, if available */ + if ( aviFormat == AVI_DV2_FORMAT ) + { + WAVEFORMATEX wav; + + if ( ! isFullyInitialized && + avi->getStreamFormat( ( void* ) &wav, make_fourcc( "auds" ) ) ) + { + if ( channels > 0 && channels < 5 ) + { + // Allocate interleaved audio buffer + audioBuffer = new int16_t[ 2 * DV_AUDIO_MAX_SAMPLES * channels ]; + + // Allocate non-interleaved audio buffers + for ( int c = 0; c < channels; c++ ) + audioChannels[ c ] = new int16_t[ 2 * DV_AUDIO_MAX_SAMPLES ]; + + // Get the audio parameters from AVI for subsequent calls to method + audioInfo.channels = wav.nChannels; + audioInfo.frequency = wav.nSamplesPerSec; + + // Skip initialization on subsequent calls to method + isFullyInitialized = true; + cerr << ">>> using audio from separate AVI audio stream" << endl; + } + } + + // Get the frame from AVI + int n = avi->getFrame( audioBuffer, frameNum, make_fourcc( "01wb" ) ); + if ( n > 0 ) + { + // Temporary pointer to audio scratch buffer + int16_t * s = audioBuffer; + + // Determine samples in this frame + audioInfo.samples = n / audioInfo.channels / sizeof( int16_t ); + + // Convert interleaved audio into non-interleaved + for ( int n = 0; n < audioInfo.samples; ++n ) + for ( int i = 0; i < audioInfo.channels; i++ ) + audioChannels[ i ][ n ] = *s++; + + // Write interleaved audio into frame + frame.EncodeAudio( audioInfo, audioChannels ); + } + } + + // Parse important metadata in DV bitstream + frame.ExtractHeader(); + } +#endif + return result; +} + + +void AVIHandler::SetOpenDML( bool flag ) +{ + isOpenDML = flag; +} + + +bool AVIHandler::GetOpenDML() const +{ + return isOpenDML; +} + + +/***************************************************************************/ + +#ifdef HAVE_LIBQUICKTIME + +#ifndef HAVE_LIBDV +#define DV_AUDIO_MAX_SAMPLES 1944 +#endif + +// Missing fourcc's in libquicktime (allows compilation) +#ifndef QUICKTIME_DV_AVID +#define QUICKTIME_DV_AVID "AVdv" +#endif + +#ifndef QUICKTIME_DV_AVID_A +#define QUICKTIME_DV_AVID_A "dvcp" +#endif + +#ifndef QUICKTIME_DVCPRO +#define QUICKTIME_DVCPRO "dvpp" +#endif + +QtHandler::QtHandler() : fd( NULL ) +{ + extension = ".mov"; + Init(); +} + + +QtHandler::~QtHandler() +{ + Close(); +} + +void QtHandler::Init() +{ + if ( fd != NULL ) + Close(); + + fd = NULL; + samplingRate = 0; + samplesPerBuffer = 0; + channels = 2; + audioBuffer = NULL; + audioChannelBuffer = NULL; + isFullyInitialized = false; +} + + +bool QtHandler::FileIsOpen() +{ + return fd != NULL; +} + + +bool QtHandler::Create( const string& filename ) +{ + Init(); + + if ( open( filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_NONBLOCK, 0644 ) != -1 ) + { + fd = quicktime_open( const_cast( filename.c_str() ), 0, 1 ); + if ( fd != NULL ) + FileTracker::GetInstance().Add( filename.c_str() ); + } + else + return false; + this->filename = filename; + return true; +} + +void QtHandler::AllocateAudioBuffers() +{ + if ( channels > 0 && channels < 5 ) + { + audioBufferSize = DV_AUDIO_MAX_SAMPLES * 2; + audioBuffer = new int16_t[ audioBufferSize * channels ]; + + audioChannelBuffer = new short int * [ channels ]; + for ( int c = 0; c < channels; c++ ) + audioChannelBuffer[ c ] = new short int[ audioBufferSize ]; + isFullyInitialized = true; + } +} + +inline void QtHandler::DeinterlaceStereo16( void* pInput, int iBytes, + void* pLOutput, void* pROutput ) +{ + short int * piSampleInput = ( short int* ) pInput; + short int* piSampleLOutput = ( short int* ) pLOutput; + short int* piSampleROutput = ( short int* ) pROutput; + + while ( ( char* ) piSampleInput < ( ( char* ) pInput + iBytes ) ) + { + *piSampleLOutput++ = *piSampleInput++; + *piSampleROutput++ = *piSampleInput++; + } +} + +#if 0 +int QtHandler::Write( const Frame& frame ) +{ + if ( ! isFullyInitialized ) + { + AudioInfo audio; + + if ( frame.GetAudioInfo( audio ) ) + { + channels = 2; + quicktime_set_audio( fd, channels, audio.frequency, 16, QUICKTIME_TWOS ); + } + else + { + channels = 0; + } + + quicktime_set_video( fd, 1, 720, frame.IsPAL() ? 576 : 480, + frame.GetFrameRate(), QUICKTIME_DV ); + AllocateAudioBuffers(); + } + + int result = quicktime_write_frame( fd, const_cast( frame.data ), + frame.GetFrameSize(), 0 ); + + if ( channels > 0 ) + { + AudioInfo audio; + if ( frame.GetAudioInfo( audio ) && ( unsigned int ) audio.samples < audioBufferSize ) + { + long bytesRead = frame.ExtractAudio( audioBuffer ); + + DeinterlaceStereo16( audioBuffer, bytesRead, + audioChannelBuffer[ 0 ], + audioChannelBuffer[ 1 ] ); + + quicktime_encode_audio( fd, audioChannelBuffer, NULL, audio.samples ); + } + } + return result; +} +#endif + +int QtHandler::Close() +{ + if ( fd != NULL ) + { + quicktime_close( fd ); + fd = NULL; + } + if ( audioBuffer != NULL ) + { + delete audioBuffer; + audioBuffer = NULL; + } + if ( audioChannelBuffer != NULL ) + { + for ( int c = 0; c < channels; c++ ) + delete audioChannelBuffer[ c ]; + delete audioChannelBuffer; + audioChannelBuffer = NULL; + } + return 0; +} + + +off_t QtHandler::GetFileSize() +{ + struct stat file_status; + stat( filename.c_str(), &file_status ); + return file_status.st_size; +} + + +int QtHandler::GetTotalFrames() +{ + return ( int ) quicktime_video_length( fd, 0 ); +} + + +bool QtHandler::Open( const char *s ) +{ + Init(); + + fd = quicktime_open( ( char * ) s, 1, 0 ); + if ( fd == NULL ) + { + fprintf( stderr, "Error opening: %s\n", s ); + return false; + } + + if ( quicktime_has_video( fd ) <= 0 ) + { + fprintf( stderr, "There must be at least one video track in the input file (%s).\n", + s ); + Close(); + return false; + } + char * fcc = quicktime_video_compressor( fd, 0 ); + if ( strncmp( fcc, QUICKTIME_DV, 4 ) != 0 && + strncmp( fcc, QUICKTIME_DV_AVID, 4 ) != 0 && + strncmp( fcc, QUICKTIME_DV_AVID_A, 4 ) != 0 && + strncmp( fcc, QUICKTIME_DVCPRO, 4 ) != 0 ) + { + Close(); + return false; + } + if ( quicktime_has_audio( fd ) ) + channels = quicktime_track_channels( fd, 0 ); + filename = s; + return true; +} + +int QtHandler::GetFrame( uint8_t *data, int frameNum ) +{ + assert( fd != NULL ); + + quicktime_set_video_position( fd, frameNum, 0 ); + quicktime_read_frame( fd, data, 0 ); + +#ifdef HAVE_LIBDV + if ( quicktime_has_audio( fd ) ) + { + if ( ! isFullyInitialized ) + AllocateAudioBuffers(); + + // Fetch the frequency of the audio track and calc number of samples needed + int frequency = quicktime_sample_rate( fd, 0 ); + float fps = ( data[ 3 ] & 0x80 ) ? 25.0f : 29.97f; + int samples = mlt_sample_calculator( fps, frequency, frameNum ); + int64_t seek = mlt_sample_calculator_to_now( fps, frequency, frameNum ); + + // Obtain a dv encoder and initialise it with minimal info + dv_encoder_t *encoder = dv_encoder_new( 0, 0, 0 ); + encoder->isPAL = ( data[ 3 ] & 0x80 ); + encoder->samples_this_frame = samples; + + // Seek to the calculated position and decode + quicktime_set_audio_position( fd, seek, 0 ); + lqt_decode_audio( fd, audioChannelBuffer, NULL, (long) samples ); + + // Encode the audio on the frame and done + dv_encode_full_audio( encoder, audioChannelBuffer, channels, frequency, data ); + dv_encoder_free( encoder ); + } +#endif + + return 0; +} +#endif diff --git a/src/modules/kino/filehandler.h b/src/modules/kino/filehandler.h new file mode 100644 index 0000000..1822309 --- /dev/null +++ b/src/modules/kino/filehandler.h @@ -0,0 +1,217 @@ +/* +* filehandler.h +* Copyright (C) 2000 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef _FILEHANDLER_H +#define _FILEHANDLER_H + +// enum { PAL_FORMAT, NTSC_FORMAT, AVI_DV1_FORMAT, AVI_DV2_FORMAT, QT_FORMAT, RAW_FORMAT, TEST_FORMAT, UNDEFINED }; + +#include +using std::vector; + +#include +using std::string; + +#include "riff.h" +#include "avi.h" +#include +#include + +enum { AVI, PLAYLIST, RAW_DV, QT, UNKNOWN_FORMAT }; +enum { PAL_FORMAT, NTSC_FORMAT, AVI_DV1_FORMAT, AVI_DV2_FORMAT, QT_FORMAT, RAW_FORMAT, TEST_FORMAT, UNDEFINED }; +enum { DISPLAY_XX, DISPLAY_GDKRGB, DISPLAY_GDKRGB32, DISPLAY_XV, DISPLAY_SDL }; + +enum { NORM_UNSPECIFIED=0, NORM_PAL=1, NORM_NTSC=2 }; +enum { AUDIO_32KHZ=0, AUDIO_44KHZ=1, AUDIO_48KHZ=2 }; +enum { ASPECT_43=0, ASPECT_169=1 }; + +enum FileCaptureMode { + CAPTURE_IGNORE, + CAPTURE_FRAME_APPEND, + CAPTURE_FRAME_INSERT, + CAPTURE_MOVIE_APPEND +}; + +class FileTracker +{ +protected: + FileTracker(); + ~FileTracker(); +public: + static FileTracker &GetInstance( ); + void SetMode( FileCaptureMode ); + FileCaptureMode GetMode( ); + unsigned int Size(); + char *Get( int ); + void Add( const char * ); + void Clear( ); +private: + static FileTracker *instance; + vector list; + FileCaptureMode mode; +}; + +class FileHandler +{ +public: + + FileHandler(); + virtual ~FileHandler(); + + virtual bool GetAutoSplit() const; + virtual bool GetTimeStamp() const; + virtual string GetBaseName() const; + virtual string GetExtension() const; + virtual int GetMaxFrameCount() const; + virtual off_t GetMaxFileSize() const; + virtual off_t GetFileSize() = 0; + virtual int GetTotalFrames() = 0; + virtual string GetFilename() const; + + virtual void SetAutoSplit( bool ); + virtual void SetTimeStamp( bool ); + virtual void SetBaseName( const string& base ); + virtual void SetMaxFrameCount( int ); + virtual void SetEveryNthFrame( int ); + virtual void SetMaxFileSize( off_t ); + //virtual void SetSampleFrame( const Frame& sample ); + + //virtual bool WriteFrame( const Frame& frame ); + virtual bool FileIsOpen() = 0; + virtual bool Create( const string& filename ) = 0; + //virtual int Write( const Frame& frame ) = 0; + virtual int Close() = 0; + virtual bool Done( void ); + + virtual bool Open( const char *s ) = 0; + virtual int GetFrame( uint8_t *data, int frameNum ) = 0; + int GetFramesWritten() const + { + return framesWritten; + } + +protected: + bool done; + bool autoSplit; + bool timeStamp; + int maxFrameCount; + int framesWritten; + int everyNthFrame; + int framesToSkip; + off_t maxFileSize; + string base; + string extension; + string filename; +}; + + +class RawHandler: public FileHandler +{ +public: + int fd; + + RawHandler(); + ~RawHandler(); + + bool FileIsOpen(); + bool Create( const string& filename ); + //int Write( const Frame& frame ); + int Close(); + off_t GetFileSize(); + int GetTotalFrames(); + bool Open( const char *s ); + int GetFrame( uint8_t *data, int frameNum ); +private: + int numBlocks; +}; + + +class AVIHandler: public FileHandler +{ +public: + AVIHandler( int format = AVI_DV1_FORMAT ); + ~AVIHandler(); + + //void SetSampleFrame( const Frame& sample ); + bool FileIsOpen(); + bool Create( const string& filename ); + //int Write( const Frame& frame ); + int Close(); + off_t GetFileSize(); + int GetTotalFrames(); + bool Open( const char *s ); + int GetFrame( uint8_t *data, int frameNum ); + bool GetOpenDML() const; + void SetOpenDML( bool ); + int GetFormat() const + { + return aviFormat; + } + +protected: + AVIFile *avi; + int aviFormat; + //AudioInfo audioInfo; + //VideoInfo videoInfo; + bool isOpenDML; + DVINFO dvinfo; + FOURCC fccHandler; + int channels; + bool isFullyInitialized; + int16_t *audioBuffer; + int16_t *audioChannels[ 4 ]; +}; + + +#ifdef HAVE_LIBQUICKTIME +#include + +class QtHandler: public FileHandler +{ +public: + QtHandler(); + ~QtHandler(); + + bool FileIsOpen(); + bool Create( const string& filename ); + //int Write( const Frame& frame ); + int Close(); + off_t GetFileSize(); + int GetTotalFrames(); + bool Open( const char *s ); + int GetFrame( uint8_t *data, int frameNum ); + void AllocateAudioBuffers(); + +private: + quicktime_t *fd; + long samplingRate; + int samplesPerBuffer; + int channels; + bool isFullyInitialized; + unsigned int audioBufferSize; + int16_t *audioBuffer; + short int** audioChannelBuffer; + + void Init(); + inline void DeinterlaceStereo16( void* pInput, int iBytes, void* pLOutput, void* pROutput ); + +}; +#endif + +#endif diff --git a/src/modules/kino/gpl b/src/modules/kino/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/kino/kino_wrapper.cc b/src/modules/kino/kino_wrapper.cc new file mode 100644 index 0000000..81c21ba --- /dev/null +++ b/src/modules/kino/kino_wrapper.cc @@ -0,0 +1,110 @@ +/* + * kino_wrapper.cc -- c wrapper for kino file handler + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include +#include +#include +#include "kino_wrapper.h" +#include "filehandler.h" + +extern "C" +{ + +#include + +struct kino_wrapper_s +{ + FileHandler *handler; + int is_pal; +}; + +kino_wrapper kino_wrapper_init( ) +{ + kino_wrapper self = ( kino_wrapper )malloc( sizeof( kino_wrapper_s ) ); + if ( self != NULL ) + self->handler = NULL; + return self; +} + +int kino_wrapper_open( kino_wrapper self, char *src ) +{ + if ( self != NULL ) + { + // Rough file determination based on file type + if ( strncasecmp( strrchr( src, '.' ), ".avi", 4 ) == 0 ) + self->handler = new AVIHandler( ); + else if ( strncasecmp( strrchr( src, '.' ), ".dv", 3 ) == 0 || strncasecmp( strrchr( src, '.' ), ".dif", 4 ) == 0 ) + self->handler = new RawHandler( ); + #ifdef HAVE_LIBQUICKTIME + else if ( strncasecmp( strrchr( src, '.' ), ".mov", 4 ) == 0 ) + self->handler = new QtHandler( ); + #endif + + // Open the file if we have a handler + if ( self->handler != NULL ) + if ( !self->handler->Open( src ) ) + self = NULL; + + // Check the first frame to see if it's PAL or NTSC + if ( self != NULL && self->handler != NULL ) + { + uint8_t *data = ( uint8_t * )mlt_pool_alloc( 144000 ); + if ( self->handler->GetFrame( data, 0 ) == 0 ) + self->is_pal = data[3] & 0x80; + else + self = NULL; + mlt_pool_release( data ); + } + } + + return kino_wrapper_is_open( self ); +} + +int kino_wrapper_get_frame_count( kino_wrapper self ) +{ + return self != NULL && self->handler != NULL ? self->handler->GetTotalFrames( ) : 0; +} + +int kino_wrapper_is_open( kino_wrapper self ) +{ + return self != NULL && self->handler != NULL ? self->handler->FileIsOpen( ) : 0; +} + +int kino_wrapper_is_pal( kino_wrapper self ) +{ + return self != NULL ? self->is_pal : 0; +} + +int kino_wrapper_get_frame( kino_wrapper self, uint8_t *data, int index ) +{ + return self != NULL && self->handler != NULL ? !self->handler->GetFrame( data, index ) : 0; +} + +void kino_wrapper_close( kino_wrapper self ) +{ + if ( self ) + delete self->handler; + free( self ); +} + +} + + diff --git a/src/modules/kino/kino_wrapper.h b/src/modules/kino/kino_wrapper.h new file mode 100644 index 0000000..458023c --- /dev/null +++ b/src/modules/kino/kino_wrapper.h @@ -0,0 +1,45 @@ +/* + * kino_wrapper.h -- c wrapper for kino file handler + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MLT_PRODUCER_KINO_WRAPPER_H_ +#define MLT_PRODUCER_KINO_WRAPPER_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct kino_wrapper_s *kino_wrapper; + +extern kino_wrapper kino_wrapper_init( ); +extern int kino_wrapper_open( kino_wrapper, char * ); +extern int kino_wrapper_is_open( kino_wrapper ); +extern int kino_wrapper_is_pal( kino_wrapper ); +extern int kino_wrapper_get_frame_count( kino_wrapper ); +extern int kino_wrapper_get_frame( kino_wrapper, uint8_t *, int ); +extern void kino_wrapper_close( kino_wrapper ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/kino/producer_kino.c b/src/modules/kino/producer_kino.c new file mode 100644 index 0000000..cd732c5 --- /dev/null +++ b/src/modules/kino/producer_kino.c @@ -0,0 +1,145 @@ +/* + * producer_kino.c -- a DV file format parser + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "producer_kino.h" +#include +#include +#include +#include "kino_wrapper.h" + +/* NB: This is an abstract producer - it provides no codec support whatsoever. */ + +#define FRAME_SIZE_525_60 10 * 150 * 80 +#define FRAME_SIZE_625_50 12 * 150 * 80 + +typedef struct producer_kino_s *producer_kino; + +struct producer_kino_s +{ + struct mlt_producer_s parent; + kino_wrapper wrapper; +}; + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_kino_init( char *filename ) +{ + kino_wrapper wrapper = kino_wrapper_init( ); + + if ( kino_wrapper_open( wrapper, filename ) ) + { + producer_kino this = calloc( sizeof( struct producer_kino_s ), 1 ); + + if ( this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + mlt_producer producer = &this->parent; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + double fps = kino_wrapper_is_pal( wrapper ) ? 25 : 30000.0 / 1001.0; + + // Assign the wrapper + this->wrapper = wrapper; + + // Pass wrapper properties (frame rate, count etc) + mlt_properties_set_position( properties, "length", kino_wrapper_get_frame_count( wrapper ) ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", kino_wrapper_get_frame_count( wrapper ) - 1 ); + mlt_properties_set_double( properties, "real_fps", fps ); + mlt_properties_set( properties, "resource", filename ); + + // Register transport implementation with the producer + producer->close = ( mlt_destructor )producer_close; + + // Register our get_frame implementation with the producer + producer->get_frame = producer_get_frame; + + // Return the producer + return producer; + } + free( this ); + } + + kino_wrapper_close( wrapper ); + + return NULL; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + producer_kino this = producer->child; + uint8_t *data = mlt_pool_alloc( FRAME_SIZE_625_50 ); + + // Obtain the current frame number + uint64_t position = mlt_producer_frame( producer ); + + // Create an empty frame + *frame = mlt_frame_init( ); + + // Seek and fetch + if ( kino_wrapper_get_frame( this->wrapper, data, position ) ) + { + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Determine if we're PAL or NTSC + int is_pal = kino_wrapper_is_pal( this->wrapper ); + + // Pass the dv data + mlt_properties_set_data( properties, "dv_data", data, FRAME_SIZE_625_50, ( mlt_destructor )mlt_pool_release, NULL ); + + // Update other info on the frame + mlt_properties_set_int( properties, "width", 720 ); + mlt_properties_set_int( properties, "height", is_pal ? 576 : 480 ); + mlt_properties_set_int( properties, "top_field_first", is_pal ? 0 : ( data[ 5 ] & 0x07 ) == 0 ? 0 : 1 ); + } + else + { + mlt_pool_release( data ); + } + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + if ( parent != NULL ) + { + // Obtain this + producer_kino this = parent->child; + + // Close the file + if ( this != NULL ) + kino_wrapper_close( this->wrapper ); + + // Close the parent + parent->close = NULL; + mlt_producer_close( parent ); + + // Free the memory + free( this ); + } +} diff --git a/src/modules/kino/producer_kino.h b/src/modules/kino/producer_kino.h new file mode 100644 index 0000000..ddc6eac --- /dev/null +++ b/src/modules/kino/producer_kino.h @@ -0,0 +1,28 @@ +/* + * producer_kino.h -- a DV file format parser + * Copyright (C) 2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _PRODUCER_KINO_H_ +#define _PRODUCER_KINO_H_ + +#include + +extern mlt_producer producer_kino_init( char *filename ); + +#endif diff --git a/src/modules/kino/riff.cc b/src/modules/kino/riff.cc new file mode 100644 index 0000000..22e4315 --- /dev/null +++ b/src/modules/kino/riff.cc @@ -0,0 +1,711 @@ +/* +* riff.cc library for RIFF file format i/o +* Copyright (C) 2000 - 2002 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Tag: $Name$ +* +* Change log: +* +* $Log$ +* Revision 1.3 2005/07/25 14:41:29 lilo_booter +* + Minor correction for entry length being less than the data length +* +* Revision 1.2 2005/07/25 07:21:39 lilo_booter +* + fixes for opendml dv avi +* +* Revision 1.1 2005/04/15 14:28:26 lilo_booter +* Initial version +* +* Revision 1.18 2005/04/01 23:43:10 ddennedy +* apply endian fixes from Daniel Kobras +* +* Revision 1.17 2004/10/11 01:37:11 ddennedy +* mutex safety locks in RIFF and AVI classes, type 2 AVI optimization, mencoder export script +* +* Revision 1.16 2003/11/25 23:01:24 ddennedy +* cleanup and a few bugfixes +* +* Revision 1.15 2003/10/21 16:34:34 ddennedy +* GNOME2 port phase 1: initial checkin +* +* Revision 1.13.2.3 2003/08/26 20:39:00 ddennedy +* relocate mutex unlock and add assert includes +* +* Revision 1.13.2.2 2003/01/28 12:54:13 lilo_booter +* New 'no change' image transition +* +* Revision 1.13.2.1 2002/11/25 04:48:31 ddennedy +* bugfix to report errors when loading files +* +* Revision 1.13 2002/09/13 06:49:49 ddennedy +* build update, cleanup, bugfixes +* +* Revision 1.12 2002/04/21 06:36:40 ddennedy +* kindler avc and 1394 bus reset support in catpure page, honor max file size +* +* Revision 1.11 2002/04/09 06:53:42 ddennedy +* cleanup, new libdv 0.9.5, large AVI, dnd storyboard +* +* Revision 1.4 2002/03/25 21:34:25 arne +* Support for large (64 bit) files mostly completed +* +* Revision 1.3 2002/03/10 21:28:29 arne +* release 1.1b1, 64 bit support for type 1 avis +* +* Revision 1.2 2002/03/04 19:22:43 arne +* updated to latest Kino avi code +* +* Revision 1.1.1.1 2002/03/03 19:08:08 arne +* import of version 1.01 +* +*/ + +#include "config.h" + +// C++ includes + +#include +//#include +#include +#include +#include + +using std::cout; +using std::hex; +using std::dec; +using std::setw; +using std::setfill; +using std::endl; + +// C includes + +#include +#include +#include + +// local includes + +#include "error.h" +#include "riff.h" + + +/** make a 32 bit "string-id" + + \param s a pointer to 4 chars + \return the 32 bit "string id" + \bugs It is not checked whether we really have 4 characters + + Some compilers understand constants like int id = 'ABCD'; but I + could not get it working on the gcc compiler so I had to use this + workaround. We can now use id = make_fourcc("ABCD") instead. */ + +FOURCC make_fourcc( char *s ) +{ + if ( s[ 0 ] == 0 ) + return 0; + else + return *( ( FOURCC* ) s ); +} + + +RIFFDirEntry::RIFFDirEntry() +{} + + +RIFFDirEntry::RIFFDirEntry ( FOURCC t, FOURCC n, int l, int o, int p ) : type( t ), name( n ), length( l ), offset( o ), parent( p ), written( 0 ) +{} + + +/** Creates the object without an output file. + +*/ + +RIFFFile::RIFFFile() : fd( -1 ) +{ + pthread_mutex_init( &file_mutex, NULL ); +} + + +/* Copy constructor + + Duplicate the file descriptor +*/ + +RIFFFile::RIFFFile( const RIFFFile& riff ) : fd( -1 ) +{ + if ( riff.fd != -1 ) + { + fd = dup( riff.fd ); + } + directory = riff.directory; +} + + +/** Destroys the object. + + If it has an associated opened file, close it. */ + +RIFFFile::~RIFFFile() +{ + Close(); + pthread_mutex_destroy( &file_mutex ); +} + + +RIFFFile& RIFFFile::operator=( const RIFFFile& riff ) +{ + if ( fd != riff.fd ) + { + Close(); + if ( riff.fd != -1 ) + { + fd = dup( riff.fd ); + } + directory = riff.directory; + } + return *this; +} + + +/** Creates or truncates the file. + + \param s the filename +*/ + +bool RIFFFile::Create( const char *s ) +{ + fd = open( s, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, 00644 ); + + if ( fd == -1 ) + return false; + else + return true; +} + + +/** Opens the file read only. + + \param s the filename +*/ + +bool RIFFFile::Open( const char *s ) +{ + fd = open( s, O_RDONLY | O_NONBLOCK ); + + if ( fd == -1 ) + return false; + else + return true; +} + + +/** Destroys the object. + + If it has an associated opened file, close it. */ + +void RIFFFile::Close() +{ + if ( fd != -1 ) + { + close( fd ); + fd = -1; + } +} + + +/** Adds an entry to the list of containers. + + \param type the type of this entry + \param name the name + \param length the length of the data in the container + \param list the container in which this object is contained. + \return the ID of the newly created entry + + The topmost object is not contained in any other container. Use + the special ID RIFF_NO_PARENT to create the topmost object. */ + +int RIFFFile::AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list ) +{ + /* Put all parameters in an RIFFDirEntry object. The offset is + currently unknown. */ + + RIFFDirEntry entry( type, name, length, 0 /* offset */, list ); + + /* If the new chunk is in a list, then get the offset and size + of that list. The offset of this chunk is the end of the list + (parent_offset + parent_length) plus the size of the chunk + header. */ + + if ( list != RIFF_NO_PARENT ) + { + RIFFDirEntry parent = GetDirectoryEntry( list ); + entry.offset = parent.offset + parent.length + RIFF_HEADERSIZE; + } + + /* The list which this new chunk is a member of has now increased in + size. Get that directory entry and bump up its length by the size + of the chunk. Since that list may also be contained in another + list, walk up to the top of the tree. */ + + while ( list != RIFF_NO_PARENT ) + { + RIFFDirEntry parent = GetDirectoryEntry( list ); + parent.length += RIFF_HEADERSIZE + length; + SetDirectoryEntry( list, parent ); + list = parent.parent; + } + + directory.insert( directory.end(), entry ); + + return directory.size() - 1; +} + + +/** Modifies an entry. + + \param i the ID of the entry which is to modify + \param type the type of this entry + \param name the name + \param length the length of the data in the container + \param list the container in which this object is contained. + \note Do not change length, offset, or the parent container. + \note Do not change an empty name ("") to a name and vice versa */ + +void RIFFFile::SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list ) +{ + RIFFDirEntry entry( type, name, length, offset, list ); + + assert( i >= 0 && i < ( int ) directory.size() ); + + directory[ i ] = entry; +} + + +/** Modifies an entry. + + The entry.written flag is set to false because the contents has been modified + + \param i the ID of the entry which is to modify + \param entry the new entry + \note Do not change length, offset, or the parent container. + \note Do not change an empty name ("") to a name and vice versa */ + +void RIFFFile::SetDirectoryEntry( int i, RIFFDirEntry &entry ) +{ + assert( i >= 0 && i < ( int ) directory.size() ); + + entry.written = false; + directory[ i ] = entry; +} + + +/** Retrieves an entry. + + Gets the most important member variables. + + \param i the ID of the entry to retrieve + \param type + \param name + \param length + \param offset + \param list */ + +void RIFFFile::GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const +{ + RIFFDirEntry entry; + + assert( i >= 0 && i < ( int ) directory.size() ); + + entry = directory[ i ]; + type = entry.type; + name = entry.name; + length = entry.length; + offset = entry.offset; + list = entry.parent; +} + + +/** Retrieves an entry. + + Gets the whole RIFFDirEntry object. + + \param i the ID of the entry to retrieve + \return the entry */ + +RIFFDirEntry RIFFFile::GetDirectoryEntry( int i ) const +{ + assert( i >= 0 && i < ( int ) directory.size() ); + + return directory[ i ]; +} + + +/** Calculates the total size of the file + + \return the size the file in bytes +*/ + +off_t RIFFFile::GetFileSize( void ) const +{ + + /* If we have at least one entry, return the length field + of the FILE entry, which is the length of its contents, + which is the actual size of whatever is currently in the + AVI directory structure. + + Note that the first entry does not belong to the AVI + file. + + If we don't have any entry, the file size is zero. */ + + if ( directory.size() > 0 ) + return directory[ 0 ].length; + else + return 0; +} + + +/** prints the attributes of the entry + + \param i the ID of the entry to print +*/ + +void RIFFFile::PrintDirectoryEntry ( int i ) const +{ + RIFFDirEntry entry; + RIFFDirEntry parent; + FOURCC entry_name; + FOURCC list_name; + + /* Get all attributes of the chunk object. If it is contained + in a list, get the name of the list too (otherwise the name of + the list is blank). If the chunk object doesn´t have a name (only + LISTs and RIFFs have a name), the name is blank. */ + + entry = GetDirectoryEntry( i ); + if ( entry.parent != RIFF_NO_PARENT ) + { + parent = GetDirectoryEntry( entry.parent ); + list_name = parent.name; + } + else + { + list_name = make_fourcc( " " ); + } + if ( entry.name != 0 ) + { + entry_name = entry.name; + } + else + { + entry_name = make_fourcc( " " ); + } + + /* Print out the ascii representation of type and name, as well as + length and file offset. */ + + cout << hex << setfill( '0' ) << "type: " + << ((char *)&entry.type)[0] + << ((char *)&entry.type)[1] + << ((char *)&entry.type)[2] + << ((char *)&entry.type)[3] + << " name: " + << ((char *)&entry_name)[0] + << ((char *)&entry_name)[1] + << ((char *)&entry_name)[2] + << ((char *)&entry_name)[3] + << " length: 0x" << setw( 12 ) << entry.length + << " offset: 0x" << setw( 12 ) << entry.offset + << " list: " + << ((char *)&list_name)[0] + << ((char *)&list_name)[1] + << ((char *)&list_name)[2] + << ((char *)&list_name)[3] << dec << endl; + + /* print the content itself */ + + PrintDirectoryEntryData( entry ); +} + + +/** prints the contents of the entry + + Prints a readable representation of the contents of an index. + Override this to print out any objects you store in the RIFF file. + + \param entry the entry to print */ + +void RIFFFile::PrintDirectoryEntryData( const RIFFDirEntry &entry ) const + {} + + +/** prints the contents of the whole directory + + Prints a readable representation of the contents of an index. + Override this to print out any objects you store in the RIFF file. + + \param entry the entry to print */ + +void RIFFFile::PrintDirectory() const +{ + int i; + int count = directory.size(); + + for ( i = 0; i < count; ++i ) + PrintDirectoryEntry( i ); +} + + +/** finds the index + + finds the index of a given directory entry type + + \todo inefficient if the directory has lots of items + \param type the type of the entry to find + \param n the zero-based instance of type to locate + \return the index of the found object in the directory, or -1 if not found */ + +int RIFFFile::FindDirectoryEntry ( FOURCC type, int n ) const +{ + int i, j = 0; + int count = directory.size(); + + for ( i = 0; i < count; ++i ) + if ( directory[ i ].type == type ) + { + if ( j == n ) + return i; + j++; + } + + return -1; +} + + +/** Reads all items that are contained in one list + + Read in one chunk and add it to the directory. If the chunk + happens to be of type LIST, then call ParseList recursively for + it. + + \param parent The id of the item to process +*/ + +void RIFFFile::ParseChunk( int parent ) +{ + FOURCC type; + DWORD length; + int typesize; + + /* Check whether it is a LIST. If so, let ParseList deal with it */ + + read( fd, &type, sizeof( type ) ); + if ( type == make_fourcc( "LIST" ) ) + { + typesize = -sizeof( type ); + fail_if( lseek( fd, typesize, SEEK_CUR ) == ( off_t ) - 1 ); + ParseList( parent ); + } + + /* it is a normal chunk, create a new directory entry for it */ + + else + { + fail_neg( read( fd, &length, sizeof( length ) ) ); + if ( length & 1 ) + length++; + AddDirectoryEntry( type, 0, length, parent ); + fail_if( lseek( fd, length, SEEK_CUR ) == ( off_t ) - 1 ); + } +} + + +/** Reads all items that are contained in one list + + \param parent The id of the list to process + +*/ + +void RIFFFile::ParseList( int parent ) +{ + FOURCC type; + FOURCC name; + int list; + DWORD length; + off_t pos; + off_t listEnd; + + /* Read in the chunk header (type and length). */ + fail_neg( read( fd, &type, sizeof( type ) ) ); + fail_neg( read( fd, &length, sizeof( length ) ) ); + + if ( length & 1 ) + length++; + + /* The contents of the list starts here. Obtain its offset. The list + name (4 bytes) is already part of the contents). */ + + pos = lseek( fd, 0, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + fail_neg( read( fd, &name, sizeof( name ) ) ); + + /* Add an entry for this list. */ + + list = AddDirectoryEntry( type, name, sizeof( name ), parent ); + + /* Read in any chunks contained in this list. This list is the + parent for all chunks it contains. */ + + listEnd = pos + length; + while ( pos < listEnd ) + { + ParseChunk( list ); + pos = lseek( fd, 0, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + } +} + + +/** Reads the directory structure of the whole RIFF file + +*/ + +void RIFFFile::ParseRIFF( void ) +{ + FOURCC type; + DWORD length; + off_t filesize; + off_t pos; + int container = AddDirectoryEntry( make_fourcc( "FILE" ), make_fourcc( "FILE" ), 0, RIFF_NO_PARENT ); + + pos = lseek( fd, 0, SEEK_SET ); + + /* calculate file size from RIFF header instead from physical file. */ + + while ( ( read( fd, &type, sizeof( type ) ) > 0 ) && + ( read( fd, &length, sizeof( length ) ) > 0 ) && + ( type == make_fourcc( "RIFF" ) ) ) + { + + filesize += length + RIFF_HEADERSIZE; + + fail_if( lseek( fd, pos, SEEK_SET ) == ( off_t ) - 1 ); + ParseList( container ); + pos = lseek( fd, 0, SEEK_CUR ); + fail_if( pos == ( off_t ) - 1 ); + } +} + + +/** Reads one item including its contents from the RIFF file + + \param chunk_index The index of the item to write + \param data A pointer to the data + +*/ + +void RIFFFile::ReadChunk( int chunk_index, void *data, off_t data_len ) +{ + RIFFDirEntry entry; + + entry = GetDirectoryEntry( chunk_index ); + pthread_mutex_lock( &file_mutex ); + fail_if( lseek( fd, entry.offset, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( read( fd, data, entry.length > data_len ? data_len : entry.length ) ); + pthread_mutex_unlock( &file_mutex ); +} + + +/** Writes one item including its contents to the RIFF file + + \param chunk_index The index of the item to write + \param data A pointer to the data + +*/ + +void RIFFFile::WriteChunk( int chunk_index, const void *data ) +{ + RIFFDirEntry entry; + + entry = GetDirectoryEntry( chunk_index ); + pthread_mutex_lock( &file_mutex ); + fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ); + fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) ); + DWORD length = entry.length; + fail_neg( write( fd, &length, sizeof( length ) ) ); + fail_neg( write( fd, data, entry.length ) ); + pthread_mutex_unlock( &file_mutex ); + + /* Remember that this entry already has been written. */ + + directory[ chunk_index ].written = true; +} + + +/** Writes out the directory structure + + For all items in the directory list that have not been written + yet, it seeks to the file position where that item should be + stored and writes the type and length field. If the item has a + name, it will also write the name field. + + \note It does not write the contents of any item. Use WriteChunk to do that. */ + +void RIFFFile::WriteRIFF( void ) +{ + int i; + RIFFDirEntry entry; + int count = directory.size(); + + /* Start at the second entry (RIFF), since the first entry (FILE) + is needed only for internal purposes and is not written to the + file. */ + + for ( i = 1; i < count; ++i ) + { + + /* Only deal with entries that haven´t been written */ + + entry = GetDirectoryEntry( i ); + if ( entry.written == false ) + { + + /* A chunk entry consist of its type and length, a list + entry has an additional name. Look up the entry, seek + to the start of the header, which is at the offset of + the data start minus the header size and write out the + items. */ + + fail_if( lseek( fd, entry.offset - RIFF_HEADERSIZE, SEEK_SET ) == ( off_t ) - 1 ) ; + fail_neg( write( fd, &entry.type, sizeof( entry.type ) ) ); + DWORD length = entry.length; + fail_neg( write( fd, &length, sizeof( length ) ) ); + + /* If it has a name, it is a list. Write out the extra name + field. */ + + if ( entry.name != 0 ) + { + fail_neg( write( fd, &entry.name, sizeof( entry.name ) ) ); + } + + /* Remember that this entry already has been written. */ + + directory[ i ].written = true; + } + } +} diff --git a/src/modules/kino/riff.h b/src/modules/kino/riff.h new file mode 100644 index 0000000..bea90f7 --- /dev/null +++ b/src/modules/kino/riff.h @@ -0,0 +1,143 @@ +/* +* riff.h library for RIFF file format i/o +* Copyright (C) 2000 - 2002 Arne Schirmacher +* +* 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 2 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, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Tag: $Name$ +* +* Change log: +* +* $Log$ +* Revision 1.2 2005/07/25 07:21:39 lilo_booter +* + fixes for opendml dv avi +* +* Revision 1.1 2005/04/15 14:28:26 lilo_booter +* Initial version +* +* Revision 1.14 2005/04/01 23:43:10 ddennedy +* apply endian fixes from Daniel Kobras +* +* Revision 1.13 2004/10/11 01:37:11 ddennedy +* mutex safety locks in RIFF and AVI classes, type 2 AVI optimization, mencoder export script +* +* Revision 1.12 2004/01/06 22:53:42 ddennedy +* metadata editing tweaks and bugfixes, new ui elements in preparation for publish functions +* +* Revision 1.11 2003/11/25 23:01:25 ddennedy +* cleanup and a few bugfixes +* +* Revision 1.10 2003/10/21 16:34:34 ddennedy +* GNOME2 port phase 1: initial checkin +* +* Revision 1.8.4.1 2002/11/25 04:48:31 ddennedy +* bugfix to report errors when loading files +* +* Revision 1.8 2002/04/21 06:36:40 ddennedy +* kindler avc and 1394 bus reset support in catpure page, honor max file size +* +* Revision 1.7 2002/04/09 06:53:42 ddennedy +* cleanup, new libdv 0.9.5, large AVI, dnd storyboard +* +* Revision 1.3 2002/03/25 21:34:25 arne +* Support for large (64 bit) files mostly completed +* +* Revision 1.2 2002/03/04 19:22:43 arne +* updated to latest Kino avi code +* +* Revision 1.1.1.1 2002/03/03 19:08:08 arne +* import of version 1.01 +* +*/ + +#ifndef _RIFF_H +#define _RIFF_H 1 + +#include +using std::vector; + +#include + +#include "endian_types.h" + +#define QUADWORD int64_le_t +#define DWORD int32_le_t +#define LONG u_int32_le_t +#define WORD int16_le_t +#define BYTE u_int8_le_t +#define FOURCC u_int32_t // No endian conversion needed. + +#define RIFF_NO_PARENT (-1) +#define RIFF_LISTSIZE (4) +#define RIFF_HEADERSIZE (8) + +#ifdef __cplusplus +extern "C" +{ + FOURCC make_fourcc( const char * s ); +} +#endif + +class RIFFDirEntry +{ +public: + FOURCC type; + FOURCC name; + off_t length; + off_t offset; + int parent; + int written; + + RIFFDirEntry(); + RIFFDirEntry( FOURCC t, FOURCC n, int l, int o, int p ); +}; + + +class RIFFFile +{ +public: + RIFFFile(); + RIFFFile( const RIFFFile& ); + virtual ~RIFFFile(); + RIFFFile& operator=( const RIFFFile& ); + + virtual bool Open( const char *s ); + virtual bool Create( const char *s ); + virtual void Close(); + virtual int AddDirectoryEntry( FOURCC type, FOURCC name, off_t length, int list ); + virtual void SetDirectoryEntry( int i, FOURCC type, FOURCC name, off_t length, off_t offset, int list ); + virtual void SetDirectoryEntry( int i, RIFFDirEntry &entry ); + virtual void GetDirectoryEntry( int i, FOURCC &type, FOURCC &name, off_t &length, off_t &offset, int &list ) const; + virtual RIFFDirEntry GetDirectoryEntry( int i ) const; + virtual off_t GetFileSize( void ) const; + virtual void PrintDirectoryEntry( int i ) const; + virtual void PrintDirectoryEntryData( const RIFFDirEntry &entry ) const; + virtual void PrintDirectory( void ) const; + virtual int FindDirectoryEntry( FOURCC type, int n = 0 ) const; + virtual void ParseChunk( int parent ); + virtual void ParseList( int parent ); + virtual void ParseRIFF( void ); + virtual void ReadChunk( int chunk_index, void *data, off_t data_len ); + virtual void WriteChunk( int chunk_index, const void *data ); + virtual void WriteRIFF( void ); + +protected: + int fd; + pthread_mutex_t file_mutex; + +private: + vector directory; +}; +#endif diff --git a/src/modules/lumas/.compress b/src/modules/lumas/.compress new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/lumas/Makefile b/src/modules/lumas/Makefile new file mode 100644 index 0000000..caec186 --- /dev/null +++ b/src/modules/lumas/Makefile @@ -0,0 +1,23 @@ +include ../../../config.mak +LDFLAGS= + +all: luma create_lumas + @./create_lumas + +luma: luma.c + +create_lumas: + +depend: + +distclean: + rm -rf PAL NTSC luma + +clean: + rm -f luma + +install: all + install -d $(DESTDIR)$(prefix)/share/mlt/modules/lumas/PAL + install -d $(DESTDIR)$(prefix)/share/mlt/modules/lumas/NTSC + install -m 644 PAL/* $(DESTDIR)$(prefix)/share/mlt/modules/lumas/PAL + install -m 644 NTSC/* $(DESTDIR)$(prefix)/share/mlt/modules/lumas/NTSC diff --git a/src/modules/lumas/configure b/src/modules/lumas/configure new file mode 100755 index 0000000..e9ce870 --- /dev/null +++ b/src/modules/lumas/configure @@ -0,0 +1,26 @@ +#!/bin/sh + +if [ "$help" = "1" ] +then + cat << EOF +Luma options: + + --luma-compress - Produce compressed (png) lumas + --luma-8bpp - Produce 8 bit pgm lumas (defaut is 16 bit) + +EOF + +else + + rm -f .8bit .compress .executed + + for i in "$@" + do + case $i in + --luma-compress ) touch .compress ;; + --luma-8bit ) touch .8bit ;; + esac + done + +fi + diff --git a/src/modules/lumas/create_lumas b/src/modules/lumas/create_lumas new file mode 100755 index 0000000..f93aa13 --- /dev/null +++ b/src/modules/lumas/create_lumas @@ -0,0 +1,48 @@ +#!/bin/sh + +[ \( -d PAL \) -a \( ! $0 -nt .executed \) ] && exit 0 + +bpp=16 +[ -f .8bit ] && bpp=8 + +for i in PAL NTSC +do + mkdir -p $i + rm -f $i/*.pgm $i/*.png + + [ "$i" == "PAL" ] && h=576 || h=480 + ./luma -h $h -bpp $bpp > $i/luma01.pgm + ./luma -h $h -bpp $bpp -bands $h > $i/luma02.pgm + ./luma -h $h -bpp $bpp -hmirror 1 > $i/luma03.pgm + ./luma -h $h -bpp $bpp -bands $h -vmirror 1 > $i/luma04.pgm + ./luma -h $h -bpp $bpp -offset 32768 -dmirror 1 > $i/luma05.pgm + ./luma -h $h -bpp $bpp -offset 32768 -dmirror 1 -flip 1 > $i/luma06.pgm + ./luma -h $h -bpp $bpp -offset 32768 -dmirror 1 -quart 1 > $i/luma07.pgm + ./luma -h $h -bpp $bpp -offset 32768 -dmirror 1 -quart 1 -flip 1 > $i/luma08.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 0 > $i/luma09.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 0 -rotate 1 -flop 1 > $i/luma10.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 1 > $i/luma11.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 1 -vmirror 1 > $i/luma12.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 1 -rotate 1 -flop 1 > $i/luma13.pgm + ./luma -h $h -bpp $bpp -bands 12 -rband 1 -rotate 1 -vmirror 1 > $i/luma14.pgm + ./luma -h $h -bpp $bpp -offset 32768 -dmirror 1 -hmirror 1 > $i/luma15.pgm + ./luma -h $h -bpp $bpp -type 1 > $i/luma16.pgm + ./luma -h $h -bpp $bpp -type 1 -bands 2 -rband 1 > $i/luma17.pgm + ./luma -h $h -bpp $bpp -type 2 > $i/luma18.pgm + ./luma -h $h -bpp $bpp -type 2 -quart 1 > $i/luma19.pgm + ./luma -h $h -bpp $bpp -type 2 -quart 1 -flip 1 > $i/luma20.pgm + ./luma -h $h -bpp $bpp -type 2 -quart 1 -bands 2 > $i/luma21.pgm + ./luma -h $h -bpp $bpp -type 3 > $i/luma22.pgm + + if [ -f .compress ] + then + for f in $i/*.pgm + do + convert $f $f.png + rm -f $f + done + fi +done + +touch .executed + diff --git a/src/modules/lumas/luma.c b/src/modules/lumas/luma.c new file mode 100644 index 0000000..965b2df --- /dev/null +++ b/src/modules/lumas/luma.c @@ -0,0 +1,441 @@ +/* + * luma.c -- image generator for transition_luma + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +typedef struct +{ + int type; + int w; + int h; + int bands; + int rband; + int vmirror; + int hmirror; + int dmirror; + int invert; + int offset; + int flip; + int flop; + int pflip; + int pflop; + int quart; + int rotate; +} +luma; + +void luma_init( luma *this ) +{ + memset( this, 0, sizeof( luma ) ); + this->type = 0; + this->w = 720; + this->h = 576; + this->bands = 1; + this->rband = 0; + this->vmirror = 0; + this->hmirror = 0; + this->dmirror = 0; + this->invert = 0; + this->offset = 0; + this->flip = 0; + this->flop = 0; + this->quart = 0; + this->pflop = 0; + this->pflip = 0; +} + +static inline int sqrti( int n ) +{ + int p = 0; + int q = 1; + int r = n; + int h = 0; + + while( q <= n ) + q = 4 * q; + + while( q != 1 ) + { + q = q / 4; + h = p + q; + p = p / 2; + if ( r >= h ) + { + p = p + q; + r = r - h; + } + } + + return p; +} + +uint16_t *luma_render( luma *this ) +{ + int i = 0; + int j = 0; + int k = 0; + + if ( this->quart ) + { + this->w *= 2; + this->h *= 2; + } + + if ( this->rotate ) + { + int t = this->w; + this->w = this->h; + this->h = t; + } + + int max = ( 1 << 16 ) - 1; + uint16_t *image = malloc( this->w * this->h * sizeof( uint16_t ) ); + uint16_t *end = image + this->w * this->h; + uint16_t *p = image; + uint16_t *r = image; + int lower = 0; + int lpb = this->h / this->bands; + int rpb = max / this->bands; + int direction = 1; + + int half_w = this->w / 2; + int half_h = this->h / 2; + + if ( !this->dmirror && ( this->hmirror || this->vmirror ) ) + rpb *= 2; + + for ( i = 0; i < this->bands; i ++ ) + { + lower = i * rpb; + direction = 1; + + if ( this->rband && i % 2 == 1 ) + { + direction = -1; + lower += rpb; + } + + switch( this->type ) + { + case 1: + { + int length = sqrti( half_w * half_w + lpb * lpb / 4 ); + int value; + int x = 0; + int y = 0; + for ( j = 0; j < lpb; j ++ ) + { + y = j - lpb / 2; + for ( k = 0; k < this->w; k ++ ) + { + x = k - half_w; + value = sqrti( x * x + y * y ); + *p ++ = lower + ( direction * rpb * ( ( max * value ) / length ) / max ) + ( j * this->offset * 2 / lpb ) + ( j * this->offset / lpb ); + } + } + } + break; + + case 2: + { + for ( j = 0; j < lpb; j ++ ) + { + int value = ( ( j * this->w ) / lpb ) - half_w; + if ( value > 0 ) + value = - value; + for ( k = - half_w; k < value; k ++ ) + *p ++ = lower + ( direction * rpb * ( ( max * abs( k ) ) / half_w ) / max ); + for ( k = value; k < abs( value ); k ++ ) + *p ++ = lower + ( direction * rpb * ( ( max * abs( value ) ) / half_w ) / max ) + ( j * this->offset * 2 / lpb ) + ( j * this->offset / lpb ); + for ( k = abs( value ); k < half_w; k ++ ) + *p ++ = lower + ( direction * rpb * ( ( max * abs( k ) ) / half_w ) / max ); + } + } + break; + + case 3: + { + int length; + for ( j = -half_h; j < half_h; j ++ ) + { + if ( j < 0 ) + { + for ( k = - half_w; k < half_w; k ++ ) + { + length = sqrti( k * k + j * j ); + *p ++ = ( max / 4 * k ) / ( length + 1 ); + } + } + else + { + for ( k = half_w; k > - half_w; k -- ) + { + length = sqrti( k * k + j * j ); + *p ++ = ( max / 2 ) + ( max / 4 * k ) / ( length + 1 ); + } + } + } + } + break; + + default: + for ( j = 0; j < lpb; j ++ ) + for ( k = 0; k < this->w; k ++ ) + *p ++ = lower + ( direction * ( rpb * ( ( k * max ) / this->w ) / max ) ) + ( j * this->offset * 2 / lpb ); + break; + } + } + + if ( this->quart ) + { + this->w /= 2; + this->h /= 2; + for ( i = 1; i < this->h; i ++ ) + { + p = image + i * this->w; + r = image + i * 2 * this->w; + j = this->w; + while ( j -- > 0 ) + *p ++ = *r ++; + } + } + + if ( this->dmirror ) + { + for ( i = 0; i < this->h; i ++ ) + { + p = image + i * this->w; + r = end - i * this->w; + j = ( this->w * ( this->h - i ) ) / this->h; + while ( j -- ) + *( -- r ) = *p ++; + } + } + + if ( this->flip ) + { + uint16_t t; + for ( i = 0; i < this->h; i ++ ) + { + p = image + i * this->w; + r = p + this->w; + while( p != r ) + { + t = *p; + *p ++ = *( -- r ); + *r = t; + } + } + } + + if ( this->flop ) + { + uint16_t t; + r = end; + for ( i = 1; i < this->h / 2; i ++ ) + { + p = image + i * this->w; + j = this->w; + while( j -- ) + { + t = *( -- p ); + *p = *( -- r ); + *r = t; + } + } + } + + if ( this->hmirror ) + { + p = image; + while ( p < end ) + { + r = p + this->w; + while ( p != r ) + *( -- r ) = *p ++; + p += this->w / 2; + } + } + + if ( this->vmirror ) + { + p = image; + r = end; + while ( p != r ) + *( -- r ) = *p ++; + } + + if ( this->invert ) + { + p = image; + r = image; + while ( p < end ) + *p ++ = max - *r ++; + } + + if ( this->pflip ) + { + uint16_t t; + for ( i = 0; i < this->h; i ++ ) + { + p = image + i * this->w; + r = p + this->w; + while( p != r ) + { + t = *p; + *p ++ = *( -- r ); + *r = t; + } + } + } + + if ( this->pflop ) + { + uint16_t t; + end = image + this->w * this->h; + r = end; + for ( i = 1; i < this->h / 2; i ++ ) + { + p = image + i * this->w; + j = this->w; + while( j -- ) + { + t = *( -- p ); + *p = *( -- r ); + *r = t; + } + } + } + + if ( this->rotate ) + { + uint16_t *image2 = malloc( this->w * this->h * sizeof( uint16_t ) ); + for ( i = 0; i < this->h; i ++ ) + { + p = image + i * this->w; + r = image2 + this->h - i - 1; + for ( j = 0; j < this->w; j ++ ) + { + *r = *( p ++ ); + r += this->h; + } + } + i = this->w; + this->w = this->h; + this->h = i; + free( image ); + image = image2; + } + + return image; +} + +int main( int argc, char **argv ) +{ + int arg = 1; + int bpp = 8; + + luma this; + uint16_t *image = NULL; + + luma_init( &this ); + + for ( arg = 1; arg < argc - 1; arg ++ ) + { + if ( !strcmp( argv[ arg ], "-bpp" ) ) + bpp = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-type" ) ) + this.type = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-w" ) ) + this.w = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-h" ) ) + this.h = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-bands" ) ) + this.bands = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-rband" ) ) + this.rband = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-hmirror" ) ) + this.hmirror = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-vmirror" ) ) + this.vmirror = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-dmirror" ) ) + this.dmirror = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-offset" ) ) + this.offset = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-invert" ) ) + this.invert = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-flip" ) ) + this.flip = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-flop" ) ) + this.flop = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-pflip" ) ) + this.pflip = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-pflop" ) ) + this.pflop = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-quart" ) ) + this.quart = atoi( argv[ ++ arg ] ); + else if ( !strcmp( argv[ arg ], "-rotate" ) ) + this.rotate = atoi( argv[ ++ arg ] ); + else + fprintf( stderr, "ignoring %s\n", argv[ arg ] ); + } + + if ( bpp != 8 && bpp != 16 ) + { + fprintf( stderr, "Invalid bpp %d\n", bpp ); + return 1; + } + + image = luma_render( &this ); + + if ( bpp == 16 ) + { + uint16_t *end = image + this.w * this.h; + uint16_t *p = image; + uint8_t *q = ( uint8_t * )image; + while ( p < end ) + { + *p ++ = ( *q << 8 ) + *( q + 1 ); + q += 2; + } + printf( "P5\n" ); + printf( "%d %d\n", this.w, this.h ); + printf( "65535\n" ); + fwrite( image, this.w * this.h * sizeof( uint16_t ), 1, stdout ); + } + else + { + uint16_t *end = image + this.w * this.h; + uint16_t *p = image; + uint8_t *q = ( uint8_t * )image; + while ( p < end ) + *q ++ = ( uint8_t )( *p ++ >> 8 ); + printf( "P5\n" ); + printf( "%d %d\n", this.w, this.h ); + printf( "255\n" ); + fwrite( image, this.w * this.h, 1, stdout ); + } + + return 0; +} + diff --git a/src/modules/motion_est/Makefile b/src/modules/motion_est/Makefile new file mode 100644 index 0000000..45ac3b9 --- /dev/null +++ b/src/modules/motion_est/Makefile @@ -0,0 +1,55 @@ +include ../../../config.mak + +TARGET = ../libmltmotion_est.so + +OBJS = factory.o \ + filter_motion_est.o \ + filter_crop_detect.o \ + filter_autotrack_rectangle.o \ + arrow_code.o \ + filter_vismv.o \ + producer_slowmotion.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +test: $(TARGET) + ~/mlt-devel/mlt/src/inigo/inigo -filter motion_est -filter vismv -filter benchmark -consumer sdl rescale=none real_time=0 audio_off=1 silent=1 /media/cdrecorder/BBC.The.Private.Life.Of.Plants.Pt5.Living.Together.DivX505.AC3.www.MVGroup.org.uk.avi in=50000 + +hist: $(TARGET) + ~/mlt-devel/mlt/src/inigo/inigo -filter motion_est -filter histogram -consumer sdl rescale=none real_time=0 audio_off=1 silent=1 /media/cdrecorder/BBC.The.Private.Life.Of.Plants.Pt5.Living.Together.DivX505.AC3.www.MVGroup.org.uk.avi in=40000 + + +test2: $(TARGET) + inigo colour:black -filter watermark:"+mello.txt" composite.geometry="0,0:10%x10%;99=90%,90%" composite.out=99 -filter crop_detect -filter motion_est -filter vismv + +realtime: $(TARGET) + ~/mlt-devel/mlt/src/inigo/inigo -filter motion_est -filter vismv -consumer sdl rescale=none /media/cdrecorder/BBC.The.Private.Life.Of.Plants.Pt5.Living.Together.DivX505.AC3.www.MVGroup.org.uk.avi in=30000 + +testhist: $(TARGET) + ~/mlt-devel/mlt/src/inigo/inigo -consumer sdl rescale=none silent=1 -filter motion_est -filter histogram -filter vismv /media/cdrecorder/BBC.The.Private.Life.Of.Plants.Pt5.Living.Together.DivX505.AC3.www.MVGroup.org.uk.avi in=10000 + + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/motion_est/README b/src/modules/motion_est/README new file mode 100644 index 0000000..5542d3f --- /dev/null +++ b/src/modules/motion_est/README @@ -0,0 +1,97 @@ +INTRO: + +This module is designed to provide application agnostic motion estimation. +I wrote it from scratch because I found the other Open Source code to be +limited by their difficulty of reuse. + + +COMPILE: + +To compile this module, you must supply these options to the root configure script: + +--enable-gpl --enable-motion-est + + +EXAMPLES: + +Estimate the motion: + + > inigo -filter motion_est + +To display the motion vectors as pretty arrows: + + > inigo -filter motion_est -filter vismv + +If your using a movie file that contains a crop, you will get better results with this: + + > inigo -filter crop_detect -filter motion_est -filter vismv + +If your computer is unable to do the above examples in real time, try this: + + > inigo -filter motion_est -filter vismv -consumer inigo real_time=0 + +If you'd like to see the motion vectors without the median denoising function, do this: + + > inigo -filter motion_est denoise=0 -filter vismv + +To reconstruct each frame by applying the motion to the previous frame: + + > inigo -filter motion_est show_reconstruction=1 + +To compare the reconstructed frame and the real frame (while paused): + + > inigo -filter motion_est show_reconstruction=1 toggle_when_paused=1 + +To show the difference (residual) between the reconstructed frame the real frame: + + > inigo -filter motion_est show_residual=1 + +To automatically track an object in the frame, try this: + + > inigo -filter autotrack_rectangle:X,Y:WxH debug=1 + +(Where X,Y is the origin of the rectangle indexed from upper left and WxH is the dimensions of the rectangle.) + +To obscure that same object in the frame, try this: + + > inigo -filter autotrack_rectangle:X,Y:WxH obscure=1 + +There is now a slow motion producer that does interpolation based on the motion vectors: + + > inigo slowmotion: _speed=0.1 method=1 debug=1 + +NOTES (and deficiencies): + +1. Ignore shot change detection when your using the autotrack_rectangle filter. + +2. Don't assume motion vectors displayed while stepping backwards and forward are that same vectors + that would be calculated while playing the footage from start to finish, nonstop. Stepping forward + should be fine after a few frames, however. + +3. SSE instructions are lazily assumed. MMX, Altivec, and SIMD-less would be good too. + +4. Motion estimation is only performed in the luma color space. + +5. Motion vectors should have sub-pixel accuracy. + +6. Motion vectors are not serializable yet. + +7. A diligent test suite is needed. (show_reconstruction & show_residual are a start) + +8. Multithreaded code will see HUGE benefits on multi-CPU systems. Donations of a multi-core cpu or a + multi-cpu system to the author will encourage development. + +9. Macroblock sizes are not dynamic (Though settable at runtime.) + +10. Notes (5), (7), and (9) would go a long ways to making this code suitable for a modern video encoder. + +11. Shot change works well but arbitrarily chosen thresholds need to be tuned. + +12. Given the documentation of other motion estimation code bases, I will GLADLY clarify and + document any piece of code upon request. + +13. Considerable effort has been put into the speed. I usually experience 10ms or less per frame for PAL on 2.8GHZ p4. + +Zachary Drew +drew0054@tc.umn.edu + diff --git a/src/modules/motion_est/arrow_code.c b/src/modules/motion_est/arrow_code.c new file mode 100644 index 0000000..5e65d44 --- /dev/null +++ b/src/modules/motion_est/arrow_code.c @@ -0,0 +1,165 @@ +/* + * /brief Draw arrows + * /author Zachary Drew, Copyright 2004 + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "arrow_code.h" + +#include +#include +#include +#include + +#define MIN(a,b) ((a) > (b) ? (b) : (a)) + +#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b)) +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + + +static int w; +static int h; +static int xstride; +static int ystride; +static mlt_image_format format; + +int init_arrows( mlt_image_format *image_format, int width, int height ) +{ + w = width; + h = height; + format = *image_format; + switch( *image_format ) { + case mlt_image_yuv422: + xstride = 2; + ystride = xstride * w; + break; + default: + // I don't know + return 0; + } + return 1; + +} + +// ffmpeg borrowed +static inline int clip(int a, int amin, int amax) +{ + if (a < amin) + return amin; + else if (a > amax) + return amax; + else + return a; +} + + +/** + * draws an line from (ex, ey) -> (sx, sy). + * Credits: modified from ffmpeg project + * @param ystride stride/linesize of the image + * @param xstride stride/element size of the image + * @param color color of the arrow + */ +void draw_line(uint8_t *buf, int sx, int sy, int ex, int ey, int color) +{ + int t, x, y, fr, f; + + + sx= clip(sx, 0, w-1); + sy= clip(sy, 0, h-1); + ex= clip(ex, 0, w-1); + ey= clip(ey, 0, h-1); + + buf[sy*ystride + sx*xstride]+= color; + + if(ABS(ex - sx) > ABS(ey - sy)){ + if(sx > ex){ + t=sx; sx=ex; ex=t; + t=sy; sy=ey; ey=t; + } + buf+= sx*xstride + sy*ystride; + ex-= sx; + f= ((ey-sy)<<16)/ex; + for(x= 0; x <= ex; x++){ + y = (x*f)>>16; + fr= (x*f)&0xFFFF; + buf[ y *ystride + x*xstride]+= (color*(0x10000-fr))>>16; + buf[(y+1)*ystride + x*xstride]+= (color* fr )>>16; + } + }else{ + if(sy > ey){ + t=sx; sx=ex; ex=t; + t=sy; sy=ey; ey=t; + } + buf+= sx*xstride + sy*ystride; + ey-= sy; + if(ey) f= ((ex-sx)<<16)/ey; + else f= 0; + for(y= 0; y <= ey; y++){ + x = (y*f)>>16; + fr= (y*f)&0xFFFF; + buf[y*ystride + x *xstride]+= (color*(0x10000-fr))>>16;; + buf[y*ystride + (x+1)*xstride]+= (color* fr )>>16;; + } + } +} + +void draw_rectangle_fill(uint8_t *buf, int x, int y, int w, int h, int color) +{ + int i,j; + for ( i = 0; i < w; i++ ) + for ( j = 0; j < h; j++ ) + buf[ (y+j)*ystride + (x+i)*xstride] = color; +} + +void draw_rectangle_outline(uint8_t *buf, int x, int y, int w, int h, int color) +{ + int i,j; + for ( i = 0; i < w; i++ ) { + buf[ y*ystride + (x+i)*xstride ] += color; + buf[ (y+h)*ystride + (x+i)*xstride ] += color; + } + for ( j = 1; j < h+1; j++ ) { + buf[ (y+j)*ystride + x*xstride ] += color; + buf[ (y+j)*ystride + (x+w)*xstride ] += color; + } +} +/** + * draws an arrow from (ex, ey) -> (sx, sy). + * Credits: modified from ffmpeg project + * @param stride stride/linesize of the image + * @param color color of the arrow + */ +void draw_arrow(uint8_t *buf, int sx, int sy, int ex, int ey, int color){ + + int dx,dy; + dx= ex - sx; + dy= ey - sy; + + if(dx*dx + dy*dy > 3*3){ + int rx= dx + dy; + int ry= -dx + dy; + int length= sqrt((rx*rx + ry*ry)<<8); + + rx= ROUNDED_DIV(rx*3<<4, length); + ry= ROUNDED_DIV(ry*3<<4, length); + + draw_line(buf, sx, sy, sx + rx, sy + ry, color); + draw_line(buf, sx, sy, sx - ry, sy + rx, color); + } + draw_line(buf, sx, sy, ex, ey, color); +} diff --git a/src/modules/motion_est/arrow_code.h b/src/modules/motion_est/arrow_code.h new file mode 100644 index 0000000..6fcca20 --- /dev/null +++ b/src/modules/motion_est/arrow_code.h @@ -0,0 +1,26 @@ +/** + * file: arrow_code.h + * + * /brief Misc functions to draw arrows + * /author Zachary Drew, Copyright 2004 + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +extern int init_arrows( mlt_image_format *image_format, int width, int height ); +extern void draw_line(uint8_t *buf, int sx, int sy, int ex, int ey, int color); +extern void draw_arrow(uint8_t *buf, int sx, int sy, int ex, int ey, int color); +extern void draw_rectangle_fill(uint8_t *buf, int x, int y, int w, int h, int color); +extern void draw_rectangle_outline(uint8_t *buf, int x, int y, int w, int h, int color); diff --git a/src/modules/motion_est/configure b/src/modules/motion_est/configure new file mode 100755 index 0000000..578862c --- /dev/null +++ b/src/modules/motion_est/configure @@ -0,0 +1,23 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +if [ "$motionest" = "false" ] +then + touch ../disable-motion_est + echo "- motion estimation components disabled. Add --enable-motion-est to enable." +else +cat << EOF >> ../producers.dat +slowmotion libmltmotion_est.so +EOF + +cat << EOF >> ../filters.dat +motion_est libmltmotion_est.so +vismv libmltmotion_est.so +crop_detect libmltmotion_est.so +autotrack_rectangle libmltmotion_est.so +EOF +fi + +fi diff --git a/src/modules/motion_est/factory.c b/src/modules/motion_est/factory.c new file mode 100644 index 0000000..a602018 --- /dev/null +++ b/src/modules/motion_est/factory.c @@ -0,0 +1,45 @@ +/* + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_motion_est.h" + +extern mlt_filter filter_motion_est_init(char *); +extern mlt_filter filter_vismv_init(char *); +extern mlt_filter filter_crop_detect_init(char *); +extern mlt_filter filter_autotrack_rectangle_init(char *); +extern mlt_producer producer_slowmotion_init(char *); + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "motion_est" ) ) + return filter_motion_est_init( arg ); + if ( !strcmp( id, "vismv" ) ) + return filter_vismv_init( arg ); + if ( !strcmp( id, "crop_detect" ) ) + return filter_crop_detect_init( arg ); + if ( !strcmp( id, "autotrack_rectangle" ) ) + return filter_autotrack_rectangle_init( arg ); + return NULL; +} + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "slowmotion" ) ) + return producer_slowmotion_init( arg ); + return NULL; +} diff --git a/src/modules/motion_est/filter_autotrack_rectangle.c b/src/modules/motion_est/filter_autotrack_rectangle.c new file mode 100644 index 0000000..34b5a77 --- /dev/null +++ b/src/modules/motion_est/filter_autotrack_rectangle.c @@ -0,0 +1,326 @@ +/* + * filter_autotrack_rectangle.c + * + * /brief + * /author Zachary Drew, Copyright 2005 + * + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_motion_est.h" +#include "arrow_code.h" + +#include + +#include +#include +#include +#include + +#define MIN(a,b) ((a) > (b) ? (b) : (a)) + +#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b)) +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + +void caculate_motion( struct motion_vector_s *vectors, + mlt_geometry_item boundry, + int macroblock_width, + int macroblock_height, + int mv_buffer_width, + int method, + int width, + int height ) +{ + + + // translate pixel units (from bounds) to macroblock units + // make sure whole macroblock stay within bounds + int left_mb = ( boundry->x + macroblock_width - 1 ) / macroblock_width; + int top_mb = ( boundry->y + macroblock_height - 1 ) / macroblock_height; + int right_mb = ( boundry->x + boundry->w ) / macroblock_width - 1; + int bottom_mb = ( boundry->y + boundry->h ) / macroblock_height - 1; + + int i, j, n = 0; + + int average_x = 0, average_y = 0; + + #define CURRENT ( vectors + j*mv_buffer_width + i ) + + for( i = left_mb; i <= right_mb; i++ ){ + for( j = top_mb; j <= bottom_mb; j++ ) + { + n++; + average_x += CURRENT->dx; + average_y += CURRENT->dy; + } + } + + if ( n == 0 ) return; + + average_x /= n; + average_y /= n; + + n = 0; + int average2_x = 0, average2_y = 0; + for( i = left_mb; i <= right_mb; i++ ){ + for( j = top_mb; j <= bottom_mb; j++ ){ + + if( ABS(CURRENT->dx - average_x) < 3 && + ABS(CURRENT->dy - average_y) < 3 ) + { + n++; + average2_x += CURRENT->dx; + average2_y += CURRENT->dy; + } + } + } + + if ( n == 0 ) return; + + boundry->x -= (double)average2_x / (double)n; + boundry->y -= (double)average2_y / (double)n; + + if ( boundry->x < 0 ) + boundry->x = 0; + + if ( boundry->y < 0 ) + boundry->y = 0; + + if ( boundry->x + boundry->w > width ) + boundry->x = width - boundry->w; + + if ( boundry->y + boundry->h > height ) + boundry->y = height - boundry->h; +} + +// Image stack(able) method +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + + // Get the filter object + mlt_filter filter = mlt_frame_pop_service( frame ); + + // Get the filter's property object + mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); + + // Get the frame position + mlt_position position = mlt_frame_get_position( frame ); + + // Get the new image + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + if( error != 0 ) + mlt_properties_debug( frame_properties, "error after mlt_frame_get_image() in autotrack_rectangle", stderr ); + + // Get the geometry object + mlt_geometry geometry = mlt_properties_get_data(filter_properties, "filter_geometry", NULL); + + // Get the current geometry item + struct mlt_geometry_item_s boundry; + mlt_geometry_fetch(geometry, &boundry, position); + + // Get the motion vectors + struct motion_vector_s *vectors = mlt_properties_get_data( frame_properties, "motion_est.vectors", NULL ); + + // How did the rectangle move? + if( vectors != NULL && + boundry.key != 1 ) // Paused? + { + + int method = mlt_properties_get_int( filter_properties, "method" ); + + // Get the size of macroblocks in pixel units + int macroblock_height = mlt_properties_get_int( frame_properties, "motion_est.macroblock_height" ); + int macroblock_width = mlt_properties_get_int( frame_properties, "motion_est.macroblock_width" ); + int mv_buffer_width = *width / macroblock_width; + + caculate_motion( vectors, &boundry, macroblock_width, macroblock_height, mv_buffer_width, method, *width, *height ); + + + // Make the geometry object a real boy + boundry.key = 1; + boundry.f[0] = 1; + boundry.f[1] = 1; + boundry.f[2] = 1; + boundry.f[3] = 1; + boundry.f[4] = 1; + mlt_geometry_insert(geometry, &boundry); + } + + if( mlt_properties_get_int( filter_properties, "debug" ) == 1 ) + { + init_arrows( format, *width, *height ); + draw_rectangle_outline(*image, boundry.x, boundry.y, boundry.w, boundry.h, 100); + } + + if( mlt_properties_get_int( filter_properties, "obscure" ) == 1 ) + { + mlt_filter obscure = mlt_properties_get_data( filter_properties, "_obscure", NULL ); + + mlt_properties_pass_list( MLT_FILTER_PROPERTIES(obscure), filter_properties, "in, out"); + + // Because filter_obscure needs to be rewritten to use mlt_geometry + char geom[100]; + sprintf( geom, "%d,%d:%dx%d", (int)boundry.x, (int)boundry.y, (int)boundry.w, (int)boundry.h ); + mlt_properties_set( MLT_FILTER_PROPERTIES( obscure ), "start", geom ); + mlt_properties_set( MLT_FILTER_PROPERTIES( obscure ), "end", geom ); + } + + if( mlt_properties_get_int( filter_properties, "collect" ) == 1 ) + { + printf( "%d,%d,%d,%d\n", (int)boundry.x, (int)boundry.y, (int)boundry.w, (int)boundry.h ); + fflush( stdout ); + } + + return error; +} + +static int attach_boundry_to_frame( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the filter object + mlt_filter filter = mlt_frame_pop_service( frame ); + + // Get the filter's property object + mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); + + // Get the frame position + mlt_position position = mlt_frame_get_position( frame ); + + // Get the geometry object + mlt_geometry geometry = mlt_properties_get_data(filter_properties, "filter_geometry", NULL); + if (geometry == NULL) { + mlt_geometry geom = mlt_geometry_init(); + char *arg = mlt_properties_get(filter_properties, "geometry"); + + // Initialize with the supplied geometry + struct mlt_geometry_item_s item; + mlt_geometry_parse_item( geom, &item, arg ); + + item.frame = 0; + item.key = 1; + item.mix = 100; + + mlt_geometry_insert( geom, &item ); + mlt_properties_set_data( filter_properties, "filter_geometry", geom, 0, (mlt_destructor)mlt_geometry_close, (mlt_serialiser)mlt_geometry_serialise ); + geometry = mlt_properties_get_data(filter_properties, "filter_geometry", NULL); + } + + // Get the current geometry item + mlt_geometry_item geometry_item = mlt_pool_alloc( sizeof( struct mlt_geometry_item_s ) ); + mlt_geometry_fetch(geometry, geometry_item, position); + + mlt_properties_set_data( frame_properties, "bounds", geometry_item, sizeof( struct mlt_geometry_item_s ), mlt_pool_release, NULL ); + + // Get the new image + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + if( error != 0 ) + mlt_properties_debug( frame_properties, "error after mlt_frame_get_image() in autotrack_rectangle attach_boundry_to_frame", stderr ); + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + + /* modify the frame with the current geometry */ + mlt_frame_push_service( frame, this); + mlt_frame_push_get_image( frame, attach_boundry_to_frame ); + + + + /* apply the motion estimation filter */ + mlt_filter motion_est = mlt_properties_get_data( MLT_FILTER_PROPERTIES(this), "_motion_est", NULL ); + mlt_filter_process( motion_est, frame); + + + + /* calculate the new geometry based on the motion */ + mlt_frame_push_service( frame, this); + mlt_frame_push_get_image( frame, filter_get_image ); + + + /* visualize the motion vectors */ + if( mlt_properties_get_int( MLT_FILTER_PROPERTIES(this), "debug" ) == 1 ) + { + mlt_filter vismv = mlt_properties_get_data( MLT_FILTER_PROPERTIES(this), "_vismv", NULL ); + if( vismv == NULL ) + { + vismv = mlt_factory_filter( "vismv", NULL ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES(this), "_vismv", vismv, 0, (mlt_destructor)mlt_filter_close, NULL ); + } + + mlt_filter_process( vismv, frame ); + } + + if( mlt_properties_get_int( MLT_FILTER_PROPERTIES(this), "obscure" ) == 1 ) + { + mlt_filter obscure = mlt_properties_get_data( MLT_FILTER_PROPERTIES(this), "_obscure", NULL ); + if( obscure == NULL ) + { + obscure = mlt_factory_filter( "obscure", NULL ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES(this), "_obscure", obscure, 0, (mlt_destructor)mlt_filter_close, NULL ); + } + + mlt_filter_process( obscure, frame ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + + +mlt_filter filter_autotrack_rectangle_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + + // Initialize with the supplied geometry if ther is one + if( arg != NULL ) + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "geometry", arg ); + else + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "geometry", "100,100:100x100" ); + + // create an instance of the motion_est and obscure filter + mlt_filter motion_est = mlt_factory_filter( "motion_est", NULL ); + if( motion_est != NULL ) + mlt_properties_set_data( MLT_FILTER_PROPERTIES(this), "_motion_est", motion_est, 0, (mlt_destructor)mlt_filter_close, NULL ); + else { + mlt_filter_close( this ); + return NULL; + } + + + } + + return this; +} + +/** This source code will self destruct in 5...4...3... +*/ diff --git a/src/modules/motion_est/filter_crop_detect.c b/src/modules/motion_est/filter_crop_detect.c new file mode 100644 index 0000000..3596f2f --- /dev/null +++ b/src/modules/motion_est/filter_crop_detect.c @@ -0,0 +1,244 @@ +/** + * /brief Crop Detection filter + * + * /author Zachary Drew, Copyright 2005 + * + * inspired by mplayer's cropdetect filter + * + * Note: The goemetry generated is zero-indexed and is inclusive of the end values + * + * Options: + * -filter crop_detect debug=1 // Visualize crop + * -filter crop_detect frequency=25 // Detect the crop once a second + * -filter crop_detect frequency=0 // Never detect unless the producer changes + * -filter crop_detect thresh=100 // Changes the threshold (default = 25) + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define DEBUG +#define DEFAULT_THRESH 20 + +#include + +#include +#include +#include +#include +#include "arrow_code.h" + +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + +// Image stack(able) method +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + + // Get the filter object and properties + mlt_filter filter = mlt_frame_pop_service( this ); + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the new image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + if( error != 0 ) { + mlt_properties_debug( MLT_FRAME_PROPERTIES(this), "error after mlt_frame_get_image()", stderr ); + return error; + } + + // Parameter that describes how often to check for the crop + int frequency = mlt_properties_get_int( properties, "frequency"); + + // Producers may start with blank footage, by default we will skip, oh, 5 frames unless overridden + int skip = mlt_properties_get_int( properties, "skip"); + + // The result + mlt_geometry_item bounds = mlt_properties_get_data( properties, "bounds", NULL ); + + // Initialize if needed + if( bounds == NULL ) { + bounds = calloc( 1, sizeof( struct mlt_geometry_item_s ) ); + bounds->w = *width; + bounds->h = *height; + mlt_properties_set_data( properties, "bounds", bounds, sizeof( struct mlt_geometry_item_s ), free, NULL ); + } + + // For periodic detection (with offset of 'skip') + if( frequency == 0 || (int)(mlt_frame_get_position(this)+skip) % frequency != 0) + { + // Inject in stream + mlt_properties_set_data( MLT_FRAME_PROPERTIES(this), "bounds", bounds, sizeof( struct mlt_geometry_item_s ), NULL, NULL ); + + return 0; + } + + + // There is no way to detect a crop for sure, so make up an arbitrary one + int thresh = mlt_properties_get_int( properties, "thresh" ); + + int xstride, ystride; + + switch( *format ) { + case mlt_image_yuv422: + xstride = 2; + ystride = 2 * *width; + break; + default: + fprintf(stderr, "image format not supported by filter_crop_detect\n"); + return -1; + } + + int x, y, average_brightness, deviation; // Scratch variables + uint8_t *q; + + // Top crop + for( y = 0; y < *height/2; y++ ) { + bounds->y = y; + average_brightness = 0; + deviation = 0; + q = *image + y*ystride; + for( x = 0; x < *width; x++ ) + average_brightness += q[x*xstride]; + + average_brightness /= *width; + + for( x = 0; x < *width; x++ ) + deviation += abs(average_brightness - q[x*xstride]); + + if( deviation*10 >= thresh * *width ) + break; + } + + // Bottom crop + for( y = *height - 1; y >= *height/2; y-- ) { + bounds->h = y; + average_brightness = 0; + deviation = 0; + q = *image + y*ystride; + for( x = 0; x < *width; x++ ) + average_brightness += q[x*xstride]; + + average_brightness /= *width; + + for( x = 0; x < *width; x++ ) + deviation += abs(average_brightness - q[x*xstride]); + + if( deviation*10 >= thresh * *width) + break; + } + + // Left crop + for( x = 0; x < *width/2; x++ ) { + bounds->x = x; + average_brightness = 0; + deviation = 0; + q = *image + x*xstride; + for( y = 0; y < *height; y++ ) + average_brightness += q[y*ystride]; + + average_brightness /= *height; + + for( y = 0; y < *height; y++ ) + deviation += abs(average_brightness - q[y*ystride]); + + if( deviation*10 >= thresh * *width ) + break; + } + + // Right crop + for( x = *width - 1; x >= *width/2; x-- ) { + bounds->w = x; + average_brightness = 0; + deviation = 0; + q = *image + x*xstride; + for( y = 0; y < *height; y++ ) + average_brightness += q[y*ystride]; + + average_brightness /= *height; + + for( y = 0; y < *height; y++ ) + deviation += abs(average_brightness - q[y*ystride]); + + if( deviation*10 >= thresh * *width ) + break; + } + + /* Debug: Draw arrows to show crop */ + if( mlt_properties_get_int( properties, "debug") == 1 ) + { + init_arrows( format, *width, *height ); + + draw_arrow(*image, bounds->x, *height/2, bounds->x+50, *height/2, 100); + draw_arrow(*image, *width/2, bounds->y, *width/2, bounds->y+50, 100); + draw_arrow(*image, bounds->w, *height/2, bounds->w-50, *height/2, 100); + draw_arrow(*image, *width/2, bounds->h, *width/2, bounds->h-50, 100); + draw_arrow(*image, bounds->x, bounds->y, bounds->x+40, bounds->y+30, 100); + draw_arrow(*image, bounds->x, bounds->h, bounds->x+40, bounds->h-30, 100); + draw_arrow(*image, bounds->w, bounds->y, bounds->w-40, bounds->y+30, 100); + draw_arrow(*image, bounds->w, bounds->h, bounds->w-40, bounds->h-30, 100); + } + + // Convert to width and correct indexing + bounds->w -= bounds->x - 1; + bounds->h -= bounds->y - 1; + + if( mlt_properties_get_int( properties, "debug") == 1 ) + fprintf(stderr, "Top:%f Left:%f Width:%f Height:%f\n", bounds->y, bounds->x, bounds->w, bounds->h); + + /* inject into frame */ + mlt_properties_set_data( MLT_FRAME_PROPERTIES(this), "bounds", bounds, sizeof( struct mlt_geometry_item_s ), NULL, NULL ); + + return error; +} + + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + + // Put the filter object somewhere we can find it + mlt_frame_push_service( frame, this); + + // Push the frame filter + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ +mlt_filter filter_crop_detect_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + + /* defaults */ + mlt_properties_set_int( MLT_FILTER_PROPERTIES(this), "frequency", 1); + mlt_properties_set_int( MLT_FILTER_PROPERTIES(this), "thresh", 5); + mlt_properties_set_int( MLT_FILTER_PROPERTIES(this), "clip", 5); + mlt_properties_set_int( MLT_FILTER_PROPERTIES(this), "former_producer_id", -1); + + } + + return this; +} + +/** This source code will self destruct in 5...4...3... +*/ + diff --git a/src/modules/motion_est/filter_motion_est.c b/src/modules/motion_est/filter_motion_est.c new file mode 100644 index 0000000..9fedc54 --- /dev/null +++ b/src/modules/motion_est/filter_motion_est.c @@ -0,0 +1,1115 @@ +/* + * /brief fast motion estimation filter + * /author Zachary Drew, Copyright 2005 + * + * Currently only uses Gamma data for comparisonon (bug or feature?) + * SSE optimized where available. + * + * Vector orientation: The vector data that is generated for the current frame specifies + * the motion from the previous frame to the current frame. To know how a macroblock + * in the current frame will move in the future, the next frame is needed. + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "filter_motion_est.h" +#include +#include +#include +#include +#include +#include +#include + +#ifndef __DARWIN__ +#include "sad_sse.h" +#endif + +#define NDEBUG +#include + +#undef DEBUG +#undef DEBUG_ASM +#undef BENCHMARK +#undef COUNT_COMPARES + +#define DIAMOND_SEARCH 0x0 +#define FULL_SEARCH 0x1 +#define SHIFT 8 +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + + +struct motion_est_context_s +{ + int initialized; // true if filter has been initialized + +#ifdef COUNT_COMPARES + int compares; +#endif + + /* same as mlt_frame's parameters */ + int width, height; + + /* Operational details */ + int mb_w, mb_h; + int xstride, ystride; + uint8_t *cache_image; // Copy of current frame + uint8_t *former_image; // Copy of former frame + int search_method; + int skip_prediction; + int shot_change; + int limit_x, limit_y; // max x and y of a motion vector + int initial_thresh; + int check_chroma; // if check_chroma == 1 then compare chroma + int denoise; + int previous_msad; + int show_reconstruction; + int toggle_when_paused; + int show_residual; + + /* bounds */ + struct mlt_geometry_item_s bounds; // Current bounds (from filters crop_detect, autotrack rectangle, or other) + + /* bounds in macroblock units; macroblocks are completely contained within the boundry */ + int left_mb, prev_left_mb, right_mb, prev_right_mb; + int top_mb, prev_top_mb, bottom_mb, prev_bottom_mb; + + /* size of our vector buffers */ + int mv_buffer_height, mv_buffer_width, mv_size; + + /* vector buffers */ + int former_vectors_valid; // right || x2 + *w > right ) + w_remains = right - ((*x > x2) ? *x : x2); + + // Origin of macroblock moves above image boundy + if( *y < top || y2 < top ) { + h_remains = *h - top + ((*y < y2) ? *y : y2); + *y += *h - h_remains; + } + // Portion of macroblock moves bellow image boundry + else if( *y + *h > bottom || y2 + *h > bottom ) + h_remains = bottom - ((*y > y2) ? *y : y2); + + if( w_remains == *w && h_remains == *h ) return penalty; + if( w_remains <= 0 || h_remains <= 0) return 0; // Block is clipped out of existance + penalty = (*w * *h * penalty) + / ( w_remains * h_remains); // Recipricol of the fraction of the block that remains + + assert(*x >= left); assert(x2 + *w - w_remains >= left); + assert(*y >= top); assert(y2 + *h - h_remains >= top); + assert(*x + w_remains <= right); assert(x2 + w_remains <= right); + assert(*y + h_remains <= bottom); assert(y2 + h_remains <= bottom); + + *w = w_remains; // Update the width and height + *h = h_remains; + + return penalty; +} + +/** /brief Reference Sum of Absolute Differences comparison function +* +*/ +static int sad_reference( uint8_t *block1, uint8_t *block2, const int xstride, const int ystride, const int w, const int h ) +{ + int i, j, score = 0; + for ( j = 0; j < h; j++ ){ + for ( i = 0; i < w; i++ ){ + score += ABS( block1[i*xstride] - block2[i*xstride] ); + } + block1 += ystride; + block2 += ystride; + } + + return score; +} + + +/** /brief Abstracted block comparison function +*/ +inline static int block_compare( uint8_t *block1, + uint8_t *block2, + int x, + int y, + int dx, + int dy, + struct motion_est_context_s *c) +{ + +#ifdef COUNT_COMPARES + c->compares++; +#endif + + int score; + + // Default comparison may be overridden by the slower, more capable reference comparison + int (*cmp)(uint8_t *, uint8_t *, int, int, int, int) = c->compare_optimized; + + // vector displacement limited has been exceeded + if( ABS( dx ) >= c->limit_x || ABS( dy ) >= c->limit_y ) + return MAX_MSAD; + + int mb_w = c->mb_w; // Some writeable local copies + int mb_h = c->mb_h; + + // Determine if either macroblock got clipped + int penalty = constrain( &x, &y, &mb_w, &mb_h, dx, dy, 0, c->width, 0, c->height); + + // Some gotchas + if( penalty == 0 ) // Clipped out of existance: Return worst score + return MAX_MSAD; + else if( penalty != 1<compare_reference; + + // Calculate the memory locations of the macroblocks + block1 += x * c->xstride + y * c->ystride; + block2 += (x+dx) * c->xstride + (y+dy) * c->ystride; + + #ifdef DEBUG_ASM + if( penalty == 1<compare_reference( block1, block2, c->xstride, c->ystride, mb_w, mb_h ); + int score2 = c->compare_optimized( block1, block2, c->xstride, c->ystride, mb_w, mb_h ); + if ( score != score2 ) + fprintf(stderr, "Your assembly doesn't work! Reference: %d Asm: %d\n", score, score2); + } + else + #endif + + score = cmp( block1, block2, c->xstride, c->ystride, mb_w, mb_h ); + + return ( score * penalty ) >> SHIFT; // Ditch the extra precision +} + +static inline void check_candidates ( uint8_t *ref, + uint8_t *candidate_base, + const int x, + const int y, + const motion_vector *candidates,// Contains to_x & to_y + const int count, // Number of candidates + const int unique, // Sometimes we know the candidates are unique + motion_vector *result, + struct motion_est_context_s *c ) +{ + int score, i, j; + /* Scan for the best candidate */ + for ( i = 0; i < count; i++ ) + { + // this little dohicky ignores duplicate candidates, if they are possible + if ( unique == 0 ) { + j = 0; + while ( j < i ) + { + if ( candidates[j].dx == candidates[i].dx && + candidates[j].dy == candidates[i].dy ) + goto next_for_loop; + + j++; + } + } + + // Luma + score = block_compare( ref, candidate_base, + x, y, + candidates[i].dx, // from + candidates[i].dy, + c); + + if ( score < result->msad ) { // New minimum + result->dx = candidates[i].dx; + result->dy = candidates[i].dy; + result->msad = score; + } + next_for_loop:; + } +} + +/* /brief Diamond search +* Operates on a single macroblock +*/ +static inline void diamond_search( + uint8_t *ref, //dx; + current.dy = result->dy; + + if ( first == 1 ) // Set the initial pattern + { + candidates[0].dx = result->dx + 1; candidates[0].dy = result->dy + 0; + candidates[1].dx = result->dx + 0; candidates[1].dy = result->dy + 1; + candidates[2].dx = result->dx - 1; candidates[2].dy = result->dy + 0; + candidates[3].dx = result->dx + 0; candidates[3].dy = result->dy - 1; + i = 4; + } + else // Construct the next portion of the search pattern + { + candidates[0].dx = result->dx + best.dx; + candidates[0].dy = result->dy + best.dy; + if (best.dx == former.dx && best.dy == former.dy) { + candidates[1].dx = result->dx + best.dy; + candidates[1].dy = result->dy + best.dx; // Yes, the wires + candidates[2].dx = result->dx - best.dy; // are crossed + candidates[2].dy = result->dy - best.dx; + i = 3; + } else { + candidates[1].dx = result->dx + former.dx; + candidates[1].dy = result->dy + former.dy; + i = 2; + } + + former.dx = best.dx; former.dy = best.dy; // Keep track of new former best + } + + check_candidates ( ref, candidate_base, x, y, candidates, i, 1, result, c ); + + // Which candidate was the best? + best.dx = result->dx - current.dx; + best.dy = result->dy - current.dy; + + // A better canidate was not found + if ( best.dx == 0 && best.dy == 0 ) + return; + + if ( first == 1 ){ + first = 0; + former.dx = best.dx; former.dy = best.dy; // First iteration, sensible value for former.d* + } + } +} + +/* /brief Full (brute) search +* Operates on a single macroblock +*/ +__attribute__((used)) +static void full_search( + uint8_t *ref, //mb_w; i <= c->mb_w; i++ ){ + for( j = -c->mb_h; j <= c->mb_h; j++ ){ + + score = block_compare( ref, candidate_base, + x, + y, + x + i, + y + j, + c); + + if ( score < result->msad ) { + result->dx = i; + result->dy = j; + result->msad = score; + } + } + } +} + +// Macros for pointer calculations +#define CURRENT(i,j) ( c->current_vectors + (j)*c->mv_buffer_width + (i) ) +#define FORMER(i,j) ( c->former_vectors + (j)*c->mv_buffer_width + (i) ) +#define DENOISE(i,j) ( c->denoise_vectors + (j)*c->mv_buffer_width + (i) ) + +int ncompare (const void * a, const void * b) +{ + return ( *(int*)a - *(int*)b ); +} + +// motion vector denoising +// for x and y components seperately, +// change the vector to be the median value of the 9 adjacent vectors +static void median_denoise( motion_vector *v, struct motion_est_context_s *c ) +{ + int xvalues[9], yvalues[9]; + + int i,j,n; + for( j = c->top_mb; j <= c->bottom_mb; j++ ) + for( i = c->left_mb; i <= c->right_mb; i++ ){ + { + n = 0; + + xvalues[n ] = CURRENT(i,j)->dx; // Center + yvalues[n++] = CURRENT(i,j)->dy; + + if( i > c->left_mb ) // Not in First Column + { + xvalues[n ] = CURRENT(i-1,j)->dx; // Left + yvalues[n++] = CURRENT(i-1,j)->dy; + + if( j > c->top_mb ) { + xvalues[n ] = CURRENT(i-1,j-1)->dx; // Upper Left + yvalues[n++] = CURRENT(i-1,j-1)->dy; + } + + if( j < c->bottom_mb ) { + xvalues[n ] = CURRENT(i-1,j+1)->dx; // Bottom Left + yvalues[n++] = CURRENT(i-1,j+1)->dy; + } + } + if( i < c->right_mb ) // Not in Last Column + { + xvalues[n ] = CURRENT(i+1,j)->dx; // Right + yvalues[n++] = CURRENT(i+1,j)->dy; + + + if( j > c->top_mb ) { + xvalues[n ] = CURRENT(i+1,j-1)->dx; // Upper Right + yvalues[n++] = CURRENT(i+1,j-1)->dy; + } + + if( j < c->bottom_mb ) { + xvalues[n ] = CURRENT(i+1,j+1)->dx; // Bottom Right + yvalues[n++] = CURRENT(i+1,j+1)->dy; + } + } + if( j > c->top_mb ) // Not in First Row + { + xvalues[n ] = CURRENT(i,j-1)->dx; // Top + yvalues[n++] = CURRENT(i,j-1)->dy; + } + + if( j < c->bottom_mb ) // Not in Last Row + { + xvalues[n ] = CURRENT(i,j+1)->dx; // Bottom + yvalues[n++] = CURRENT(i,j+1)->dy; + } + + qsort (xvalues, n, sizeof(int), ncompare); + qsort (yvalues, n, sizeof(int), ncompare); + + if( n % 2 == 1 ) { + DENOISE(i,j)->dx = xvalues[n/2]; + DENOISE(i,j)->dy = yvalues[n/2]; + } + else { + DENOISE(i,j)->dx = (xvalues[n/2] + xvalues[n/2+1])/2; + DENOISE(i,j)->dy = (yvalues[n/2] + yvalues[n/2+1])/2; + } + } + } + + motion_vector *t = c->current_vectors; + c->current_vectors = c->denoise_vectors; + c->denoise_vectors = t; + +} + +// Credits: ffmpeg +// return the median +static inline int median_predictor(int a, int b, int c) { + if ( a > b ){ + if ( c > b ){ + if ( c > a ) b = a; + else b = c; + } + } else { + if ( b > c ){ + if ( c > a ) b = c; + else b = a; + } + } + return b; +} + + +/** /brief Motion search +* +* For each macroblock in the current frame, estimate the block from the last frame that +* matches best. +* +* Vocab: Colocated - the pixel in the previous frame at the current position +* +* Based on enhanced predictive zonal search. [Tourapis 2002] +*/ +static void motion_search( uint8_t *from, //left_mb; i <= c->right_mb; i++ ){ + for( j = c->top_mb; j <= c->bottom_mb; j++ ){ + + here = CURRENT(i,j); + here->valid = 1; + here->color = 100; + here->msad = MAX_MSAD; + count++; + n = 0; + + + /* Stack the predictors [i.e. checked in reverse order] */ + + /* Adjacent to collocated */ + if( c->former_vectors_valid ) + { + // Top of colocated + if( j > c->prev_top_mb ){// && COL_TOP->valid ){ + candidates[n ].dx = FORMER(i,j-1)->dx; + candidates[n++].dy = FORMER(i,j-1)->dy; + } + + // Left of colocated + if( i > c->prev_left_mb ){// && COL_LEFT->valid ){ + candidates[n ].dx = FORMER(i-1,j)->dx; + candidates[n++].dy = FORMER(i-1,j)->dy; + } + + // Right of colocated + if( i < c->prev_right_mb ){// && COL_RIGHT->valid ){ + candidates[n ].dx = FORMER(i+1,j)->dx; + candidates[n++].dy = FORMER(i+1,j)->dy; + } + + // Bottom of colocated + if( j < c->prev_bottom_mb ){// && COL_BOTTOM->valid ){ + candidates[n ].dx = FORMER(i,j+1)->dx; + candidates[n++].dy = FORMER(i,j+1)->dy; + } + + // And finally, colocated + candidates[n ].dx = FORMER(i,j)->dx; + candidates[n++].dy = FORMER(i,j)->dy; + } + + // For macroblocks not in the top row + if ( j > c->top_mb) { + + // Top if ( TOP->valid ) { + candidates[n ].dx = CURRENT(i,j-1)->dx; + candidates[n++].dy = CURRENT(i,j-1)->dy; + //} + + // Top-Right, macroblocks not in the right row + if ( i < c->right_mb ){// && TOP_RIGHT->valid ) { + candidates[n ].dx = CURRENT(i+1,j-1)->dx; + candidates[n++].dy = CURRENT(i+1,j-1)->dy; + } + } + + // Left, Macroblocks not in the left column + if ( i > c->left_mb ){// && LEFT->valid ) { + candidates[n ].dx = CURRENT(i-1,j)->dx; + candidates[n++].dy = CURRENT(i-1,j)->dy; + } + + /* Median predictor vector (median of left, top, and top right adjacent vectors) */ + if ( i > c->left_mb && j > c->top_mb && i < c->right_mb + )//&& LEFT->valid && TOP->valid && TOP_RIGHT->valid ) + { + candidates[n ].dx = median_predictor( CURRENT(i-1,j)->dx, CURRENT(i,j-1)->dx, CURRENT(i+1,j-1)->dx); + candidates[n++].dy = median_predictor( CURRENT(i-1,j)->dy, CURRENT(i,j-1)->dy, CURRENT(i+1,j-1)->dy); + } + + // Zero vector + candidates[n ].dx = 0; + candidates[n++].dy = 0; + + int x = i * c->mb_w; + int y = j * c->mb_h; + check_candidates ( to, from, x, y, candidates, n, 0, here, c ); + + +#ifndef FULLSEARCH + diamond_search( to, from, x, y, here, c); +#else + full_search( to, from, x, y, here, c); +#endif + + assert( x + c->mb_w + here->dx > 0 ); // All macroblocks must have area > 0 + assert( y + c->mb_h + here->dy > 0 ); + assert( x + here->dx < c->width ); + assert( y + here->dy < c->height ); + + } /* End column loop */ + } /* End row loop */ + +#ifndef __DARWIN__ + asm volatile ( "emms" ); +#endif + +#ifdef COUNT_COMPARES + fprintf(stderr, "%d comparisons per block were made", compares/count); +#endif + return; +} + +void collect_post_statistics( struct motion_est_context_s *c ) { + + c->comparison_average = 0; + c->average_length = 0; + c->average_x = 0; + c->average_y = 0; + + int i, j, count = 0; + + for ( i = c->left_mb; i <= c->right_mb; i++ ){ + for ( j = c->top_mb; j <= c->bottom_mb; j++ ){ + + count++; + c->comparison_average += CURRENT(i,j)->msad; + c->average_x += CURRENT(i,j)->dx; + c->average_y += CURRENT(i,j)->dy; + + + } + } + + if ( count > 0 ) + { + c->comparison_average /= count; + c->average_x /= count; + c->average_y /= count; + c->average_length = sqrt( c->average_x * c->average_x + c->average_y * c->average_y ); + } + +} + +static void init_optimizations( struct motion_est_context_s *c ) +{ + switch(c->mb_w){ +#ifndef __DARWIN__ + case 4: if(c->mb_h == 4) c->compare_optimized = sad_sse_422_luma_4x4; + else c->compare_optimized = sad_sse_422_luma_4w; + break; + case 8: if(c->mb_h == 8) c->compare_optimized = sad_sse_422_luma_8x8; + else c->compare_optimized = sad_sse_422_luma_8w; + break; + case 16: if(c->mb_h == 16) c->compare_optimized = sad_sse_422_luma_16x16; + else c->compare_optimized = sad_sse_422_luma_16w; + break; + case 32: if(c->mb_h == 32) c->compare_optimized = sad_sse_422_luma_32x32; + else c->compare_optimized = sad_sse_422_luma_32w; + break; + case 64: c->compare_optimized = sad_sse_422_luma_64w; + break; +#endif + default: c->compare_optimized = sad_reference; + break; + } +} + +inline static void set_red(uint8_t *image, struct motion_est_context_s *c) +{ + int n; + for( n = 0; n < c->width * c->height * 2; n+=4 ) + { + image[n] = 79; + image[n+1] = 91; + image[n+2] = 79; + image[n+3] = 237; + } + +} + +static void show_residual( uint8_t *result, struct motion_est_context_s *c ) +{ + int i, j; + int x,y,w,h; + int dx, dy; + int tx,ty; + uint8_t *b, *r; + +// set_red(result,c); + + for( j = c->top_mb; j <= c->bottom_mb; j++ ){ + for( i = c->left_mb; i <= c->right_mb; i++ ){ + + dx = CURRENT(i,j)->dx; + dy = CURRENT(i,j)->dy; + w = c->mb_w; + h = c->mb_h; + x = i * w; + y = j * h; + + // Denoise function caused some blocks to be completely clipped, ignore them + if (constrain( &x, &y, &w, &h, dx, dy, 0, c->width, 0, c->height) == 0 ) + continue; + + for( ty = y; ty < y + h ; ty++ ){ + for( tx = x; tx < x + w ; tx++ ){ + + b = c->former_image + (tx+dx)*c->xstride + (ty+dy)*c->ystride; + r = result + tx*c->xstride + ty*c->ystride; + + r[0] = 16 + ABS( r[0] - b[0] ); + + if( dx % 2 == 0 ) + r[1] = 128 + ABS( r[1] - b[1] ); + else + // FIXME: may exceed boundies + r[1] = 128 + ABS( r[1] - ( *(b-1) + b[3] ) /2 ); + } + } + } + } +} + +static void show_reconstruction( uint8_t *result, struct motion_est_context_s *c ) +{ + int i, j; + int x,y,w,h; + int dx,dy; + uint8_t *r, *s; + int tx,ty; + + for( i = c->left_mb; i <= c->right_mb; i++ ){ + for( j = c->top_mb; j <= c->bottom_mb; j++ ){ + + dx = CURRENT(i,j)->dx; + dy = CURRENT(i,j)->dy; + w = c->mb_w; + h = c->mb_h; + x = i * w; + y = j * h; + + // Denoise function caused some blocks to be completely clipped, ignore them + if (constrain( &x, &y, &w, &h, dx, dy, 0, c->width, 0, c->height) == 0 ) + continue; + + for( ty = y; ty < y + h ; ty++ ){ + for( tx = x; tx < x + w ; tx++ ){ + + r = result + tx*c->xstride + ty*c->ystride; + s = c->former_image + (tx+dx)*c->xstride + (ty+dy)*c->ystride; + + r[0] = s[0]; + + if( dx % 2 == 0 ) + r[1] = s[1]; + else + // FIXME: may exceed boundies + r[1] = ( *(s-1) + s[3] ) /2; + } + } + } + } +} + +// Image stack(able) method +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the filter + mlt_filter filter = mlt_frame_pop_service( frame ); + + // Get the motion_est context object + struct motion_est_context_s *c = mlt_properties_get_data( MLT_FILTER_PROPERTIES( filter ), "context", NULL); + + + // Get the new image and frame number + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + #ifdef BENCHMARK + struct timeval start; gettimeofday(&start, NULL ); + #endif + + + if( error != 0 ) + mlt_properties_debug( MLT_FRAME_PROPERTIES(frame), "error after mlt_frame_get_image() in motion_est", stderr ); + + c->current_frame_position = mlt_frame_get_position( frame ); + + /* Context Initialization */ + if ( c->initialized == 0 ) { + + // Get the filter properties object + mlt_properties properties = mlt_filter_properties( filter ); + + c->width = *width; + c->height = *height; + + /* Get parameters that may have been overridden */ + if( mlt_properties_get( properties, "macroblock_width") != NULL ) + c->mb_w = mlt_properties_get_int( properties, "macroblock_width"); + + if( mlt_properties_get( properties, "macroblock_height") != NULL ) + c->mb_h = mlt_properties_get_int( properties, "macroblock_height"); + + if( mlt_properties_get( properties, "prediction_thresh") != NULL ) + c->initial_thresh = mlt_properties_get_int( properties, "prediction_thresh" ); + else + c->initial_thresh = c->mb_w * c->mb_h; + + if( mlt_properties_get( properties, "search_method") != NULL ) + c->search_method = mlt_properties_get_int( properties, "search_method"); + + if( mlt_properties_get( properties, "skip_prediction") != NULL ) + c->skip_prediction = mlt_properties_get_int( properties, "skip_prediction"); + + if( mlt_properties_get( properties, "limit_x") != NULL ) + c->limit_x = mlt_properties_get_int( properties, "limit_x"); + + if( mlt_properties_get( properties, "limit_y") != NULL ) + c->limit_y = mlt_properties_get_int( properties, "limit_y"); + + if( mlt_properties_get( properties, "check_chroma" ) != NULL ) + c->check_chroma = mlt_properties_get_int( properties, "check_chroma" ); + + if( mlt_properties_get( properties, "denoise" ) != NULL ) + c->denoise = mlt_properties_get_int( properties, "denoise" ); + + if( mlt_properties_get( properties, "show_reconstruction" ) != NULL ) + c->show_reconstruction = mlt_properties_get_int( properties, "show_reconstruction" ); + + if( mlt_properties_get( properties, "show_residual" ) != NULL ) + c->show_residual = mlt_properties_get_int( properties, "show_residual" ); + + if( mlt_properties_get( properties, "toggle_when_paused" ) != NULL ) + c->toggle_when_paused = mlt_properties_get_int( properties, "toggle_when_paused" ); + + init_optimizations( c ); + + // Calculate the dimensions in macroblock units + c->mv_buffer_width = (*width / c->mb_w); + c->mv_buffer_height = (*height / c->mb_h); + + // Size of the motion vector buffer + c->mv_size = c->mv_buffer_width * c->mv_buffer_height * sizeof(struct motion_vector_s); + + // Allocate the motion vector buffers + c->former_vectors = mlt_pool_alloc( c->mv_size ); + c->current_vectors = mlt_pool_alloc( c->mv_size ); + c->denoise_vectors = mlt_pool_alloc( c->mv_size ); + + // Register motion buffers for destruction + mlt_properties_set_data( properties, "current_motion_vectors", (void *)c->current_vectors, 0, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "former_motion_vectors", (void *)c->former_vectors, 0, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "denoise_motion_vectors", (void *)c->denoise_vectors, 0, mlt_pool_release, NULL ); + + c->former_vectors_valid = 0; + memset( c->former_vectors, 0, c->mv_size ); + + // Calculate the size of our steps (the number of bytes that seperate adjacent pixels in X and Y direction) + switch( *format ) { + case mlt_image_yuv422: + c->xstride = 2; + c->ystride = c->xstride * *width; + break; + default: + // I don't know + fprintf(stderr, "\"I am unfamiliar with your new fangled pixel format!\" -filter_motion_est\n"); + return -1; + } + + // Allocate a cache for the previous frame's image + c->former_image = mlt_pool_alloc( *width * *height * 2 ); + c->cache_image = mlt_pool_alloc( *width * *height * 2 ); + + // Register for destruction + mlt_properties_set_data( properties, "cache_image", (void *)c->cache_image, 0, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "former_image", (void *)c->former_image, 0, mlt_pool_release, NULL ); + + c->former_frame_position = c->current_frame_position; + c->previous_msad = 0; + + c->initialized = 1; + } + + /* Check to see if somebody else has given us bounds */ + struct mlt_geometry_item_s *bounds = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "bounds", NULL ); + + if( bounds != NULL ) { + // translate pixel units (from bounds) to macroblock units + // make sure whole macroblock stays within bounds + c->left_mb = ( bounds->x + c->mb_w - 1 ) / c->mb_w; + c->top_mb = ( bounds->y + c->mb_h - 1 ) / c->mb_h; + c->right_mb = ( bounds->x + bounds->w ) / c->mb_w - 1; + c->bottom_mb = ( bounds->y + bounds->h ) / c->mb_h - 1; + c->bounds.x = bounds->x; + c->bounds.y = bounds->y; + c->bounds.w = bounds->w; + c->bounds.h = bounds->h; + } else { + c->left_mb = c->prev_left_mb = 0; + c->top_mb = c->prev_top_mb = 0; + c->right_mb = c->prev_right_mb = c->mv_buffer_width - 1; // Zero indexed + c->bottom_mb = c->prev_bottom_mb = c->mv_buffer_height - 1; + c->bounds.x = 0; + c->bounds.y = 0; + c->bounds.w = *width; + c->bounds.h = *height; + } + + // If video is advancing, run motion vector algorithm and etc... + if( c->former_frame_position + 1 == c->current_frame_position ) + { + + // Swap the motion vector buffers and reuse allocated memory + struct motion_vector_s *temp = c->current_vectors; + c->current_vectors = c->former_vectors; + c->former_vectors = temp; + + // This is done because filter_vismv doesn't pay attention to frame boundry + memset( c->current_vectors, 0, c->mv_size ); + + // Perform the motion search + motion_search( c->cache_image, *image, c ); + + collect_post_statistics( c ); + + + // Detect shot changes + if( c->comparison_average > 10 * c->mb_w * c->mb_h && + c->comparison_average > c->previous_msad * 2 ) + { + fprintf(stderr, " - SAD: %d <>\n", c->comparison_average); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "shot_change", 1); + // c->former_vectors_valid = 0; // Invalidate the previous frame's predictors + c->shot_change = 1; + } + else { + c->former_vectors_valid = 1; + c->shot_change = 0; + //fprintf(stderr, " - SAD: %d\n", c->comparison_average); + } + + c->previous_msad = c->comparison_average; + + if( c->comparison_average != 0 ) { // If the frame is not a duplicate of the previous frame + + // denoise the vector buffer + if( c->denoise ) + median_denoise( c->current_vectors, c ); + + // Pass the new vector data into the frame + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "motion_est.vectors", + (void*)c->current_vectors, c->mv_size, NULL, NULL ); + + // Cache the frame's image. Save the old cache. Reuse memory. + // After this block, exactly two unique frames will be cached + uint8_t *timg = c->cache_image; + c->cache_image = c->former_image; + c->former_image = timg; + memcpy( c->cache_image, *image, *width * *height * c->xstride ); + + + } + else { + // Undo the Swap, This fixes the ugliness caused by a duplicate frame + temp = c->current_vectors; + c->current_vectors = c->former_vectors; + c->former_vectors = temp; + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "motion_est.vectors", + (void*)c->former_vectors, c->mv_size, NULL, NULL ); + } + + + if( c->shot_change == 1) + ; + else if( c->show_reconstruction ) + show_reconstruction( *image, c ); + else if( c->show_residual ) + show_residual( *image, c ); + + } + // paused + else if( c->former_frame_position == c->current_frame_position ) + { + // Pass the old vector data into the frame if it's valid + if( c->former_vectors_valid == 1 ) { + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "motion_est.vectors", + (void*)c->current_vectors, c->mv_size, NULL, NULL ); + + if( c->shot_change == 1) + ; + else if( c->toggle_when_paused == 1 ) { + if( c->show_reconstruction ) + show_reconstruction( *image, c ); + else if( c->show_residual ) + show_residual( *image, c ); + c->toggle_when_paused = 2; + } + else if( c->toggle_when_paused == 2 ) + c->toggle_when_paused = 1; + else { + if( c->show_reconstruction ) + show_reconstruction( *image, c ); + else if( c->show_residual ) + show_residual( *image, c ); + } + + } + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "shot_change", c->shot_change); + } + // there was jump in frame number + else { +// fprintf(stderr, "Warning: there was a frame number jumped from %d to %d.\n", c->former_frame_position, c->current_frame_position); + c->former_vectors_valid = 0; + } + + + // Cache our bounding geometry for the next frame's processing + c->prev_left_mb = c->left_mb; + c->prev_top_mb = c->top_mb; + c->prev_right_mb = c->right_mb; + c->prev_bottom_mb = c->bottom_mb; + + // Remember which frame this is + c->former_frame_position = c->current_frame_position; + + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.macroblock_width", c->mb_w ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.macroblock_height", c->mb_h ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.left_mb", c->left_mb ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.right_mb", c->right_mb ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.top_mb", c->top_mb ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "motion_est.bottom_mb", c->bottom_mb ); + + #ifdef BENCHMARK + struct timeval finish; gettimeofday(&finish, NULL ); int difference = (finish.tv_sec - start.tv_sec) * 1000000 + (finish.tv_usec - start.tv_usec); + fprintf(stderr, " in frame %d:%d usec\n", c->current_frame_position, difference); + #endif + + + return error; +} + + + +/** filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + + // Keeps tabs on the filter object + mlt_frame_push_service( frame, this); + + // Push the frame filter + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ +mlt_filter filter_motion_est_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + // Get the properties object + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Initialize the motion estimation context + struct motion_est_context_s *context; + context = mlt_pool_alloc( sizeof(struct motion_est_context_s) ); + mlt_properties_set_data( properties, "context", (void *)context, sizeof( struct motion_est_context_s ), + mlt_pool_release, NULL ); + + + // Register the filter + this->process = filter_process; + + /* defaults that may be overridden */ + context->mb_w = 16; + context->mb_h = 16; + context->skip_prediction = 0; + context->limit_x = 64; + context->limit_y = 64; + context->search_method = DIAMOND_SEARCH; // FIXME: not used + context->check_chroma = 0; + context->denoise = 1; + context->show_reconstruction = 0; + context->show_residual = 0; + context->toggle_when_paused = 0; + + /* reference functions that may have optimized versions */ + context->compare_reference = sad_reference; + + // The rest of the buffers will be initialized when the filter is first processed + context->initialized = 0; + } + return this; +} diff --git a/src/modules/motion_est/filter_motion_est.h b/src/modules/motion_est/filter_motion_est.h new file mode 100644 index 0000000..a3c640d --- /dev/null +++ b/src/modules/motion_est/filter_motion_est.h @@ -0,0 +1,42 @@ +/* + * Perform motion estimation + * Zachary K Drew, Copyright 2004 + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_MOTION_EST_H_ +#define _FILTER_MOTION_EST_H_ + +#include + +extern mlt_filter filter_motion_est_init( char *arg ); + +#define MAX_MSAD 0xffff + +struct motion_vector_s +{ + int msad; // + +#include +#include +#include +#include + +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + +static void paint_arrows( uint8_t *image, struct motion_vector_s *vectors, int w, int h, int mb_w, int mb_h ) +{ + int i, j, x, y; + struct motion_vector_s *p; + for( i = 0; i < w/mb_w; i++ ){ + for( j = 0; j < h/mb_h; j++ ){ + x = i*mb_w; + y = j*mb_h; + p = vectors + (w/mb_w)*j + i; + + if ( p->valid == 1 ) { + //draw_rectangle_outline(image, x-1, y-1, mb_w+1, mb_h+1,100); + //x += mb_w/4; + //y += mb_h/4; + //draw_rectangle_outline(image, x + p->dx, y + p->dy, mb_w, mb_h,100); + x += mb_w/2; + y += mb_h/2; + draw_arrow(image, x, y, x + p->dx, y + p->dy, 100); + //draw_rectangle_fill(image, x + p->dx, y + p->dy, mb_w, mb_h, 100); + } + else if ( p->valid == 2 ) { + draw_rectangle_outline(image, x+1, y+1, mb_w-2, mb_h-2,100); + } + else if ( p->valid == 3 ) { + draw_rectangle_fill(image, x-p->dx, y-p->dy, mb_w, mb_h,0); + } + else if ( p->valid == 4 ) { + draw_line(image, x, y, x + 4, y, 100); + draw_line(image, x, y, x, y + 4, 100); + draw_line(image, x + 4, y, x, y + 4, 100); + + draw_line(image, x+mb_w-1, y+mb_h-1, x+mb_w-5, y+mb_h-1, 100); + draw_line(image, x+mb_w-1, y+mb_h-1, x+mb_w-1, y+mb_h-5, 100); + draw_line(image, x+mb_w-5, y+mb_h-1, x+mb_w-1, y+mb_h-5, 100); + } + } + } +} + +// Image stack(able) method +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the frame properties + mlt_properties properties = MLT_FRAME_PROPERTIES(frame); + + // Get the new image + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + if( error != 0 ) + mlt_properties_debug( MLT_FRAME_PROPERTIES(frame), "error after mlt_frame_get_image()", stderr ); + + + // Get the size of macroblocks in pixel units + int macroblock_height = mlt_properties_get_int( properties, "motion_est.macroblock_height" ); + int macroblock_width = mlt_properties_get_int( properties, "motion_est.macroblock_width" ); + + // Get the motion vectors + struct motion_vector_s *current_vectors = mlt_properties_get_data( properties, "motion_est.vectors", NULL ); + + init_arrows( format, *width, *height ); + + if ( mlt_properties_get_int( properties, "shot_change" ) == 1 ) + { + draw_line(*image, 0, 0, *width, *height, 100); + draw_line(*image, 0, *height, *width, 0, 100); + } + if( current_vectors != NULL ) { + paint_arrows( *image, current_vectors, *width, *height, macroblock_width, macroblock_height); + } + + return error; +} + + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the frame filter + mlt_frame_push_get_image( frame, filter_get_image ); + + + return frame; +} + +/** Constructor for the filter. +*/ + + +mlt_filter filter_vismv_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + + } + + return this; +} + +/** This source code will self destruct in 5...4...3... +*/ + + + + + + + + diff --git a/src/modules/motion_est/gpl b/src/modules/motion_est/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/motion_est/producer_slowmotion.c b/src/modules/motion_est/producer_slowmotion.c new file mode 100644 index 0000000..512987e --- /dev/null +++ b/src/modules/motion_est/producer_slowmotion.c @@ -0,0 +1,418 @@ +/* + * producer_slowmotion.c -- create subspeed frames + * Author: Zachary Drew + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_motion_est.h" +#include + +#include +#include +#include +#include +#include +#include +#define SHIFT 8 +#define ABS(a) ((a) >= 0 ? (a) : (-(a))) + +// This is used to constrains pixel operations between two blocks to be within the image boundry +inline static int constrain( int *x, int *y, int *w, int *h, + const int dx, const int dy, + const int left, const int right, + const int top, const int bottom) +{ + uint32_t penalty = 1 << SHIFT; // Retain a few extra bits of precision + int x2 = *x + dx; + int y2 = *y + dy; + int w_remains = *w; + int h_remains = *h; + + // Origin of macroblock moves left of image boundy + if( *x < left || x2 < left ) { + w_remains = *w - left + ((*x < x2) ? *x : x2); + *x += *w - w_remains; + } + // Portion of macroblock moves right of image boundry + else if( *x + *w > right || x2 + *w > right ) + w_remains = right - ((*x > x2) ? *x : x2); + + // Origin of macroblock moves above image boundy + if( *y < top || y2 < top ) { + h_remains = *h - top + ((*y < y2) ? *y : y2); + *y += *h - h_remains; + } + // Portion of macroblock moves bellow image boundry + else if( *y + *h > bottom || y2 + *h > bottom ) + h_remains = bottom - ((*y > y2) ? *y : y2); + + if( w_remains == *w && h_remains == *h ) return penalty; + if( w_remains <= 0 || h_remains <= 0) return 0; // Block is clipped out of existance + penalty = (*w * *h * penalty) + / ( w_remains * h_remains); // Recipricol of the fraction of the block that remains + + *w = w_remains; // Update the width and height + *h = h_remains; + + return penalty; +} + +static void motion_interpolate( uint8_t *first_image, uint8_t *second_image, uint8_t *output, + int top_mb, int bottom_mb, int left_mb, int right_mb, + int mb_w, int mb_h, + int width, int height, + int xstride, int ystride, + double scale, + motion_vector *vectors ) +{ + assert ( scale >= 0.0 && scale <= 1.0 ); + + int i, j; + int x,y,w,h; + int dx, dy; + int scaled_dx, scaled_dy; + int tx,ty; + uint8_t *f,*s,*r; + motion_vector *here; + int mv_width = width / mb_w; + + for( j = top_mb; j <= bottom_mb; j++ ){ + for( i = left_mb; i <= right_mb; i++ ){ + + here = vectors + j*mv_width + i; + scaled_dx = (1.0 - scale) * (double)here->dx; + scaled_dy = (1.0 - scale) * (double)here->dy; + dx = here->dx; + dy = here->dy; + w = mb_w; h = mb_h; + x = i * w; y = j * h; + + // Denoise function caused some blocks to be completely clipped, ignore them + if (constrain( &x, &y, &w, &h, dx, dy, 0, width, 0, height) == 0 ) + continue; + + for( ty = y; ty < y + h ; ty++ ){ + for( tx = x; tx < x + w ; tx++ ){ + + f = first_image + (tx + dx )*xstride + (ty + dy )*ystride; + s = second_image + (tx )*xstride + (ty )*ystride; + r = output + (tx+scaled_dx)*xstride + (ty+scaled_dy)*ystride; +/* + if( ABS(f[0] - s[0]) > 3 * here->msad / (mb_w * mb_h * 2) ) + { + r[0] = f[0]; + r[1] = f[1]; + } + + else + { + +*/ + r[0] = ( 1.0 - scale ) * (double)f[0] + scale * (double)s[0]; + + if( dx % 2 == 0 ) + { + if( scaled_dx % 2 == 0 ) + r[1] = ( 1.0 - scale ) * (double)f[1] + scale * (double) s[1]; + else + *(r-1) = ( 1.0 - scale ) * (double)f[1] + scale * (double) s[1]; + } + else + { + if( scaled_dx %2 == 0 ) + // FIXME: may exceed boundies + r[1] = ( 1.0 - scale ) * ( (double)(*(f-1) + (double)f[3]) / 2.0 ) + scale * (double) s[1]; + else + // FIXME: may exceed boundies + *(r-1) = ( 1.0 - scale ) * ( (double)(*(f-1) + (double)f[3]) / 2.0 ) + scale * (double) s[1]; + } +// } + } + } + } + } +} + +// Image stack(able) method +static int slowmotion_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + + // Get the filter object and properties + mlt_producer producer = mlt_frame_pop_service( this ); + mlt_frame second_frame = mlt_frame_pop_service( this ); + mlt_frame first_frame = mlt_frame_pop_service( this ); + + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Frame properties objects + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( this ); + mlt_properties first_frame_properties = MLT_FRAME_PROPERTIES( first_frame ); + mlt_properties second_frame_properties = MLT_FRAME_PROPERTIES( second_frame ); + + // image stride + int size, xstride, ystride; + switch( *format ){ + case mlt_image_yuv422: + size = *width * *height * 2; + xstride = 2; + ystride = 2 * *width; + break; + default: + fprintf(stderr, "Unsupported image format\n"); + return -1; + } + + uint8_t *output = mlt_properties_get_data( producer_properties, "output_buffer", 0 ); + if( output == NULL ) + { + output = mlt_pool_alloc( size ); + + // Let someone else clean up + mlt_properties_set_data( producer_properties, "output_buffer", output, size, mlt_pool_release, NULL ); + } + + uint8_t *first_image = mlt_properties_get_data( first_frame_properties, "image", NULL ); + uint8_t *second_image = mlt_properties_get_data( second_frame_properties, "image", NULL ); + + // which frames are buffered? + + int error = 0; + + if( first_image == NULL ) + { + error = mlt_frame_get_image( first_frame, &first_image, format, width, height, writable ); + + if( error != 0 ) { + fprintf(stderr, "first_image == NULL get image died\n"); + return error; + } + } + + if( second_image == NULL ) + { + error = mlt_frame_get_image( second_frame, &second_image, format, width, height, writable ); + + if( error != 0 ) { + fprintf(stderr, "second_image == NULL get image died\n"); + return error; + } + } + + // These need to passed onto the frame for other + mlt_properties_pass_list( frame_properties, second_frame_properties, + "motion_est.left_mb, motion_est.right_mb, \ + motion_est.top_mb, motion_est.bottom_mb, \ + motion_est.macroblock_width, motion_est.macroblock_height" ); + + // Pass the pointer to the vectors without serializing + mlt_properties_set_data( frame_properties, "motion_est.vectors", + mlt_properties_get_data( second_frame_properties, "motion_est.vectors", NULL ), + 0, NULL, NULL ); + + + // Start with a base image + memcpy( output, first_image, size ); + + if( mlt_properties_get_int( producer_properties, "method" ) == 1 ) { + + mlt_position first_position = mlt_frame_get_position( first_frame ); + double actual_position = mlt_producer_get_speed( producer ) * (double)mlt_frame_get_position( this ); + double scale = actual_position - first_position; + + motion_interpolate + ( + first_image, second_image, output, + mlt_properties_get_int( second_frame_properties, "motion_est.top_mb" ), + mlt_properties_get_int( second_frame_properties, "motion_est.bottom_mb" ), + mlt_properties_get_int( second_frame_properties, "motion_est.left_mb" ), + mlt_properties_get_int( second_frame_properties, "motion_est.right_mb" ), + mlt_properties_get_int( second_frame_properties, "motion_est.macroblock_width" ), + mlt_properties_get_int( second_frame_properties, "motion_est.macroblock_height" ), + *width, *height, + xstride, ystride, + scale, + mlt_properties_get_data( second_frame_properties, "motion_est.vectors", NULL ) + ); + + if( mlt_properties_get_int( producer_properties, "debug" ) == 1 ) { + mlt_filter watermark = mlt_properties_get_data( producer_properties, "watermark", NULL ); + + if( watermark == NULL ) { + watermark = mlt_factory_filter( "watermark", NULL ); + mlt_properties_set_data( producer_properties, "watermark", watermark, 0, (mlt_destructor)mlt_filter_close, NULL ); + mlt_producer_attach( producer, watermark ); + } + + mlt_properties wm_properties = MLT_FILTER_PROPERTIES( watermark ); + + char disp[30]; + sprintf(disp, "+%10.2f.txt", actual_position); + mlt_properties_set( wm_properties, "resource", disp ); + + } + + } + + *image = output; + mlt_properties_set_data( frame_properties, "image", output, size, NULL, NULL ); + + // Make sure that no further scaling is done + mlt_properties_set( frame_properties, "rescale.interps", "none" ); + mlt_properties_set( frame_properties, "scale", "off" ); + + mlt_frame_close( first_frame ); + mlt_frame_close( second_frame ); + + return 0; +} + +static int slowmotion_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Construct a new frame + *frame = mlt_frame_init( ); + + mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); + + + if( frame != NULL ) + { + + mlt_frame first_frame = mlt_properties_get_data( properties, "first_frame", NULL ); + mlt_frame second_frame = mlt_properties_get_data( properties, "second_frame", NULL ); + + mlt_position first_position = (first_frame != NULL) ? mlt_frame_get_position( first_frame ) : -1; + mlt_position second_position = (second_frame != NULL) ? mlt_frame_get_position( second_frame ) : -1; + + // Get the real producer + mlt_producer real_producer = mlt_properties_get_data( properties, "producer", NULL ); + + // Our "in" needs to be the same, keep it so + mlt_properties_pass_list( MLT_PRODUCER_PROPERTIES( real_producer ), properties, "in" ); + + // Calculate our positions + double actual_position = mlt_producer_get_speed( this ) * (double)mlt_producer_position( this ); + mlt_position need_first = floor( actual_position ); + mlt_position need_second = need_first + 1; + + if( need_first != first_position ) + { + mlt_frame_close( first_frame ); + first_position = -1; + first_frame = NULL; + } + + if( need_second != second_position) + { + mlt_frame_close( second_frame ); + second_position = -1; + second_frame = NULL; + } + + if( first_frame == NULL ) + { + // Seek the producer to the correct place + mlt_producer_seek( real_producer, need_first ); + + // Get the frame + mlt_service_get_frame( MLT_PRODUCER_SERVICE( real_producer ), &first_frame, index ); + } + + if( second_frame == NULL ) + { + // Seek the producer to the correct place + mlt_producer_seek( real_producer, need_second ); + + // Get the frame + mlt_service_get_frame( MLT_PRODUCER_SERVICE( real_producer ), &second_frame, index ); + } + + // Make sure things are in their place + mlt_properties_set_data( properties, "first_frame", first_frame, 0, NULL, NULL ); + mlt_properties_set_data( properties, "second_frame", second_frame, 0, NULL, NULL ); + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", 0 ); + + // Stack the producer and producer's get image + mlt_frame_push_service( *frame, first_frame ); + mlt_properties_inc_ref( MLT_FRAME_PROPERTIES( first_frame ) ); + + mlt_frame_push_service( *frame, second_frame ); + mlt_properties_inc_ref( MLT_FRAME_PROPERTIES( second_frame ) ); + + mlt_frame_push_service( *frame, this ); + mlt_frame_push_service( *frame, slowmotion_get_image ); + + // Give the returned frame temporal identity + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + } + + return 0; +} + +mlt_producer producer_slowmotion_init( char *arg ) +{ + mlt_producer this = mlt_producer_new( ); + + // Wrap fezzik + mlt_producer real_producer = mlt_factory_producer( "fezzik", arg ); + + // We need to apply the motion estimation filter manually + mlt_filter filter = mlt_factory_filter( "motion_est", NULL ); + + if ( this != NULL && real_producer != NULL && filter != NULL) + { + // attach the motion_est filter to the real producer + mlt_producer_attach( real_producer, filter ); + + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fezzik normalised it for us already + mlt_properties_set_int( properties, "fezzik_normalised", 1); + + // Store the producer and fitler + mlt_properties_set_data( properties, "producer", real_producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + mlt_properties_set_data( properties, "motion_est", filter, 0, ( mlt_destructor )mlt_filter_close, NULL ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "macroblock_width", 16 ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "macroblock_height", 16 ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "denoise", 0 ); + + // Grap some stuff from the real_producer + mlt_properties_pass_list( properties, MLT_PRODUCER_PROPERTIES( real_producer ), + "in, out, length, resource" ); + + // Since we control the seeking, prevent it from seeking on its own + mlt_producer_set_speed( real_producer, 0 ); + + //mlt_properties_set( properties, "method", "onefield" ); + + // Override the get_frame method + this->get_frame = slowmotion_get_frame; + + } + else + { + if ( this ) + mlt_producer_close( this ); + if ( real_producer ) + mlt_producer_close( real_producer ); + if ( filter ) + mlt_filter_close( filter ); + + this = NULL; + } + return this; +} diff --git a/src/modules/motion_est/sad_sse.h b/src/modules/motion_est/sad_sse.h new file mode 100644 index 0000000..fb4ae3a --- /dev/null +++ b/src/modules/motion_est/sad_sse.h @@ -0,0 +1,429 @@ +/* + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + + +#define SAD_SSE_INIT \ + asm volatile ( "pxor %%mm6,%%mm6\n\t" :: );\ + +// Sum two 8x1 pixel blocks +#define SAD_SSE_SUM_8(OFFSET) \ + "movq " #OFFSET "(%0),%%mm0 \n\t"\ + "movq " #OFFSET "(%1),%%mm1 \n\t"\ + "psadbw %%mm1,%%mm0 \n\t"\ + "paddw %%mm0,%%mm6 \n\t"\ + +#define SAD_SSE_FINISH(RESULT) \ + asm volatile( "movd %%mm6,%0" : "=r" (RESULT) : ); + +// Advance by ystride +#define SAD_SSE_NEXTROW \ + "add %2,%0 \n\t"\ + "add %2,%1 \n\t"\ + +// BROKEN! +inline static int sad_sse_4x4( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_INIT + #define ROW SAD_SSE_SUM_8(0) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +inline static int sad_sse_8x8( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_INIT + #define ROW SAD_SSE_SUM_8(0) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +inline static int sad_sse_16x16( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_INIT + #define ROW SAD_SSE_SUM_8(0) SAD_SSE_SUM_8(8) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +inline static int sad_sse_32x32( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_INIT + #define ROW SAD_SSE_SUM_8(0) SAD_SSE_SUM_8(8) SAD_SSE_SUM_8(16) SAD_SSE_SUM_8(24)\ + SAD_SSE_NEXTROW + + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} +// BROKEN! +inline static int sad_sse_4w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_SUM_8(0) + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +inline static int sad_sse_8w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_SUM_8(0) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +inline static int sad_sse_16w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_SUM_8(0) + SAD_SSE_SUM_8(8) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +inline static int sad_sse_32w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_SUM_8(0) + SAD_SSE_SUM_8(8) + SAD_SSE_SUM_8(16) + SAD_SSE_SUM_8(24) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +inline static int sad_sse_64w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_SUM_8(0) + SAD_SSE_SUM_8(8) + SAD_SSE_SUM_8(16) + SAD_SSE_SUM_8(24) + SAD_SSE_SUM_8(32) + SAD_SSE_SUM_8(40) + SAD_SSE_SUM_8(48) + SAD_SSE_SUM_8(56) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} +static __attribute__((used)) __attribute__((aligned(8))) uint64_t sad_sse_422_mask_chroma = 0x00ff00ff00ff00ffULL; + +#define SAD_SSE_422_LUMA_INIT \ + asm volatile ( "movq %0,%%mm7\n\t"\ + "pxor %%mm6,%%mm6\n\t" :: "m" (sad_sse_422_mask_chroma) );\ + +// Sum two 4x1 pixel blocks +#define SAD_SSE_422_LUMA_SUM_4(OFFSET) \ + "movq " #OFFSET "(%0),%%mm0 \n\t"\ + "movq " #OFFSET "(%1),%%mm1 \n\t"\ + "pand %%mm7,%%mm0 \n\t"\ + "pand %%mm7,%%mm1 \n\t"\ + "psadbw %%mm1,%%mm0 \n\t"\ + "paddw %%mm0,%%mm6 \n\t"\ + +static int sad_sse_422_luma_4x4( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_422_LUMA_INIT + #define ROW SAD_SSE_422_LUMA_SUM_4(0) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +static int sad_sse_422_luma_8x8( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_422_LUMA_INIT + #define ROW SAD_SSE_422_LUMA_SUM_4(0) SAD_SSE_422_LUMA_SUM_4(8) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +static int sad_sse_422_luma_16x16( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_422_LUMA_INIT + #define ROW SAD_SSE_422_LUMA_SUM_4(0) SAD_SSE_422_LUMA_SUM_4(8) SAD_SSE_422_LUMA_SUM_4(16) SAD_SSE_422_LUMA_SUM_4(24) SAD_SSE_NEXTROW + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +static int sad_sse_422_luma_32x32( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + SAD_SSE_422_LUMA_INIT + #define ROW SAD_SSE_422_LUMA_SUM_4(0) SAD_SSE_422_LUMA_SUM_4(8) SAD_SSE_422_LUMA_SUM_4(16) SAD_SSE_422_LUMA_SUM_4(24)\ + SAD_SSE_422_LUMA_SUM_4(32) SAD_SSE_422_LUMA_SUM_4(40) SAD_SSE_422_LUMA_SUM_4(48) SAD_SSE_422_LUMA_SUM_4(56)\ + SAD_SSE_NEXTROW + + asm volatile ( ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + ROW ROW ROW ROW ROW ROW ROW ROW + :: "r" (block1), "r" (block2), "r" ((long int)(ystride))); + + SAD_SSE_FINISH(result) + return result; + #undef ROW + +} + +static int sad_sse_422_luma_4w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_422_LUMA_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_422_LUMA_SUM_4(0) + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +static int sad_sse_422_luma_8w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_422_LUMA_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_422_LUMA_SUM_4(0) + SAD_SSE_422_LUMA_SUM_4(8) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +static int sad_sse_422_luma_16w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_422_LUMA_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_422_LUMA_SUM_4(0) + SAD_SSE_422_LUMA_SUM_4(8) + SAD_SSE_422_LUMA_SUM_4(16) + SAD_SSE_422_LUMA_SUM_4(24) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +static int sad_sse_422_luma_32w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_422_LUMA_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_422_LUMA_SUM_4(0) + SAD_SSE_422_LUMA_SUM_4(8) + SAD_SSE_422_LUMA_SUM_4(16) + SAD_SSE_422_LUMA_SUM_4(24) + SAD_SSE_422_LUMA_SUM_4(32) + SAD_SSE_422_LUMA_SUM_4(40) + SAD_SSE_422_LUMA_SUM_4(48) + SAD_SSE_422_LUMA_SUM_4(56) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; + +} + +static int sad_sse_422_luma_64w( uint8_t *block1, uint8_t *block2, int xstride, int ystride, int w, int h ) +{ + int result; + + SAD_SSE_422_LUMA_INIT + + while( h != 0 ) { + asm volatile ( + SAD_SSE_422_LUMA_SUM_4(0) + SAD_SSE_422_LUMA_SUM_4(8) + SAD_SSE_422_LUMA_SUM_4(16) + SAD_SSE_422_LUMA_SUM_4(24) + SAD_SSE_422_LUMA_SUM_4(32) + SAD_SSE_422_LUMA_SUM_4(40) + SAD_SSE_422_LUMA_SUM_4(48) + SAD_SSE_422_LUMA_SUM_4(56) + SAD_SSE_422_LUMA_SUM_4(64) + SAD_SSE_422_LUMA_SUM_4(72) + SAD_SSE_422_LUMA_SUM_4(80) + SAD_SSE_422_LUMA_SUM_4(88) + SAD_SSE_422_LUMA_SUM_4(96) + SAD_SSE_422_LUMA_SUM_4(104) + SAD_SSE_422_LUMA_SUM_4(112) + SAD_SSE_422_LUMA_SUM_4(120) + + :: "r" (block1), "r" (block2) + ); + + h--; + block1 += ystride; + block2 += ystride; + } + SAD_SSE_FINISH(result) + return result; +} diff --git a/src/modules/normalize/Makefile b/src/modules/normalize/Makefile new file mode 100644 index 0000000..ac74f1c --- /dev/null +++ b/src/modules/normalize/Makefile @@ -0,0 +1,33 @@ +include ../../../config.mak + +TARGET = ../libmltnormalize$(LIBSUF) + +OBJS = factory.o \ + filter_volume.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/normalize/configure b/src/modules/normalize/configure new file mode 100755 index 0000000..ec36a0e --- /dev/null +++ b/src/modules/normalize/configure @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../filters.dat +volume libmltnormalize$LIBSUF +EOF + +fi diff --git a/src/modules/normalize/factory.c b/src/modules/normalize/factory.c new file mode 100644 index 0000000..12b1772 --- /dev/null +++ b/src/modules/normalize/factory.c @@ -0,0 +1,45 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_volume.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "volume" ) ) + return filter_volume_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/normalize/filter_volume.c b/src/modules/normalize/filter_volume.c new file mode 100644 index 0000000..e6a86a3 --- /dev/null +++ b/src/modules/normalize/filter_volume.c @@ -0,0 +1,458 @@ +/* + * filter_volume.c -- adjust audio volume + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_volume.h" + +#include + +#include +#include +#include +#include +#include + +#define MAX_CHANNELS 6 +#define EPSILON 0.00001 + +/* The following normalise functions come from the normalize utility: + Copyright (C) 1999--2002 Chris Vaill */ + +#define samp_width 16 + +#ifndef ROUND +# define ROUND(x) floor((x) + 0.5) +#endif + +#define DBFSTOAMP(x) pow(10,(x)/20.0) + +/** Return nonzero if the two strings are equal, ignoring case, up to + the first n characters. +*/ +int strncaseeq(const char *s1, const char *s2, size_t n) +{ + for ( ; n > 0; n--) + { + if (tolower(*s1++) != tolower(*s2++)) + return 0; + } + return 1; +} + +/** Limiter function. + + / tanh((x + lev) / (1-lev)) * (1-lev) - lev (for x < -lev) + | + x' = | x (for |x| <= lev) + | + \ tanh((x - lev) / (1-lev)) * (1-lev) + lev (for x > lev) + + With limiter level = 0, this is equivalent to a tanh() function; + with limiter level = 1, this is equivalent to clipping. +*/ +static inline double limiter( double x, double lmtr_lvl ) +{ + double xp = x; + + if (x < -lmtr_lvl) + xp = tanh((x + lmtr_lvl) / (1-lmtr_lvl)) * (1-lmtr_lvl) - lmtr_lvl; + else if (x > lmtr_lvl) + xp = tanh((x - lmtr_lvl) / (1-lmtr_lvl)) * (1-lmtr_lvl) + lmtr_lvl; + +// if ( x != xp ) +// fprintf( stderr, "filter_volume: sample %f limited %f\n", x, xp ); + + return xp; +} + + +/** Takes a full smoothing window, and returns the value of the center + element, smoothed. + + Currently, just does a mean filter, but we could do a median or + gaussian filter here instead. +*/ +static inline double get_smoothed_data( double *buf, int count ) +{ + int i, j; + double smoothed = 0; + + for ( i = 0, j = 0; i < count; i++ ) + { + if ( buf[ i ] != -1.0 ) + { + smoothed += buf[ i ]; + j++; + } + } + smoothed /= j; +// fprintf( stderr, "smoothed over %d values, result %f\n", j, smoothed ); + + return smoothed; +} + +/** Get the max power level (using RMS) and peak level of the audio segment. + */ +double signal_max_power( int16_t *buffer, int channels, int samples, int16_t *peak ) +{ + // Determine numeric limits + int bytes_per_samp = (samp_width - 1) / 8 + 1; + int16_t max = (1 << (bytes_per_samp * 8 - 1)) - 1; + int16_t min = -max - 1; + + double *sums = (double *) calloc( channels, sizeof(double) ); + int c, i; + int16_t sample; + double pow, maxpow = 0; + + /* initialize peaks to effectively -inf and +inf */ + int16_t max_sample = min; + int16_t min_sample = max; + + for ( i = 0; i < samples; i++ ) + { + for ( c = 0; c < channels; c++ ) + { + sample = *buffer++; + sums[ c ] += (double) sample * (double) sample; + + /* track peak */ + if ( sample > max_sample ) + max_sample = sample; + else if ( sample < min_sample ) + min_sample = sample; + } + } + for ( c = 0; c < channels; c++ ) + { + pow = sums[ c ] / (double) samples; + if ( pow > maxpow ) + maxpow = pow; + } + + free( sums ); + + /* scale the pow value to be in the range 0.0 -- 1.0 */ + maxpow /= ( (double) min * (double) min); + + if ( -min_sample > max_sample ) + *peak = min_sample / (double) min; + else + *peak = max_sample / (double) max; + + return sqrt( maxpow ); +} + +/* ------ End normalize functions --------------------------------------- */ + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the a frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + double gain = mlt_properties_get_double( properties, "volume.gain" ); + double max_gain = mlt_properties_get_double( properties, "volume.max_gain" ); + double limiter_level = 0.5; /* -6 dBFS */ + int normalise = mlt_properties_get_int( properties, "volume.normalise" ); + double amplitude = mlt_properties_get_double( properties, "volume.amplitude" ); + int i, j; + double sample; + int16_t peak; + + // Get the filter from the frame + mlt_filter this = mlt_properties_get_data( properties, "filter_volume", NULL ); + + // Get the properties from the filter + mlt_properties filter_props = MLT_FILTER_PROPERTIES( this ); + + if ( mlt_properties_get( properties, "volume.limiter" ) != NULL ) + limiter_level = mlt_properties_get_double( properties, "volume.limiter" ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); +// fprintf( stderr, "filter_volume: frequency %d\n", *frequency ); + + // Determine numeric limits + int bytes_per_samp = (samp_width - 1) / 8 + 1; + int samplemax = (1 << (bytes_per_samp * 8 - 1)) - 1; + int samplemin = -samplemax - 1; + + if ( normalise ) + { + int window = mlt_properties_get_int( filter_props, "window" ); + double *smooth_buffer = mlt_properties_get_data( filter_props, "smooth_buffer", NULL ); + + if ( window > 0 && smooth_buffer != NULL ) + { + int smooth_index = mlt_properties_get_int( filter_props, "_smooth_index" ); + + // Compute the signal power and put into smoothing buffer + smooth_buffer[ smooth_index ] = signal_max_power( *buffer, *channels, *samples, &peak ); +// fprintf( stderr, "filter_volume: raw power %f ", smooth_buffer[ smooth_index ] ); + if ( smooth_buffer[ smooth_index ] > EPSILON ) + { + mlt_properties_set_int( filter_props, "_smooth_index", ( smooth_index + 1 ) % window ); + + // Smooth the data and compute the gain +// fprintf( stderr, "smoothed %f over %d frames\n", get_smoothed_data( smooth_buffer, window ), window ); + gain *= amplitude / get_smoothed_data( smooth_buffer, window ); + } + } + else + { + gain *= amplitude / signal_max_power( *buffer, *channels, *samples, &peak ); + } + } + +// if ( gain > 1.0 && normalise ) +// fprintf(stderr, "filter_volume: limiter level %f gain %f\n", limiter_level, gain ); + + if ( max_gain > 0 && gain > max_gain ) + gain = max_gain; + + // Initialise filter's previous gain value to prevent an inadvertant jump from 0 + if ( mlt_properties_get( filter_props, "previous_gain" ) == NULL ) + mlt_properties_set_double( filter_props, "previous_gain", gain ); + + // Start the gain out at the previous + double previous_gain = mlt_properties_get_double( filter_props, "previous_gain" ); + + // Determine ramp increment + double gain_step = ( gain - previous_gain ) / *samples; +// fprintf( stderr, "filter_volume: previous gain %f current gain %f step %f\n", previous_gain, gain, gain_step ); + + // Save the current gain for the next iteration + mlt_properties_set_double( filter_props, "previous_gain", gain ); + + // Ramp from the previous gain to the current + gain = previous_gain; + + int16_t *p = *buffer; + + // Apply the gain + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + sample = *p * gain; + *p = ROUND( sample ); + + if ( gain > 1.0 ) + { + /* use limiter function instead of clipping */ + if ( normalise ) + *p = ROUND( samplemax * limiter( sample / (double) samplemax, limiter_level ) ); + + /* perform clipping */ + else if ( sample > samplemax ) + *p = samplemax; + else if ( sample < samplemin ) + *p = samplemin; + } + p++; + } + gain += gain_step; + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties filter_props = MLT_FILTER_PROPERTIES( this ); + + // Parse the gain property + if ( mlt_properties_get( properties, "gain" ) == NULL ) + { + double gain = 1.0; // no adjustment + + if ( mlt_properties_get( filter_props, "gain" ) != NULL ) + { + char *p = mlt_properties_get( filter_props, "gain" ); + + if ( strncaseeq( p, "normalise", 9 ) ) + mlt_properties_set( filter_props, "normalise", "" ); + else + { + if ( strcmp( p, "" ) != 0 ) + gain = fabs( strtod( p, &p) ); + + while ( isspace( *p ) ) + p++; + + /* check if "dB" is given after number */ + if ( strncaseeq( p, "db", 2 ) ) + gain = DBFSTOAMP( gain ); + + // If there is an end adjust gain to the range + if ( mlt_properties_get( filter_props, "end" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + mlt_position time = mlt_frame_get_position( frame ); + double position = ( double )( time - in ) / ( double )( out - in + 1 ); + + double end = -1; + char *p = mlt_properties_get( filter_props, "end" ); + if ( strcmp( p, "" ) != 0 ) + end = fabs( strtod( p, &p) ); + + while ( isspace( *p ) ) + p++; + + /* check if "dB" is given after number */ + if ( strncaseeq( p, "db", 2 ) ) + end = DBFSTOAMP( gain ); + + if ( end != -1 ) + gain += ( end - gain ) * position; + } + } + } + mlt_properties_set_double( properties, "volume.gain", gain ); + } + + // Parse the maximum gain property + if ( mlt_properties_get( filter_props, "max_gain" ) != NULL ) + { + char *p = mlt_properties_get( filter_props, "max_gain" ); + double gain = fabs( strtod( p, &p) ); // 0 = no max + + while ( isspace( *p ) ) + p++; + + /* check if "dB" is given after number */ + if ( strncaseeq( p, "db", 2 ) ) + gain = DBFSTOAMP( gain ); + + mlt_properties_set_double( properties, "volume.max_gain", gain ); + } + + // Parse the limiter property + if ( mlt_properties_get( filter_props, "limiter" ) != NULL ) + { + char *p = mlt_properties_get( filter_props, "limiter" ); + double level = 0.5; /* -6dBFS */ + if ( strcmp( p, "" ) != 0 ) + level = strtod( p, &p); + + while ( isspace( *p ) ) + p++; + + /* check if "dB" is given after number */ + if ( strncaseeq( p, "db", 2 ) ) + { + if ( level > 0 ) + level = -level; + level = DBFSTOAMP( level ); + } + else + { + if ( level < 0 ) + level = -level; + } + mlt_properties_set_double( properties, "volume.limiter", level ); + } + + // Parse the normalise property + if ( mlt_properties_get( filter_props, "normalise" ) != NULL ) + { + char *p = mlt_properties_get( filter_props, "normalise" ); + double amplitude = 0.2511886431509580; /* -12dBFS */ + if ( strcmp( p, "" ) != 0 ) + amplitude = strtod( p, &p); + + while ( isspace( *p ) ) + p++; + + /* check if "dB" is given after number */ + if ( strncaseeq( p, "db", 2 ) ) + { + if ( amplitude > 0 ) + amplitude = -amplitude; + amplitude = DBFSTOAMP( amplitude ); + } + else + { + if ( amplitude < 0 ) + amplitude = -amplitude; + if ( amplitude > 1.0 ) + amplitude = 1.0; + } + + // If there is an end adjust gain to the range + if ( mlt_properties_get( filter_props, "end" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + mlt_position time = mlt_frame_get_position( frame ); + double position = ( double )( time - in ) / ( double )( out - in + 1 ); + amplitude *= position; + } + mlt_properties_set_int( properties, "volume.normalise", 1 ); + mlt_properties_set_double( properties, "volume.amplitude", amplitude ); + } + + // Parse the window property and allocate smoothing buffer if needed + int window = mlt_properties_get_int( filter_props, "window" ); + if ( mlt_properties_get( filter_props, "smooth_buffer" ) == NULL && window > 1 ) + { + // Create a smoothing buffer for the calculated "max power" of frame of audio used in normalisation + double *smooth_buffer = (double*) calloc( window, sizeof( double ) ); + int i; + for ( i = 0; i < window; i++ ) + smooth_buffer[ i ] = -1.0; + mlt_properties_set_data( filter_props, "smooth_buffer", smooth_buffer, 0, free, NULL ); + } + + // Put a filter reference onto the frame + mlt_properties_set_data( properties, "filter_volume", this, 0, NULL, NULL ); + + // Override the get_audio method + mlt_frame_push_audio( frame, filter_get_audio ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_volume_init( char *arg ) +{ + mlt_filter this = calloc( sizeof( struct mlt_filter_s ), 1 ); + if ( this != NULL && mlt_filter_init( this, NULL ) == 0 ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set( properties, "gain", arg ); + + mlt_properties_set_int( properties, "window", 75 ); + mlt_properties_set( properties, "max_gain", "20dB" ); + } + return this; +} diff --git a/src/modules/normalize/filter_volume.h b/src/modules/normalize/filter_volume.h new file mode 100644 index 0000000..3dd1399 --- /dev/null +++ b/src/modules/normalize/filter_volume.h @@ -0,0 +1,28 @@ +/* + * filter_volume.h -- adjust audio volume + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_VOLUME_H_ +#define _FILTER_VOLUME_H_ + +#include + +extern mlt_filter filter_volume_init( char *arg ); + +#endif diff --git a/src/modules/normalize/gpl b/src/modules/normalize/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/plus/Makefile b/src/modules/plus/Makefile new file mode 100644 index 0000000..3c58148 --- /dev/null +++ b/src/modules/plus/Makefile @@ -0,0 +1,37 @@ +include ../../../config.mak + +TARGET = ../libmltplus$(LIBSUF) + +OBJS = factory.o \ + filter_affine.o \ + filter_charcoal.o \ + filter_invert.o \ + filter_sepia.o \ + transition_affine.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/plus/configure b/src/modules/plus/configure new file mode 100755 index 0000000..77d9ce0 --- /dev/null +++ b/src/modules/plus/configure @@ -0,0 +1,23 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +EOF + +cat << EOF >> ../filters.dat +affine libmltplus$LIBSUF +charcoal libmltplus$LIBSUF +invert libmltplus$LIBSUF +sepia libmltplus$LIBSUF +EOF + +cat << EOF >> ../transitions.dat +affine libmltplus$LIBSUF +EOF + +cat << EOF >> ../consumers.dat +EOF + +fi diff --git a/src/modules/plus/factory.c b/src/modules/plus/factory.c new file mode 100644 index 0000000..948c45b --- /dev/null +++ b/src/modules/plus/factory.c @@ -0,0 +1,57 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "filter_affine.h" +#include "filter_charcoal.h" +#include "filter_invert.h" +#include "filter_sepia.h" +#include "transition_affine.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "affine" ) ) + return filter_affine_init( arg ); + if ( !strcmp( id, "charcoal" ) ) + return filter_charcoal_init( arg ); + if ( !strcmp( id, "invert" ) ) + return filter_invert_init( arg ); + if ( !strcmp( id, "sepia" ) ) + return filter_sepia_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + if ( !strcmp( id, "affine" ) ) + return transition_affine_init( arg ); + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/plus/filter_affine.c b/src/modules/plus/filter_affine.c new file mode 100644 index 0000000..2481071 --- /dev/null +++ b/src/modules/plus/filter_affine.c @@ -0,0 +1,133 @@ +/* + * filter_affine.c -- affine filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_affine.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the filter + mlt_filter filter = mlt_frame_pop_service( this ); + + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 0 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + mlt_transition transition = mlt_properties_get_data( properties, "transition", NULL ); + mlt_frame a_frame = NULL; + + if ( producer == NULL ) + { + char *background = mlt_properties_get( properties, "background" ); + producer = mlt_factory_producer( "fezzik", background ); + mlt_properties_set_data( properties, "producer", producer, 0, (mlt_destructor)mlt_producer_close, NULL ); + } + + if ( transition == NULL ) + { + transition = mlt_factory_transition( "affine", NULL ); + mlt_properties_set_data( properties, "transition", transition, 0, (mlt_destructor)mlt_transition_close, NULL ); + } + + if ( producer != NULL && transition != NULL ) + { + char *name = mlt_properties_get( properties, "_unique_id" ); + mlt_position position = mlt_properties_get_position( MLT_FRAME_PROPERTIES( this ), name ); + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( this ); + double consumer_ar = mlt_properties_get_double( frame_properties, "consumer_aspect_ratio" ); + mlt_properties_set_position( MLT_TRANSITION_PROPERTIES( transition ), "in", mlt_filter_get_in( filter ) ); + mlt_properties_set_position( MLT_TRANSITION_PROPERTIES( transition ), "out", mlt_filter_get_out( filter ) ); + mlt_producer_seek( producer, position ); + mlt_frame_set_position( this, position ); + mlt_properties_pass( MLT_PRODUCER_PROPERTIES( producer ), properties, "producer." ); + mlt_properties_pass( MLT_TRANSITION_PROPERTIES( transition ), properties, "transition." ); + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &a_frame, 0 ); + mlt_properties_set( MLT_FRAME_PROPERTIES( a_frame ), "rescale.interp", "nearest" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( a_frame ), "distort", 1 ); + + // Special case - aspect_ratio = 0 + if ( mlt_properties_get_double( frame_properties, "aspect_ratio" ) == 0 ) + mlt_properties_set_double( frame_properties, "aspect_ratio", consumer_ar ); + if ( mlt_properties_get_double( MLT_FRAME_PROPERTIES( a_frame ), "aspect_ratio" ) == 0 ) + mlt_properties_set_double( MLT_FRAME_PROPERTIES( a_frame ), "aspect_ratio", consumer_ar ); + mlt_properties_set_double( MLT_FRAME_PROPERTIES( a_frame ), "consumer_aspect_ratio", consumer_ar ); + + mlt_transition_process( transition, a_frame, this ); + mlt_frame_get_image( a_frame, image, format, width, height, writable ); + mlt_properties_set_data( frame_properties, "affine_frame", a_frame, 0, (mlt_destructor)mlt_frame_close, NULL ); + mlt_properties_set_data( frame_properties, "image", *image, *width * *height * 2, NULL, NULL ); + mlt_properties_set_data( frame_properties, "alpha", mlt_frame_get_alpha_mask( a_frame ), *width * *height, NULL, NULL ); + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get a unique name to store the frame position + char *name = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "_unique_id" ); + + // Assign the current position to the name + mlt_properties_set_position( properties, name, mlt_frame_get_position( frame ) - mlt_filter_get_in( this ) ); + + // Push the frame filter + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_affine_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "background", "colour:black" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "transition.rotate_x", "10" ); + } + return this; +} + + diff --git a/src/modules/plus/filter_affine.h b/src/modules/plus/filter_affine.h new file mode 100644 index 0000000..d4d3651 --- /dev/null +++ b/src/modules/plus/filter_affine.h @@ -0,0 +1,28 @@ +/* + * filter_affine.h -- affine filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AFFINEH_ +#define _FILTER_AFFINEH_ + +#include + +extern mlt_filter filter_affine_init( char *arg ); + +#endif diff --git a/src/modules/plus/filter_charcoal.c b/src/modules/plus/filter_charcoal.c new file mode 100644 index 0000000..ab1f2f5 --- /dev/null +++ b/src/modules/plus/filter_charcoal.c @@ -0,0 +1,175 @@ +/* + * filter_charcoal.c -- charcoal filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_charcoal.h" + +#include + +#include +#include +#include + +static inline int get_Y( uint8_t *pixels, int width, int height, int x, int y ) +{ + if ( x < 0 || x >= width || y < 0 || y >= height ) + { + return 235; + } + else + { + uint8_t *pixel = pixels + y * ( width << 1 ) + ( x << 1 ); + return *pixel; + } +} + +static inline int sqrti( int n ) +{ + int p = 0; + int q = 1; + int r = n; + int h = 0; + + while( q <= n ) + q = q << 2; + + while( q != 1 ) + { + q = q >> 2; + h = p + q; + p = p >> 1; + if ( r >= h ) + { + p = p + q; + r = r - h; + } + } + + return p; +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the filter + mlt_filter filter = mlt_frame_pop_service( this ); + + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // Get the charcoal scatter value + int x_scatter = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "x_scatter" ); + int y_scatter = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "y_scatter" ); + float scale = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "scale" ); + float mix = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "mix" ); + int invert = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "invert" ); + + // We'll process pixel by pixel + int x = 0; + int y = 0; + + // We need to create a new frame as this effect modifies the input + uint8_t *temp = mlt_pool_alloc( *width * *height * 2 ); + uint8_t *p = temp; + uint8_t *q = *image; + + // Calculations are carried out on a 3x3 matrix + int matrix[ 3 ][ 3 ]; + + // Used to carry out the matrix calculations + int sum1; + int sum2; + float sum; + int val; + + // Loop for each row + for ( y = 0; y < *height; y ++ ) + { + // Loop for each pixel + for ( x = 0; x < *width; x ++ ) + { + // Populate the matrix + matrix[ 0 ][ 0 ] = get_Y( *image, *width, *height, x - x_scatter, y - y_scatter ); + matrix[ 0 ][ 1 ] = get_Y( *image, *width, *height, x , y - y_scatter ); + matrix[ 0 ][ 2 ] = get_Y( *image, *width, *height, x + x_scatter, y - y_scatter ); + matrix[ 1 ][ 0 ] = get_Y( *image, *width, *height, x - x_scatter, y ); + matrix[ 1 ][ 2 ] = get_Y( *image, *width, *height, x + x_scatter, y ); + matrix[ 2 ][ 0 ] = get_Y( *image, *width, *height, x - x_scatter, y + y_scatter ); + matrix[ 2 ][ 1 ] = get_Y( *image, *width, *height, x , y + y_scatter ); + matrix[ 2 ][ 2 ] = get_Y( *image, *width, *height, x + x_scatter, y + y_scatter ); + + // Do calculations + sum1 = (matrix[2][0] - matrix[0][0]) + ( (matrix[2][1] - matrix[0][1]) << 1 ) + (matrix[2][2] - matrix[2][0]); + sum2 = (matrix[0][2] - matrix[0][0]) + ( (matrix[1][2] - matrix[1][0]) << 1 ) + (matrix[2][2] - matrix[2][0]); + sum = scale * sqrti( sum1 * sum1 + sum2 * sum2 ); + + // Assign value + *p ++ = !invert ? ( sum >= 16 && sum <= 235 ? 251 - sum : sum < 16 ? 235 : 16 ) : + ( sum >= 16 && sum <= 235 ? sum : sum < 16 ? 16 : 235 ); + q ++; + val = 128 + mix * ( *q ++ - 128 ); + val = val < 16 ? 16 : val > 240 ? 240 : val; + *p ++ = val; + } + } + + // Return the created image + *image = temp; + + // Store new and destroy old + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "image", *image, *width * *height * 2, mlt_pool_release, NULL ); + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the frame filter + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_charcoal_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "x_scatter", "1" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "y_scatter", "1" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "scale", "1.5" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "mix", "0" ); + } + return this; +} + diff --git a/src/modules/plus/filter_charcoal.h b/src/modules/plus/filter_charcoal.h new file mode 100644 index 0000000..7bf6c44 --- /dev/null +++ b/src/modules/plus/filter_charcoal.h @@ -0,0 +1,28 @@ +/* + * filter_charcoal.h -- charcoal filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_CHARCOAL_H_ +#define _FILTER_CHARCOAL_H_ + +#include + +extern mlt_filter filter_charcoal_init( char *arg ); + +#endif diff --git a/src/modules/plus/filter_invert.c b/src/modules/plus/filter_invert.c new file mode 100644 index 0000000..d6b1e69 --- /dev/null +++ b/src/modules/plus/filter_invert.c @@ -0,0 +1,90 @@ +/* + * filter_invert.c -- invert filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_invert.h" + +#include + +#include +#include +#include +#include + +static inline int clamp( int v, int l, int u ) +{ + return v < l ? l : ( v > u ? u : v ); +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the image + mlt_filter filter = mlt_frame_pop_service( this ); + int mask = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "alpha" ); + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + uint8_t *r = *image; + + while ( p != q ) + { + *p ++ = clamp( 251 - *r ++, 16, 235 ); + *p ++ = clamp( 256 - *r ++, 16, 240 ); + } + + if ( mask ) + { + uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + int size = *width * *height; + memset( alpha, mask, size ); + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the frame filter + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_invert_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = filter_process; + return this; +} + diff --git a/src/modules/plus/filter_invert.h b/src/modules/plus/filter_invert.h new file mode 100644 index 0000000..83d7b9d --- /dev/null +++ b/src/modules/plus/filter_invert.h @@ -0,0 +1,28 @@ +/* + * filter_invert.h -- invert filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_INVERT_H_ +#define _FILTER_INVERT_H_ + +#include + +extern mlt_filter filter_invert_init( char *arg ); + +#endif diff --git a/src/modules/plus/filter_sepia.c b/src/modules/plus/filter_sepia.c new file mode 100644 index 0000000..482ae3d --- /dev/null +++ b/src/modules/plus/filter_sepia.c @@ -0,0 +1,101 @@ +/* + * filter_sepia.c -- sepia filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_sepia.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the filter + mlt_filter filter = mlt_frame_pop_service( this ); + + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *image && *format == mlt_image_yuv422 ) + { + // We modify the whole image + uint8_t *p = *image; + int h = *height; + int uneven = *width % 2; + int w = ( *width - uneven ) / 2; + int t; + + // Get u and v values + int u = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "u" ); + int v = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "v" ); + + // Loop through image + while( h -- ) + { + t = w; + while( t -- ) + { + p ++; + *p ++ = u; + p ++; + *p ++ = v; + } + if ( uneven ) + { + p ++; + *p ++ = u; + } + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the frame filter + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_sepia_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "u", "75" ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "v", "150" ); + } + return this; +} + diff --git a/src/modules/plus/filter_sepia.h b/src/modules/plus/filter_sepia.h new file mode 100644 index 0000000..894c1c1 --- /dev/null +++ b/src/modules/plus/filter_sepia.h @@ -0,0 +1,28 @@ +/* + * filter_sepia.h -- sepia filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_SEPIA_H_ +#define _FILTER_SEPIA_H_ + +#include + +extern mlt_filter filter_sepia_init( char *arg ); + +#endif diff --git a/src/modules/plus/transition_affine.c b/src/modules/plus/transition_affine.c new file mode 100644 index 0000000..7d9f948 --- /dev/null +++ b/src/modules/plus/transition_affine.c @@ -0,0 +1,609 @@ +/* + * transition_affine.c -- affine transformations + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "transition_affine.h" +#include + +#include +#include +#include +#include +#include + +/** Calculate real geometry. +*/ + +static void geometry_calculate( mlt_transition this, char *store, struct mlt_geometry_item_s *output, float position ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + mlt_geometry geometry = mlt_properties_get_data( properties, store, NULL ); + int mirror_off = mlt_properties_get_int( properties, "mirror_off" ); + int repeat_off = mlt_properties_get_int( properties, "repeat_off" ); + int length = mlt_geometry_get_length( geometry ); + + // Allow wrapping + if ( !repeat_off && position >= length && length != 0 ) + { + int section = position / length; + position -= section * length; + if ( !mirror_off && section % 2 == 1 ) + position = length - position; + } + + // Fetch the key for the position + mlt_geometry_fetch( geometry, output, position ); +} + + +static mlt_geometry transition_parse_keys( mlt_transition this, char *name, char *store, int normalised_width, int normalised_height ) +{ + // Get the properties of the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Try to fetch it first + mlt_geometry geometry = mlt_properties_get_data( properties, store, NULL ); + + // Get the in and out position + mlt_position in = mlt_transition_get_in( this ); + mlt_position out = mlt_transition_get_out( this ); + + // Determine length and obtain cycle + int length = out - in + 1; + double cycle = mlt_properties_get_double( properties, "cycle" ); + + // Allow a geometry repeat cycle + if ( cycle >= 1 ) + length = cycle; + else if ( cycle > 0 ) + length *= cycle; + + if ( geometry == NULL ) + { + // Get the new style geometry string + char *property = mlt_properties_get( properties, name ); + + // Create an empty geometries object + geometry = mlt_geometry_init( ); + + // Parse the geometry if we have one + mlt_geometry_parse( geometry, property, length, normalised_width, normalised_height ); + + // Store it + mlt_properties_set_data( properties, store, geometry, 0, ( mlt_destructor )mlt_geometry_close, NULL ); + } + else + { + // Check for updates and refresh if necessary + mlt_geometry_refresh( geometry, mlt_properties_get( properties, name ), length, normalised_width, normalised_height ); + } + + return geometry; +} + +static mlt_geometry composite_calculate( mlt_transition this, struct mlt_geometry_item_s *result, int nw, int nh, float position ) +{ + // Structures for geometry + mlt_geometry start = transition_parse_keys( this, "geometry", "geometries", nw, nh ); + + // Do the calculation + geometry_calculate( this, "geometries", result, position ); + + return start; +} + +static inline float composite_calculate_key( mlt_transition this, char *name, char *store, int norm, float position ) +{ + // Struct for the result + struct mlt_geometry_item_s result; + + // Structures for geometry + transition_parse_keys( this, name, store, norm, 0 ); + + // Do the calculation + geometry_calculate( this, store, &result, position ); + + return result.x; +} + +typedef struct +{ + float matrix[3][3]; +} +affine_t; + +static void affine_init( float this[3][3] ) +{ + this[0][0] = 1; + this[0][1] = 0; + this[0][2] = 0; + this[1][0] = 0; + this[1][1] = 1; + this[1][2] = 0; + this[2][0] = 0; + this[2][1] = 0; + this[2][2] = 1; +} + +// Multiply two this affine transform with that +static void affine_multiply( float this[3][3], float that[3][3] ) +{ + float output[3][3]; + int i; + int j; + + for ( i = 0; i < 3; i ++ ) + for ( j = 0; j < 3; j ++ ) + output[i][j] = this[i][0] * that[j][0] + this[i][1] * that[j][1] + this[i][2] * that[j][2]; + + this[0][0] = output[0][0]; + this[0][1] = output[0][1]; + this[0][2] = output[0][2]; + this[1][0] = output[1][0]; + this[1][1] = output[1][1]; + this[1][2] = output[1][2]; + this[2][0] = output[2][0]; + this[2][1] = output[2][1]; + this[2][2] = output[2][2]; +} + +// Rotate by a given angle +static void affine_rotate_x( float this[3][3], float angle ) +{ + float affine[3][3]; + affine[0][0] = cos( angle * M_PI / 180 ); + affine[0][1] = 0 - sin( angle * M_PI / 180 ); + affine[0][2] = 0; + affine[1][0] = sin( angle * M_PI / 180 ); + affine[1][1] = cos( angle * M_PI / 180 ); + affine[1][2] = 0; + affine[2][0] = 0; + affine[2][1] = 0; + affine[2][2] = 1; + affine_multiply( this, affine ); +} + +static void affine_rotate_y( float this[3][3], float angle ) +{ + float affine[3][3]; + affine[0][0] = cos( angle * M_PI / 180 ); + affine[0][1] = 0; + affine[0][2] = 0 - sin( angle * M_PI / 180 ); + affine[1][0] = 0; + affine[1][1] = 1; + affine[1][2] = 0; + affine[2][0] = sin( angle * M_PI / 180 ); + affine[2][1] = 0; + affine[2][2] = cos( angle * M_PI / 180 ); + affine_multiply( this, affine ); +} + +static void affine_rotate_z( float this[3][3], float angle ) +{ + float affine[3][3]; + affine[0][0] = 1; + affine[0][1] = 0; + affine[0][2] = 0; + affine[1][0] = 0; + affine[1][1] = cos( angle * M_PI / 180 ); + affine[1][2] = sin( angle * M_PI / 180 ); + affine[2][0] = 0; + affine[2][1] = - sin( angle * M_PI / 180 ); + affine[2][2] = cos( angle * M_PI / 180 ); + affine_multiply( this, affine ); +} + +static void affine_scale( float this[3][3], float sx, float sy ) +{ + float affine[3][3]; + affine[0][0] = sx; + affine[0][1] = 0; + affine[0][2] = 0; + affine[1][0] = 0; + affine[1][1] = sy; + affine[1][2] = 0; + affine[2][0] = 0; + affine[2][1] = 0; + affine[2][2] = 1; + affine_multiply( this, affine ); +} + +// Shear by a given value +static void affine_shear( float this[3][3], float shear_x, float shear_y, float shear_z ) +{ + float affine[3][3]; + affine[0][0] = 1; + affine[0][1] = tan( shear_x * M_PI / 180 ); + affine[0][2] = 0; + affine[1][0] = tan( shear_y * M_PI / 180 ); + affine[1][1] = 1; + affine[1][2] = tan( shear_z * M_PI / 180 ); + affine[2][0] = 0; + affine[2][1] = 0; + affine[2][2] = 1; + affine_multiply( this, affine ); +} + +static void affine_offset( float this[3][3], int x, int y ) +{ + this[0][2] += x; + this[1][2] += y; +} + +// Obtain the mapped x coordinate of the input +static inline double MapX( float this[3][3], int x, int y ) +{ + return this[0][0] * x + this[0][1] * y + this[0][2]; +} + +// Obtain the mapped y coordinate of the input +static inline double MapY( float this[3][3], int x, int y ) +{ + return this[1][0] * x + this[1][1] * y + this[1][2]; +} + +static inline double MapZ( float this[3][3], int x, int y ) +{ + return this[2][0] * x + this[2][1] * y + this[2][2]; +} + +#define MAX( x, y ) x > y ? x : y +#define MIN( x, y ) x < y ? x : y + +static void affine_max_output( float this[3][3], float *w, float *h, float dz ) +{ + int tlx = MapX( this, -720, 576 ) / dz; + int tly = MapY( this, -720, 576 ) / dz; + int trx = MapX( this, 720, 576 ) / dz; + int try = MapY( this, 720, 576 ) / dz; + int blx = MapX( this, -720, -576 ) / dz; + int bly = MapY( this, -720, -576 ) / dz; + int brx = MapX( this, 720, -576 ) / dz; + int bry = MapY( this, 720, -576 ) / dz; + + int max_x; + int max_y; + int min_x; + int min_y; + + max_x = MAX( tlx, trx ); + max_x = MAX( max_x, blx ); + max_x = MAX( max_x, brx ); + + min_x = MIN( tlx, trx ); + min_x = MIN( min_x, blx ); + min_x = MIN( min_x, brx ); + + max_y = MAX( tly, try ); + max_y = MAX( max_y, bly ); + max_y = MAX( max_y, bry ); + + min_y = MIN( tly, try ); + min_y = MIN( min_y, bly ); + min_y = MIN( min_y, bry ); + + *w = ( float )( max_x - min_x + 1 ) / 1440.0; + *h = ( float )( max_y - min_y + 1 ) / 1152.0; +} + +#define IN_RANGE( v, r ) ( v >= - r / 2 && v < r / 2 ) + +static inline void get_affine( affine_t *affine, mlt_transition this, float position ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + int keyed = mlt_properties_get_int( properties, "keyed" ); + affine_init( affine->matrix ); + + if ( keyed == 0 ) + { + float fix_rotate_x = mlt_properties_get_double( properties, "fix_rotate_x" ); + float fix_rotate_y = mlt_properties_get_double( properties, "fix_rotate_y" ); + float fix_rotate_z = mlt_properties_get_double( properties, "fix_rotate_z" ); + float rotate_x = mlt_properties_get_double( properties, "rotate_x" ); + float rotate_y = mlt_properties_get_double( properties, "rotate_y" ); + float rotate_z = mlt_properties_get_double( properties, "rotate_z" ); + float fix_shear_x = mlt_properties_get_double( properties, "fix_shear_x" ); + float fix_shear_y = mlt_properties_get_double( properties, "fix_shear_y" ); + float fix_shear_z = mlt_properties_get_double( properties, "fix_shear_z" ); + float shear_x = mlt_properties_get_double( properties, "shear_x" ); + float shear_y = mlt_properties_get_double( properties, "shear_y" ); + float shear_z = mlt_properties_get_double( properties, "shear_z" ); + float ox = mlt_properties_get_double( properties, "ox" ); + float oy = mlt_properties_get_double( properties, "oy" ); + + affine_rotate_x( affine->matrix, fix_rotate_x + rotate_x * position ); + affine_rotate_y( affine->matrix, fix_rotate_y + rotate_y * position ); + affine_rotate_z( affine->matrix, fix_rotate_z + rotate_z * position ); + affine_shear( affine->matrix, + fix_shear_x + shear_x * position, + fix_shear_y + shear_y * position, + fix_shear_z + shear_z * position ); + affine_offset( affine->matrix, ox, oy ); + } + else + { + float rotate_x = composite_calculate_key( this, "rotate_x", "rotate_x_info", 360, position ); + float rotate_y = composite_calculate_key( this, "rotate_y", "rotate_y_info", 360, position ); + float rotate_z = composite_calculate_key( this, "rotate_z", "rotate_z_info", 360, position ); + float shear_x = composite_calculate_key( this, "shear_x", "shear_x_info", 360, position ); + float shear_y = composite_calculate_key( this, "shear_y", "shear_y_info", 360, position ); + float shear_z = composite_calculate_key( this, "shear_z", "shear_z_info", 360, position ); + + affine_rotate_x( affine->matrix, rotate_x ); + affine_rotate_y( affine->matrix, rotate_y ); + affine_rotate_z( affine->matrix, rotate_z ); + affine_shear( affine->matrix, shear_x, shear_y, shear_z ); + } +} + +/** Get the image. +*/ + +static int transition_get_image( mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the b frame from the stack + mlt_frame b_frame = mlt_frame_pop_frame( a_frame ); + + // Get the transition object + mlt_transition this = mlt_frame_pop_service( a_frame ); + + // Get the properties of the transition + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + // Get the properties of the a frame + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // Get the properties of the b frame + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Image, format, width, height and image for the b frame + uint8_t *b_image = NULL; + mlt_image_format b_format = mlt_image_yuv422; + int b_width; + int b_height; + + // Get the unique name to retrieve the frame position + char *name = mlt_properties_get( properties, "_unique_id" ); + + // Assign the current position to the name + mlt_position position = mlt_properties_get_position( a_props, name ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + int mirror = mlt_properties_get_position( properties, "mirror" ); + int length = out - in + 1; + + // Obtain the normalised width and height from the a_frame + int normalised_width = mlt_properties_get_int( a_props, "normalised_width" ); + int normalised_height = mlt_properties_get_int( a_props, "normalised_height" ); + + double consumer_ar = mlt_properties_get_double( a_props, "consumer_aspect_ratio" ) ; + + // Structures for geometry + struct mlt_geometry_item_s result; + + if ( mirror && position > length / 2 ) + position = abs( position - length ); + + // Fetch the a frame image + mlt_frame_get_image( a_frame, image, format, width, height, 1 ); + + // Calculate the region now + composite_calculate( this, &result, normalised_width, normalised_height, ( float )position ); + + // Fetch the b frame image + result.w = ( int )( result.w * *width / normalised_width ); + result.h = ( int )( result.h * *height / normalised_height ); + result.x = ( int )( result.x * *width / normalised_width ); + result.y = ( int )( result.y * *height / normalised_height ); + //result.w -= ( int )abs( result.w ) % 2; + //result.x -= ( int )abs( result.x ) % 2; + b_width = result.w; + b_height = result.h; + + if ( mlt_properties_get_double( b_props, "aspect_ratio" ) == 0.0 ) + mlt_properties_set_double( b_props, "aspect_ratio", consumer_ar ); + + if ( !strcmp( mlt_properties_get( a_props, "rescale.interp" ), "none" ) ) + { + mlt_properties_set( b_props, "rescale.interp", "nearest" ); + mlt_properties_set_double( b_props, "consumer_aspect_ratio", consumer_ar ); + } + else + { + mlt_properties_set( b_props, "rescale.interp", mlt_properties_get( a_props, "rescale.interp" ) ); + mlt_properties_set_double( b_props, "consumer_aspect_ratio", consumer_ar ); + } + + mlt_properties_set_int( b_props, "distort", mlt_properties_get_int( properties, "distort" ) ); + mlt_frame_get_image( b_frame, &b_image, &b_format, &b_width, &b_height, 0 ); + result.w = b_width; + result.h = b_height; + + // Check that both images are of the correct format and process + if ( *format == mlt_image_yuv422 && b_format == mlt_image_yuv422 ) + { + register int x, y; + register int dx, dy; + double dz; + float sw, sh; + + // Get values from the transition + float scale_x = mlt_properties_get_double( properties, "scale_x" ); + float scale_y = mlt_properties_get_double( properties, "scale_y" ); + int scale = mlt_properties_get_int( properties, "scale" ); + + uint8_t *p = *image; + uint8_t *q = *image; + + int cx = result.x + ( b_width >> 1 ); + int cy = result.y + ( b_height >> 1 ); + + int lower_x = 0 - cx; + int upper_x = *width - cx; + int lower_y = 0 - cy; + int upper_y = *height - cy; + + int b_stride = b_width << 1; + int a_stride = *width << 1; + int x_offset = ( int )result.w >> 1; + int y_offset = ( int )result.h >> 1; + + uint8_t *alpha = mlt_frame_get_alpha_mask( b_frame ); + uint8_t *mask = mlt_frame_get_alpha_mask( a_frame ); + uint8_t *pmask = mask; + float mix; + + affine_t affine; + + get_affine( &affine, this, ( float )position ); + + q = *image; + + dz = MapZ( affine.matrix, 0, 0 ); + + if ( mask == NULL ) + { + mask = mlt_pool_alloc( *width * *height ); + pmask = mask; + memset( mask, 255, *width * *height ); + } + + if ( ( int )abs( dz * 1000 ) < 25 ) + goto getout; + + if ( scale ) + { + affine_max_output( affine.matrix, &sw, &sh, dz ); + affine_scale( affine.matrix, sw, sh ); + } + else if ( scale_x != 0 && scale_y != 0 ) + { + affine_scale( affine.matrix, scale_x, scale_y ); + } + + if ( alpha == NULL ) + { + for ( y = lower_y; y < upper_y; y ++ ) + { + p = q; + + for ( x = lower_x; x < upper_x; x ++ ) + { + dx = MapX( affine.matrix, x, y ) / dz + x_offset; + dy = MapY( affine.matrix, x, y ) / dz + y_offset; + + if ( dx >= 0 && dx < b_width && dy >=0 && dy < b_height ) + { + pmask ++; + dx -= dx & 1; + *p ++ = *( b_image + dy * b_stride + ( dx << 1 ) ); + *p ++ = *( b_image + dy * b_stride + ( dx << 1 ) + ( ( x & 1 ) << 1 ) + 1 ); + } + else + { + p += 2; + pmask ++; + } + } + + q += a_stride; + } + } + else + { + for ( y = lower_y; y < upper_y; y ++ ) + { + p = q; + + for ( x = lower_x; x < upper_x; x ++ ) + { + dx = MapX( affine.matrix, x, y ) / dz + x_offset; + dy = MapY( affine.matrix, x, y ) / dz + y_offset; + + if ( dx >= 0 && dx < b_width && dy >=0 && dy < b_height ) + { + *pmask ++ = *( alpha + dy * b_width + dx ); + mix = ( float )*( alpha + dy * b_width + dx ) / 255.0; + dx -= dx & 1; + *p = *p * ( 1 - mix ) + mix * *( b_image + dy * b_stride + ( dx << 1 ) ); + p ++; + *p = *p * ( 1 - mix ) + mix * *( b_image + dy * b_stride + ( dx << 1 ) + ( ( x & 1 ) << 1 ) + 1 ); + p ++; + } + else + { + p += 2; + pmask ++; + } + } + + q += a_stride; + } + } + +getout: + a_frame->get_alpha_mask = NULL; + mlt_properties_set_data( a_props, "alpha", mask, 0, mlt_pool_release, NULL ); + } + + return 0; +} + +/** Affine transition processing. +*/ + +static mlt_frame transition_process( mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame ) +{ + // Get a unique name to store the frame position + char *name = mlt_properties_get( MLT_TRANSITION_PROPERTIES( transition ), "_unique_id" ); + + // Assign the current position to the name + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + mlt_properties_set_position( a_props, name, mlt_frame_get_position( a_frame ) ); + + // Push the transition on to the frame + mlt_frame_push_service( a_frame, transition ); + + // Push the b_frame on to the stack + mlt_frame_push_frame( a_frame, b_frame ); + + // Push the transition method + mlt_frame_push_get_image( a_frame, transition_get_image ); + + return a_frame; +} + +/** Constructor for the filter. +*/ + +mlt_transition transition_affine_init( char *arg ) +{ + mlt_transition transition = mlt_transition_new( ); + if ( transition != NULL ) + { + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "sx", 1 ); + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "sy", 1 ); + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "distort", 0 ); + mlt_properties_set( MLT_TRANSITION_PROPERTIES( transition ), "geometry", "0,0:100%x100%" ); + // Inform apps and framework that this is a video only transition + mlt_properties_set_int( MLT_TRANSITION_PROPERTIES( transition ), "_transition_type", 1 ); + transition->process = transition_process; + } + return transition; +} diff --git a/src/modules/plus/transition_affine.h b/src/modules/plus/transition_affine.h new file mode 100644 index 0000000..6c6e105 --- /dev/null +++ b/src/modules/plus/transition_affine.h @@ -0,0 +1,28 @@ +/* + * transition_affine.h -- compose one image over another using affine transforms + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TRANSITION_AFFINE_H_ +#define _TRANSITION_AFFINE_H_ + +#include + +extern mlt_transition transition_affine_init( char *arg ); + +#endif diff --git a/src/modules/qimage/Makefile b/src/modules/qimage/Makefile new file mode 100644 index 0000000..3e27a00 --- /dev/null +++ b/src/modules/qimage/Makefile @@ -0,0 +1,33 @@ +include ../../../config.mak +include config.mak + +TARGET=../libmltqimage$(LIBSUF) + +OBJS=factory.o producer_qimage.o +CPPOBJS=qimage_wrapper.o +CFLAGS+=-I../../ +LDFLAGS=-L../../framework $(QTLIBS) -lmlt -lstdc++ +CXXFLAGS+=$(CFLAGS) $(QTCXXFLAGS) -Wno-deprecated + +SRCS := $(OBJS:.o=.c) $(CPPOBJS:.o=.cpp) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(CPPOBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(CPPOBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $(QTCXXFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend config.h config.mak + +clean: + rm -f $(OBJS) $(TARGET) $(CPPOBJS) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/qimage/configure b/src/modules/qimage/configure new file mode 100755 index 0000000..0e648f0 --- /dev/null +++ b/src/modules/qimage/configure @@ -0,0 +1,80 @@ +#!/bin/sh + +if [ "$help" = "1" ] +then + cat << EOF +QImage options: + + --qimage-libdir - Location of QT lib directory [/usr/lib/qt3] + --qimage-includedir - Location of QT include directory [/usr/include/qt3] + --kde-libdir - Location of KDE lib directory [/usr/lib] + --kde-includedir - Location of KDE include directory [/usr/include/kde] + +EOF + +else + targetos=$(uname -s) + case $targetos in + MINGW32*) + export LIBSUF=.dll + ;; + Darwin) + export LIBSUF=.dylib + ;; + Linux) + export LIBSUF=.so + ;; + *) + ;; + esac + + qimage_includedir=/usr/include/qt3 + qimage_libdir=/usr/lib/qt3 + + kde_includedir=/usr/include/kde + kde_libdir=/usr/lib + + if [ "$QTDIR" != "" ] + then + qimage_includedir="$QTDIR/include" + qimage_libdir="$QTDIR" + fi + + if [ "$KDEDIR" != "" ] + then + kde_includedir="$KDEDIR/include" + kde_libdir="$KDEDIR" + fi + + for i in "$@" + do + case $i in + --qimage-libdir=* ) qimage_libdir="${i#--qimage-libdir=}" ;; + --qimage-includedir=* ) qimage_includedir="${i#--qimage-includedir=}" ;; + --kde-libdir=* ) kde_libdir="${i#--kde-libdir=}" ;; + --kde-includedir=* ) kde_includedir="${i#--kde-includedir=}" ;; + esac + done + + if [ -d "$qimage_libdir" -a -d "$qimage_includedir" ] + then + echo > config.h + echo > config.mak + if [ -d "$kde_includedir" ] + then + echo "#define USE_KDE" >> config.h + echo "USE_KDE=1" >> config.mak + echo QTCXXFLAGS=-I$qimage_includedir -I$kde_includedir >> config.mak + echo QTLIBS=-L$qimage_libdir/lib -L$kde_libdir/lib -lqt-mt >> config.mak + else + echo "qimage: KDE environment not found - disabling extra image formats" + echo QTCXXFLAGS=-I$qimage_includedir >> config.mak + echo QTLIBS=-L$qimage_libdir/lib -lqt-mt >> config.mak + fi + echo qimage libmltqimage$LIBSUF >> ../producers.dat + else + echo "qimage: QT environment not found - disabling" + touch ../disable-qimage + fi + +fi diff --git a/src/modules/qimage/factory.c b/src/modules/qimage/factory.c new file mode 100644 index 0000000..f2ba837 --- /dev/null +++ b/src/modules/qimage/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2006 Visual Media + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "producer_qimage.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "qimage" ) ) + return producer_qimage_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/qimage/gpl b/src/modules/qimage/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/qimage/producer_qimage.c b/src/modules/qimage/producer_qimage.c new file mode 100644 index 0000000..c4cd29f --- /dev/null +++ b/src/modules/qimage/producer_qimage.c @@ -0,0 +1,287 @@ +/* + * producer_image.c -- a QT/QImage based producer for MLT + * Copyright (C) 2006 Visual Media + * Author: Charles Yates + * + * NB: This module is designed to be functionally equivalent to the + * gtk2 image loading module so it can be used as replacement. + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "producer_qimage.h" +#include "qimage_wrapper.h" + +#include +#include +#include +#include +#include +#include +#include + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_qimage_init( char *filename ) +{ + producer_qimage this = calloc( sizeof( struct producer_qimage_s ), 1 ); + if ( this != NULL && mlt_producer_init( &this->parent, this ) == 0 ) + { + mlt_producer producer = &this->parent; + + // Get the properties interface + mlt_properties properties = MLT_PRODUCER_PROPERTIES( &this->parent ); + + // Callback registration + init_qimage(); + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + // Set the default properties + mlt_properties_set( properties, "resource", filename ); + mlt_properties_set_int( properties, "ttl", 25 ); + mlt_properties_set_int( properties, "aspect_ratio", 1 ); + mlt_properties_set_int( properties, "progressive", 1 ); + + return producer; + } + free( this ); + return NULL; +} + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // We need to know the size of the image to clone it + int image_size = 0; + int alpha_size = 0; + + // Alpha channel + uint8_t *alpha = NULL; + + *width = mlt_properties_get_int( properties, "rescale_width" ); + *height = mlt_properties_get_int( properties, "rescale_height" ); + + // Refresh the image + refresh_qimage( frame, *width, *height ); + + // Get the image + *buffer = mlt_properties_get_data( properties, "image", &image_size ); + alpha = mlt_properties_get_data( properties, "alpha", &alpha_size ); + + // Get width and height (may have changed during the refresh) + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + + // NB: Cloning is necessary with this producer (due to processing of images ahead of use) + // The fault is not in the design of mlt, but in the implementation of the qimage producer... + if ( *buffer != NULL ) + { + if ( *format == mlt_image_yuv422 || *format == mlt_image_yuv420p ) + { + // Clone the image and the alpha + uint8_t *image_copy = mlt_pool_alloc( image_size ); + uint8_t *alpha_copy = mlt_pool_alloc( alpha_size ); + + memcpy( image_copy, *buffer, image_size ); + + // Copy or default the alpha + if ( alpha != NULL ) + memcpy( alpha_copy, alpha, alpha_size ); + else + memset( alpha_copy, 255, alpha_size ); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", image_copy, image_size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha_copy, alpha_size, mlt_pool_release, NULL ); + + // We're going to pass the copy on + *buffer = image_copy; + } + else if ( *format == mlt_image_rgb24a ) + { + // Clone the image and the alpha + image_size = *width * ( *height + 1 ) * 4; + alpha_size = *width * ( *height + 1 ); + uint8_t *image_copy = mlt_pool_alloc( image_size ); + uint8_t *alpha_copy = mlt_pool_alloc( alpha_size ); + + mlt_convert_yuv422_to_rgb24a(*buffer, image_copy, (*width)*(*height)); + + // Now update properties so we free the copy after + mlt_properties_set_data( properties, "image", image_copy, image_size, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha_copy, alpha_size, mlt_pool_release, NULL ); + + // We're going to pass the copy on + *buffer = image_copy; + } + } + else + { + // TODO: Review all cases of invalid images + *buffer = mlt_pool_alloc( 50 * 50 * 2 ); + mlt_properties_set_data( properties, "image", *buffer, image_size, mlt_pool_release, NULL ); + *width = 50; + *height = 50; + } + + return 0; +} + +static uint8_t *producer_get_alpha_mask( mlt_frame this ) +{ + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Return the alpha mask + return mlt_properties_get_data( properties, "alpha", NULL ); +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Get the real structure for this producer + producer_qimage this = producer->child; + + // Fetch the producers properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + if ( this->filenames == NULL && mlt_properties_get( producer_properties, "resource" ) != NULL ) + { + char *filename = mlt_properties_get( producer_properties, "resource" ); + this->filenames = mlt_properties_new( ); + + // Read xml string + if ( strstr( filename, " -1 ) + { + // Write the svg into the temp file + ssize_t remaining_bytes; + char *xml = filename; + + // Strip leading crap + while ( xml[0] != '<' ) + xml++; + + remaining_bytes = strlen( xml ); + while ( remaining_bytes > 0 ) + remaining_bytes -= write( fd, xml + strlen( xml ) - remaining_bytes, remaining_bytes ); + close( fd ); + + mlt_properties_set( this->filenames, "0", fullname ); + + // Teehe - when the producer closes, delete the temp file and the space allo + mlt_properties_set_data( producer_properties, "__temporary_file__", fullname, 0, ( mlt_destructor )unlink, NULL ); + } + } + // Obtain filenames + else if ( strchr( filename, '%' ) != NULL ) + { + // handle picture sequences + int i = mlt_properties_get_int( producer_properties, "begin" ); + int gap = 0; + char full[1024]; + int keyvalue = 0; + char key[ 50 ]; + + while ( gap < 100 ) + { + struct stat buf; + snprintf( full, 1023, filename, i ++ ); + if ( stat( full, &buf ) == 0 ) + { + sprintf( key, "%d", keyvalue ++ ); + mlt_properties_set( this->filenames, "0", full ); + gap = 0; + } + else + { + gap ++; + } + } + } + else if ( strstr( filename, "/.all." ) != NULL ) + { + char wildcard[ 1024 ]; + char *dir_name = strdup( filename ); + char *extension = strrchr( dir_name, '.' ); + + *( strstr( dir_name, "/.all." ) + 1 ) = '\0'; + sprintf( wildcard, "*%s", extension ); + + mlt_properties_dir_list( this->filenames, dir_name, wildcard, 1 ); + + free( dir_name ); + } + else + { + mlt_properties_set( this->filenames, "0", filename ); + } + + this->count = mlt_properties_count( this->filenames ); + } + + // Generate a frame + *frame = mlt_frame_init( ); + + if ( *frame != NULL && this->count > 0 ) + { + // Obtain properties of frame and producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Set the producer on the frame properties + mlt_properties_set_data( properties, "producer_qimage", this, 0, NULL, NULL ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Ensure that we have a way to obtain the position in the get_image + mlt_properties_set_position( properties, "qimage_position", mlt_producer_position( producer ) ); + + // Refresh the image + refresh_qimage( *frame, 0, 0 ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", mlt_properties_get_int( producer_properties, "progressive" ) ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_properties, "aspect_ratio" ) ); + + // Set alpha call back + ( *frame )->get_alpha_mask = producer_get_alpha_mask; + + // Push the get_image method + mlt_frame_push_get_image( *frame, producer_get_image ); + } + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + producer_qimage this = parent->child; + parent->close = NULL; + mlt_producer_close( parent ); + mlt_properties_close( this->filenames ); + free( this ); +} diff --git a/src/modules/qimage/producer_qimage.h b/src/modules/qimage/producer_qimage.h new file mode 100644 index 0000000..3a8f265 --- /dev/null +++ b/src/modules/qimage/producer_qimage.h @@ -0,0 +1,28 @@ +/* + * producer_qimage.h -- a QT/QImage based producer for MLT + * Copyright (C) 2006 Visual Media + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _PRODUCER_QIMAGE_H_ +#define _PRODUCER_QIMAGE_H_ + +#include + +extern mlt_producer producer_qimage_init( char *filename ); + +#endif diff --git a/src/modules/qimage/qimage_wrapper.cpp b/src/modules/qimage/qimage_wrapper.cpp new file mode 100644 index 0000000..f6c6d54 --- /dev/null +++ b/src/modules/qimage/qimage_wrapper.cpp @@ -0,0 +1,259 @@ +/* + * qimage_wrapper.cpp -- a QT/QImage based producer for MLT + * Copyright (C) 2006 Visual Media + * Author: Charles Yates + * + * NB: This module is designed to be functionally equivalent to the + * gtk2 image loading module so it can be used as replacement. + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "qimage_wrapper.h" +#include + + +#include "config.h" + +#ifdef USE_KDE +#include +#include +#endif + +#include + +extern "C" { + +#include + +#ifdef USE_KDE +static KInstance *instance = 0L; +#endif + +static void qimage_delete( void *data ) +{ + QImage *image = ( QImage * )data; + delete image; +#ifdef USE_KDE + if (instance) delete instance; + instance = 0L; +#endif +} + +static void clear_buffered_image( mlt_properties producer_props, uint8_t **current_image, uint8_t **current_alpha ) +{ + mlt_events_block( producer_props, NULL ); + mlt_properties_set_data( producer_props, "_qimage_image", NULL, 0, NULL, NULL ); + mlt_properties_set_data( producer_props, "_qimage_alpha", NULL, 0, NULL, NULL ); + *current_image = NULL; + *current_alpha = NULL; + mlt_events_unblock( producer_props, NULL ); +} + +static void assign_buffered_image( mlt_properties producer_props, uint8_t *current_image, uint8_t *current_alpha, int width, int height ) +{ + int use_cache = mlt_properties_get_int( producer_props, "cache" ); + mlt_destructor destructor = use_cache ? NULL : mlt_pool_release; + mlt_events_block( producer_props, NULL ); + mlt_properties_set_data( producer_props, "_qimage_image", current_image, 0, destructor, NULL ); + mlt_properties_set_data( producer_props, "_qimage_alpha", current_alpha, 0, destructor, NULL ); + mlt_properties_set_int( producer_props, "_qimage_width", width ); + mlt_properties_set_int( producer_props, "_qimage_height", height ); + mlt_events_unblock( producer_props, NULL ); +} + +void init_qimage() +{ +#ifdef USE_KDE + if (!instance) { + instance = new KInstance("qimage_prod"); + KImageIO::registerFormats(); + } +#endif +} + +void refresh_qimage( mlt_frame frame, int width, int height ) +{ + // Obtain a previous assigned qimage (if it exists) + QImage *qimage = ( QImage * )mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "qimage", NULL ); + + // Obtain properties of frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the producer for this frame + producer_qimage self = ( producer_qimage )mlt_properties_get_data( properties, "producer_qimage", NULL ); + + // Obtain the producer + mlt_producer producer = &self->parent; + + // Obtain properties of producer + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Obtain the cache flag and structure + int use_cache = mlt_properties_get_int( producer_props, "cache" ); + mlt_properties cache = ( mlt_properties )mlt_properties_get_data( producer_props, "_cache", NULL ); + int update_cache = 0; + + // Retrieve current info if available + uint8_t *current_image = ( uint8_t * )mlt_properties_get_data( producer_props, "_qimage_image", NULL ); + uint8_t *current_alpha = ( uint8_t * )mlt_properties_get_data( producer_props, "_qimage_alpha", NULL ); + int current_width = mlt_properties_get_int( producer_props, "_qimage_width" ); + int current_height = mlt_properties_get_int( producer_props, "_qimage_height" ); + + // Get the time to live for each frame + double ttl = mlt_properties_get_int( producer_props, "ttl" ); + + // Get the original position of this frame + mlt_position position = mlt_properties_get_position( properties, "qimage_position" ); + + // Image index + int image_idx = ( int )floor( ( double )position / ttl ) % self->count; + + // Key for the cache + char image_key[ 10 ]; + sprintf( image_key, "%d", image_idx ); + + // Check if the frame is already loaded + if ( use_cache ) + { + if ( cache == NULL ) + { + cache = mlt_properties_new( ); + mlt_properties_set_data( producer_props, "_cache", cache, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + mlt_frame cached = ( mlt_frame )mlt_properties_get_data( cache, image_key, NULL ); + + if ( cached ) + { + self->image_idx = image_idx; + mlt_properties cached_props = MLT_FRAME_PROPERTIES( cached ); + current_width = mlt_properties_get_int( cached_props, "width" ); + current_height = mlt_properties_get_int( cached_props, "height" ); + mlt_properties_set_int( producer_props, "_real_width", mlt_properties_get_int( cached_props, "real_width" ) ); + mlt_properties_set_int( producer_props, "_real_height", mlt_properties_get_int( cached_props, "real_height" ) ); + current_image = ( uint8_t * )mlt_properties_get_data( cached_props, "image", NULL ); + current_alpha = ( uint8_t * )mlt_properties_get_data( cached_props, "alpha", NULL ); + + if ( width != 0 && ( width != current_width || height != current_height ) ) + current_image = NULL; + + assign_buffered_image( producer_props, current_image, current_alpha, current_width, current_height ); + } + } + + // optimization for subsequent iterations on single picture + if ( width != 0 && current_image != NULL && image_idx == self->image_idx ) + { + if ( width != current_width || height != current_height ) + { + qimage = ( QImage * )mlt_properties_get_data( producer_props, "_qimage", NULL ); + clear_buffered_image( producer_props, ¤t_image, ¤t_alpha ); + } + } + else if ( qimage == NULL && ( current_image == NULL || image_idx != self->image_idx ) ) + { + clear_buffered_image( producer_props, ¤t_image, ¤t_alpha ); + + self->image_idx = image_idx; + qimage = new QImage( mlt_properties_get_value( self->filenames, image_idx ) ); + + if ( !qimage->isNull( ) ) + { + QImage *frame_copy = new QImage( *qimage ); + + // Store the width/height of the pixbuf + current_width = qimage->width( ); + current_height = qimage->height( ); + + // Register qimage for destruction and reuse + mlt_events_block( producer_props, NULL ); + mlt_properties_set_data( producer_props, "_qimage", qimage, 0, ( mlt_destructor )qimage_delete, NULL ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), "qimage", frame_copy, 0, ( mlt_destructor )qimage_delete, NULL ); + mlt_properties_set_int( producer_props, "_real_width", current_width ); + mlt_properties_set_int( producer_props, "_real_height", current_height ); + mlt_events_unblock( producer_props, NULL ); + } + else + { + delete qimage; + qimage = NULL; + } + } + + // If we have a pixbuf and this request specifies a valid dimension and we haven't already got a cached version... + if ( qimage && width > 0 && current_image == NULL ) + { + char *interps = mlt_properties_get( properties, "rescale.interp" ); + int interp = 0; + + // QImage has two scaling modes - we'll toggle between them here + if ( strcmp( interps, "tiles" ) == 0 ) + interp = 1; + else if ( strcmp( interps, "hyper" ) == 0 ) + interp = 1; + + // Note - the original qimage is already safe and ready for destruction + QImage scaled = interp == 0 ? qimage->scale( width, height, QImage::ScaleFree ) : qimage->smoothScale( width, height, QImage::ScaleFree ); + QImage temp = scaled.convertDepth( 32 ); + + // Store width and height + current_width = width; + current_height = height; + + // Allocate/define image + current_image = ( uint8_t * )mlt_pool_alloc( width * ( height + 1 ) * 2 ); + + // Allocate the alpha mask + current_alpha = ( uint8_t * )mlt_pool_alloc( current_width * current_height ); + + // Convert the image + if ( QImage::systemByteOrder( ) == QImage::BigEndian ) + mlt_convert_argb_to_yuv422( temp.bits( ), current_width, current_height, temp.bytesPerLine( ), current_image, current_alpha ); + else + mlt_convert_bgr24a_to_yuv422( temp.bits( ), current_width, current_height, temp.bytesPerLine( ), current_image, current_alpha ); + + assign_buffered_image( producer_props, current_image, current_alpha, current_width, current_height ); + + // Ensure we update the cache when we need to + update_cache = use_cache; + } + + // Set width/height of frame + mlt_properties_set_int( properties, "width", current_width ); + mlt_properties_set_int( properties, "height", current_height ); + mlt_properties_set_int( properties, "real_width", mlt_properties_get_int( producer_props, "_real_width" ) ); + mlt_properties_set_int( properties, "real_height", mlt_properties_get_int( producer_props, "_real_height" ) ); + + // pass the image data without destructor + mlt_properties_set_data( properties, "image", current_image, current_width * ( current_height + 1 ) * 2, NULL, NULL ); + mlt_properties_set_data( properties, "alpha", current_alpha, current_width * current_height, NULL, NULL ); + + if ( update_cache ) + { + mlt_frame cached = mlt_frame_init( ); + mlt_properties cached_props = MLT_FRAME_PROPERTIES( cached ); + mlt_properties_set_int( cached_props, "width", current_width ); + mlt_properties_set_int( cached_props, "height", current_height ); + mlt_properties_set_int( cached_props, "real_width", mlt_properties_get_int( producer_props, "_real_width" ) ); + mlt_properties_set_int( cached_props, "real_height", mlt_properties_get_int( producer_props, "_real_height" ) ); + mlt_properties_set_data( cached_props, "image", current_image, current_width * ( current_height + 1 ) * 2, mlt_pool_release, NULL ); + mlt_properties_set_data( cached_props, "alpha", current_alpha, current_width * current_height, mlt_pool_release, NULL ); + mlt_properties_set_data( cache, image_key, cached, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } +} + +} + diff --git a/src/modules/qimage/qimage_wrapper.h b/src/modules/qimage/qimage_wrapper.h new file mode 100644 index 0000000..45472cb --- /dev/null +++ b/src/modules/qimage/qimage_wrapper.h @@ -0,0 +1,50 @@ +/* + * qimage_wrapper.h -- a QT/QImage based producer for MLT + * Copyright (C) 2006 Visual Media + * Author: Charles Yates + * + * NB: This module is designed to be functionally equivalent to the + * gtk2 image loading module so it can be used as replacement. + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MLT_QIMAGE_WRAPPER +#define MLT_QIMAGE_WRAPPER + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct producer_qimage_s +{ + struct mlt_producer_s parent; + mlt_properties filenames; + int count; + int image_idx; +}; + +typedef struct producer_qimage_s *producer_qimage; + +extern void refresh_qimage( mlt_frame, int width, int height ); +extern void init_qimage(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/resample/Makefile b/src/modules/resample/Makefile new file mode 100644 index 0000000..974fa0c --- /dev/null +++ b/src/modules/resample/Makefile @@ -0,0 +1,35 @@ +include ../../../config.mak + +TARGET = ../libmltresample$(LIBSUF) + +OBJS = factory.o \ + filter_resample.o + +CFLAGS += -I../.. + +LDFLAGS += -lsamplerate + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/resample/configure b/src/modules/resample/configure new file mode 100755 index 0000000..58dd85b --- /dev/null +++ b/src/modules/resample/configure @@ -0,0 +1,18 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + pkg-config samplerate 2> /dev/null + disable_samplerate=$? + + if [ "$disable_samplerate" = "0" ] + then + echo "resample libmltresample$LIBSUF" >> ../filters.dat + else + echo "- libsamplerate not found: disabling" + touch ../disable-resample + fi + +fi + diff --git a/src/modules/resample/factory.c b/src/modules/resample/factory.c new file mode 100644 index 0000000..de90fb0 --- /dev/null +++ b/src/modules/resample/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_resample.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "resample" ) ) + return filter_resample_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/resample/filter_resample.c b/src/modules/resample/filter_resample.c new file mode 100644 index 0000000..fafd5e0 --- /dev/null +++ b/src/modules/resample/filter_resample.c @@ -0,0 +1,201 @@ +/* + * filter_resample.c -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_resample.h" + +#include + +#include +#include +#include +#define __USE_ISOC99 1 +#include + +#define BUFFER_LEN 20480 +#define RESAMPLE_TYPE SRC_SINC_FASTEST + +/** Get the audio. +*/ + +static int resample_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the resample information + int output_rate = mlt_properties_get_int( filter_properties, "frequency" ); + SRC_STATE *state = mlt_properties_get_data( filter_properties, "state", NULL ); + float *input_buffer = mlt_properties_get_data( filter_properties, "input_buffer", NULL ); + float *output_buffer = mlt_properties_get_data( filter_properties, "output_buffer", NULL ); + int channels_avail = *channels; + SRC_DATA data; + int i; + + // If no resample frequency is specified, default to requested value + if ( output_rate == 0 ) + output_rate = *frequency; + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, &channels_avail, samples ); + + // Duplicate channels as necessary + if ( channels_avail < *channels ) + { + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + int j, k = 0; + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * channels_avail ) + k ]; + k = ( k + 1 ) % channels_avail; + } + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + else if ( channels_avail == 6 && *channels == 2 ) + { + // Nasty hack for ac3 5.1 audio - may be a cause of failure? + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + + // Drop all but the first *channels + for ( i = 0; i < *samples; i++ ) + { + new_buffer[ ( i * *channels ) + 0 ] = (*buffer)[ ( i * channels_avail ) + 2 ]; + new_buffer[ ( i * *channels ) + 1 ] = (*buffer)[ ( i * channels_avail ) + 3 ]; + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + + // Return now if no work to do + if ( output_rate != *frequency ) + { + float *p = input_buffer; + float *end = p + *samples * *channels; + int16_t *q = *buffer; + + // Convert to floating point + while( p != end ) + *p ++ = ( float )( *q ++ ) / 32768.0; + + // Resample + data.data_in = input_buffer; + data.data_out = output_buffer; + data.src_ratio = ( float ) output_rate / ( float ) *frequency; + data.input_frames = *samples; + data.output_frames = BUFFER_LEN / *channels; + data.end_of_input = 0; + i = src_process( state, &data ); + if ( i == 0 ) + { + if ( data.output_frames_gen > *samples ) + { + *buffer = mlt_pool_realloc( *buffer, data.output_frames_gen * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( properties, "audio", *buffer, *channels * data.output_frames_gen * 2, mlt_pool_release, NULL ); + } + + *samples = data.output_frames_gen; + *frequency = output_rate; + + p = output_buffer; + q = *buffer; + end = p + *samples * *channels; + + // Convert from floating back to signed 16bit + while( p != end ) + { + if ( *p > 1.0 ) + *p = 1.0; + if ( *p < -1.0 ) + *p = -1.0; + if ( *p > 0 ) + *q ++ = 32767 * *p ++; + else + *q ++ = 32768 * *p ++; + } + } + else + fprintf( stderr, "resample_get_audio: %s %d,%d,%d\n", src_strerror( i ), *frequency, *samples, output_rate ); + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + if ( mlt_frame_is_test_audio( frame ) == 0 ) + { + mlt_frame_push_audio( frame, this ); + mlt_frame_push_audio( frame, resample_get_audio ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_resample_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + int error; + SRC_STATE *state = src_new( RESAMPLE_TYPE, 2 /* channels */, &error ); + if ( error == 0 ) + { + void *input_buffer = mlt_pool_alloc( BUFFER_LEN ); + void *output_buffer = mlt_pool_alloc( BUFFER_LEN ); + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "frequency", atoi( arg ) ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", 2 ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "state", state, 0, (mlt_destructor)src_delete, NULL ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "input_buffer", input_buffer, BUFFER_LEN, mlt_pool_release, NULL ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "output_buffer", output_buffer, BUFFER_LEN, mlt_pool_release, NULL ); + } + else + { + fprintf( stderr, "filter_resample_init: %s\n", src_strerror( error ) ); + } + } + return this; +} diff --git a/src/modules/resample/filter_resample.h b/src/modules/resample/filter_resample.h new file mode 100644 index 0000000..ba51700 --- /dev/null +++ b/src/modules/resample/filter_resample.h @@ -0,0 +1,28 @@ +/* + * filter_resample.h -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_RESAMPLE_H_ +#define _FILTER_RESAMPLE_H_ + +#include + +extern mlt_filter filter_resample_init( char *arg ); + +#endif diff --git a/src/modules/resample/gpl b/src/modules/resample/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/sdl/Makefile b/src/modules/sdl/Makefile new file mode 100644 index 0000000..37d0403 --- /dev/null +++ b/src/modules/sdl/Makefile @@ -0,0 +1,52 @@ +include ../../../config.mak + +include config.mak + +TARGET = ../libmltsdl$(LIBSUF) + +OBJS = factory.o \ + consumer_sdl.o \ + consumer_sdl_preview.o \ + consumer_sdl_still.o + +ifeq ($(targetos),Darwin) + CFLAGS +=-ObjC + LDFLAGS +=-lobjc -framework Foundation +else + LDFLAGS +=-lX11 +endif + +CFLAGS +=-I../.. `sdl-config --cflags` + +LDFLAGS +=`sdl-config --libs` + +LDFLAGS +=-L../../framework -lmlt + +ifeq ($(WITH_SDL_IMAGE),1) +OBJS += producer_sdl_image.o +CFLAGS += -DWITH_SDL_IMAGE +LDFLAGS += -lSDL_image +endif + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/sdl/config.mak b/src/modules/sdl/config.mak new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/modules/sdl/config.mak @@ -0,0 +1 @@ + diff --git a/src/modules/sdl/configure b/src/modules/sdl/configure new file mode 100755 index 0000000..af5568c --- /dev/null +++ b/src/modules/sdl/configure @@ -0,0 +1,27 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + sdl-config --version > /dev/null 2>&1 + disable_sdl=$? + + if [ "$disable_sdl" = "0" ] + then + echo > config.mak + image=`sdl-config --prefix`/include/SDL/SDL_image.h + echo "sdl libmltsdl$LIBSUF" >> ../consumers.dat + echo "sdl_preview libmltsdl$LIBSUF" >> ../consumers.dat + echo "sdl_still libmltsdl$LIBSUF" >> ../consumers.dat + if [ -f "$image" ] + then + echo "sdl_image libmltsdl$LIBSUF" >> ../producers.dat + echo "WITH_SDL_IMAGE=1" >> config.mak + fi + else + echo "- sdl development libs not found: disabling" + touch ../disable-sdl + fi + +fi + diff --git a/src/modules/sdl/consumer_sdl.c b/src/modules/sdl/consumer_sdl.c new file mode 100644 index 0000000..a5c168c --- /dev/null +++ b/src/modules/sdl/consumer_sdl.c @@ -0,0 +1,837 @@ +/* + * consumer_sdl.c -- A Simple DirectMedia Layer consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_sdl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** This classes definition. +*/ + +typedef struct consumer_sdl_s *consumer_sdl; + +struct consumer_sdl_s +{ + struct mlt_consumer_s parent; + mlt_properties properties; + mlt_deque queue; + pthread_t thread; + int joined; + int running; + uint8_t audio_buffer[ 4096 * 10 ]; + int audio_avail; + pthread_mutex_t audio_mutex; + pthread_cond_t audio_cond; + pthread_mutex_t video_mutex; + pthread_cond_t video_cond; + int window_width; + int window_height; + int previous_width; + int previous_height; + int width; + int height; + int playing; + int sdl_flags; + SDL_Surface *sdl_screen; + SDL_Overlay *sdl_overlay; + SDL_Rect rect; + uint8_t *buffer; + int bpp; + int filtered; +}; + +/** Forward references to static functions. +*/ + +static int consumer_start( mlt_consumer parent ); +static int consumer_stop( mlt_consumer parent ); +static int consumer_is_stopped( mlt_consumer parent ); +static void consumer_close( mlt_consumer parent ); +static void *consumer_thread( void * ); +static int consumer_get_dimensions( int *width, int *height ); +static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); + +/** This is what will be called by the factory - anything can be passed in + via the argument, but keep it simple. +*/ + +mlt_consumer consumer_sdl_init( char *arg ) +{ + // Create the consumer object + consumer_sdl this = calloc( sizeof( struct consumer_sdl_s ), 1 ); + + // If no malloc'd and consumer init ok + if ( this != NULL && mlt_consumer_init( &this->parent, this ) == 0 ) + { + // Create the queue + this->queue = mlt_deque_init( ); + + // Get the parent consumer object + mlt_consumer parent = &this->parent; + + // We have stuff to clean up, so override the close method + parent->close = consumer_close; + + // get a handle on properties + mlt_service service = MLT_CONSUMER_SERVICE( parent ); + this->properties = MLT_SERVICE_PROPERTIES( service ); + + // Set the default volume + mlt_properties_set_double( this->properties, "volume", 1.0 ); + + // This is the initialisation of the consumer + pthread_mutex_init( &this->audio_mutex, NULL ); + pthread_cond_init( &this->audio_cond, NULL); + pthread_mutex_init( &this->video_mutex, NULL ); + pthread_cond_init( &this->video_cond, NULL); + + // Default scaler (for now we'll use nearest) + mlt_properties_set( this->properties, "rescale", "nearest" ); + + // Default buffer for low latency + mlt_properties_set_int( this->properties, "buffer", 1 ); + + // Default progressive true + mlt_properties_set_int( this->properties, "progressive", 0 ); + + // Default audio buffer + mlt_properties_set_int( this->properties, "audio_buffer", 512 ); + + // Ensure we don't join on a non-running object + this->joined = 1; + + // process actual param + if ( arg == NULL || sscanf( arg, "%dx%d", &this->width, &this->height ) != 2 ) + { + this->width = mlt_properties_get_int( this->properties, "width" ); + this->height = mlt_properties_get_int( this->properties, "height" ); + } + + // Set the sdl flags + this->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_DOUBLEBUF; + + // Allow thread to be started/stopped + parent->start = consumer_start; + parent->stop = consumer_stop; + parent->is_stopped = consumer_is_stopped; + + // Register specific events + mlt_events_register( this->properties, "consumer-sdl-event", ( mlt_transmitter )consumer_sdl_event ); + + // Return the consumer produced + return parent; + } + + // malloc or consumer init failed + free( this ); + + // Indicate failure + return NULL; +} + +static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( SDL_Event * )args[ 0 ] ); +} + +int consumer_start( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + + if ( !this->running ) + { + int video_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "video_off" ); + int preview_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "preview_off" ); + int display_off = video_off | preview_off; + int audio_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "audio_off" ); + int sdl_started = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" ); + + consumer_stop( parent ); + + this->running = 1; + this->joined = 0; + + if ( mlt_properties_get_int( this->properties, "width" ) > 0 ) + this->width = mlt_properties_get_int( this->properties, "width" ); + if ( mlt_properties_get_int( this->properties, "height" ) > 0 ) + this->height = mlt_properties_get_int( this->properties, "height" ); + + this->bpp = mlt_properties_get_int( this->properties, "bpp" ); + + // Attach a colour space converter + if ( preview_off && !this->filtered ) + { + mlt_filter filter = mlt_factory_filter( "avcolour_space", NULL ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "forced", mlt_image_yuv422 ); + mlt_service_attach( MLT_CONSUMER_SERVICE( parent ), filter ); + mlt_filter_close( filter ); + this->filtered = 1; + } + + if ( sdl_started == 0 && display_off == 0 ) + { + if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) + { + fprintf( stderr, "Failed to initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ); + SDL_EnableUNICODE( 1 ); + } + else if ( display_off == 0 ) + { + this->sdl_screen = SDL_GetVideoSurface( ); + } + + if ( audio_off == 0 ) + SDL_InitSubSystem( SDL_INIT_AUDIO ); + + // Default window size + double display_ratio = mlt_properties_get_double( this->properties, "display_ratio" ); + this->window_width = ( double )this->height * display_ratio; + this->window_height = this->height; + + if ( this->sdl_screen == NULL && display_off == 0 ) + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags ); + + pthread_create( &this->thread, NULL, consumer_thread, this ); + } + + return 0; +} + +int consumer_stop( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + if ( this->joined == 0 ) + { + // Kill the thread and clean up + this->joined = 1; + this->running = 0; + pthread_join( this->thread, NULL ); + + // internal cleanup + if ( this->sdl_overlay != NULL ) + SDL_FreeYUVOverlay( this->sdl_overlay ); + this->sdl_overlay = NULL; + + if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "audio_off" ) ) + { + pthread_mutex_lock( &this->audio_mutex ); + pthread_cond_broadcast( &this->audio_cond ); + pthread_mutex_unlock( &this->audio_mutex ); + SDL_QuitSubSystem( SDL_INIT_AUDIO ); + } + + if ( mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" ) == 0 ) + SDL_Quit( ); + + this->sdl_screen = NULL; + } + + return 0; +} + +int consumer_is_stopped( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + return !this->running; +} + +static int sdl_lock_display( ) +{ + SDL_Surface *screen = SDL_GetVideoSurface( ); + return screen != NULL && ( !SDL_MUSTLOCK( screen ) || SDL_LockSurface( screen ) >= 0 ); +} + +static void sdl_unlock_display( ) +{ + SDL_Surface *screen = SDL_GetVideoSurface( ); + if ( screen != NULL && SDL_MUSTLOCK( screen ) ) + SDL_UnlockSurface( screen ); +} + +static void sdl_fill_audio( void *udata, uint8_t *stream, int len ) +{ + consumer_sdl this = udata; + + // Get the volume + double volume = mlt_properties_get_double( this->properties, "volume" ); + + pthread_mutex_lock( &this->audio_mutex ); + + // Block until audio received + while ( this->running && len > this->audio_avail ) + pthread_cond_wait( &this->audio_cond, &this->audio_mutex ); + + if ( this->audio_avail >= len ) + { + // Place in the audio buffer + if ( volume != 1.0 ) + SDL_MixAudio( stream, this->audio_buffer, len, ( int )( ( float )SDL_MIX_MAXVOLUME * volume ) ); + else + memcpy( stream, this->audio_buffer, len ); + + // Remove len from the audio available + this->audio_avail -= len; + + // Remove the samples + memmove( this->audio_buffer, this->audio_buffer + len, this->audio_avail ); + } + else + { + // Just to be safe, wipe the stream first + memset( stream, 0, len ); + + // Mix the audio + SDL_MixAudio( stream, this->audio_buffer, len, ( int )( ( float )SDL_MIX_MAXVOLUME * volume ) ); + + // No audio left + this->audio_avail = 0; + } + + // We're definitely playing now + this->playing = 1; + + pthread_cond_broadcast( &this->audio_cond ); + pthread_mutex_unlock( &this->audio_mutex ); +} + +static int consumer_play_audio( consumer_sdl this, mlt_frame frame, int init_audio, int *duration ) +{ + // Get the properties of this consumer + mlt_properties properties = this->properties; + mlt_audio_format afmt = mlt_audio_pcm; + + // Set the preferred params of the test card signal + int channels = mlt_properties_get_int( properties, "channels" ); + int frequency = mlt_properties_get_int( properties, "frequency" ); + static int counter = 0; + + int samples = mlt_sample_calculator( mlt_properties_get_double( this->properties, "fps" ), frequency, counter++ ); + + int16_t *pcm; + int bytes; + + mlt_frame_get_audio( frame, &pcm, &afmt, &frequency, &channels, &samples ); + *duration = ( ( samples * 1000 ) / frequency ); + + if ( mlt_properties_get_int( properties, "audio_off" ) ) + { + this->playing = 1; + init_audio = 1; + return init_audio; + } + + if ( init_audio == 1 ) + { + SDL_AudioSpec request; + SDL_AudioSpec got; + + int audio_buffer = mlt_properties_get_int( properties, "audio_buffer" ); + + // specify audio format + memset( &request, 0, sizeof( SDL_AudioSpec ) ); + this->playing = 0; + request.freq = frequency; + request.format = AUDIO_S16SYS; + request.channels = channels; + request.samples = audio_buffer; + request.callback = sdl_fill_audio; + request.userdata = (void *)this; + if ( SDL_OpenAudio( &request, &got ) != 0 ) + { + fprintf( stderr, "SDL failed to open audio: %s\n", SDL_GetError() ); + init_audio = 2; + } + else if ( got.size != 0 ) + { + SDL_PauseAudio( 0 ); + init_audio = 0; + } + } + + if ( init_audio == 0 ) + { + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + bytes = ( samples * channels * 2 ); + pthread_mutex_lock( &this->audio_mutex ); + while ( this->running && bytes > ( sizeof( this->audio_buffer) - this->audio_avail ) ) + pthread_cond_wait( &this->audio_cond, &this->audio_mutex ); + if ( this->running ) + { + if ( mlt_properties_get_double( properties, "_speed" ) == 1 ) + memcpy( &this->audio_buffer[ this->audio_avail ], pcm, bytes ); + else + memset( &this->audio_buffer[ this->audio_avail ], 0, bytes ); + this->audio_avail += bytes; + } + pthread_cond_broadcast( &this->audio_cond ); + pthread_mutex_unlock( &this->audio_mutex ); + } + else + { + this->playing = 1; + } + + return init_audio; +} + +static int consumer_play_video( consumer_sdl this, mlt_frame frame ) +{ + // Get the properties of this consumer + mlt_properties properties = this->properties; + + mlt_image_format vfmt = mlt_image_yuv422; + int width = this->width, height = this->height; + uint8_t *image; + int changed = 0; + + int video_off = mlt_properties_get_int( properties, "video_off" ); + int preview_off = mlt_properties_get_int( properties, "preview_off" ); + mlt_image_format preview_format = mlt_properties_get_int( properties, "preview_format" ); + int display_off = video_off | preview_off; + + if ( this->running && display_off == 0 ) + { + // Get the image, width and height + mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "format", vfmt ); + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + + // Handle events + if ( this->sdl_screen != NULL ) + { + SDL_Event event; + + sdl_lock_display( ); + changed = consumer_get_dimensions( &this->window_width, &this->window_height ); + sdl_unlock_display( ); + + while ( SDL_PollEvent( &event ) ) + { + mlt_events_fire( this->properties, "consumer-sdl-event", &event, NULL ); + + switch( event.type ) + { + case SDL_VIDEORESIZE: + this->window_width = event.resize.w; + this->window_height = event.resize.h; + changed = 1; + break; + case SDL_QUIT: + this->running = 0; + break; + case SDL_KEYDOWN: + { + mlt_producer producer = mlt_properties_get_data( properties, "transport_producer", NULL ); + char keyboard[ 2 ] = " "; + void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( properties, "transport_callback", NULL ); + if ( callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 ) + { + keyboard[ 0 ] = ( char )event.key.keysym.unicode; + callback( producer, keyboard ); + } + } + break; + } + } + } + + sdl_lock_display(); + + if ( width != this->width || height != this->height ) + { + if ( this->sdl_overlay != NULL ) + SDL_FreeYUVOverlay( this->sdl_overlay ); + this->sdl_overlay = NULL; + } + + if ( this->running && ( this->sdl_screen == NULL || changed ) ) + { + // Force an overlay recreation + if ( this->sdl_overlay != NULL ) + SDL_FreeYUVOverlay( this->sdl_overlay ); + this->sdl_overlay = NULL; + + // open SDL window with video overlay, if possible + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, this->bpp, this->sdl_flags ); + if ( consumer_get_dimensions( &this->window_width, &this->window_height ) ) + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, this->bpp, this->sdl_flags ); + } + + if ( this->running ) + { + // Determine window's new display aspect ratio + double this_aspect = ( double )this->window_width / this->window_height; + + // Get the display aspect ratio + double display_ratio = mlt_properties_get_double( properties, "display_ratio" ); + + // Determine frame's display aspect ratio + double frame_aspect = mlt_frame_get_aspect_ratio( frame ) * width / height; + + // Store the width and height received + this->width = width; + this->height = height; + + // If using hardware scaler + if ( mlt_properties_get( properties, "rescale" ) != NULL && + !strcmp( mlt_properties_get( properties, "rescale" ), "none" ) ) + { + // Use hardware scaler to normalise display aspect ratio + this->rect.w = frame_aspect / this_aspect * this->window_width; + this->rect.h = this->window_height; + if ( this->rect.w > this->window_width ) + { + this->rect.w = this->window_width; + this->rect.h = this_aspect / frame_aspect * this->window_height; + } + } + // Special case optimisation to negate odd effect of sample aspect ratio + // not corresponding exactly with image resolution. + else if ( (int)( this_aspect * 1000 ) == (int)( display_ratio * 1000 ) ) + { + this->rect.w = this->window_width; + this->rect.h = this->window_height; + } + // Use hardware scaler to normalise sample aspect ratio + else if ( this->window_height * display_ratio > this->window_width ) + { + this->rect.w = this->window_width; + this->rect.h = this->window_width / display_ratio; + } + else + { + this->rect.w = this->window_height * display_ratio; + this->rect.h = this->window_height; + } + + this->rect.x = ( this->window_width - this->rect.w ) / 2; + this->rect.y = ( this->window_height - this->rect.h ) / 2; + this->rect.x -= this->rect.x % 2; + + mlt_properties_set_int( this->properties, "rect_x", this->rect.x ); + mlt_properties_set_int( this->properties, "rect_y", this->rect.y ); + mlt_properties_set_int( this->properties, "rect_w", this->rect.w ); + mlt_properties_set_int( this->properties, "rect_h", this->rect.h ); + + SDL_SetClipRect( this->sdl_screen, &this->rect ); + } + + if ( this->running && this->sdl_screen != NULL && this->sdl_overlay == NULL ) + { + SDL_SetClipRect( this->sdl_screen, &this->rect ); + this->sdl_overlay = SDL_CreateYUVOverlay( width, height, SDL_YUY2_OVERLAY, this->sdl_screen ); + } + + if ( this->running && this->sdl_screen != NULL && this->sdl_overlay != NULL ) + { + this->buffer = this->sdl_overlay->pixels[ 0 ]; + if ( SDL_LockYUVOverlay( this->sdl_overlay ) >= 0 ) + { + if ( image != NULL ) + memcpy( this->buffer, image, width * height * 2 ); + SDL_UnlockYUVOverlay( this->sdl_overlay ); + SDL_DisplayYUVOverlay( this->sdl_overlay, &this->sdl_screen->clip_rect ); + } + } + + sdl_unlock_display(); + } + else if ( this->running ) + { + vfmt = preview_format == mlt_image_none ? mlt_image_rgb24a : preview_format; + if ( !video_off ) + mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "format", vfmt ); + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + } + + return 0; +} + +static void *video_thread( void *arg ) +{ + // Identify the arg + consumer_sdl this = arg; + + // Obtain time of thread start + struct timeval now; + int64_t start = 0; + int64_t elapsed = 0; + struct timespec tm; + mlt_frame next = NULL; + mlt_properties properties = NULL; + double speed = 0; + + // Get real time flag + int real_time = mlt_properties_get_int( this->properties, "real_time" ); + + // Get the current time + gettimeofday( &now, NULL ); + + // Determine start time + start = ( int64_t )now.tv_sec * 1000000 + now.tv_usec; + + while ( this->running ) + { + // Pop the next frame + pthread_mutex_lock( &this->video_mutex ); + next = mlt_deque_pop_front( this->queue ); + while ( next == NULL && this->running ) + { + pthread_cond_wait( &this->video_cond, &this->video_mutex ); + next = mlt_deque_pop_front( this->queue ); + } + pthread_mutex_unlock( &this->video_mutex ); + + if ( !this->running || next == NULL ) break; + + // Get the properties + properties = MLT_FRAME_PROPERTIES( next ); + + // Get the speed of the frame + speed = mlt_properties_get_double( properties, "_speed" ); + + // Get the current time + gettimeofday( &now, NULL ); + + // Get the elapsed time + elapsed = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - start; + + // See if we have to delay the display of the current frame + if ( mlt_properties_get_int( properties, "rendered" ) == 1 && this->running ) + { + // Obtain the scheduled playout time + int64_t scheduled = mlt_properties_get_int( properties, "playtime" ); + + // Determine the difference between the elapsed time and the scheduled playout time + int64_t difference = scheduled - elapsed; + + // Smooth playback a bit + if ( real_time && ( difference > 20000 && speed == 1.0 ) ) + { + tm.tv_sec = difference / 1000000; + tm.tv_nsec = ( difference % 1000000 ) * 500; + nanosleep( &tm, NULL ); + } + + // Show current frame if not too old + if ( !real_time || ( difference > -10000 || speed != 1.0 || mlt_deque_count( this->queue ) < 2 ) ) + consumer_play_video( this, next ); + + // If the queue is empty, recalculate start to allow build up again + if ( real_time && ( mlt_deque_count( this->queue ) == 0 && speed == 1.0 ) ) + { + gettimeofday( &now, NULL ); + start = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - scheduled + 20000; + } + } + + // This frame can now be closed + mlt_frame_close( next ); + next = NULL; + } + + if ( next != NULL ) + mlt_frame_close( next ); + + mlt_consumer_stopped( &this->parent ); + + return NULL; +} + +/** Threaded wrapper for pipe. +*/ + +static void *consumer_thread( void *arg ) +{ + // Identify the arg + consumer_sdl this = arg; + + // Get the consumer + mlt_consumer consumer = &this->parent; + + // Convenience functionality + int terminate_on_pause = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "terminate_on_pause" ); + int terminated = 0; + + // Video thread + pthread_t thread; + + // internal intialization + int init_audio = 1; + int init_video = 1; + mlt_frame frame = NULL; + mlt_properties properties = NULL; + int duration = 0; + int64_t playtime = 0; + struct timespec tm = { 0, 100000 }; + + // Loop until told not to + while( !terminated && this->running ) + { + // Get a frame from the attached producer + frame = mlt_consumer_rt_frame( consumer ); + + // Check for termination + if ( terminate_on_pause && frame != NULL ) + terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0; + + // Ensure that we have a frame + if ( frame != NULL ) + { + // Get the frame properties + properties = MLT_FRAME_PROPERTIES( frame ); + + // Play audio + init_audio = consumer_play_audio( this, frame, init_audio, &duration ); + + // Determine the start time now + if ( this->playing && init_video ) + { + // Create the video thread + pthread_create( &thread, NULL, video_thread, this ); + + // Video doesn't need to be initialised any more + init_video = 0; + } + + // Set playtime for this frame + mlt_properties_set_int( properties, "playtime", playtime ); + + while ( this->running && mlt_deque_count( this->queue ) > 15 ) + nanosleep( &tm, NULL ); + + // Push this frame to the back of the queue + pthread_mutex_lock( &this->video_mutex ); + mlt_deque_push_back( this->queue, frame ); + pthread_cond_broadcast( &this->video_cond ); + pthread_mutex_unlock( &this->video_mutex ); + + // Calculate the next playtime + playtime += ( duration * 1000 ); + } + } + + this->running = 0; + + // Kill the video thread + if ( init_video == 0 ) + { + pthread_mutex_lock( &this->video_mutex ); + pthread_cond_broadcast( &this->video_cond ); + pthread_mutex_unlock( &this->video_mutex ); + pthread_join( thread, NULL ); + } + + while( mlt_deque_count( this->queue ) ) + mlt_frame_close( mlt_deque_pop_back( this->queue ) ); + + this->sdl_screen = NULL; + this->audio_avail = 0; + + return NULL; +} + +static int consumer_get_dimensions( int *width, int *height ) +{ + int changed = 0; + + // SDL windows manager structure + SDL_SysWMinfo wm; + + // Specify the SDL Version + SDL_VERSION( &wm.version ); + + // Lock the display + //sdl_lock_display(); + +#ifndef __DARWIN__ + // Get the wm structure + if ( SDL_GetWMInfo( &wm ) == 1 ) + { + // Check that we have the X11 wm + if ( wm.subsystem == SDL_SYSWM_X11 ) + { + // Get the SDL window + Window window = wm.info.x11.window; + + // Get the display session + Display *display = wm.info.x11.display; + + // Get the window attributes + XWindowAttributes attr; + XGetWindowAttributes( display, window, &attr ); + + // Determine whether window has changed + changed = *width != attr.width || *height != attr.height; + + // Return width and height + *width = attr.width; + *height = attr.height; + } + } +#endif + + // Unlock the display + //sdl_unlock_display(); + + return changed; +} + +/** Callback to allow override of the close method. +*/ + +static void consumer_close( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + // Stop the consumer + ///mlt_consumer_stop( parent ); + + // Now clean up the rest + mlt_consumer_close( parent ); + + // Close the queue + mlt_deque_close( this->queue ); + + // Destroy mutexes + pthread_mutex_destroy( &this->audio_mutex ); + pthread_cond_destroy( &this->audio_cond ); + + // Finally clean up this + free( this ); +} diff --git a/src/modules/sdl/consumer_sdl.h b/src/modules/sdl/consumer_sdl.h new file mode 100644 index 0000000..355f4d4 --- /dev/null +++ b/src/modules/sdl/consumer_sdl.h @@ -0,0 +1,30 @@ +/* + * consumer_sdl.h -- A Simple DirectMedia Layer consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_SDL_H_ +#define _CONSUMER_SDL_H_ + +#include + +extern mlt_consumer consumer_sdl_init( char * ); +extern mlt_consumer consumer_sdl_still_init( char * ); +extern mlt_consumer consumer_sdl_preview_init( char * ); + +#endif diff --git a/src/modules/sdl/consumer_sdl_osx_hack.h b/src/modules/sdl/consumer_sdl_osx_hack.h new file mode 100644 index 0000000..15a8c9d --- /dev/null +++ b/src/modules/sdl/consumer_sdl_osx_hack.h @@ -0,0 +1,37 @@ +/* + * Purpose: A dummy thread object to inform Cocoa that it needs to be thread safe. + * Author: Zachary Drew + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#import + +@interface DummyThread : NSObject +- init; +- (void)startThread:(id)arg; +@end + +@implementation DummyThread +- init +{ + [super init]; + return self; +} +- (void)startThread:(id)arg +{ + return; +} +@end diff --git a/src/modules/sdl/consumer_sdl_preview.c b/src/modules/sdl/consumer_sdl_preview.c new file mode 100644 index 0000000..4ef4767 --- /dev/null +++ b/src/modules/sdl/consumer_sdl_preview.c @@ -0,0 +1,423 @@ +/* + * consumer_sdl_preview.c -- A Simple DirectMedia Layer consumer + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_sdl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct consumer_sdl_s *consumer_sdl; + +struct consumer_sdl_s +{ + struct mlt_consumer_s parent; + mlt_consumer active; + int ignore_change; + mlt_consumer play; + mlt_consumer still; + pthread_t thread; + int joined; + int running; + int sdl_flags; + double last_speed; + + pthread_cond_t refresh_cond; + pthread_mutex_t refresh_mutex; + int refresh_count; +}; + +/** Forward references to static functions. +*/ + +static int consumer_start( mlt_consumer parent ); +static int consumer_stop( mlt_consumer parent ); +static int consumer_is_stopped( mlt_consumer parent ); +static void consumer_close( mlt_consumer parent ); +static void *consumer_thread( void * ); +static void consumer_frame_show_cb( mlt_consumer sdl, mlt_consumer this, mlt_frame frame ); +static void consumer_sdl_event_cb( mlt_consumer sdl, mlt_consumer this, SDL_Event *event ); +static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer this, char *name ); + +mlt_consumer consumer_sdl_preview_init( char *arg ) +{ + consumer_sdl this = calloc( sizeof( struct consumer_sdl_s ), 1 ); + if ( this != NULL && mlt_consumer_init( &this->parent, this ) == 0 ) + { + // Get the parent consumer object + mlt_consumer parent = &this->parent; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent ); + + // Get the width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + + // Process actual param + if ( arg == NULL || sscanf( arg, "%dx%d", &width, &height ) == 2 ) + { + mlt_properties_set_int( properties, "width", width ); + mlt_properties_set_int( properties, "height", height ); + } + + // Create child consumers + this->play = mlt_factory_consumer( "sdl", arg ); + this->still = mlt_factory_consumer( "sdl_still", arg ); + mlt_properties_set( MLT_CONSUMER_PROPERTIES( parent ), "real_time", "0" ); + mlt_properties_set( MLT_CONSUMER_PROPERTIES( parent ), "rescale", "nearest" ); + parent->close = consumer_close; + parent->start = consumer_start; + parent->stop = consumer_stop; + parent->is_stopped = consumer_is_stopped; + this->joined = 1; + mlt_events_listen( MLT_CONSUMER_PROPERTIES( this->play ), this, "consumer-frame-show", ( mlt_listener )consumer_frame_show_cb ); + mlt_events_listen( MLT_CONSUMER_PROPERTIES( this->still ), this, "consumer-frame-show", ( mlt_listener )consumer_frame_show_cb ); + mlt_events_listen( MLT_CONSUMER_PROPERTIES( this->play ), this, "consumer-sdl-event", ( mlt_listener )consumer_sdl_event_cb ); + mlt_events_listen( MLT_CONSUMER_PROPERTIES( this->still ), this, "consumer-sdl-event", ( mlt_listener )consumer_sdl_event_cb ); + pthread_cond_init( &this->refresh_cond, NULL ); + pthread_mutex_init( &this->refresh_mutex, NULL ); + mlt_events_listen( MLT_CONSUMER_PROPERTIES( parent ), this, "property-changed", ( mlt_listener )consumer_refresh_cb ); + return parent; + } + free( this ); + return NULL; +} + +void consumer_frame_show_cb( mlt_consumer sdl, mlt_consumer parent, mlt_frame frame ) +{ + consumer_sdl this = parent->child; + this->last_speed = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ); + mlt_events_fire( MLT_CONSUMER_PROPERTIES( parent ), "consumer-frame-show", frame, NULL ); +} + +static void consumer_sdl_event_cb( mlt_consumer sdl, mlt_consumer parent, SDL_Event *event ) +{ + mlt_events_fire( MLT_CONSUMER_PROPERTIES( parent ), "consumer-sdl-event", event, NULL ); +} + +static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer parent, char *name ) +{ + if ( !strcmp( name, "refresh" ) ) + { + consumer_sdl this = parent->child; + pthread_mutex_lock( &this->refresh_mutex ); + this->refresh_count = this->refresh_count <= 0 ? 1 : this->refresh_count ++; + pthread_cond_broadcast( &this->refresh_cond ); + pthread_mutex_unlock( &this->refresh_mutex ); + } +} + +static int consumer_start( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + + if ( !this->running ) + { + // properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent ); + mlt_properties play = MLT_CONSUMER_PROPERTIES( this->play ); + mlt_properties still = MLT_CONSUMER_PROPERTIES( this->still ); + + char *window_id = mlt_properties_get( properties, "window_id" ); + char *audio_driver = mlt_properties_get( properties, "audio_driver" ); + char *video_driver = mlt_properties_get( properties, "video_driver" ); + char *audio_device = mlt_properties_get( properties, "audio_device" ); + char *output_display = mlt_properties_get( properties, "output_display" ); + int progressive = mlt_properties_get_int( properties, "progressive" ) | mlt_properties_get_int( properties, "deinterlace" ); + + consumer_stop( parent ); + + this->running = 1; + this->joined = 0; + this->last_speed = 1; + + if ( output_display != NULL ) + setenv( "DISPLAY", output_display, 1 ); + + if ( window_id != NULL ) + setenv( "SDL_WINDOWID", window_id, 1 ); + + if ( video_driver != NULL ) + setenv( "SDL_VIDEODRIVER", video_driver, 1 ); + + if ( audio_driver != NULL ) + setenv( "SDL_AUDIODRIVER", audio_driver, 1 ); + + if ( audio_device != NULL ) + setenv( "AUDIODEV", audio_device, 1 ); + + if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) + { + fprintf( stderr, "Failed to initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ); + SDL_EnableUNICODE( 1 ); + + // Pass properties down + mlt_properties_set_data( play, "transport_producer", mlt_properties_get_data( properties, "transport_producer", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( still, "transport_producer", mlt_properties_get_data( properties, "transport_producer", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( play, "transport_callback", mlt_properties_get_data( properties, "transport_callback", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( still, "transport_callback", mlt_properties_get_data( properties, "transport_callback", NULL ), 0, NULL, NULL ); + + mlt_properties_set_int( play, "progressive", progressive ); + mlt_properties_set_int( still, "progressive", progressive ); + + mlt_properties_pass_list( play, properties, "resize,rescale,width,height,aspect_ratio,display_ratio,volume" ); + mlt_properties_pass_list( still, properties, "resize,rescale,width,height,aspect_ratio,display_ratio" ); + mlt_properties_pass_list( play, properties, "deinterlace_method" ); + mlt_properties_pass_list( still, properties, "deinterlace_method" ); + mlt_properties_pass_list( play, properties, "preview_off,preview_format" ); + mlt_properties_pass_list( still, properties, "preview_off,preview_format" ); + + mlt_properties_pass( play, properties, "play." ); + mlt_properties_pass( still, properties, "still." ); + + mlt_properties_set_data( play, "app_lock", mlt_properties_get_data( properties, "app_lock", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( still, "app_lock", mlt_properties_get_data( properties, "app_lock", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( play, "app_unlock", mlt_properties_get_data( properties, "app_unlock", NULL ), 0, NULL, NULL ); + mlt_properties_set_data( still, "app_unlock", mlt_properties_get_data( properties, "app_unlock", NULL ), 0, NULL, NULL ); + + mlt_properties_set_int( play, "put_mode", 1 ); + mlt_properties_set_int( still, "put_mode", 1 ); + + // Start the still producer just to initialise the gui + mlt_consumer_start( this->still ); + this->active = this->still; + + // Inform child consumers that we control the sdl + mlt_properties_set_int( play, "sdl_started", 1 ); + mlt_properties_set_int( still, "sdl_started", 1 ); + + pthread_create( &this->thread, NULL, consumer_thread, this ); + } + + return 0; +} + +static int consumer_stop( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + if ( this->joined == 0 ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent ); + int app_locked = mlt_properties_get_int( properties, "app_locked" ); + void ( *lock )( void ) = mlt_properties_get_data( properties, "app_lock", NULL ); + void ( *unlock )( void ) = mlt_properties_get_data( properties, "app_unlock", NULL ); + + if ( app_locked && unlock ) unlock( ); + + // Kill the thread and clean up + this->running = 0; + + pthread_mutex_lock( &this->refresh_mutex ); + pthread_cond_broadcast( &this->refresh_cond ); + pthread_mutex_unlock( &this->refresh_mutex ); + + pthread_join( this->thread, NULL ); + this->joined = 1; + + if ( app_locked && lock ) lock( ); + + SDL_Quit( ); + } + + return 0; +} + +static int consumer_is_stopped( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + return !this->running; +} + +static void *consumer_thread( void *arg ) +{ + // Identify the arg + consumer_sdl this = arg; + + // Get the consumer + mlt_consumer consumer = &this->parent; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + + // internal intialization + int first = 1; + mlt_frame frame = NULL; + int last_position = -1; + + // Determine if the application is dealing with the preview + int preview_off = mlt_properties_get_int( properties, "preview_off" ); + + this->refresh_count = 0; + + // Loop until told not to + while( this->running ) + { + // Get a frame from the attached producer + frame = mlt_consumer_get_frame( consumer ); + + // Ensure that we have a frame + if ( this->running && frame != NULL ) + { + // Get the speed of the frame + double speed = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ); + + // Lock during the operation + mlt_service_lock( MLT_CONSUMER_SERVICE( consumer ) ); + + // Get refresh request for the current frame + int refresh = mlt_properties_get_int( properties, "refresh" ); + + // Decrement refresh and clear changed + mlt_events_block( properties, properties ); + mlt_properties_set_int( properties, "refresh", 0 ); + mlt_events_unblock( properties, properties ); + + // Unlock after the operation + mlt_service_unlock( MLT_CONSUMER_SERVICE( consumer ) ); + + // Set the changed property on this frame for the benefit of still + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "refresh", refresh ); + + // Make sure the recipient knows that this frame isn't really rendered + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 0 ); + + // Optimisation to reduce latency + if ( speed == 1.0 ) + { + if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) ) + mlt_consumer_purge( this->play ); + last_position = mlt_frame_get_position( frame ); + } + else + { + //mlt_consumer_purge( this->play ); + last_position = -1; + } + + // If we're not the first frame and both consumers are stopped, then stop ourselves + if ( !first && mlt_consumer_is_stopped( this->play ) && mlt_consumer_is_stopped( this->still ) ) + { + this->running = 0; + mlt_frame_close( frame ); + } + // Allow a little grace time before switching consumers on speed changes + else if ( this->ignore_change -- > 0 && this->active != NULL && !mlt_consumer_is_stopped( this->active ) ) + { + mlt_consumer_put_frame( this->active, frame ); + } + // If we aren't playing normally, then use the still + else if ( speed != 1 ) + { + if ( !mlt_consumer_is_stopped( this->play ) ) + mlt_consumer_stop( this->play ); + if ( mlt_consumer_is_stopped( this->still ) ) + { + this->last_speed = speed; + this->active = this->still; + this->ignore_change = 0; + mlt_consumer_start( this->still ); + } + mlt_consumer_put_frame( this->still, frame ); + } + // Otherwise use the normal player + else + { + if ( !mlt_consumer_is_stopped( this->still ) ) + mlt_consumer_stop( this->still ); + if ( mlt_consumer_is_stopped( this->play ) ) + { + this->last_speed = speed; + this->active = this->play; + this->ignore_change = 25; + mlt_consumer_start( this->play ); + } + mlt_consumer_put_frame( this->play, frame ); + } + + // Copy the rectangle info from the active consumer + if ( this->running && preview_off == 0 ) + { + mlt_properties active = MLT_CONSUMER_PROPERTIES( this->active ); + mlt_service_lock( MLT_CONSUMER_SERVICE( consumer ) ); + mlt_properties_set_int( properties, "rect_x", mlt_properties_get_int( active, "rect_x" ) ); + mlt_properties_set_int( properties, "rect_y", mlt_properties_get_int( active, "rect_y" ) ); + mlt_properties_set_int( properties, "rect_w", mlt_properties_get_int( active, "rect_w" ) ); + mlt_properties_set_int( properties, "rect_h", mlt_properties_get_int( active, "rect_h" ) ); + mlt_service_unlock( MLT_CONSUMER_SERVICE( consumer ) ); + } + + if ( this->active == this->still ) + { + pthread_mutex_lock( &this->refresh_mutex ); + if ( this->running && speed == 0 && this->refresh_count <= 0 ) + pthread_cond_wait( &this->refresh_cond, &this->refresh_mutex ); + this->refresh_count --; + pthread_mutex_unlock( &this->refresh_mutex ); + } + + // We are definitely not waiting on the first frame any more + first = 0; + } + else + { + if ( frame ) mlt_frame_close( frame ); + mlt_consumer_put_frame( this->active, NULL ); + this->running = 0; + } + } + + if ( this->play ) mlt_consumer_stop( this->play ); + if ( this->still ) mlt_consumer_stop( this->still ); + + return NULL; +} + +/** Callback to allow override of the close method. +*/ + +static void consumer_close( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + // Stop the consumer + mlt_consumer_stop( parent ); + + // Close the child consumers + mlt_consumer_close( this->play ); + mlt_consumer_close( this->still ); + + // Now clean up the rest + mlt_consumer_close( parent ); + + // Finally clean up this + free( this ); +} diff --git a/src/modules/sdl/consumer_sdl_still.c b/src/modules/sdl/consumer_sdl_still.c new file mode 100644 index 0000000..c04481a --- /dev/null +++ b/src/modules/sdl/consumer_sdl_still.c @@ -0,0 +1,640 @@ +/* + * consumer_sdl_still.c -- A Simple DirectMedia Layer consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_sdl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** This classes definition. +*/ + +typedef struct consumer_sdl_s *consumer_sdl; + +struct consumer_sdl_s +{ + struct mlt_consumer_s parent; + mlt_properties properties; + pthread_t thread; + int joined; + int running; + int window_width; + int window_height; + int width; + int height; + int playing; + int sdl_flags; + SDL_Surface *sdl_screen; + SDL_Rect rect; + uint8_t *buffer; + int last_position; + mlt_producer last_producer; + int filtered; +}; + +/** Forward references to static functions. +*/ + +static int consumer_start( mlt_consumer parent ); +static int consumer_stop( mlt_consumer parent ); +static int consumer_is_stopped( mlt_consumer parent ); +static void consumer_close( mlt_consumer parent ); +static void *consumer_thread( void * ); +static int consumer_get_dimensions( int *width, int *height ); +static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); + +/** This is what will be called by the factory - anything can be passed in + via the argument, but keep it simple. +*/ + +mlt_consumer consumer_sdl_still_init( char *arg ) +{ + // Create the consumer object + consumer_sdl this = calloc( sizeof( struct consumer_sdl_s ), 1 ); + + // If no malloc'd and consumer init ok + if ( this != NULL && mlt_consumer_init( &this->parent, this ) == 0 ) + { + // Get the parent consumer object + mlt_consumer parent = &this->parent; + + // get a handle on properties + mlt_service service = MLT_CONSUMER_SERVICE( parent ); + this->properties = MLT_SERVICE_PROPERTIES( service ); + + // We have stuff to clean up, so override the close method + parent->close = consumer_close; + + // Default scaler (for now we'll use nearest) + mlt_properties_set( this->properties, "rescale", "nearest" ); + + // We're always going to run this in non-realtime mode + mlt_properties_set( this->properties, "real_time", "0" ); + + // Default progressive true + mlt_properties_set_int( this->properties, "progressive", 1 ); + + // Ensure we don't join on a non-running object + this->joined = 1; + + // process actual param + if ( arg == NULL || sscanf( arg, "%dx%d", &this->width, &this->height ) != 2 ) + { + this->width = mlt_properties_get_int( this->properties, "width" ); + this->height = mlt_properties_get_int( this->properties, "height" ); + } + else + { + mlt_properties_set_int( this->properties, "width", this->width ); + mlt_properties_set_int( this->properties, "height", this->height ); + } + + // Set the sdl flags + this->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_DOUBLEBUF; + + // Allow thread to be started/stopped + parent->start = consumer_start; + parent->stop = consumer_stop; + parent->is_stopped = consumer_is_stopped; + + // Register specific events + mlt_events_register( this->properties, "consumer-sdl-event", ( mlt_transmitter )consumer_sdl_event ); + + // Return the consumer produced + return parent; + } + + // malloc or consumer init failed + free( this ); + + // Indicate failure + return NULL; +} + +static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( SDL_Event * )args[ 0 ] ); +} + +static int consumer_start( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + + if ( !this->running ) + { + int preview_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "preview_off" ); + int sdl_started = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" ); + + // Attach a colour space converter + if ( !this->filtered ) + { + mlt_filter filter = mlt_factory_filter( "avcolour_space", NULL ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "forced", mlt_image_yuv422 ); + mlt_service_attach( MLT_CONSUMER_SERVICE( parent ), filter ); + mlt_filter_close( filter ); + this->filtered = 1; + } + + consumer_stop( parent ); + + this->last_position = -1; + this->running = 1; + this->joined = 0; + + // Allow the user to force resizing to window size + this->width = mlt_properties_get_int( this->properties, "width" ); + this->height = mlt_properties_get_int( this->properties, "height" ); + + // Default window size + double display_ratio = mlt_properties_get_double( this->properties, "display_ratio" ); + this->window_width = ( double )this->height * display_ratio; + this->window_height = this->height; + + if ( sdl_started == 0 && preview_off == 0 ) + { + if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) + { + fprintf( stderr, "Failed to initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ); + SDL_EnableUNICODE( 1 ); + } + else if ( preview_off == 0 ) + { + if ( SDL_GetVideoSurface( ) != NULL ) + { + this->sdl_screen = SDL_GetVideoSurface( ); + } + } + + if ( this->sdl_screen == NULL && preview_off == 0 ) + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags ); + + pthread_create( &this->thread, NULL, consumer_thread, this ); + } + + return 0; +} + +static int consumer_stop( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + if ( this->joined == 0 ) + { + int preview_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "preview_off" ); + int sdl_started = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" ); + + // Kill the thread and clean up + this->running = 0; + + pthread_join( this->thread, NULL ); + this->joined = 1; + + if ( sdl_started == 0 && preview_off == 0 ) + SDL_Quit( ); + + this->sdl_screen = NULL; + } + + return 0; +} + +static int consumer_is_stopped( mlt_consumer parent ) +{ + consumer_sdl this = parent->child; + return !this->running; +} + +static int sdl_lock_display( ) +{ + SDL_Surface *screen = SDL_GetVideoSurface( ); + return screen != NULL && ( !SDL_MUSTLOCK( screen ) || SDL_LockSurface( screen ) >= 0 ); +} + +static void sdl_unlock_display( ) +{ + SDL_Surface *screen = SDL_GetVideoSurface( ); + if ( screen != NULL && SDL_MUSTLOCK( screen ) ) + SDL_UnlockSurface( screen ); +} + +static inline void display_1( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height ) +{ + // Generate the affine transform scaling values + int scale_width = ( width << 16 ) / rect.w; + int scale_height = ( height << 16 ) / rect.h; + int stride = width * 3; + int x, y, row_index; + uint8_t *q, *row; + + // Constants defined for clarity and optimisation + int scanlength = screen->pitch; + uint8_t *start = ( uint8_t * )screen->pixels + rect.y * scanlength + rect.x; + uint8_t *p; + + // Iterate through the screen using a very basic scaling algorithm + for ( y = 0; y < rect.h; y ++ ) + { + p = start; + row_index = ( 32768 + scale_height * y ) >> 16; + row = image + stride * row_index; + for ( x = 0; x < rect.w; x ++ ) + { + q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 ); + *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) ); + } + start += scanlength; + } +} + +static inline void display_2( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height ) +{ + // Generate the affine transform scaling values + int scale_width = ( width << 16 ) / rect.w; + int scale_height = ( height << 16 ) / rect.h; + int stride = width * 3; + int x, y, row_index; + uint8_t *q, *row; + + // Constants defined for clarity and optimisation + int scanlength = screen->pitch / 2; + uint16_t *start = ( uint16_t * )screen->pixels + rect.y * scanlength + rect.x; + uint16_t *p; + + // Iterate through the screen using a very basic scaling algorithm + for ( y = 0; y < rect.h; y ++ ) + { + p = start; + row_index = ( 32768 + scale_height * y ) >> 16; + row = image + stride * row_index; + for ( x = 0; x < rect.w; x ++ ) + { + q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 ); + *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) ); + } + start += scanlength; + } +} + +static inline void display_3( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height ) +{ + // Generate the affine transform scaling values + int scale_width = ( width << 16 ) / rect.w; + int scale_height = ( height << 16 ) / rect.h; + int stride = width * 3; + int x, y, row_index; + uint8_t *q, *row; + + // Constants defined for clarity and optimisation + int scanlength = screen->pitch; + uint8_t *start = ( uint8_t * )screen->pixels + rect.y * scanlength + rect.x; + uint8_t *p; + uint32_t pixel; + + // Iterate through the screen using a very basic scaling algorithm + for ( y = 0; y < rect.h; y ++ ) + { + p = start; + row_index = ( 32768 + scale_height * y ) >> 16; + row = image + stride * row_index; + for ( x = 0; x < rect.w; x ++ ) + { + q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 ); + pixel = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) ); + *p ++ = (pixel & 0xFF0000) >> 16; + *p ++ = (pixel & 0x00FF00) >> 8; + *p ++ = (pixel & 0x0000FF); + } + start += scanlength; + } +} + +static inline void display_4( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height ) +{ + // Generate the affine transform scaling values + int scale_width = ( width << 16 ) / rect.w; + int scale_height = ( height << 16 ) / rect.h; + int stride = width * 3; + int x, y, row_index; + uint8_t *q, *row; + + // Constants defined for clarity and optimisation + int scanlength = screen->pitch / 4; + uint32_t *start = ( uint32_t * )screen->pixels + rect.y * scanlength + rect.x; + uint32_t *p; + + // Iterate through the screen using a very basic scaling algorithm + for ( y = 0; y < rect.h; y ++ ) + { + p = start; + row_index = ( 32768 + scale_height * y ) >> 16; + row = image + stride * row_index; + for ( x = 0; x < rect.w; x ++ ) + { + q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 ); + *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) ); + } + start += scanlength; + } +} + +static int consumer_play_video( consumer_sdl this, mlt_frame frame ) +{ + // Get the properties of this consumer + mlt_properties properties = this->properties; + + mlt_image_format vfmt = mlt_image_rgb24; + int height = this->height; + int width = this->width; + uint8_t *image = NULL; + int changed = 0; + double display_ratio = mlt_properties_get_double( this->properties, "display_ratio" ); + + void ( *lock )( void ) = mlt_properties_get_data( properties, "app_lock", NULL ); + void ( *unlock )( void ) = mlt_properties_get_data( properties, "app_unlock", NULL ); + + if ( lock != NULL ) lock( ); + + sdl_lock_display(); + + // Handle events + if ( this->sdl_screen != NULL ) + { + SDL_Event event; + + changed = consumer_get_dimensions( &this->window_width, &this->window_height ); + + while ( SDL_PollEvent( &event ) ) + { + mlt_events_fire( this->properties, "consumer-sdl-event", &event, NULL ); + + switch( event.type ) + { + case SDL_VIDEORESIZE: + this->window_width = event.resize.w; + this->window_height = event.resize.h; + changed = 1; + break; + case SDL_QUIT: + this->running = 0; + break; + case SDL_KEYDOWN: + { + mlt_producer producer = mlt_properties_get_data( properties, "transport_producer", NULL ); + char keyboard[ 2 ] = " "; + void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( properties, "transport_callback", NULL ); + if ( callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 ) + { + keyboard[ 0 ] = ( char )event.key.keysym.unicode; + callback( producer, keyboard ); + } + } + break; + } + } + } + + if ( this->sdl_screen == NULL || changed ) + { + // open SDL window + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags ); + if ( consumer_get_dimensions( &this->window_width, &this->window_height ) ) + this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags ); + changed = 1; + } + else + { + changed = 1; + } + + if ( changed == 0 && + this->last_position == mlt_frame_get_position( frame ) && + this->last_producer == mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "_producer", NULL ) ) + { + sdl_unlock_display( ); + if ( unlock != NULL ) unlock( ); + return 0; + } + + // Update last frame shown info + this->last_position = mlt_frame_get_position( frame ); + this->last_producer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "_producer", NULL ); + + // Get the image, width and height + mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 ); + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + + if ( image != NULL ) + { + char *rescale = mlt_properties_get( properties, "rescale" ); + if ( rescale != NULL && strcmp( rescale, "none" ) ) + { + double this_aspect = display_ratio / ( ( double )this->window_width / ( double )this->window_height ); + this->rect.w = this_aspect * this->window_width; + this->rect.h = this->window_height; + if ( this->rect.w > this->window_width ) + { + this->rect.w = this->window_width; + this->rect.h = ( 1.0 / this_aspect ) * this->window_height; + } + } + else + { + double frame_aspect = mlt_frame_get_aspect_ratio( frame ) * width / height; + this->rect.w = frame_aspect * this->window_height; + this->rect.h = this->window_height; + if ( this->rect.w > this->window_width ) + { + this->rect.w = this->window_width; + this->rect.h = ( 1.0 / frame_aspect ) * this->window_width; + } + } + + this->rect.x = ( this->window_width - this->rect.w ) / 2; + this->rect.y = ( this->window_height - this->rect.h ) / 2; + + mlt_properties_set_int( this->properties, "rect_x", this->rect.x ); + mlt_properties_set_int( this->properties, "rect_y", this->rect.y ); + mlt_properties_set_int( this->properties, "rect_w", this->rect.w ); + mlt_properties_set_int( this->properties, "rect_h", this->rect.h ); + } + + if ( !mlt_consumer_is_stopped( &this->parent ) && SDL_GetVideoSurface( ) != NULL && this->sdl_screen != NULL && this->sdl_screen->pixels != NULL ) + { + memset( this->sdl_screen->pixels, 0, this->window_width * this->window_height * this->sdl_screen->format->BytesPerPixel ); + + switch( this->sdl_screen->format->BytesPerPixel ) + { + case 1: + display_1( this->sdl_screen, this->rect, image, width, height ); + break; + case 2: + display_2( this->sdl_screen, this->rect, image, width, height ); + break; + case 3: + display_3( this->sdl_screen, this->rect, image, width, height ); + break; + case 4: + display_4( this->sdl_screen, this->rect, image, width, height ); + break; + default: + fprintf( stderr, "Unsupported video depth %d\n", this->sdl_screen->format->BytesPerPixel ); + break; + } + + // Flip it into sight + SDL_Flip( this->sdl_screen ); + } + + sdl_unlock_display(); + + if ( unlock != NULL ) unlock( ); + + return 1; +} + +/** Threaded wrapper for pipe. +*/ + +static void *consumer_thread( void *arg ) +{ + // Identify the arg + consumer_sdl this = arg; + + // Get the consumer + mlt_consumer consumer = &this->parent; + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + + // internal intialization + mlt_frame frame = NULL; + mlt_image_format vfmt = mlt_image_rgb24a; + int height = this->height; + int width = this->width; + uint8_t *image = NULL; + + // Allow the hosting app to provide the preview + int preview_off = mlt_properties_get_int( properties, "preview_off" ); + mlt_image_format preview_format = mlt_properties_get_int( properties, "preview_format" ); + + // Check if a specific colour space has been requested + if ( preview_off && preview_format != mlt_image_none ) + vfmt = preview_format; + + // Loop until told not to + while( this->running ) + { + // Get a frame from the attached producer + frame = mlt_consumer_rt_frame( consumer ); + + // Ensure that we have a frame + if ( this->running && frame != NULL ) + { + if ( preview_off == 0 ) + { + consumer_play_video( this, frame ); + } + else + { + mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "format", vfmt ); + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + } + mlt_frame_close( frame ); + } + else + { + if ( frame ) mlt_frame_close( frame ); + this->running = 0; + } + } + + return NULL; +} + +static int consumer_get_dimensions( int *width, int *height ) +{ + int changed = 0; + + // SDL windows manager structure + SDL_SysWMinfo wm; + + // Specify the SDL Version + SDL_VERSION( &wm.version ); + + // Get the wm structure + if ( SDL_GetWMInfo( &wm ) == 1 ) + { +#ifndef __DARWIN__ + // Check that we have the X11 wm + if ( wm.subsystem == SDL_SYSWM_X11 ) + { + // Get the SDL window + Window window = wm.info.x11.window; + + // Get the display session + Display *display = wm.info.x11.display; + + // Get the window attributes + XWindowAttributes attr; + XGetWindowAttributes( display, window, &attr ); + + // Determine whether window has changed + changed = *width != attr.width || *height != attr.height; + + // Return width and height + *width = attr.width; + *height = attr.height; + } +#endif + } + + return changed; +} + +/** Callback to allow override of the close method. +*/ + +static void consumer_close( mlt_consumer parent ) +{ + // Get the actual object + consumer_sdl this = parent->child; + + // Stop the consumer + mlt_consumer_stop( parent ); + + // Now clean up the rest + mlt_consumer_close( parent ); + + // Finally clean up this + free( this ); +} diff --git a/src/modules/sdl/factory.c b/src/modules/sdl/factory.c new file mode 100644 index 0000000..144e345 --- /dev/null +++ b/src/modules/sdl/factory.c @@ -0,0 +1,58 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "consumer_sdl.h" + +#ifdef WITH_SDL_IMAGE +#include "producer_sdl_image.h" +#endif + +void *mlt_create_producer( char *id, void *arg ) +{ +#ifdef WITH_SDL_IMAGE + if ( !strcmp( id, "sdl_image" ) ) + return producer_sdl_image_init( arg ); +#endif + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "sdl" ) ) + return consumer_sdl_init( arg ); + if ( !strcmp( id, "sdl_still" ) ) + return consumer_sdl_still_init( arg ); + if ( !strcmp( id, "sdl_preview" ) ) + return consumer_sdl_preview_init( arg ); + return NULL; +} + diff --git a/src/modules/sdl/producer_sdl_image.c b/src/modules/sdl/producer_sdl_image.c new file mode 100644 index 0000000..f34b915 --- /dev/null +++ b/src/modules/sdl/producer_sdl_image.c @@ -0,0 +1,245 @@ +/* + * producer_sdl_image.c -- Image loader which wraps SDL_image + * Copyright (C) 2005 Visual Media FX + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "producer_sdl_image.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int producer_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + SDL_Surface *surface = mlt_properties_get_data( properties, "surface", NULL ); + SDL_Surface *converted = NULL; + uint8_t *alpha; + + *width = surface->w; + *height = surface->h; + *format = mlt_image_yuv422; + *image = mlt_pool_alloc( *width * *height * 2 ); + alpha = mlt_pool_alloc( *width * *height ); + + if ( surface->format->BitsPerPixel != 32 && surface->format->BitsPerPixel != 24 ) + { + SDL_PixelFormat fmt; + fmt.BitsPerPixel = 24; + fmt.BytesPerPixel = 3; + fmt.Rshift = 16; + fmt.Gshift = 8; + fmt.Bshift = 0; + fmt.Rmask = 0xff << 16; + fmt.Gmask = 0xff << 8; + fmt.Bmask = 0xff; + converted = SDL_ConvertSurface( surface, &fmt, 0 ); + } + + switch( surface->format->BitsPerPixel ) + { + case 32: + mlt_convert_rgb24a_to_yuv422( surface->pixels, *width, *height, surface->pitch, *image, alpha ); + break; + case 24: + mlt_convert_rgb24_to_yuv422( surface->pixels, *width, *height, surface->pitch, *image ); + memset( alpha, 255, *width * *height ); + break; + default: + mlt_convert_rgb24_to_yuv422( converted->pixels, *width, *height, converted->pitch, *image ); + memset( alpha, 255, *width * *height ); + break; + } + + if ( converted ) + SDL_FreeSurface( converted ); + + // Update the frame + mlt_properties_set_data( properties, "image", *image, *width * *height * 2, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "alpha", alpha, *width * *height, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + + return 0; +} + +static int filter_files( const struct dirent *de ) +{ + return de->d_name[ 0 ] != '.'; +} + +static mlt_properties parse_file_names( char *resource ) +{ + mlt_properties properties = mlt_properties_new( ); + + if ( strstr( resource, "/.all." ) != NULL ) + { + char *dir_name = strdup( resource ); + char *extension = strrchr( resource, '.' ); + *( strstr( dir_name, "/.all." ) + 1 ) = '\0'; + char fullname[ 1024 ]; + strcpy( fullname, dir_name ); + struct dirent **de = NULL; + int n = scandir( fullname, &de, filter_files, alphasort ); + int i; + struct stat info; + + for (i = 0; i < n; i++ ) + { + snprintf( fullname, 1023, "%s%s", dir_name, de[i]->d_name ); + if ( strstr( fullname, extension ) && lstat( fullname, &info ) == 0 && + ( S_ISREG( info.st_mode ) || info.st_mode | S_IXUSR ) ) + { + char temp[ 20 ]; + sprintf( temp, "%d", i ); + mlt_properties_set( properties, temp, fullname ); + } + free( de[ i ] ); + } + + free( de ); + free( dir_name ); + } + else + { + mlt_properties_set( properties, "0", resource ); + } + + return properties; +} + +static SDL_Surface *load_image( mlt_producer producer ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + char *resource = mlt_properties_get( properties, "resource" ); + char *last_resource = mlt_properties_get( properties, "_last_resource" ); + int image_idx = 0; + char *this_resource = NULL; + double ttl = mlt_properties_get_int( properties, "ttl" ); + mlt_position position = mlt_producer_position( producer ); + SDL_Surface *surface = mlt_properties_get_data( properties, "_surface", NULL ); + mlt_properties filenames = mlt_properties_get_data( properties, "_filenames", NULL ); + + if ( filenames == NULL ) + { + filenames = parse_file_names( resource ); + mlt_properties_set_data( properties, "_surface", surface, 0, ( mlt_destructor )SDL_FreeSurface, 0 ); + } + + image_idx = ( int )floor( ( double )position / ttl ) % mlt_properties_count( filenames ); + this_resource = mlt_properties_get_value( filenames, image_idx ); + + if ( last_resource == NULL || strcmp( last_resource, this_resource ) ) + { + surface = IMG_Load( this_resource ); + if ( surface != NULL ) + { + surface->refcount ++; + mlt_properties_set_data( properties, "_surface", surface, 0, ( mlt_destructor )SDL_FreeSurface, 0 ); + mlt_properties_set( properties, "_last_resource", this_resource ); + } + } + else if ( surface != NULL ) + { + surface->refcount ++; + } + + return surface; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Generate a frame + *frame = mlt_frame_init( ); + + if ( *frame != NULL ) + { + // Create the surface for the current image + SDL_Surface *surface = load_image( producer ); + + if ( surface != NULL ) + { + // Obtain properties of frame and producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Obtain properties of producer + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Set producer-specific frame properties + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_props, "aspect_ratio" ) ); + mlt_properties_set_data( properties, "surface", surface, 0, ( mlt_destructor )SDL_FreeSurface, NULL ); + mlt_properties_set_int( properties, "real_width", surface->w ); + mlt_properties_set_int( properties, "real_height", surface->h ); + + // Push the get_image method + mlt_frame_push_get_image( *frame, producer_get_image ); + } + } + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer producer ) +{ + producer->close = NULL; + mlt_producer_close( producer ); + free( producer ); +} + +mlt_producer producer_sdl_image_init( char *file ) +{ + mlt_producer producer = calloc( 1, sizeof( struct mlt_producer_s ) ); + if ( producer != NULL && mlt_producer_init( producer, NULL ) == 0 ) + { + // Get the properties interface + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Callback registration + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + + // Set the default properties + mlt_properties_set( properties, "resource", file ); + mlt_properties_set( properties, "_resource", "" ); + mlt_properties_set_double( properties, "aspect_ratio", 1 ); + mlt_properties_set_int( properties, "ttl", 25 ); + mlt_properties_set_int( properties, "progressive", 1 ); + + return producer; + } + free( producer ); + return NULL; +} + + diff --git a/src/modules/sdl/producer_sdl_image.h b/src/modules/sdl/producer_sdl_image.h new file mode 100644 index 0000000..1f90914 --- /dev/null +++ b/src/modules/sdl/producer_sdl_image.h @@ -0,0 +1,28 @@ +/* + * producer_sdl_image.h -- Image loader which wraps SDL_image + * Copyright (C) 2005 Visual Media FX + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PRODUCER_SDL_IMAGE_H_ +#define PRODUCER_SDL_IMAGE_H_ + +#include + +extern mlt_producer producer_sdl_image_init( char *filename ); + +#endif diff --git a/src/modules/sox/Makefile b/src/modules/sox/Makefile new file mode 100644 index 0000000..03d521a --- /dev/null +++ b/src/modules/sox/Makefile @@ -0,0 +1,34 @@ +include ../../../config.mak +include config.mak + +TARGET = ../libmltsox$(LIBSUF) + +OBJS = factory.o \ + filter_sox.o + +CFLAGS += -I../../ + +LDFLAGS += -L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/sox/config.mak b/src/modules/sox/config.mak new file mode 100644 index 0000000..9a7d0a7 --- /dev/null +++ b/src/modules/sox/config.mak @@ -0,0 +1,2 @@ +CFLAGS += -DSOX14 -I/usr/include +LDFLAGS += -L/usr/lib -lsox -lsfx diff --git a/src/modules/sox/configure b/src/modules/sox/configure new file mode 100755 index 0000000..e306c0b --- /dev/null +++ b/src/modules/sox/configure @@ -0,0 +1,46 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + which libst-config > /dev/null 2>&1 + if [ $? -eq 0 ] + then + disable_sox=0 + echo "CFLAGS += $(libst-config --cflags) -I../../" > config.mak + echo "LDFLAGS += -lst $(libst-config --libs)" >> config.mak + else + sox --version 2> /dev/null | grep 'v14.' > /dev/null + disable_sox=$? + if [ $disable_sox -eq 0 ] + then + LIBDIR=lib + #bits=$(uname -m) + #case $bits in + #x86_64) + # export LIBDIR=lib64 + # ;; + #*) + # export LIBDIR=lib + # ;; + #esac + + sox=$(which sox) + # chop sox + soxdir=$(dirname $sox) + # chop bin + soxdir=$(dirname $soxdir) + echo "CFLAGS += -DSOX14 -I$soxdir/include" > config.mak + echo "LDFLAGS += -L$soxdir/$LIBDIR -lsox -lsfx" >> config.mak + fi + fi + + if [ "$disable_sox" = "0" ] + then + echo "sox libmltsox$LIBSUF" >> ../filters.dat + else + echo "- sox not found: disabling" + touch ../disable-sox + fi + +fi diff --git a/src/modules/sox/factory.c b/src/modules/sox/factory.c new file mode 100644 index 0000000..36856b0 --- /dev/null +++ b/src/modules/sox/factory.c @@ -0,0 +1,45 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "filter_sox.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "sox" ) ) + return filter_sox_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/sox/filter_sox.c b/src/modules/sox/filter_sox.c new file mode 100644 index 0000000..5162aeb --- /dev/null +++ b/src/modules/sox/filter_sox.c @@ -0,0 +1,457 @@ +/* + * filter_sox.c -- apply any number of SOX effects using libst + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_sox.h" + +#include +#include + +#include +#include +#include +#include + +#ifdef SOX14 +# include +# define ST_EOF SOX_EOF +# define ST_SUCCESS SOX_SUCCESS +# define st_sample_t sox_sample_t +# define eff_t sox_effect_t* +# define st_size_t sox_size_t +# define ST_LIB_VERSION_CODE SOX_LIB_VERSION_CODE +# define ST_LIB_VERSION SOX_LIB_VERSION +# define ST_SIGNED_WORD_TO_SAMPLE(d,clips) SOX_SIGNED_16BIT_TO_SAMPLE(d,clips) +# define ST_SSIZE_MIN SOX_SSIZE_MIN +# define ST_SAMPLE_TO_SIGNED_WORD(d,clips) SOX_SAMPLE_TO_SIGNED_16BIT(d,clips) +#else +# include +#endif + +#define BUFFER_LEN 8192 +#define AMPLITUDE_NORM 0.2511886431509580 /* -12dBFS */ +#define AMPLITUDE_MIN 0.00001 + +/** Compute the mean of a set of doubles skipping unset values flagged as -1 +*/ +static inline double mean( double *buf, int count ) +{ + double mean = 0; + int i; + int j = 0; + + for ( i = 0; i < count; i++ ) + { + if ( buf[ i ] != -1.0 ) + { + mean += buf[ i ]; + j ++; + } + } + if ( j > 0 ) + mean /= j; + + return mean; +} + +/** Create an effect state instance for a channels +*/ +static int create_effect( mlt_filter this, char *value, int count, int channel, int frequency ) +{ + mlt_tokeniser tokeniser = mlt_tokeniser_init(); +#ifdef SOX14 + eff_t eff = mlt_pool_alloc( sizeof( sox_effect_t ) ); +#else + eff_t eff = mlt_pool_alloc( sizeof( struct st_effect ) ); +#endif + char id[ 256 ]; + int error = 1; + + // Tokenise the effect specification + mlt_tokeniser_parse_new( tokeniser, value, " " ); + if ( tokeniser->count < 1 ) + return error; + + // Locate the effect +#ifdef SOX14 + //fprintf(stderr, "%s: effect %s count %d\n", __FUNCTION__, tokeniser->tokens[0], tokeniser->count ); + sox_create_effect( eff, sox_find_effect( tokeniser->tokens[0] ) ); + int opt_count = tokeniser->count - 1; +#else + int opt_count = st_geteffect_opt( eff, tokeniser->count, tokeniser->tokens ); +#endif + + // If valid effect + if ( opt_count != ST_EOF ) + { + // Supply the effect parameters +#ifdef SOX14 + if ( ( * eff->handler.getopts )( eff, opt_count, &tokeniser->tokens[ tokeniser->count > 1 ? 1 : 0 ] ) == ST_SUCCESS ) +#else + if ( ( * eff->h->getopts )( eff, opt_count, &tokeniser->tokens[ tokeniser->count - opt_count ] ) == ST_SUCCESS ) +#endif + { + // Set the sox signal parameters + eff->ininfo.rate = frequency; + eff->outinfo.rate = frequency; + eff->ininfo.channels = 1; + eff->outinfo.channels = 1; + + // Start the effect +#ifdef SOX14 + if ( ( * eff->handler.start )( eff ) == ST_SUCCESS ) +#else + if ( ( * eff->h->start )( eff ) == ST_SUCCESS ) +#endif + { + // Construct id + sprintf( id, "_effect_%d_%d", count, channel ); + + // Save the effect state + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), id, eff, 0, mlt_pool_release, NULL ); + error = 0; + } + } + } + // Some error occurred so delete the temp effect state + if ( error == 1 ) + mlt_pool_release( eff ); + + mlt_tokeniser_close( tokeniser ); + + return error; +} + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the properties + st_sample_t *input_buffer = mlt_properties_get_data( filter_properties, "input_buffer", NULL ); + st_sample_t *output_buffer = mlt_properties_get_data( filter_properties, "output_buffer", NULL ); + int channels_avail = *channels; + int i; // channel + int count = mlt_properties_get_int( filter_properties, "_effect_count" ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, &channels_avail, samples ); + + // Duplicate channels as necessary + if ( channels_avail < *channels ) + { + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + int j, k = 0; + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * channels_avail ) + k ]; + k = ( k + 1 ) % channels_avail; + } + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + else if ( channels_avail == 6 && *channels == 2 ) + { + // Nasty hack for ac3 5.1 audio - may be a cause of failure? + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + + // Drop all but the first *channels + for ( i = 0; i < *samples; i++ ) + { + new_buffer[ ( i * *channels ) + 0 ] = (*buffer)[ ( i * channels_avail ) + 2 ]; + new_buffer[ ( i * *channels ) + 1 ] = (*buffer)[ ( i * channels_avail ) + 3 ]; + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + + // Even though some effects are multi-channel aware, it is not reliable + // We must maintain a separate effect state for each channel + for ( i = 0; i < *channels; i++ ) + { + char id[ 256 ]; + sprintf( id, "_effect_0_%d", i ); + + // Get an existing effect state + eff_t e = mlt_properties_get_data( filter_properties, id, NULL ); + + // Validate the existing effect state + if ( e != NULL && ( e->ininfo.rate != *frequency || + e->outinfo.rate != *frequency ) ) + e = NULL; + + // (Re)Create the effect state + if ( e == NULL ) + { + int j = 0; + + // Reset the count + count = 0; + + // Loop over all properties + for ( j = 0; j < mlt_properties_count( filter_properties ); j ++ ) + { + // Get the name of this property + char *name = mlt_properties_get_name( filter_properties, j ); + + // If the name does not contain a . and matches effect + if ( !strncmp( name, "effect", 6 ) ) + { + // Get the effect specification + char *value = mlt_properties_get( filter_properties, name ); + + // Create an instance + if ( create_effect( filter, value, count, i, *frequency ) == 0 ) + count ++; + } + } + + // Save the number of filters + mlt_properties_set_int( filter_properties, "_effect_count", count ); + + } + if ( *samples > 0 && count > 0 ) + { + st_sample_t *p = input_buffer; + st_sample_t *end = p + *samples; + int16_t *q = *buffer + i; + st_size_t isamp = *samples; + st_size_t osamp = *samples; + double rms = 0; + int j; + char *normalise = mlt_properties_get( filter_properties, "normalise" ); + double normalised_gain = 1.0; +#if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(13,0,0)) + st_sample_t dummy_clipped_count = 0; +#endif + + // Convert to sox encoding + while( p != end ) + { +#if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(13,0,0)) + *p = ST_SIGNED_WORD_TO_SAMPLE( *q, dummy_clipped_count ); +#else + *p = ST_SIGNED_WORD_TO_SAMPLE( *q ); +#endif + // Compute rms amplitude while we are accessing each sample + rms += ( double )*p * ( double )*p; + + p ++; + q += *channels; + } + + // Compute final rms amplitude + rms = sqrt( rms / *samples / ST_SSIZE_MIN / ST_SSIZE_MIN ); + + if ( normalise ) + { + int window = mlt_properties_get_int( filter_properties, "window" ); + double *smooth_buffer = mlt_properties_get_data( filter_properties, "smooth_buffer", NULL ); + double max_gain = mlt_properties_get_double( filter_properties, "max_gain" ); + + // Default the maximum gain factor to 20dBFS + if ( max_gain == 0 ) + max_gain = 10.0; + + // The smoothing buffer prevents radical shifts in the gain level + if ( window > 0 && smooth_buffer != NULL ) + { + int smooth_index = mlt_properties_get_int( filter_properties, "_smooth_index" ); + smooth_buffer[ smooth_index ] = rms; + + // Ignore very small values that adversely affect the mean + if ( rms > AMPLITUDE_MIN ) + mlt_properties_set_int( filter_properties, "_smooth_index", ( smooth_index + 1 ) % window ); + + // Smoothing is really just a mean over the past N values + normalised_gain = AMPLITUDE_NORM / mean( smooth_buffer, window ); + } + else if ( rms > 0 ) + { + // Determine gain to apply as current amplitude + normalised_gain = AMPLITUDE_NORM / rms; + } + + //printf("filter_sox: rms %.3f gain %.3f\n", rms, normalised_gain ); + + // Govern the maximum gain + if ( normalised_gain > max_gain ) + normalised_gain = max_gain; + } + + // For each effect + for ( j = 0; j < count; j++ ) + { + sprintf( id, "_effect_%d_%d", j, i ); + e = mlt_properties_get_data( filter_properties, id, NULL ); + + // We better have this guy + if ( e != NULL ) + { + float saved_gain = 1.0; + + // XXX: hack to apply the normalised gain level to the vol effect +#ifdef SOX14 + if ( normalise && strcmp( e->handler.name, "vol" ) == 0 ) +#else + if ( normalise && strcmp( e->name, "vol" ) == 0 ) +#endif + { + float *f = ( float * )( e->priv ); + saved_gain = *f; + *f = saved_gain * normalised_gain; + } + + // Apply the effect +#ifdef SOX14 + if ( ( * e->handler.flow )( e, input_buffer, output_buffer, &isamp, &osamp ) == ST_SUCCESS ) +#else + if ( ( * e->h->flow )( e, input_buffer, output_buffer, &isamp, &osamp ) == ST_SUCCESS ) +#endif + { + // Swap input and output buffer pointers for subsequent effects + p = input_buffer; + input_buffer = output_buffer; + output_buffer = p; + } + + // XXX: hack to restore the original vol gain to prevent accumulation +#ifdef SOX14 + if ( normalise && strcmp( e->handler.name, "vol" ) == 0 ) +#else + if ( normalise && strcmp( e->name, "vol" ) == 0 ) +#endif + { + float *f = ( float * )( e->priv ); + *f = saved_gain; + } + } + } + + // Convert back to signed 16bit + p = input_buffer; + q = *buffer + i; + end = p + *samples; + while ( p != end ) + { +#if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(13,0,0)) + *q = ST_SAMPLE_TO_SIGNED_WORD( *p ++, dummy_clipped_count ); +#else + *q = ST_SAMPLE_TO_SIGNED_WORD( *p ++ ); +#endif + q += *channels; + } + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + if ( mlt_frame_is_test_audio( frame ) == 0 ) + { + // Add the filter to the frame + mlt_frame_push_audio( frame, this ); + mlt_frame_push_audio( frame, filter_get_audio ); + + // Parse the window property and allocate smoothing buffer if needed + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + int window = mlt_properties_get_int( properties, "window" ); + if ( mlt_properties_get( properties, "smooth_buffer" ) == NULL && window > 1 ) + { + // Create a smoothing buffer for the calculated "max power" of frame of audio used in normalisation + double *smooth_buffer = (double*) calloc( window, sizeof( double ) ); + int i; + for ( i = 0; i < window; i++ ) + smooth_buffer[ i ] = -1.0; + mlt_properties_set_data( properties, "smooth_buffer", smooth_buffer, 0, free, NULL ); + } + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_sox_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + void *input_buffer = mlt_pool_alloc( BUFFER_LEN ); + void *output_buffer = mlt_pool_alloc( BUFFER_LEN ); + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + this->process = filter_process; + + if ( arg != NULL ) + mlt_properties_set( properties, "effect", arg ); + mlt_properties_set_data( properties, "input_buffer", input_buffer, BUFFER_LEN, mlt_pool_release, NULL ); + mlt_properties_set_data( properties, "output_buffer", output_buffer, BUFFER_LEN, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "window", 75 ); + } + return this; +} + +// What to do when a libst internal failure occurs +void cleanup(void){} + +// Is there a build problem with my sox-devel package? +#ifndef gsm_create +void gsm_create(void){} +#endif +#ifndef gsm_decode +void gsm_decode(void){} +#endif +#ifndef gdm_encode +void gsm_encode(void){} +#endif +#ifndef gsm_destroy +void gsm_destroy(void){} +#endif +#ifndef gsm_option +void gsm_option(void){} +#endif diff --git a/src/modules/sox/filter_sox.h b/src/modules/sox/filter_sox.h new file mode 100644 index 0000000..094afc1 --- /dev/null +++ b/src/modules/sox/filter_sox.h @@ -0,0 +1,28 @@ +/* + * filter_sox.h -- apply any number of SOX effects using libst + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_SOX_H_ +#define _FILTER_SOX_H_ + +#include + +extern mlt_filter filter_sox_init( char *arg ); + +#endif diff --git a/src/modules/valerie/Makefile b/src/modules/valerie/Makefile new file mode 100644 index 0000000..4e0950a --- /dev/null +++ b/src/modules/valerie/Makefile @@ -0,0 +1,33 @@ +include ../../../config.mak + +TARGET = ../libmltvalerie$(LIBSUF) + +OBJS = factory.o \ + consumer_valerie.o + +CFLAGS += -I../../ + +LDFLAGS+=-L../../valerie -lvalerie -L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/valerie/configure b/src/modules/valerie/configure new file mode 100755 index 0000000..4149c6d --- /dev/null +++ b/src/modules/valerie/configure @@ -0,0 +1,11 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../consumers.dat +valerie libmltvalerie$LIBSUF +EOF + +fi + diff --git a/src/modules/valerie/consumer_valerie.c b/src/modules/valerie/consumer_valerie.c new file mode 100644 index 0000000..ba8a276 --- /dev/null +++ b/src/modules/valerie/consumer_valerie.c @@ -0,0 +1,175 @@ +/* + * consumer_valerie.c -- pushes a service via valerie + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_valerie.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static int consumer_is_stopped( mlt_consumer this ); +static int consumer_start( mlt_consumer this ); + +/** This is what will be called by the factory +*/ + +mlt_consumer consumer_valerie_init( char *arg ) +{ + // Create the consumer object + mlt_consumer this = calloc( sizeof( struct mlt_consumer_s ), 1 ); + + // If no malloc'd and consumer init ok + if ( this != NULL && mlt_consumer_init( this, NULL ) == 0 ) + { + if ( arg != NULL && strchr( arg, ':' ) ) + { + char *temp = NULL; + int port = atoi( strchr( arg, ':' ) + 1 ); + mlt_properties_set( MLT_CONSUMER_PROPERTIES( this ), "server", arg ); + temp = mlt_properties_get( MLT_CONSUMER_PROPERTIES( this ), "server" ); + *( strchr( temp, ':' ) ) = '\0'; + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( this ), "port", port ); + } + else + { + mlt_properties_set( MLT_CONSUMER_PROPERTIES( this ), "server", arg == NULL ? "localhost" : arg ); + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( this ), "port", 5250 ); + } + + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( this ), "unit", 0 ); + mlt_properties_set( MLT_CONSUMER_PROPERTIES( this ), "command", "append" ); + + // Allow thread to be started/stopped + this->start = consumer_start; + this->is_stopped = consumer_is_stopped; + + // Return the consumer produced + return this; + } + + // malloc or consumer init failed + free( this ); + + // Indicate failure + return NULL; +} + +static int consumer_start( mlt_consumer this ) +{ + // Get the producer service + mlt_service service = mlt_service_producer( MLT_CONSUMER_SERVICE( this ) ); + + // Get the properties object + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get all the properties now + char *server = mlt_properties_get( properties, "server" ); + int port = mlt_properties_get_int( properties, "port" ); + char *cmd = mlt_properties_get( properties, "command" ); + int unit = mlt_properties_get_int( properties, "unit" ); + char *title = mlt_properties_get( properties, "title" ); + char command[ 2048 ]; + + // If this is a reuse, then a valerie object will exist + valerie connection = mlt_properties_get_data( properties, "connection", NULL ); + + // Special case - we can get a doc too... + char *doc = mlt_properties_get( properties, "westley" ); + + // Set the title if provided + if ( service != NULL ) + { + if ( title != NULL ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "title", title ); + else if ( mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "title" ) == NULL ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "title", "Anonymous Submission" ); + title = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "title" ); + } + + strcpy( command, cmd == NULL ? "" : cmd ); + if ( strstr( command, "title=" ) == NULL && title != NULL ) + { + strcat( command, " title=\"" ); + strcat( command, title ); + strcat( command, "\"" ); + } + + if ( service != NULL || doc != NULL ) + { + // Initiate the connection if required + if ( connection == NULL ) + { + valerie_parser parser = valerie_parser_init_remote( server, port ); + connection = valerie_init( parser ); + if ( valerie_connect( connection ) == valerie_ok ) + { + mlt_properties_set_data( properties, "connection", connection, 0, ( mlt_destructor )valerie_close, NULL ); + mlt_properties_set_data( properties, "parser", parser, 0, ( mlt_destructor )valerie_parser_close, NULL ); + } + else + { + fprintf( stderr, "Unable to connect to the server at %s:%d\n", server, port ); + mlt_properties_set_int( properties, "_error", 1 ); + valerie_close( connection ); + valerie_parser_close( parser ); + connection = NULL; + } + } + + // If we have connection, push the service over + if ( connection != NULL ) + { + if ( doc == NULL ) + { + int error; + + // Push the service + error = valerie_unit_push( connection, unit, command, service ); + + // Report error + if ( error != valerie_ok ) + fprintf( stderr, "Push failed on %s:%d %s u%d (%d)\n", server, port, command, unit, error ); + } + else + { + // Push the service + int error = valerie_unit_receive( connection, unit, command, doc ); + + // Report error + if ( error != valerie_ok ) + fprintf( stderr, "Send failed on %s:%d %s u%d (%d)\n", server, port, command, unit, error ); + } + } + } + + mlt_consumer_stop( this ); + mlt_consumer_stopped( this ); + + return 0; +} + +static int consumer_is_stopped( mlt_consumer this ) +{ + return 1; +} diff --git a/src/modules/valerie/consumer_valerie.h b/src/modules/valerie/consumer_valerie.h new file mode 100644 index 0000000..dba8a47 --- /dev/null +++ b/src/modules/valerie/consumer_valerie.h @@ -0,0 +1,28 @@ +/* + * consumer_valerie.h -- pushes a service via valerie + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_VALERIE_H_ +#define _CONSUMER_VALERIE_H_ + +#include + +extern mlt_consumer consumer_valerie_init( char * ); + +#endif diff --git a/src/modules/valerie/factory.c b/src/modules/valerie/factory.c new file mode 100644 index 0000000..87c4526 --- /dev/null +++ b/src/modules/valerie/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "consumer_valerie.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "valerie" ) ) + return consumer_valerie_init( arg ); + return NULL; +} + diff --git a/src/modules/vmfx/Makefile b/src/modules/vmfx/Makefile new file mode 100644 index 0000000..1bc27a2 --- /dev/null +++ b/src/modules/vmfx/Makefile @@ -0,0 +1,37 @@ +include ../../../config.mak + +TARGET = ../libmltvmfx$(LIBSUF) + +OBJS = factory.o \ + filter_chroma.o \ + filter_chroma_hold.o \ + filter_mono.o \ + filter_shape.o \ + producer_pgm.o + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/vmfx/configure b/src/modules/vmfx/configure new file mode 100755 index 0000000..70339bb --- /dev/null +++ b/src/modules/vmfx/configure @@ -0,0 +1,23 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +pgm libmltvmfx$LIBSUF +EOF + +cat << EOF >> ../filters.dat +chroma libmltvmfx$LIBSUF +chroma_hold libmltvmfx$LIBSUF +threshold libmltvmfx$LIBSUF +shape libmltvmfx$LIBSUF +EOF + +cat << EOF >> ../transitions.dat +EOF + +cat << EOF >> ../consumers.dat +EOF + +fi diff --git a/src/modules/vmfx/factory.c b/src/modules/vmfx/factory.c new file mode 100644 index 0000000..087d1c0 --- /dev/null +++ b/src/modules/vmfx/factory.c @@ -0,0 +1,57 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_chroma.h" +#include "filter_chroma_hold.h" +#include "filter_mono.h" +#include "filter_shape.h" +#include "producer_pgm.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "pgm" ) ) + return producer_pgm_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "chroma" ) ) + return filter_chroma_init( arg ); + if ( !strcmp( id, "chroma_hold" ) ) + return filter_chroma_hold_init( arg ); + if ( !strcmp( id, "threshold" ) ) + return filter_mono_init( arg ); + if ( !strcmp( id, "shape" ) ) + return filter_shape_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} diff --git a/src/modules/vmfx/filter_chroma.c b/src/modules/vmfx/filter_chroma.c new file mode 100644 index 0000000..560d221 --- /dev/null +++ b/src/modules/vmfx/filter_chroma.c @@ -0,0 +1,100 @@ +/* + * filter_chroma.c -- Maps a chroma key to the alpha channel + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_chroma.h" +#include +#include +#include +#include +#include +#include + +static inline int in_range( uint8_t v, uint8_t c, int var ) +{ + return ( ( int )v >= c - var ) && ( ( int )v <= c + var ); +} + +static inline uint8_t alpha_value( uint8_t a, uint8_t *p, uint8_t u, uint8_t v, int var, int odd ) +{ + if ( odd == 0 ) + return ( in_range( *( p + 1 ), u, var ) && in_range( *( p + 3 ), v, var ) ) ? 0 : a; + else + return ( in_range( ( *( p + 1 ) + *( p + 5 ) ) / 2, u, var ) && in_range( ( *( p + 3 ) + *( p + 7 ) ) / 2, v, var ) ) ? 0 : a; +} + +/** Get the images and map the chroma to the alpha of the frame. +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_filter this = mlt_frame_pop_service( frame ); + char *key = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "key" ); + int variance = 200 * mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "variance" ); + int32_t key_val = strtol( key, &key, 0 ); + uint8_t b = key_val & 0xff; + uint8_t g = ( key_val >> 8 ) & 0xff; + uint8_t r = ( key_val >> 16 ) & 0xff; + uint8_t y, u, v; + + RGB2YUV( r, g, b, y, u, v ); + + if ( mlt_frame_get_image( frame, image, format, width, height, writable ) == 0 ) + { + uint8_t *alpha = mlt_frame_get_alpha_mask( frame ); + uint8_t *p = *image; + int size = *width * *height / 2; + while ( size -- ) + { + *alpha = alpha_value( *alpha, p, u, v, variance, 0 ); + alpha ++; + *alpha = alpha_value( *alpha, p, u, v, variance, 1 ); + alpha ++; + p += 4; + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_service( frame, this ); + mlt_frame_push_service( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_chroma_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "key", arg == NULL ? "0x0000ff" : arg ); + mlt_properties_set_double( MLT_FILTER_PROPERTIES( this ), "variance", 0.15 ); + this->process = filter_process; + } + return this; +} + diff --git a/src/modules/vmfx/filter_chroma.h b/src/modules/vmfx/filter_chroma.h new file mode 100644 index 0000000..ab1bb06 --- /dev/null +++ b/src/modules/vmfx/filter_chroma.h @@ -0,0 +1,28 @@ +/* + * filter_chroma.h -- Maps a chroma key to the alpha channel + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_CHROMA_H_ +#define _FILTER_CHROMA_H_ + +#include + +extern mlt_filter filter_chroma_init( char *arg ); + +#endif diff --git a/src/modules/vmfx/filter_chroma_hold.c b/src/modules/vmfx/filter_chroma_hold.c new file mode 100644 index 0000000..d7f0558 --- /dev/null +++ b/src/modules/vmfx/filter_chroma_hold.c @@ -0,0 +1,101 @@ +/* + * filter_chroma.c -- Maps a chroma key to the alpha channel + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_chroma.h" +#include +#include +#include +#include +#include + +static inline int in_range( uint8_t v, uint8_t c, int var ) +{ + return ( ( int )v >= c - var ) && ( ( int )v <= c + var ); +} + +static inline uint8_t alpha_value( uint8_t a, uint8_t *p, uint8_t u, uint8_t v, int var, int odd ) +{ + if ( odd == 0 ) + return ( in_range( *( p + 1 ), u, var ) && in_range( *( p + 3 ), v, var ) ) ? 0 : a; + else + return ( in_range( ( *( p + 1 ) + *( p + 5 ) ) / 2, u, var ) && in_range( ( *( p + 3 ) + *( p + 7 ) ) / 2, v, var ) ) ? 0 : a; +} + +/** Get the images and map the chroma to the alpha of the frame. +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_filter this = mlt_frame_pop_service( frame ); + char *key = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "key" ); + int variance = 200 * mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "variance" ); + int32_t key_val = strtol( key, &key, 0 ); + uint8_t b = key_val & 0xff; + uint8_t g = ( key_val >> 8 ) & 0xff; + uint8_t r = ( key_val >> 16 ) & 0xff; + uint8_t y, u, v; + + RGB2YUV( r, g, b, y, u, v ); + + if ( mlt_frame_get_image( frame, image, format, width, height, writable ) == 0 ) + { + uint8_t alpha = 0; + uint8_t *p = *image; + int size = *width * *height / 2; + while ( size -- ) + { + alpha = alpha_value( 255, p, u, v, variance, 0 ); + if ( alpha ) + *( p + 1 )= 128; + alpha = alpha_value( 255, p, u, v, variance, 1 ); + if ( alpha ) + *( p + 3 ) = 128; + p += 4; + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_service( frame, this ); + mlt_frame_push_service( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_chroma_hold_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "key", arg == NULL ? "0xc00000" : arg ); + mlt_properties_set_double( MLT_FILTER_PROPERTIES( this ), "variance", 0.15 ); + this->process = filter_process; + } + return this; +} + diff --git a/src/modules/vmfx/filter_chroma_hold.h b/src/modules/vmfx/filter_chroma_hold.h new file mode 100644 index 0000000..d485ee6 --- /dev/null +++ b/src/modules/vmfx/filter_chroma_hold.h @@ -0,0 +1,28 @@ +/* + * filter_chroma.h -- Maps a chroma key to the alpha channel + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_CHROMA_HOLD_H_ +#define _FILTER_CHROMA_HOLD_H_ + +#include + +extern mlt_filter filter_chroma_hold_init( char *arg ); + +#endif diff --git a/src/modules/vmfx/filter_mono.c b/src/modules/vmfx/filter_mono.c new file mode 100644 index 0000000..551e519 --- /dev/null +++ b/src/modules/vmfx/filter_mono.c @@ -0,0 +1,97 @@ +/* + * filter_mono.c -- Arbitrary alpha channel shaping + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_mono.h" +#include +#include +#include +#include +#include + +/** Get the images and apply the luminance of the mask to the alpha of the frame. +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int use_alpha = mlt_deque_pop_back_int( MLT_FRAME_IMAGE_STACK( this ) ); + int midpoint = mlt_deque_pop_back_int( MLT_FRAME_IMAGE_STACK( this ) ); + + // Render the frame + if ( mlt_frame_get_image( this, image, format, width, height, writable ) == 0 ) + { + uint8_t *p = *image; + int size = *width * *height; + + if ( !use_alpha ) + { + while( size -- ) + { + if ( *p >= midpoint ) + *p ++ = 16; + else + *p ++ = 235; + *p ++ = 128; + } + } + else + { + uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + while( size -- ) + { + if ( *alpha ++ < midpoint ) + *p ++ = 16; + else + *p ++ = 235; + *p ++ = 128; + } + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + int midpoint = mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "midpoint" ); + int use_alpha = mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "use_alpha" ); + mlt_deque_push_back_int( MLT_FRAME_IMAGE_STACK( frame ), midpoint ); + mlt_deque_push_back_int( MLT_FRAME_IMAGE_STACK( frame ), use_alpha ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_mono_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "midpoint", 128 ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "use_alpha", 0 ); + this->process = filter_process; + } + return this; +} + diff --git a/src/modules/vmfx/filter_mono.h b/src/modules/vmfx/filter_mono.h new file mode 100644 index 0000000..6bf6f6e --- /dev/null +++ b/src/modules/vmfx/filter_mono.h @@ -0,0 +1,28 @@ +/* + * filter_shape.h -- Arbitrary alpha channel shaping + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_MONO_H_ +#define _FILTER_MONO_H_ + +#include + +extern mlt_filter filter_mono_init( char *arg ); + +#endif diff --git a/src/modules/vmfx/filter_shape.c b/src/modules/vmfx/filter_shape.c new file mode 100644 index 0000000..a4a8e0f --- /dev/null +++ b/src/modules/vmfx/filter_shape.c @@ -0,0 +1,229 @@ +/* + * filter_shape.c -- Arbitrary alpha channel shaping + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_shape.h" +#include +#include +#include +#include +#include + +inline double smoothstep( const double e1, const double e2, const double a ) +{ + if ( a < e1 ) return 0.0; + if ( a > e2 ) return 1.0; + double v = ( a - e1 ) / ( e2 - e1 ); + return ( v * v * ( 3 - 2 * v ) ); +} + +/** Get the images and apply the luminance of the mask to the alpha of the frame. +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Fetch the data from the stack (mix, mask, filter) + double mix = mlt_deque_pop_back_double( MLT_FRAME_IMAGE_STACK( this ) ); + mlt_frame mask = mlt_frame_pop_service( this ); + mlt_filter filter = mlt_frame_pop_service( this ); + + // Obtain the constants + double softness = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "softness" ); + int use_luminance = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "use_luminance" ); + int invert = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "invert" ) * 255; + + // Render the frame + if ( mlt_frame_get_image( this, image, format, width, height, writable ) == 0 && ( !use_luminance || ( int )mix != 1 ) ) + { + // Get the alpha mask of the source + uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + + // Obtain a scaled/distorted mask to match + uint8_t *mask_img = NULL; + mlt_image_format mask_fmt = mlt_image_yuv422; + mlt_properties_set_int( MLT_FRAME_PROPERTIES( mask ), "distort", 1 ); + mlt_properties_pass_list( MLT_FRAME_PROPERTIES( mask ), MLT_FRAME_PROPERTIES( this ), "deinterlace,deinterlace_method,rescale.interp" ); + + if ( mlt_frame_get_image( mask, &mask_img, &mask_fmt, width, height, 0 ) == 0 ) + { + int size = *width * *height; + uint8_t *p = alpha; + double a = 0; + double b = 0; + if ( !use_luminance ) + { + uint8_t *q = mlt_frame_get_alpha_mask( mask ); + while( size -- ) + { + a = ( double )*q ++ / 255.0; + b = 1.0 - smoothstep( a, a + softness, mix ); + *p = ( uint8_t )( *p * b ) ^ invert; + p ++; + } + } + else if ( ( int )mix != 1 ) + { + uint8_t *q = mask_img; + // Ensure softness tends to zero has mix tends to 1 + softness *= ( 1.0 - mix ); + while( size -- ) + { + a = ( ( double )*q - 16 ) / 235.0; + b = smoothstep( a, a + softness, mix ); + *p = ( uint8_t )( *p * b ) ^ invert; + p ++; + q += 2; + } + } + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Obtain the shape instance + char *resource = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "resource" ); + char *last_resource = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "_resource" ); + mlt_producer producer = mlt_properties_get_data( MLT_FILTER_PROPERTIES( this ), "instance", NULL ); + + // Get the key framed values + mlt_geometry alpha = mlt_properties_get_data( MLT_FILTER_PROPERTIES( this ), "_alpha", NULL ); + char *alpha_data = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "mix" ); + double alpha_mix = 0.0; + + // Calculate the position and length + int position = mlt_frame_get_position( frame ) - mlt_filter_get_in( this ); + int in = mlt_filter_get_in( this ); + int out = mlt_filter_get_out( this ); + int length; + + // Special case for attached filters - in/out come through on the frame + if ( out == 0 ) + { + in = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "in" ); + out = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "out" ); + position -= in; + } + + // Duration of the shape + length = out - in + 1; + + // If we haven't created the instance or it's changed + if ( producer == NULL || strcmp( resource, last_resource ) ) + { + char temp[ 512 ]; + char *extension = strrchr( resource, '.' ); + + // Store the last resource now + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "_resource", resource ); + + // This is a hack - the idea is that we can indirectly reference the + // luma modules pgm or png images by a short cut like %luma01.pgm - we then replace + // the % with the full path to the image and use it if it exists, if not, check for + // the file ending in a .png, and failing that, default to a fade in + if ( strchr( resource, '%' ) ) + { + FILE *test; + sprintf( temp, "%s/lumas/%s/%s", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ), strchr( resource, '%' ) + 1 ); + test = fopen( temp, "r" ); + + if ( test == NULL ) + { + strcat( temp, ".png" ); + test = fopen( temp, "r" ); + } + + if ( test ) + fclose( test ); + else + strcpy( temp, "colour:0x00000080" ); + + resource = temp; + extension = strrchr( resource, '.' ); + } + + producer = mlt_factory_producer( NULL, resource ); + if ( producer != NULL ) + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "instance", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + + // Construct the geometry item if needed, otherwise refresh it + if ( alpha == NULL ) + { + alpha = mlt_geometry_init( ); + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "_alpha", alpha, 0, ( mlt_destructor )mlt_geometry_close, NULL ); + mlt_geometry_parse( alpha, alpha_data, length, 100, 100 ); + } + else + { + mlt_geometry_refresh( alpha, alpha_data, length, 100, 100 ); + } + + // We may still not have a producer in which case, we do nothing + if ( producer != NULL ) + { + mlt_frame mask = NULL; + struct mlt_geometry_item_s item; + mlt_geometry_fetch( alpha, &item, position ); + alpha_mix = item.x; + mlt_properties_pass( MLT_PRODUCER_PROPERTIES( producer ), MLT_FILTER_PROPERTIES( this ), "producer." ); + mlt_producer_seek( producer, position ); + if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &mask, 0 ) == 0 ) + { + char *name = mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "_unique_id" ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), name, mask, 0, ( mlt_destructor )mlt_frame_close, NULL ); + mlt_frame_push_service( frame, this ); + mlt_frame_push_service( frame, mask ); + mlt_deque_push_back_double( MLT_FRAME_IMAGE_STACK( frame ), alpha_mix / 100.0 ); + mlt_frame_push_get_image( frame, filter_get_image ); + if ( mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "audio_match" ) ) + { + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "meta.mixdown", 1 ); + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "meta.volume", alpha_mix / 100.0 ); + } + } + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_shape_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "resource", arg ); + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "mix", "100" ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "audio_match", 1 ); + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "invert", 0 ); + mlt_properties_set_double( MLT_FILTER_PROPERTIES( this ), "softness", 0.1 ); + this->process = filter_process; + } + return this; +} + diff --git a/src/modules/vmfx/filter_shape.h b/src/modules/vmfx/filter_shape.h new file mode 100644 index 0000000..49bc2d9 --- /dev/null +++ b/src/modules/vmfx/filter_shape.h @@ -0,0 +1,28 @@ +/* + * filter_shape.h -- Arbitrary alpha channel shaping + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_SHAPE_H_ +#define _FILTER_SHAPE_H_ + +#include + +extern mlt_filter filter_shape_init( char *arg ); + +#endif diff --git a/src/modules/vmfx/producer_pgm.c b/src/modules/vmfx/producer_pgm.c new file mode 100644 index 0000000..0b4b698 --- /dev/null +++ b/src/modules/vmfx/producer_pgm.c @@ -0,0 +1,209 @@ +/* + * producer_pgm.c -- PGM producer + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "producer_pgm.h" +#include +#include +#include + +static int read_pgm( char *name, uint8_t **image, int *width, int *height, int *maxval ); +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ); +static void producer_close( mlt_producer parent ); + +mlt_producer producer_pgm_init( void *resource ) +{ + mlt_producer this = NULL; + uint8_t *image = NULL; + int width = 0; + int height = 0; + int maxval = 0; + + if ( read_pgm( resource, &image, &width, &height, &maxval ) == 0 ) + { + this = calloc( 1, sizeof( struct mlt_producer_s ) ); + if ( this != NULL && mlt_producer_init( this, NULL ) == 0 ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + this->get_frame = producer_get_frame; + this->close = ( mlt_destructor )producer_close; + mlt_properties_set( properties, "resource", resource ); + mlt_properties_set_data( properties, "image", image, 0, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "real_width", width ); + mlt_properties_set_int( properties, "real_height", height ); + } + else + { + mlt_pool_release( image ); + free( this ); + this = NULL; + } + } + + return this; +} + +/** Load the PGM file. +*/ + +static int read_pgm( char *name, uint8_t **image, int *width, int *height, int *maxval ) +{ + uint8_t *input = NULL; + int error = 0; + FILE *f = fopen( name, "r" ); + char data[ 512 ]; + + // Initialise + *image = NULL; + *width = 0; + *height = 0; + *maxval = 0; + + // Get the magic code + if ( f != NULL && fgets( data, 511, f ) != NULL && data[ 0 ] == 'P' && data[ 1 ] == '5' ) + { + char *p = data + 2; + int i = 0; + int val = 0; + + // PGM Header parser (probably needs to be strengthened) + for ( i = 0; !error && i < 3; i ++ ) + { + if ( *p != '\0' && *p != '\n' ) + val = strtol( p, &p, 10 ); + else + p = NULL; + + while ( error == 0 && p == NULL ) + { + if ( fgets( data, 511, f ) == NULL ) + error = 1; + else if ( data[ 0 ] != '#' ) + val = strtol( data, &p, 10 ); + } + + switch( i ) + { + case 0: *width = val; break; + case 1: *height = val; break; + case 2: *maxval = val; break; + } + } + + if ( !error ) + { + // Determine if this is one or two bytes per pixel + int bpp = *maxval > 255 ? 2 : 1; + int size = *width * *height * bpp; + uint8_t *p; + + // Allocate temporary storage for the data and the image + input = mlt_pool_alloc( *width * *height * bpp ); + *image = mlt_pool_alloc( *width * *height * sizeof( uint8_t ) * 2 ); + p = *image; + + error = *image == NULL || input == NULL; + + if ( !error ) + { + // Read the raw data + error = fread( input, *width * *height * bpp, 1, f ) != 1; + + if ( !error ) + { + // Convert to yuv422 (very lossy - need to extend this to allow 16 bit alpha out) + for ( i = 0; i < size; i += bpp ) + { + *p ++ = 16 + ( input[ i ] * 219 ) / 255; + *p ++ = 128; + } + } + } + } + + if ( error ) + mlt_pool_release( *image ); + mlt_pool_release( input ); + } + else + { + error = 1; + } + + if ( f != NULL ) + fclose( f ); + + return error; +} + +static int producer_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_producer producer = mlt_frame_pop_service( this ); + int real_width = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "real_width" ); + int real_height = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "real_height" ); + int size = real_width * real_height; + uint8_t *image = mlt_pool_alloc( size * 2 ); + uint8_t *source = mlt_properties_get_data( MLT_PRODUCER_PROPERTIES( producer ), "image", NULL ); + + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "image", image, size * 2, mlt_pool_release, NULL ); + + *width = real_width; + *height = real_height; + *format = mlt_image_yuv422; + *buffer = image; + + if ( image != NULL && source != NULL ) + memcpy( image, source, size * 2 ); + + return 0; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Construct a test frame + *frame = mlt_frame_init( ); + + // Get the frames properties + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + + // Pass the data on the frame properties + mlt_properties_pass_list( properties, MLT_PRODUCER_PROPERTIES( producer ), "real_width,real_height" ); + mlt_properties_set_int( properties, "has_image", 1 ); + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_double( properties, "aspect_ratio", 1 ); + + // Push the image callback + mlt_frame_push_service( *frame, producer ); + mlt_frame_push_get_image( *frame, producer_get_image ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer parent ) +{ + parent->close = NULL; + mlt_producer_close( parent ); + free( parent ); +} diff --git a/src/modules/vmfx/producer_pgm.h b/src/modules/vmfx/producer_pgm.h new file mode 100644 index 0000000..e147e4d --- /dev/null +++ b/src/modules/vmfx/producer_pgm.h @@ -0,0 +1,28 @@ +/* + * producer_pgm.h -- PGM producer + * Copyright (C) 2005 Visual Media Fx Inc. + * Author: Charles Yates + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PRODUCER_PGM_H_ +#define PRODUCER_PGM_H_ + +#include + +extern mlt_producer producer_pgm_init( void * ); + +#endif diff --git a/src/modules/vorbis/Makefile b/src/modules/vorbis/Makefile new file mode 100644 index 0000000..a2e5578 --- /dev/null +++ b/src/modules/vorbis/Makefile @@ -0,0 +1,35 @@ +include ../../../config.mak + +TARGET = ../libmltvorbis$(LIBSUF) + +OBJS = factory.o \ + producer_vorbis.o + +CFLAGS += -I../.. + +LDFLAGS += -lvorbisfile -lvorbis + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/vorbis/configure b/src/modules/vorbis/configure new file mode 100755 index 0000000..bfc4043 --- /dev/null +++ b/src/modules/vorbis/configure @@ -0,0 +1,18 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + pkg-config vorbisfile 2> /dev/null + disable_vorbis=$? + + if [ "$disable_vorbis" = "0" ] + then + echo "vorbis libmltvorbis$LIBSUF" >> ../producers.dat + else + echo "- ogg vorbis not found: disabling" + touch ../disable-vorbis + fi + +fi + diff --git a/src/modules/vorbis/factory.c b/src/modules/vorbis/factory.c new file mode 100644 index 0000000..16e5d13 --- /dev/null +++ b/src/modules/vorbis/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_vorbis.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "vorbis" ) ) + return producer_vorbis_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/vorbis/producer_vorbis.c b/src/modules/vorbis/producer_vorbis.c new file mode 100644 index 0000000..776156f --- /dev/null +++ b/src/modules/vorbis/producer_vorbis.c @@ -0,0 +1,365 @@ +/* + * producer_vorbis.c -- vorbis producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "producer_vorbis.h" + +// MLT Header files +#include + +// vorbis Header files +#include +#include + +// System header files +#include +#include +#include + +// Forward references. +static int producer_open( mlt_producer this, char *file ); +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); + +/** Structure for metadata reading +*/ + +typedef struct _sw_metadata sw_metadata; + +struct _sw_metadata { + char * name; + char * content; +}; + +static sw_metadata *vorbis_metadata_from_str (char * str) +{ + sw_metadata * meta = NULL; + int i; + + for (i = 0; str[i]; i++) { + str[i] = tolower(str[i]); + if (str[i] == '=') { + str[i] = '\0'; + meta = malloc (sizeof (sw_metadata)); + meta->name = malloc( strlen(str) + 18 ); + sprintf(meta->name, "meta.attr.%s.markup", str); + meta->content = strdup (&str[i+1]); + break; + } + } + return meta; +} + +/** Constructor for libvorbis. +*/ + +mlt_producer producer_vorbis_init( char *file ) +{ + mlt_producer this = NULL; + + // Check that we have a non-NULL argument + if ( file != NULL ) + { + // Construct the producer + this = calloc( 1, sizeof( struct mlt_producer_s ) ); + + // Initialise it + if ( mlt_producer_init( this, NULL ) == 0 ) + { + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Set the resource property (required for all producers) + mlt_properties_set( properties, "resource", file ); + + // Register our get_frame implementation + this->get_frame = producer_get_frame; + + // Open the file + if ( producer_open( this, file ) != 0 ) + { + // Clean up + mlt_producer_close( this ); + this = NULL; + } + } + } + + return this; +} + +/** Destuctor for ogg files. +*/ + +static void producer_file_close( void *file ) +{ + if ( file != NULL ) + { + // Close the ogg vorbis structure + ov_clear( file ); + + // Free the memory + free( file ); + } +} + +/** Open the file. +*/ + +static int producer_open( mlt_producer this, char *file ) +{ + // FILE pointer for file + FILE *input = fopen( file, "r" ); + + // Error code to return + int error = input == NULL; + + // Continue if file is open + if ( error == 0 ) + { + // OggVorbis file structure + OggVorbis_File *ov = calloc( 1, sizeof( OggVorbis_File ) ); + + // Attempt to open the stream + error = ov == NULL || ov_open( input, ov, NULL, 0 ) != 0; + + // Assign to producer properties if successful + if ( error == 0 ) + { + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Assign the ov structure + mlt_properties_set_data( properties, "ogg_vorbis_file", ov, 0, producer_file_close, NULL ); + + // Read metadata + sw_metadata * metadata = NULL; + char **ptr = ov_comment(ov, -1)->user_comments; + while(*ptr) { + metadata = vorbis_metadata_from_str (*ptr); + if (metadata != NULL) + mlt_properties_set(properties, metadata->name, metadata->content); + ++ptr; + } + + if ( ov_seekable( ov ) ) + { + // Get the length of the file + double length = ov_time_total( ov, -1 ); + + // We will treat everything with the producer fps + double fps = mlt_producer_get_fps( this ); + + // Set out and length of file + mlt_properties_set_position( properties, "out", ( length * fps ) - 1 ); + mlt_properties_set_position( properties, "length", ( length * fps ) ); + + // Get the vorbis info + vorbis_info *vi = ov_info( ov, -1 ); + mlt_properties_set_int( properties, "frequency", (int) vi->rate ); + mlt_properties_set_int( properties, "channels", vi->channels ); + } + } + else + { + // Clean up + free( ov ); + + // Must close input file when open fails + fclose( input ); + } + } + + return error; +} + +/** Convert a frame position to a time code. +*/ + +static double producer_time_of_frame( mlt_producer this, mlt_position position ) +{ + return ( double )position / mlt_producer_get_fps( this ); +} + +/** Get the audio from a frame. +*/ + +static int producer_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties from the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the frame number of this frame + mlt_position position = mlt_properties_get_position( frame_properties, "vorbis_position" ); + + // Get the producer + mlt_producer this = mlt_frame_pop_audio( frame ); + + // Get the producer properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Get the ogg vorbis file + OggVorbis_File *ov = mlt_properties_get_data( properties, "ogg_vorbis_file", NULL ); + + // Obtain the expected frame numer + mlt_position expected = mlt_properties_get_position( properties, "audio_expected" ); + + // Get the fps for this producer + double fps = mlt_producer_get_fps( this ); + + // Get the vorbis info + vorbis_info *vi = ov_info( ov, -1 ); + + // Obtain the audio buffer + int16_t *audio_buffer = mlt_properties_get_data( properties, "audio_buffer", NULL ); + + // Get amount of audio used + int audio_used = mlt_properties_get_int( properties, "audio_used" ); + + // Number of frames to ignore (for ffwd) + int ignore = 0; + + // Flag for paused (silence) + int paused = 0; + + // Check for audio buffer and create if necessary + if ( audio_buffer == NULL ) + { + // Allocate the audio buffer + audio_buffer = mlt_pool_alloc( 131072 * sizeof( int16_t ) ); + + // And store it on properties for reuse + mlt_properties_set_data( properties, "audio_buffer", audio_buffer, 0, mlt_pool_release, NULL ); + } + + // Seek if necessary + if ( position != expected ) + { + if ( position + 1 == expected ) + { + // We're paused - silence required + paused = 1; + } + else if ( position > expected && ( position - expected ) < 250 ) + { + // Fast forward - seeking is inefficient for small distances - just ignore following frames + ignore = position - expected; + } + else + { + // Seek to the required position + ov_time_seek( ov, producer_time_of_frame( this, position ) ); + expected = position; + audio_used = 0; + } + } + + // Return info in frame + *frequency = vi->rate; + *channels = vi->channels; + + // Get the audio if required + if ( !paused ) + { + // Bitstream section + int current_section; + + // Get the number of samples for the current frame + *samples = mlt_sample_calculator( fps, *frequency, expected ++ ); + + while( *samples > audio_used ) + { + // Read the samples + int bytes = ov_read( ov, ( char * )( &audio_buffer[ audio_used * 2 ] ), 4096, 0, 2, 1, ¤t_section ); + + // Break if error or eof + if ( bytes <= 0 ) + break; + + // Increment number of samples used + audio_used += bytes / ( sizeof( int16_t ) * *channels ); + + // Handle ignore + while ( ignore && audio_used >= *samples ) + { + ignore --; + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * sizeof( int16_t ) ); + *samples = mlt_sample_calculator( fps, *frequency, expected ++ ); + } + } + + // Now handle the audio if we have enough + if ( audio_used >= *samples ) + { + *buffer = mlt_pool_alloc( *samples * *channels * sizeof( int16_t ) ); + memcpy( *buffer, audio_buffer, *samples * *channels * sizeof( int16_t ) ); + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( frame_properties, "audio", *buffer, 0, mlt_pool_release, NULL ); + } + else + { + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + audio_used = 0; + } + + // Store the number of audio samples still available + mlt_properties_set_int( properties, "audio_used", audio_used ); + } + else + { + // Get silence and don't touch the context + *samples = mlt_sample_calculator( fps, *frequency, position ); + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + } + + // Regardless of speed, we expect to get the next frame (cos we ain't too bright) + mlt_properties_set_position( properties, "audio_expected", position + 1 ); + + return 0; +} + +/** Our get frame implementation. +*/ + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Create an empty frame + *frame = mlt_frame_init( ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Set the position of this producer + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame ); + mlt_properties_set_position( frame_properties, "vorbis_position", mlt_producer_frame( this ) ); + + // Set up the audio + mlt_frame_push_audio( *frame, this ); + mlt_frame_push_audio( *frame, producer_get_audio ); + + // Pass audio properties to the frame + mlt_properties_pass_list( frame_properties, MLT_PRODUCER_PROPERTIES( this ), "frequency, channels" ); + + // Calculate the next timecode + mlt_producer_prepare_next( this ); + + return 0; +} diff --git a/src/modules/vorbis/producer_vorbis.h b/src/modules/vorbis/producer_vorbis.h new file mode 100644 index 0000000..321237f --- /dev/null +++ b/src/modules/vorbis/producer_vorbis.h @@ -0,0 +1,28 @@ +/* + * producer_vorbis.h -- vorbis producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_VORBIS_H_ +#define _PRODUCER_VORBIS_H_ + +#include + +extern mlt_producer producer_vorbis_init( char *file ); + +#endif diff --git a/src/modules/westley/Makefile b/src/modules/westley/Makefile new file mode 100644 index 0000000..73f9712 --- /dev/null +++ b/src/modules/westley/Makefile @@ -0,0 +1,37 @@ +include ../../../config.mak + +TARGET = ../libmltwestley$(LIBSUF) + +OBJS = factory.o \ + consumer_westley.o \ + producer_westley.o + +CFLAGS += -I../../ `xml2-config --cflags` + +LDFLAGS += `xml2-config --libs` + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -m 644 westley.dtd "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/westley/configure b/src/modules/westley/configure new file mode 100755 index 0000000..65cbb5f --- /dev/null +++ b/src/modules/westley/configure @@ -0,0 +1,19 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + which xml2-config > /dev/null 2>&1 + disable_xml2=$? + + if [ "$disable_xml2" = "0" ] + then + echo "westley libmltwestley$LIBSUF" >> ../producers.dat + echo "westley-xml libmltwestley$LIBSUF" >> ../producers.dat + echo "westley libmltwestley$LIBSUF" >> ../consumers.dat + else + echo "- xml2 not found: disabling westley modules" + touch ../disable-westley + fi +fi + diff --git a/src/modules/westley/consumer_westley.c b/src/modules/westley/consumer_westley.c new file mode 100644 index 0000000..4141e0d --- /dev/null +++ b/src/modules/westley/consumer_westley.c @@ -0,0 +1,745 @@ +/* + * consumer_westley.c -- a libxml2 serialiser of mlt service networks + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "consumer_westley.h" +#include +#include +#include +#include +#include +#include +#include + +#define ID_SIZE 128 + +#define _x (xmlChar*) +#define _s (char*) + +// This maintains counters for adding ids to elements +struct serialise_context_s +{ + mlt_properties id_map; + int producer_count; + int multitrack_count; + int playlist_count; + int tractor_count; + int filter_count; + int transition_count; + int pass; + mlt_properties hide_map; + char *root; + char *store; +}; +typedef struct serialise_context_s* serialise_context; + +/** Forward references to static functions. +*/ + +static int consumer_start( mlt_consumer parent ); +static int consumer_is_stopped( mlt_consumer this ); +static void serialise_service( serialise_context context, mlt_service service, xmlNode *node ); + +typedef enum +{ + westley_existing, + westley_producer, + westley_multitrack, + westley_playlist, + westley_tractor, + westley_filter, + westley_transition +} +westley_type; + +/** Create or retrieve an id associated to this service. +*/ + +static char *westley_get_id( serialise_context context, mlt_service service, westley_type type ) +{ + char *id = NULL; + int i = 0; + mlt_properties map = context->id_map; + + // Search the map for the service + for ( i = 0; i < mlt_properties_count( map ); i ++ ) + if ( mlt_properties_get_data_at( map, i, NULL ) == service ) + break; + + // If the service is not in the map, and the type indicates a new id is needed... + if ( i >= mlt_properties_count( map ) && type != westley_existing ) + { + // Attempt to reuse existing id + id = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "id" ); + + // If no id, or the id is used in the map (for another service), then + // create a new one. + if ( id == NULL || mlt_properties_get_data( map, id, NULL ) != NULL ) + { + char temp[ ID_SIZE ]; + do + { + switch( type ) + { + case westley_producer: + sprintf( temp, "producer%d", context->producer_count ++ ); + break; + case westley_multitrack: + sprintf( temp, "multitrack%d", context->multitrack_count ++ ); + break; + case westley_playlist: + sprintf( temp, "playlist%d", context->playlist_count ++ ); + break; + case westley_tractor: + sprintf( temp, "tractor%d", context->tractor_count ++ ); + break; + case westley_filter: + sprintf( temp, "filter%d", context->filter_count ++ ); + break; + case westley_transition: + sprintf( temp, "transition%d", context->transition_count ++ ); + break; + case westley_existing: + // Never gets here + break; + } + } + while( mlt_properties_get_data( map, temp, NULL ) != NULL ); + + // Set the data at the generated name + mlt_properties_set_data( map, temp, service, 0, NULL, NULL ); + + // Get the pointer to the name (i is the end of the list) + id = mlt_properties_get_name( map, i ); + } + else + { + // Store the existing id in the map + mlt_properties_set_data( map, id, service, 0, NULL, NULL ); + } + } + else if ( type == westley_existing ) + { + id = mlt_properties_get_name( map, i ); + } + + return id; +} + +/** This is what will be called by the factory - anything can be passed in + via the argument, but keep it simple. +*/ + +mlt_consumer consumer_westley_init( char *arg ) +{ + // Create the consumer object + mlt_consumer this = calloc( sizeof( struct mlt_consumer_s ), 1 ); + + // If no malloc'd and consumer init ok + if ( this != NULL && mlt_consumer_init( this, NULL ) == 0 ) + { + // Allow thread to be started/stopped + this->start = consumer_start; + this->is_stopped = consumer_is_stopped; + + mlt_properties_set( MLT_CONSUMER_PROPERTIES( this ), "resource", arg ); + + // Return the consumer produced + return this; + } + + // malloc or consumer init failed + free( this ); + + // Indicate failure + return NULL; +} + +static void serialise_properties( serialise_context context, mlt_properties properties, xmlNode *node ) +{ + int i; + xmlNode *p; + + // Enumerate the properties + for ( i = 0; i < mlt_properties_count( properties ); i++ ) + { + char *name = mlt_properties_get_name( properties, i ); + if ( name != NULL && + name[ 0 ] != '_' && + mlt_properties_get_value( properties, i ) != NULL && + strcmp( name, "westley" ) != 0 && + strcmp( name, "in" ) != 0 && + strcmp( name, "out" ) != 0 && + strcmp( name, "id" ) != 0 && + strcmp( name, "title" ) != 0 && + strcmp( name, "root" ) != 0 && + strcmp( name, "width" ) != 0 && + strcmp( name, "height" ) != 0 ) + { + char *value = mlt_properties_get_value( properties, i ); + if ( strcmp( context->root, "" ) && !strncmp( value, context->root, strlen( context->root ) ) ) + value += strlen( context->root ) + 1; + p = xmlNewTextChild( node, NULL, _x("property"), _x(value) ); + xmlNewProp( p, _x("name"), _x(name) ); + } + } +} + +static void serialise_store_properties( serialise_context context, mlt_properties properties, xmlNode *node, char *store ) +{ + int i; + xmlNode *p; + + // Enumerate the properties + for ( i = 0; store != NULL && i < mlt_properties_count( properties ); i++ ) + { + char *name = mlt_properties_get_name( properties, i ); + if ( !strncmp( name, store, strlen( store ) ) ) + { + char *value = mlt_properties_get_value( properties, i ); + if ( value != NULL ) + { + if ( strcmp( context->root, "" ) && !strncmp( value, context->root, strlen( context->root ) ) ) + value += strlen( context->root ) + 1; + p = xmlNewTextChild( node, NULL, _x("property"), _x(value) ); + xmlNewProp( p, _x("name"), _x(name) ); + } + } + } +} + +static inline void serialise_service_filters( serialise_context context, mlt_service service, xmlNode *node ) +{ + int i; + xmlNode *p; + mlt_filter filter = NULL; + + // Enumerate the filters + for ( i = 0; ( filter = mlt_producer_filter( MLT_PRODUCER( service ), i ) ) != NULL; i ++ ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + if ( mlt_properties_get_int( properties, "_fezzik" ) == 0 ) + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, MLT_FILTER_SERVICE( filter ), westley_filter ); + if ( id != NULL ) + { + int in = mlt_properties_get_position( properties, "in" ); + int out = mlt_properties_get_position( properties, "out" ); + p = xmlNewChild( node, NULL, _x("filter"), NULL ); + xmlNewProp( p, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( p, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + if ( in != 0 || out != 0 ) + { + char temp[ 20 ]; + sprintf( temp, "%d", in ); + xmlNewProp( p, _x("in"), _x(temp) ); + sprintf( temp, "%d", out ); + xmlNewProp( p, _x("out"), _x(temp) ); + } + serialise_properties( context, properties, p ); + serialise_service_filters( context, MLT_FILTER_SERVICE( filter ), p ); + } + } + } +} + +static void serialise_producer( serialise_context context, mlt_service service, xmlNode *node ) +{ + xmlNode *child = node; + mlt_service parent = MLT_SERVICE( mlt_producer_cut_parent( MLT_PRODUCER( service ) ) ); + + if ( context->pass == 0 ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( parent ); + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, parent, westley_producer ); + if ( id == NULL ) + return; + + child = xmlNewChild( node, NULL, _x("producer"), NULL ); + + // Set the id + xmlNewProp( child, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + xmlNewProp( child, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( child, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + serialise_properties( context, properties, child ); + serialise_service_filters( context, service, child ); + + // Add producer to the map + mlt_properties_set_int( context->hide_map, id, mlt_properties_get_int( properties, "hide" ) ); + } + else + { + char *id = westley_get_id( context, parent, westley_existing ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + xmlNewProp( node, _x("parent"), _x(id) ); + xmlNewProp( node, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( node, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + } +} + +static void serialise_tractor( serialise_context context, mlt_service service, xmlNode *node ); + +static void serialise_multitrack( serialise_context context, mlt_service service, xmlNode *node ) +{ + int i; + + if ( context->pass == 0 ) + { + // Iterate over the tracks to collect the producers + for ( i = 0; i < mlt_multitrack_count( MLT_MULTITRACK( service ) ); i++ ) + { + mlt_producer producer = mlt_producer_cut_parent( mlt_multitrack_track( MLT_MULTITRACK( service ), i ) ); + serialise_service( context, MLT_SERVICE( producer ), node ); + } + } + else + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, service, westley_multitrack ); + if ( id == NULL ) + return; + + // Serialise the tracks + for ( i = 0; i < mlt_multitrack_count( MLT_MULTITRACK( service ) ); i++ ) + { + xmlNode *track = xmlNewChild( node, NULL, _x("track"), NULL ); + int hide = 0; + mlt_producer producer = mlt_multitrack_track( MLT_MULTITRACK( service ), i ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + mlt_service parent = MLT_SERVICE( mlt_producer_cut_parent( producer ) ); + + char *id = westley_get_id( context, MLT_SERVICE( parent ), westley_existing ); + xmlNewProp( track, _x("producer"), _x(id) ); + if ( mlt_producer_is_cut( producer ) ) + { + xmlNewProp( track, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( track, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( producer ), track, context->store ); + serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( producer ), track, "meta." ); + serialise_service_filters( context, MLT_PRODUCER_SERVICE( producer ), track ); + } + + hide = mlt_properties_get_int( context->hide_map, id ); + if ( hide ) + xmlNewProp( track, _x("hide"), _x( hide == 1 ? "video" : ( hide == 2 ? "audio" : "both" ) ) ); + } + serialise_service_filters( context, service, node ); + } +} + +static void serialise_playlist( serialise_context context, mlt_service service, xmlNode *node ) +{ + int i; + xmlNode *child = node; + mlt_playlist_clip_info info; + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + if ( context->pass == 0 ) + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, service, westley_playlist ); + if ( id == NULL ) + return; + + // Iterate over the playlist entries to collect the producers + for ( i = 0; i < mlt_playlist_count( MLT_PLAYLIST( service ) ); i++ ) + { + if ( ! mlt_playlist_get_clip_info( MLT_PLAYLIST( service ), &info, i ) ) + { + if ( info.producer != NULL ) + { + mlt_producer producer = mlt_producer_cut_parent( info.producer ); + char *service_s = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "mlt_service" ); + char *resource_s = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "resource" ); + if ( resource_s != NULL && !strcmp( resource_s, "" ) ) + serialise_playlist( context, MLT_SERVICE( producer ), node ); + else if ( service_s != NULL && strcmp( service_s, "blank" ) != 0 ) + serialise_service( context, MLT_SERVICE( producer ), node ); + } + } + } + + child = xmlNewChild( node, NULL, _x("playlist"), NULL ); + + // Set the id + xmlNewProp( child, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + + // Store application specific properties + serialise_store_properties( context, properties, child, context->store ); + serialise_store_properties( context, properties, child, "meta." ); + + // Add producer to the map + mlt_properties_set_int( context->hide_map, id, mlt_properties_get_int( properties, "hide" ) ); + + // Iterate over the playlist entries + for ( i = 0; i < mlt_playlist_count( MLT_PLAYLIST( service ) ); i++ ) + { + if ( ! mlt_playlist_get_clip_info( MLT_PLAYLIST( service ), &info, i ) ) + { + mlt_producer producer = mlt_producer_cut_parent( info.producer ); + char *service_s = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "mlt_service" ); + if ( service_s != NULL && strcmp( service_s, "blank" ) == 0 ) + { + char length[ 20 ]; + length[ 19 ] = '\0'; + xmlNode *entry = xmlNewChild( child, NULL, _x("blank"), NULL ); + snprintf( length, 19, "%d", (int)info.frame_count ); + xmlNewProp( entry, _x("length"), _x(length) ); + } + else + { + char temp[ 20 ]; + xmlNode *entry = xmlNewChild( child, NULL, _x("entry"), NULL ); + id = westley_get_id( context, MLT_SERVICE( producer ), westley_existing ); + xmlNewProp( entry, _x("producer"), _x(id) ); + sprintf( temp, "%d", (int)info.frame_in ); + xmlNewProp( entry, _x("in"), _x(temp) ); + sprintf( temp, "%d", (int)info.frame_out ); + xmlNewProp( entry, _x("out"), _x(temp) ); + if ( info.repeat > 1 ) + { + sprintf( temp, "%d", info.repeat ); + xmlNewProp( entry, _x("repeat"), _x(temp) ); + } + if ( mlt_producer_is_cut( info.cut ) ) + { + serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( info.cut ), entry, context->store ); + serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( info.cut ), entry, "meta." ); + serialise_service_filters( context, MLT_PRODUCER_SERVICE( info.cut ), entry ); + } + } + } + } + + serialise_service_filters( context, service, child ); + } + else if ( xmlStrcmp( node->name, _x("tractor") ) != 0 ) + { + char *id = westley_get_id( context, service, westley_existing ); + xmlNewProp( node, _x("producer"), _x(id) ); + } +} + +static void serialise_tractor( serialise_context context, mlt_service service, xmlNode *node ) +{ + xmlNode *child = node; + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + if ( context->pass == 0 ) + { + // Recurse on connected producer + serialise_service( context, mlt_service_producer( service ), node ); + } + else + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, service, westley_tractor ); + if ( id == NULL ) + return; + + child = xmlNewChild( node, NULL, _x("tractor"), NULL ); + + // Set the id + xmlNewProp( child, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + if ( mlt_properties_get( properties, "global_feed" ) ) + xmlNewProp( child, _x("global_feed"), _x(mlt_properties_get( properties, "global_feed" )) ); + xmlNewProp( child, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( child, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + + // Store application specific properties + serialise_store_properties( context, MLT_SERVICE_PROPERTIES( service ), child, context->store ); + serialise_store_properties( context, MLT_SERVICE_PROPERTIES( service ), child, "meta." ); + + // Recurse on connected producer + serialise_service( context, mlt_service_producer( service ), child ); + serialise_service_filters( context, service, child ); + } +} + +static void serialise_filter( serialise_context context, mlt_service service, xmlNode *node ) +{ + xmlNode *child = node; + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + // Recurse on connected producer + serialise_service( context, mlt_service_producer( service ), node ); + + if ( context->pass == 1 ) + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, service, westley_filter ); + if ( id == NULL ) + return; + + child = xmlNewChild( node, NULL, _x("filter"), NULL ); + + // Set the id + xmlNewProp( child, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + xmlNewProp( child, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( child, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + + serialise_properties( context, properties, child ); + serialise_service_filters( context, service, child ); + } +} + +static void serialise_transition( serialise_context context, mlt_service service, xmlNode *node ) +{ + xmlNode *child = node; + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + // Recurse on connected producer + serialise_service( context, MLT_SERVICE( MLT_TRANSITION( service )->producer ), node ); + + if ( context->pass == 1 ) + { + // Get a new id - if already allocated, do nothing + char *id = westley_get_id( context, service, westley_transition ); + if ( id == NULL ) + return; + + child = xmlNewChild( node, NULL, _x("transition"), NULL ); + + // Set the id + xmlNewProp( child, _x("id"), _x(id) ); + if ( mlt_properties_get( properties, "title" ) ) + xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + xmlNewProp( child, _x("in"), _x(mlt_properties_get( properties, "in" )) ); + xmlNewProp( child, _x("out"), _x(mlt_properties_get( properties, "out" )) ); + + serialise_properties( context, properties, child ); + serialise_service_filters( context, service, child ); + } +} + +static void serialise_service( serialise_context context, mlt_service service, xmlNode *node ) +{ + // Iterate over consumer/producer connections + while ( service != NULL ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + char *mlt_type = mlt_properties_get( properties, "mlt_type" ); + + // Tell about the producer + if ( strcmp( mlt_type, "producer" ) == 0 ) + { + char *mlt_service = mlt_properties_get( properties, "mlt_service" ); + if ( mlt_properties_get( properties, "westley" ) == NULL && ( mlt_service != NULL && !strcmp( mlt_service, "tractor" ) ) ) + { + context->pass = 0; + serialise_tractor( context, service, node ); + context->pass = 1; + serialise_tractor( context, service, node ); + context->pass = 0; + break; + } + else + { + serialise_producer( context, service, node ); + } + if ( mlt_properties_get( properties, "westley" ) != NULL ) + break; + } + + // Tell about the framework container producers + else if ( strcmp( mlt_type, "mlt_producer" ) == 0 ) + { + char *resource = mlt_properties_get( properties, "resource" ); + + // Recurse on multitrack's tracks + if ( strcmp( resource, "" ) == 0 ) + { + serialise_multitrack( context, service, node ); + break; + } + + // Recurse on playlist's clips + else if ( strcmp( resource, "" ) == 0 ) + { + serialise_playlist( context, service, node ); + } + + // Recurse on tractor's producer + else if ( strcmp( resource, "" ) == 0 ) + { + context->pass = 0; + serialise_tractor( context, service, node ); + context->pass = 1; + serialise_tractor( context, service, node ); + context->pass = 0; + break; + } + + // Treat it as a normal producer + else + { + serialise_producer( context, service, node ); + } + } + + // Tell about a filter + else if ( strcmp( mlt_type, "filter" ) == 0 ) + { + serialise_filter( context, service, node ); + break; + } + + // Tell about a transition + else if ( strcmp( mlt_type, "transition" ) == 0 ) + { + serialise_transition( context, service, node ); + break; + } + + // Get the next connected service + service = mlt_service_producer( service ); + } +} + +xmlDocPtr westley_make_doc( mlt_consumer consumer, mlt_service service ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + xmlDocPtr doc = xmlNewDoc( _x("1.0") ); + xmlNodePtr root = xmlNewNode( NULL, _x("westley") ); + struct serialise_context_s *context = calloc( 1, sizeof( struct serialise_context_s ) ); + + xmlDocSetRootElement( doc, root ); + + // If we have root, then deal with it now + if ( mlt_properties_get( properties, "root" ) != NULL ) + { + xmlNewProp( root, _x("root"), _x(mlt_properties_get( properties, "root" )) ); + context->root = strdup( mlt_properties_get( properties, "root" ) ); + } + else + { + context->root = strdup( "" ); + } + + // Assign the additional 'storage' pattern for properties + context->store = mlt_properties_get( MLT_CONSUMER_PROPERTIES( consumer ), "store" ); + + // Assign a title property + if ( mlt_properties_get( properties, "title" ) != NULL ) + xmlNewProp( root, _x("title"), _x(mlt_properties_get( properties, "title" )) ); + mlt_properties_set_int( properties, "global_feed", 1 ); + + // Construct the context maps + context->id_map = mlt_properties_new(); + context->hide_map = mlt_properties_new(); + + // Ensure producer is a framework producer + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "mlt_type", "mlt_producer" ); + + // In pass one, we serialise the end producers and playlists, + // adding them to a map keyed by address. + serialise_service( context, service, root ); + + // In pass two, we serialise the tractor and reference the + // producers and playlists + context->pass++; + serialise_service( context, service, root ); + + // Cleanup resource + mlt_properties_close( context->id_map ); + mlt_properties_close( context->hide_map ); + free( context->root ); + free( context ); + + return doc; +} + +static int consumer_start( mlt_consumer this ) +{ + xmlDocPtr doc = NULL; + + // Get the producer service + mlt_service service = mlt_service_producer( MLT_CONSUMER_SERVICE( this ) ); + if ( service != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + char *resource = mlt_properties_get( properties, "resource" ); + + // Set the title if provided + if ( mlt_properties_get( properties, "title" ) ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "title", mlt_properties_get( properties, "title" ) ); + else if ( mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "title" ) == NULL ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "title", "Anonymous Submission" ); + + // Check for a root on the consumer properties and pass to service + if ( mlt_properties_get( properties, "root" ) ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "root", mlt_properties_get( properties, "root" ) ); + + // Specify roots in other cases... + if ( resource != NULL && mlt_properties_get( properties, "root" ) == NULL ) + { + // Get the current working directory + char *cwd = getcwd( NULL, 0 ); + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "root", cwd ); + free( cwd ); + } + + // Make the document + doc = westley_make_doc( this, service ); + + // Handle the output + if ( resource == NULL || !strcmp( resource, "" ) ) + { + xmlDocFormatDump( stdout, doc, 1 ); + } + else if ( strchr( resource, '.' ) == NULL ) + { + xmlChar *buffer = NULL; + int length = 0; + xmlDocDumpMemoryEnc( doc, &buffer, &length, "utf-8" ); + mlt_properties_set( properties, resource, _s(buffer) ); + xmlFree( buffer ); + } + else + { + xmlSaveFormatFileEnc( resource, doc, "utf-8", 1 ); + } + + // Close the document + xmlFreeDoc( doc ); + } + + mlt_consumer_stop( this ); + + mlt_consumer_stopped( this ); + + return 0; +} + +static int consumer_is_stopped( mlt_consumer this ) +{ + return 1; +} diff --git a/src/modules/westley/consumer_westley.h b/src/modules/westley/consumer_westley.h new file mode 100644 index 0000000..3a1e726 --- /dev/null +++ b/src/modules/westley/consumer_westley.h @@ -0,0 +1,28 @@ +/* + * consumer_westley.h -- a libxml2 serialiser of mlt service networks + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_WESTLEY_H_ +#define _CONSUMER_WESTLEY_H_ + +#include + +extern mlt_consumer consumer_westley_init( char * ); + +#endif diff --git a/src/modules/westley/factory.c b/src/modules/westley/factory.c new file mode 100644 index 0000000..8a405cb --- /dev/null +++ b/src/modules/westley/factory.c @@ -0,0 +1,51 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "consumer_westley.h" +#include "producer_westley.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "westley" ) ) + return producer_westley_init( 0, arg ); + if ( !strcmp( id, "westley-xml" ) ) + return producer_westley_init( 1, arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "westley" ) ) + return consumer_westley_init( arg ); + return NULL; +} + diff --git a/src/modules/westley/producer_westley.c b/src/modules/westley/producer_westley.c new file mode 100644 index 0000000..8df50f1 --- /dev/null +++ b/src/modules/westley/producer_westley.c @@ -0,0 +1,1512 @@ +/* + * producer_westley.c -- a libxml2 parser of mlt service networks + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// TODO: destroy unreferenced producers (they are currently destroyed +// when the returned producer is closed). + +#include "producer_westley.h" +#include +#include +#include +#include +#include +#include + +#include +#include // for xmlCreateFileParserCtxt +#include + +#define STACK_SIZE 1000 +#define BRANCH_SIG_LEN 4000 + +#define _x (xmlChar*) +#define _s (char*) + +#undef DEBUG +#ifdef DEBUG +extern xmlDocPtr westley_make_doc( mlt_service service ); +#endif + +enum service_type +{ + mlt_invalid_type, + mlt_unknown_type, + mlt_producer_type, + mlt_playlist_type, + mlt_entry_type, + mlt_tractor_type, + mlt_multitrack_type, + mlt_filter_type, + mlt_transition_type, + mlt_consumer_type, + mlt_field_type, + mlt_services_type, + mlt_dummy_filter_type, + mlt_dummy_transition_type, + mlt_dummy_producer_type, +}; + +struct deserialise_context_s +{ + enum service_type stack_types[ STACK_SIZE ]; + mlt_service stack_service[ STACK_SIZE ]; + int stack_service_size; + mlt_properties producer_map; + mlt_properties destructors; + char *property; + int is_value; + xmlDocPtr value_doc; + xmlNodePtr stack_node[ STACK_SIZE ]; + int stack_node_size; + xmlDocPtr entity_doc; + int entity_is_replace; + int depth; + int branch[ STACK_SIZE ]; + const xmlChar *publicId; + const xmlChar *systemId; + mlt_properties params; +}; +typedef struct deserialise_context_s *deserialise_context; + +/** Convert the numerical current branch address to a dot-delimited string. +*/ +static char *serialise_branch( deserialise_context this, char *s ) +{ + int i; + + s[0] = 0; + for ( i = 0; i < this->depth; i++ ) + { + int len = strlen( s ); + snprintf( s + len, BRANCH_SIG_LEN - len, "%d.", this->branch[ i ] ); + } + return s; +} + +/** Push a service. +*/ + +static int context_push_service( deserialise_context this, mlt_service that, enum service_type type ) +{ + int ret = this->stack_service_size >= STACK_SIZE - 1; + if ( ret == 0 ) + { + this->stack_service[ this->stack_service_size ] = that; + this->stack_types[ this->stack_service_size++ ] = type; + + // Record the tree branch on which this service lives + if ( that != NULL && mlt_properties_get( MLT_SERVICE_PROPERTIES( that ), "_westley_branch" ) == NULL ) + { + char s[ BRANCH_SIG_LEN ]; + mlt_properties_set( MLT_SERVICE_PROPERTIES( that ), "_westley_branch", serialise_branch( this, s ) ); + } + } + return ret; +} + +/** Pop a service. +*/ + +static mlt_service context_pop_service( deserialise_context this, enum service_type *type ) +{ + mlt_service result = NULL; + if ( this->stack_service_size > 0 ) + { + result = this->stack_service[ -- this->stack_service_size ]; + if ( type != NULL ) + *type = this->stack_types[ this->stack_service_size ]; + } + return result; +} + +/** Push a node. +*/ + +static int context_push_node( deserialise_context this, xmlNodePtr node ) +{ + int ret = this->stack_node_size >= STACK_SIZE - 1; + if ( ret == 0 ) + this->stack_node[ this->stack_node_size ++ ] = node; + return ret; +} + +/** Pop a node. +*/ + +static xmlNodePtr context_pop_node( deserialise_context this ) +{ + xmlNodePtr result = NULL; + if ( this->stack_node_size > 0 ) + result = this->stack_node[ -- this->stack_node_size ]; + return result; +} + + +// Set the destructor on a new service +static void track_service( mlt_properties properties, void *service, mlt_destructor destructor ) +{ + int registered = mlt_properties_get_int( properties, "registered" ); + char *key = mlt_properties_get( properties, "registered" ); + mlt_properties_set_data( properties, key, service, 0, destructor, NULL ); + mlt_properties_set_int( properties, "registered", ++ registered ); +} + + +// Prepend the property value with the document root +static inline void qualify_property( deserialise_context context, mlt_properties properties, char *name ) +{ + char *resource = mlt_properties_get( properties, name ); + if ( resource != NULL ) + { + // Qualify file name properties + char *root = mlt_properties_get( context->producer_map, "root" ); + if ( root != NULL && strcmp( root, "" ) ) + { + char *full_resource = malloc( strlen( root ) + strlen( resource ) + 2 ); + if ( resource[ 0 ] != '/' && strchr( resource, ':' ) == NULL ) + { + strcpy( full_resource, root ); + strcat( full_resource, "/" ); + strcat( full_resource, resource ); + } + else + { + strcpy( full_resource, resource ); + } + mlt_properties_set( properties, name, full_resource ); + free( full_resource ); + } + } +} + + +/** This function adds a producer to a playlist or multitrack when + there is no entry or track element. +*/ + +static int add_producer( deserialise_context context, mlt_service service, mlt_position in, mlt_position out ) +{ + // Return value (0 = service remains top of stack, 1 means it can be removed) + int result = 0; + + // Get the parent producer + enum service_type type; + mlt_service container = context_pop_service( context, &type ); + int contained = 0; + + if ( service != NULL && container != NULL ) + { + char *container_branch = mlt_properties_get( MLT_SERVICE_PROPERTIES( container ), "_westley_branch" ); + char *service_branch = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "_westley_branch" ); + contained = !strncmp( container_branch, service_branch, strlen( container_branch ) ); + } + + if ( contained ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + char *hide_s = mlt_properties_get( properties, "hide" ); + + // Indicate that this service is no longer top of stack + result = 1; + + switch( type ) + { + case mlt_tractor_type: + { + mlt_multitrack multitrack = mlt_tractor_multitrack( MLT_TRACTOR( container ) ); + mlt_multitrack_connect( multitrack, MLT_PRODUCER( service ), mlt_multitrack_count( multitrack ) ); + } + break; + case mlt_multitrack_type: + { + mlt_multitrack_connect( MLT_MULTITRACK( container ), + MLT_PRODUCER( service ), + mlt_multitrack_count( MLT_MULTITRACK( container ) ) ); + } + break; + case mlt_playlist_type: + { + mlt_playlist_append_io( MLT_PLAYLIST( container ), MLT_PRODUCER( service ), in, out ); + } + break; + default: + result = 0; + fprintf( stderr, "Producer defined inside something that isn't a container\n" ); + break; + }; + + // Set the hide state of the track producer + if ( hide_s != NULL ) + { + if ( strcmp( hide_s, "video" ) == 0 ) + mlt_properties_set_int( properties, "hide", 1 ); + else if ( strcmp( hide_s, "audio" ) == 0 ) + mlt_properties_set_int( properties, "hide", 2 ); + else if ( strcmp( hide_s, "both" ) == 0 ) + mlt_properties_set_int( properties, "hide", 3 ); + } + } + + // Put the parent producer back + if ( container != NULL ) + context_push_service( context, container, type ); + + return result; +} + +/** Attach filters defined on that to this. +*/ + +static void attach_filters( mlt_service this, mlt_service that ) +{ + if ( that != NULL ) + { + int i = 0; + mlt_filter filter = NULL; + for ( i = 0; ( filter = mlt_service_filter( that, i ) ) != NULL; i ++ ) + { + mlt_service_attach( this, filter ); + attach_filters( MLT_FILTER_SERVICE( filter ), MLT_FILTER_SERVICE( filter ) ); + } + } +} + +static void on_start_tractor( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + mlt_tractor tractor = mlt_tractor_new( ); + mlt_service service = MLT_TRACTOR_SERVICE( tractor ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + track_service( context->destructors, service, (mlt_destructor) mlt_tractor_close ); + + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); + + mlt_properties_set_int( MLT_TRACTOR_PROPERTIES( tractor ), "global_feed", 1 ); + + if ( mlt_properties_get( properties, "id" ) != NULL ) + mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL ); + + context_push_service( context, service, mlt_tractor_type ); +} + +static void on_end_tractor( deserialise_context context, const xmlChar *name ) +{ + // Get the tractor + enum service_type type; + mlt_service tractor = context_pop_service( context, &type ); + + if ( tractor != NULL && type == mlt_tractor_type ) + { + // See if the tractor should be added to a playlist or multitrack + if ( add_producer( context, tractor, 0, mlt_producer_get_out( MLT_PRODUCER( tractor ) ) ) == 0 ) + context_push_service( context, tractor, type ); + } + else + { + fprintf( stderr, "Invalid state for tractor\n" ); + } +} + +static void on_start_multitrack( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + enum service_type type; + mlt_service parent = context_pop_service( context, &type ); + + // If we don't have a parent, then create one now, providing we're in a state where we can + if ( parent == NULL || ( type == mlt_playlist_type || type == mlt_multitrack_type ) ) + { + mlt_tractor tractor = NULL; + // Push the parent back + if ( parent != NULL ) + context_push_service( context, parent, type ); + + // Create a tractor to contain the multitrack + tractor = mlt_tractor_new( ); + parent = MLT_TRACTOR_SERVICE( tractor ); + track_service( context->destructors, parent, (mlt_destructor) mlt_tractor_close ); + type = mlt_tractor_type; + + // Flag it as a synthesised tractor for clean up later + mlt_properties_set_int( MLT_SERVICE_PROPERTIES( parent ), "fezzik_synth", 1 ); + } + + if ( type == mlt_tractor_type ) + { + mlt_service service = MLT_SERVICE( mlt_tractor_multitrack( MLT_TRACTOR( parent ) ) ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( properties, (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); + + if ( mlt_properties_get( properties, "id" ) != NULL ) + mlt_properties_set_data( context->producer_map, mlt_properties_get( properties,"id" ), service, 0, NULL, NULL ); + + context_push_service( context, parent, type ); + context_push_service( context, service, mlt_multitrack_type ); + } + else + { + fprintf( stderr, "Invalid multitrack position\n" ); + } +} + +static void on_end_multitrack( deserialise_context context, const xmlChar *name ) +{ + // Get the multitrack from the stack + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + + if ( service == NULL || type != mlt_multitrack_type ) + fprintf( stderr, "End multitrack in the wrong state...\n" ); +} + +static void on_start_playlist( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + mlt_playlist playlist = mlt_playlist_init( ); + mlt_service service = MLT_PLAYLIST_SERVICE( playlist ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + track_service( context->destructors, service, (mlt_destructor) mlt_playlist_close ); + + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + { + mlt_properties_set( properties, (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); + + // Out will be overwritten later as we append, so we need to save it + if ( xmlStrcmp( atts[ 0 ], _x("out") ) == 0 ) + mlt_properties_set( properties, "_westley.out", ( char* )atts[ 1 ] ); + } + + if ( mlt_properties_get( properties, "id" ) != NULL ) + mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL ); + + context_push_service( context, service, mlt_playlist_type ); +} + +static void on_end_playlist( deserialise_context context, const xmlChar *name ) +{ + // Get the playlist from the stack + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + + if ( service != NULL && type == mlt_playlist_type ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + + // See if the playlist should be added to a playlist or multitrack + if ( add_producer( context, service, in, out ) == 0 ) + context_push_service( context, service, type ); + } + else + { + fprintf( stderr, "Invalid state of playlist end\n" ); + } +} + +static void on_start_producer( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + // use a dummy service to hold properties to allow arbitrary nesting + mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) ); + mlt_service_init( service, NULL ); + + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + context_push_service( context, service, mlt_dummy_producer_type ); + + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( properties, (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); +} + +// Parse a SMIL clock value (as produced by Kino 0.9.1) and return position in frames +static mlt_position parse_clock_value( char *value, double fps ) +{ + // This implementation expects a fully specified clock value - no optional + // parts (e.g. 1:05) + char *pos, *copy = strdup( value ); + int hh, mm, ss, ms; + mlt_position result = -1; + + value = copy; + pos = strchr( value, ':' ); + if ( !pos ) + return result; + *pos = '\0'; + hh = atoi( value ); + value = pos + 1; + + pos = strchr( value, ':' ); + if ( !pos ) + return result; + *pos = '\0'; + mm = atoi( value ); + value = pos + 1; + + pos = strchr( value, '.' ); + if ( !pos ) + return result; + *pos = '\0'; + ss = atoi( value ); + value = pos + 1; + + ms = atoi( value ); + free( copy ); + result = ( fps * ( ( (hh * 3600) + (mm * 60) + ss ) * 1000 + ms ) / 1000 + 0.5 ); + + return result; +} + +static void on_end_producer( deserialise_context context, const xmlChar *name ) +{ + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + if ( service != NULL && type == mlt_dummy_producer_type ) + { + mlt_service producer = NULL; + + qualify_property( context, properties, "resource" ); + char *resource = mlt_properties_get( properties, "resource" ); + + // Let Kino-SMIL src be a synonym for resource + if ( resource == NULL ) + { + qualify_property( context, properties, "src" ); + resource = mlt_properties_get( properties, "src" ); + } + + // Instantiate the producer + if ( mlt_properties_get( properties, "mlt_service" ) != NULL ) + { + char temp[ 1024 ]; + strncpy( temp, mlt_properties_get( properties, "mlt_service" ), 1024 ); + if ( resource != NULL ) + { + strcat( temp, ":" ); + strncat( temp, resource, 1023 - strlen( temp ) ); + } + producer = MLT_SERVICE( mlt_factory_producer( "fezzik", temp ) ); + } + + // Just in case the plugin requested doesn't exist... + if ( producer == NULL && resource != NULL ) + producer = MLT_SERVICE( mlt_factory_producer( "fezzik", resource ) ); + + if ( producer == NULL ) + producer = MLT_SERVICE( mlt_factory_producer( "fezzik", "+INVALID.txt" ) ); + + if ( producer == NULL ) + producer = MLT_SERVICE( mlt_factory_producer( "fezzik", "colour:red" ) ); + + // Track this producer + track_service( context->destructors, producer, (mlt_destructor) mlt_producer_close ); + + // Propogate the properties + qualify_property( context, properties, "resource" ); + qualify_property( context, properties, "luma" ); + qualify_property( context, properties, "luma.resource" ); + qualify_property( context, properties, "composite.luma" ); + qualify_property( context, properties, "producer.resource" ); + + // Handle in/out properties separately + mlt_position in = -1; + mlt_position out = -1; + + // Get in + if ( mlt_properties_get( properties, "in" ) != NULL ) + in = mlt_properties_get_position( properties, "in" ); + // Let Kino-SMIL clipBegin be a synonym for in + if ( mlt_properties_get( properties, "clipBegin" ) != NULL ) + { + if ( strchr( mlt_properties_get( properties, "clipBegin" ), ':' ) ) + // Parse clock value + in = parse_clock_value( mlt_properties_get( properties, "clipBegin" ), + mlt_producer_get_fps( MLT_PRODUCER( producer ) ) ); + else + // Parse frames value + in = mlt_properties_get_position( properties, "clipBegin" ); + } + // Get out + if ( mlt_properties_get( properties, "out" ) != NULL ) + out = mlt_properties_get_position( properties, "out" ); + // Let Kino-SMIL clipEnd be a synonym for out + if ( mlt_properties_get( properties, "clipEnd" ) != NULL ) + { + if ( strchr( mlt_properties_get( properties, "clipEnd" ), ':' ) ) + // Parse clock value + out = parse_clock_value( mlt_properties_get( properties, "clipEnd" ), + mlt_producer_get_fps( MLT_PRODUCER( producer ) ) ); + else + // Parse frames value + out = mlt_properties_get_position( properties, "clipEnd" ); + } + // Remove in and out + mlt_properties_set( properties, "in", NULL ); + mlt_properties_set( properties, "out", NULL ); + + // Inherit the properties + mlt_properties_inherit( MLT_SERVICE_PROPERTIES( producer ), properties ); + + // Attach all filters from service onto producer + attach_filters( producer, service ); + + // Add the producer to the producer map + if ( mlt_properties_get( properties, "id" ) != NULL ) + mlt_properties_set_data( context->producer_map, mlt_properties_get(properties, "id"), producer, 0, NULL, NULL ); + + // See if the producer should be added to a playlist or multitrack + if ( add_producer( context, producer, in, out ) == 0 ) + { + // Otherwise, set in and out on... + if ( in != -1 || out != -1 ) + { + // Get the parent service + enum service_type type; + mlt_service parent = context_pop_service( context, &type ); + if ( parent != NULL ) + { + // Get the parent properties + properties = MLT_SERVICE_PROPERTIES( parent ); + + char *resource = mlt_properties_get( properties, "resource" ); + + // Put the parent producer back + context_push_service( context, parent, type ); + + // If the parent is a track or entry + if ( resource && ( strcmp( resource, "" ) == 0 ) ) + { + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); + } + else + { + // Otherwise, set in and out on producer directly + mlt_producer_set_in_and_out( MLT_PRODUCER( producer ), in, out ); + } + } + else + { + // Otherwise, set in and out on producer directly + mlt_producer_set_in_and_out( MLT_PRODUCER( producer ), in, out ); + } + } + + // Push the producer onto the stack + context_push_service( context, producer, mlt_producer_type ); + } + + mlt_service_close( service ); + } +} + +static void on_start_blank( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + // Get the playlist from the stack + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_position length = 0; + + if ( type == mlt_playlist_type && service != NULL ) + { + // Look for the length attribute + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + { + if ( xmlStrcmp( atts[0], _x("length") ) == 0 ) + { + length = atoll( _s(atts[1]) ); + break; + } + } + + // Append a blank to the playlist + mlt_playlist_blank( MLT_PLAYLIST( service ), length - 1 ); + + // Push the playlist back onto the stack + context_push_service( context, service, type ); + } + else + { + fprintf( stderr, "blank without a playlist - a definite no no\n" ); + } +} + +static void on_start_entry( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + mlt_producer entry = NULL; + mlt_properties temp = mlt_properties_new( ); + + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + { + mlt_properties_set( temp, (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); + + // Look for the producer attribute + if ( xmlStrcmp( atts[ 0 ], _x("producer") ) == 0 ) + { + mlt_producer producer = mlt_properties_get_data( context->producer_map, (char*) atts[1], NULL ); + if ( producer != NULL ) + mlt_properties_set_data( temp, "producer", producer, 0, NULL, NULL ); + } + } + + // If we have a valid entry + if ( mlt_properties_get_data( temp, "producer", NULL ) != NULL ) + { + mlt_playlist_clip_info info; + enum service_type parent_type = invalid_type; + mlt_service parent = context_pop_service( context, &parent_type ); + mlt_producer producer = mlt_properties_get_data( temp, "producer", NULL ); + + if ( parent_type == mlt_playlist_type ) + { + // Append the producer to the playlist + if ( mlt_properties_get( temp, "in" ) != NULL || mlt_properties_get( temp, "out" ) != NULL ) + { + mlt_playlist_append_io( MLT_PLAYLIST( parent ), producer, + mlt_properties_get_position( temp, "in" ), + mlt_properties_get_position( temp, "out" ) ); + } + else + { + mlt_playlist_append( MLT_PLAYLIST( parent ), producer ); + } + + // Handle the repeat property + if ( mlt_properties_get_int( temp, "repeat" ) > 0 ) + { + mlt_playlist_repeat_clip( MLT_PLAYLIST( parent ), + mlt_playlist_count( MLT_PLAYLIST( parent ) ) - 1, + mlt_properties_get_int( temp, "repeat" ) ); + } + + mlt_playlist_get_clip_info( MLT_PLAYLIST( parent ), &info, mlt_playlist_count( MLT_PLAYLIST( parent ) ) - 1 ); + entry = info.cut; + } + else + { + fprintf( stderr, "Entry not part of a playlist...\n" ); + } + + context_push_service( context, parent, parent_type ); + } + + // Push the cut onto the stack + context_push_service( context, MLT_PRODUCER_SERVICE( entry ), mlt_entry_type ); + + mlt_properties_close( temp ); +} + +static void on_end_entry( deserialise_context context, const xmlChar *name ) +{ + // Get the entry from the stack + enum service_type entry_type = invalid_type; + mlt_service entry = context_pop_service( context, &entry_type ); + + if ( entry == NULL && entry_type != mlt_entry_type ) + { + fprintf( stderr, "Invalid state at end of entry\n" ); + } +} + +static void on_start_track( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + // use a dummy service to hold properties to allow arbitrary nesting + mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) ); + mlt_service_init( service, NULL ); + + // Push the dummy service onto the stack + context_push_service( context, service, mlt_entry_type ); + + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "resource", "" ); + + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + { + mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), (char*) atts[0], atts[1] == NULL ? "" : (char*) atts[1] ); + + // Look for the producer attribute + if ( xmlStrcmp( atts[ 0 ], _x("producer") ) == 0 ) + { + mlt_producer producer = mlt_properties_get_data( context->producer_map, (char*) atts[1], NULL ); + if ( producer != NULL ) + mlt_properties_set_data( MLT_SERVICE_PROPERTIES( service ), "producer", producer, 0, NULL, NULL ); + } + } +} + +static void on_end_track( deserialise_context context, const xmlChar *name ) +{ + // Get the track from the stack + enum service_type track_type; + mlt_service track = context_pop_service( context, &track_type ); + + if ( track != NULL && track_type == mlt_entry_type ) + { + mlt_properties track_props = MLT_SERVICE_PROPERTIES( track ); + enum service_type parent_type = invalid_type; + mlt_service parent = context_pop_service( context, &parent_type ); + mlt_multitrack multitrack = NULL; + + mlt_producer producer = mlt_properties_get_data( track_props, "producer", NULL ); + mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer ); + + if ( parent_type == mlt_tractor_type ) + multitrack = mlt_tractor_multitrack( MLT_TRACTOR( parent ) ); + else if ( parent_type == mlt_multitrack_type ) + multitrack = MLT_MULTITRACK( parent ); + else + fprintf( stderr, "track contained in an invalid container\n" ); + + if ( multitrack != NULL ) + { + // Set producer i/o if specified + if ( mlt_properties_get( track_props, "in" ) != NULL || + mlt_properties_get( track_props, "out" ) != NULL ) + { + mlt_producer cut = mlt_producer_cut( MLT_PRODUCER( producer ), + mlt_properties_get_position( track_props, "in" ), + mlt_properties_get_position( track_props, "out" ) ); + mlt_multitrack_connect( multitrack, cut, mlt_multitrack_count( multitrack ) ); + mlt_properties_inherit( MLT_PRODUCER_PROPERTIES( cut ), track_props ); + track_props = MLT_PRODUCER_PROPERTIES( cut ); + mlt_producer_close( cut ); + } + else + { + mlt_multitrack_connect( multitrack, producer, mlt_multitrack_count( multitrack ) ); + } + + // Set the hide state of the track producer + char *hide_s = mlt_properties_get( track_props, "hide" ); + if ( hide_s != NULL ) + { + if ( strcmp( hide_s, "video" ) == 0 ) + mlt_properties_set_int( producer_props, "hide", 1 ); + else if ( strcmp( hide_s, "audio" ) == 0 ) + mlt_properties_set_int( producer_props, "hide", 2 ); + else if ( strcmp( hide_s, "both" ) == 0 ) + mlt_properties_set_int( producer_props, "hide", 3 ); + } + } + + if ( parent != NULL ) + context_push_service( context, parent, parent_type ); + + mlt_service_close( track ); + } + else + { + fprintf( stderr, "Invalid state at end of track\n" ); + } +} + +static void on_start_filter( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + // use a dummy service to hold properties to allow arbitrary nesting + mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) ); + mlt_service_init( service, NULL ); + + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + context_push_service( context, service, mlt_dummy_filter_type ); + + // Set the properties + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] ); +} + +static void on_end_filter( deserialise_context context, const xmlChar *name ) +{ + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + enum service_type parent_type = invalid_type; + mlt_service parent = context_pop_service( context, &parent_type ); + + if ( service != NULL && type == mlt_dummy_filter_type ) + { + mlt_service filter = MLT_SERVICE( mlt_factory_filter( mlt_properties_get( properties, "mlt_service" ), NULL ) ); + mlt_properties filter_props = MLT_SERVICE_PROPERTIES( filter ); + + track_service( context->destructors, filter, (mlt_destructor) mlt_filter_close ); + + // Propogate the properties + qualify_property( context, properties, "resource" ); + qualify_property( context, properties, "luma" ); + qualify_property( context, properties, "luma.resource" ); + qualify_property( context, properties, "composite.luma" ); + qualify_property( context, properties, "producer.resource" ); + mlt_properties_inherit( filter_props, properties ); + + // Attach all filters from service onto filter + attach_filters( filter, service ); + + // Associate the filter with the parent + if ( parent != NULL ) + { + if ( parent_type == mlt_tractor_type ) + { + mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) ); + mlt_field_plant_filter( field, MLT_FILTER( filter ), mlt_properties_get_int( properties, "track" ) ); + mlt_filter_set_in_and_out( MLT_FILTER( filter ), + mlt_properties_get_int( properties, "in" ), + mlt_properties_get_int( properties, "out" ) ); + } + else + { + mlt_service_attach( parent, MLT_FILTER( filter ) ); + } + + // Put the parent back on the stack + context_push_service( context, parent, parent_type ); + } + else + { + fprintf( stderr, "filter closed with invalid parent...\n" ); + } + + // Close the dummy filter service + mlt_service_close( service ); + } + else + { + fprintf( stderr, "Invalid top of stack on filter close\n" ); + } +} + +static void on_start_transition( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + // use a dummy service to hold properties to allow arbitrary nesting + mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) ); + mlt_service_init( service, NULL ); + + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + context_push_service( context, service, mlt_dummy_transition_type ); + + // Set the properties + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] ); +} + +static void on_end_transition( deserialise_context context, const xmlChar *name ) +{ + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + enum service_type parent_type = invalid_type; + mlt_service parent = context_pop_service( context, &parent_type ); + + if ( service != NULL && type == mlt_dummy_transition_type ) + { + mlt_service effect = MLT_SERVICE( mlt_factory_transition(mlt_properties_get(properties,"mlt_service"), NULL ) ); + mlt_properties effect_props = MLT_SERVICE_PROPERTIES( effect ); + + track_service( context->destructors, effect, (mlt_destructor) mlt_transition_close ); + + // Propogate the properties + qualify_property( context, properties, "resource" ); + qualify_property( context, properties, "luma" ); + qualify_property( context, properties, "luma.resource" ); + qualify_property( context, properties, "composite.luma" ); + qualify_property( context, properties, "producer.resource" ); + mlt_properties_inherit( effect_props, properties ); + + // Attach all filters from service onto effect + attach_filters( effect, service ); + + // Associate the filter with the parent + if ( parent != NULL ) + { + if ( parent_type == mlt_tractor_type ) + { + mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) ); + if ( mlt_properties_get_int( properties, "a_track" ) == mlt_properties_get_int( properties, "b_track" ) ) + mlt_properties_set_int( properties, "b_track", mlt_properties_get_int( properties, "a_track" ) + 1 ); + mlt_field_plant_transition( field, MLT_TRANSITION( effect ), + mlt_properties_get_int( properties, "a_track" ), + mlt_properties_get_int( properties, "b_track" ) ); + mlt_transition_set_in_and_out( MLT_TRANSITION( effect ), + mlt_properties_get_int( properties, "in" ), + mlt_properties_get_int( properties, "out" ) ); + } + else + { + fprintf( stderr, "Misplaced transition - ignoring\n" ); + } + + // Put the parent back on the stack + context_push_service( context, parent, parent_type ); + } + else + { + fprintf( stderr, "transition closed with invalid parent...\n" ); + } + + // Close the dummy filter service + mlt_service_close( service ); + } + else + { + fprintf( stderr, "Invalid top of stack on transition close\n" ); + } +} + +static void on_start_property( deserialise_context context, const xmlChar *name, const xmlChar **atts) +{ + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + char *value = NULL; + + if ( service != NULL ) + { + // Set the properties + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + { + if ( xmlStrcmp( atts[ 0 ], _x("name") ) == 0 ) + context->property = strdup( _s(atts[ 1 ]) ); + else if ( xmlStrcmp( atts[ 0 ], _x("value") ) == 0 ) + value = _s(atts[ 1 ]); + } + + if ( context->property != NULL ) + mlt_properties_set( properties, context->property, value == NULL ? "" : value ); + + // Tell parser to collect any further nodes for serialisation + context->is_value = 1; + + context_push_service( context, service, type ); + } + else + { + fprintf( stderr, "Property without a service '%s'?\n", ( char * )name ); + } +} + +static void on_end_property( deserialise_context context, const xmlChar *name ) +{ + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + if ( service != NULL ) + { + // Tell parser to stop building a tree + context->is_value = 0; + + // See if there is a xml tree for the value + if ( context->property != NULL && context->value_doc != NULL ) + { + xmlChar *value; + int size; + + // Serialise the tree to get value + xmlDocDumpMemory( context->value_doc, &value, &size ); + mlt_properties_set( properties, context->property, _s(value) ); + xmlFree( value ); + xmlFreeDoc( context->value_doc ); + context->value_doc = NULL; + } + + // Close this property handling + free( context->property ); + context->property = NULL; + + context_push_service( context, service, type ); + } + else + { + fprintf( stderr, "Property without a service '%s'??\n", (char *)name ); + } +} + +static void on_start_element( void *ctx, const xmlChar *name, const xmlChar **atts) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + +//printf("on_start_element: %s\n", name ); + context->branch[ context->depth ] ++; + context->depth ++; + + // Build a tree from nodes within a property value + if ( context->is_value == 1 ) + { + xmlNodePtr node = xmlNewNode( NULL, name ); + + if ( context->value_doc == NULL ) + { + // Start a new tree + context->value_doc = xmlNewDoc( _x("1.0") ); + xmlDocSetRootElement( context->value_doc, node ); + } + else + { + // Append child to tree + xmlAddChild( context->stack_node[ context->stack_node_size - 1 ], node ); + } + context_push_node( context, node ); + + // Set the attributes + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + xmlSetProp( node, atts[ 0 ], atts[ 1 ] ); + } + else if ( xmlStrcmp( name, _x("tractor") ) == 0 ) + on_start_tractor( context, name, atts ); + else if ( xmlStrcmp( name, _x("multitrack") ) == 0 ) + on_start_multitrack( context, name, atts ); + else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 ) + on_start_playlist( context, name, atts ); + else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 ) + on_start_producer( context, name, atts ); + else if ( xmlStrcmp( name, _x("blank") ) == 0 ) + on_start_blank( context, name, atts ); + else if ( xmlStrcmp( name, _x("entry") ) == 0 ) + on_start_entry( context, name, atts ); + else if ( xmlStrcmp( name, _x("track") ) == 0 ) + on_start_track( context, name, atts ); + else if ( xmlStrcmp( name, _x("filter") ) == 0 ) + on_start_filter( context, name, atts ); + else if ( xmlStrcmp( name, _x("transition") ) == 0 ) + on_start_transition( context, name, atts ); + else if ( xmlStrcmp( name, _x("property") ) == 0 ) + on_start_property( context, name, atts ); + else if ( xmlStrcmp( name, _x("westley") ) == 0 ) + for ( ; atts != NULL && *atts != NULL; atts += 2 ) + mlt_properties_set( context->producer_map, ( char * )atts[ 0 ], ( char * )atts[ 1 ] ); +} + +static void on_end_element( void *ctx, const xmlChar *name ) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + +//printf("on_end_element: %s\n", name ); + if ( context->is_value == 1 && xmlStrcmp( name, _x("property") ) != 0 ) + context_pop_node( context ); + else if ( xmlStrcmp( name, _x("multitrack") ) == 0 ) + on_end_multitrack( context, name ); + else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 ) + on_end_playlist( context, name ); + else if ( xmlStrcmp( name, _x("track") ) == 0 ) + on_end_track( context, name ); + else if ( xmlStrcmp( name, _x("entry") ) == 0 ) + on_end_entry( context, name ); + else if ( xmlStrcmp( name, _x("tractor") ) == 0 ) + on_end_tractor( context, name ); + else if ( xmlStrcmp( name, _x("property") ) == 0 ) + on_end_property( context, name ); + else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 ) + on_end_producer( context, name ); + else if ( xmlStrcmp( name, _x("filter") ) == 0 ) + on_end_filter( context, name ); + else if ( xmlStrcmp( name, _x("transition") ) == 0 ) + on_end_transition( context, name ); + + context->branch[ context->depth ] = 0; + context->depth --; +} + +static void on_characters( void *ctx, const xmlChar *ch, int len ) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + char *value = calloc( len + 1, 1 ); + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + if ( service != NULL ) + context_push_service( context, service, type ); + + value[ len ] = 0; + strncpy( value, (const char*) ch, len ); + + if ( context->stack_node_size > 0 ) + xmlNodeAddContent( context->stack_node[ context->stack_node_size - 1 ], ( xmlChar* )value ); + + // libxml2 generates an on_characters immediately after a get_entity within + // an element value, and we ignore it because it is called again during + // actual substitution. + else if ( context->property != NULL && context->entity_is_replace == 0 ) + { + char *s = mlt_properties_get( properties, context->property ); + if ( s != NULL ) + { + // Append new text to existing content + char *new = calloc( strlen( s ) + len + 1, 1 ); + strcat( new, s ); + strcat( new, value ); + mlt_properties_set( properties, context->property, new ); + free( new ); + } + else + mlt_properties_set( properties, context->property, value ); + } + context->entity_is_replace = 0; + + free( value); +} + +/** Convert parameters parsed from resource into entity declarations. +*/ +static void params_to_entities( deserialise_context context ) +{ + if ( context->params != NULL ) + { + int i; + + // Add our params as entitiy declarations + for ( i = 0; i < mlt_properties_count( context->params ); i++ ) + { + xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i ); + xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY, + context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, _s(name) ) ); + } + + // Flag completion + mlt_properties_close( context->params ); + context->params = NULL; + } +} + +// The following 3 facilitate entity substitution in the SAX parser +static void on_internal_subset( void *ctx, const xmlChar* name, + const xmlChar* publicId, const xmlChar* systemId ) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + + context->publicId = publicId; + context->systemId = systemId; + xmlCreateIntSubset( context->entity_doc, name, publicId, systemId ); + + // Override default entities with our parameters + params_to_entities( context ); +} + +// TODO: Check this with Dan... I think this is for westley parameterisation +// but it's breaking standard escaped entities (like < etc). +static void on_entity_declaration( void *ctx, const xmlChar* name, int type, + const xmlChar* publicId, const xmlChar* systemId, xmlChar* content) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + + xmlAddDocEntity( context->entity_doc, name, type, publicId, systemId, content ); +} + +// TODO: Check this functionality (see on_entity_declaration) +static xmlEntityPtr on_get_entity( void *ctx, const xmlChar* name ) +{ + struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; + deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + xmlEntityPtr e = NULL; + + // Setup for entity declarations if not ready + if ( xmlGetIntSubset( context->entity_doc ) == NULL ) + { + xmlCreateIntSubset( context->entity_doc, _x("westley"), _x(""), _x("") ); + context->publicId = _x(""); + context->systemId = _x(""); + } + + // Add our parameters if not already + params_to_entities( context ); + + e = xmlGetPredefinedEntity( name ); + + // Send signal to on_characters that an entity substitutin is pending + if ( e == NULL ) + { + e = xmlGetDocEntity( context->entity_doc, name ); + if ( e != NULL ) + context->entity_is_replace = 1; + } + + return e; +} + +/** Convert a hexadecimal character to its value. +*/ +static int tohex( char p ) +{ + return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10; +} + +/** Decode a url-encoded string containing hexadecimal character sequences. +*/ +static char *url_decode( char *dest, char *src ) +{ + char *p = dest; + + while ( *src ) + { + if ( *src == '%' ) + { + *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) ); + src += 3; + } + else + { + *p ++ = *src ++; + } + } + + *p = *src; + return dest; +} + +/** Extract the filename from a URL attaching parameters to a properties list. +*/ +static void parse_url( mlt_properties properties, char *url ) +{ + int i; + int n = strlen( url ); + char *name = NULL; + char *value = NULL; + + for ( i = 0; i < n; i++ ) + { + switch ( url[ i ] ) + { + case '?': + url[ i++ ] = '\0'; + name = &url[ i ]; + break; + + case ':': + case '=': + url[ i++ ] = '\0'; + value = &url[ i ]; + break; + + case '&': + url[ i++ ] = '\0'; + if ( name != NULL && value != NULL ) + mlt_properties_set( properties, name, value ); + name = &url[ i ]; + value = NULL; + break; + } + } + if ( name != NULL && value != NULL ) + mlt_properties_set( properties, name, value ); +} + +// Quick workaround to avoid unecessary libxml2 warnings +static int file_exists( char *file ) +{ + char *name = strdup( file ); + int exists = 0; + if ( name != NULL && strchr( name, '?' ) ) + *( strchr( name, '?' ) ) = '\0'; + if ( name != NULL ) + { + FILE *f = fopen( name, "r" ); + exists = f != NULL; + if ( exists ) fclose( f ); + } + free( name ); + return exists; +} + +mlt_producer producer_westley_init( int info, char *data ) +{ + xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) ); + struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) ); + mlt_properties properties = NULL; + int i = 0; + struct _xmlParserCtxt *xmlcontext; + int well_formed = 0; + char *filename = NULL; + + if ( data == NULL || !strcmp( data, "" ) || ( info == 0 && !file_exists( data ) ) ) + return NULL; + + context = calloc( 1, sizeof( struct deserialise_context_s ) ); + if ( context == NULL ) + return NULL; + + context->producer_map = mlt_properties_new(); + context->destructors = mlt_properties_new(); + context->params = mlt_properties_new(); + + // Decode URL and parse parameters + mlt_properties_set( context->producer_map, "root", "" ); + if ( info == 0 ) + { + filename = strdup( data ); + parse_url( context->params, url_decode( filename, data ) ); + + // We need the directory prefix which was used for the westley + if ( strchr( filename, '/' ) ) + { + char *root = NULL; + mlt_properties_set( context->producer_map, "root", filename ); + root = mlt_properties_get( context->producer_map, "root" ); + *( strrchr( root, '/' ) ) = '\0'; + + // If we don't have an absolute path here, we're heading for disaster... + if ( root[ 0 ] != '/' ) + { + char *cwd = getcwd( NULL, 0 ); + char *real = malloc( strlen( cwd ) + strlen( root ) + 2 ); + sprintf( real, "%s/%s", cwd, root ); + mlt_properties_set( context->producer_map, "root", real ); + free( real ); + free( cwd ); + } + } + } + + // We need to track the number of registered filters + mlt_properties_set_int( context->destructors, "registered", 0 ); + + // Setup SAX callbacks + sax->startElement = on_start_element; + sax->endElement = on_end_element; + sax->characters = on_characters; + sax->cdataBlock = on_characters; + sax->internalSubset = on_internal_subset; + sax->entityDecl = on_entity_declaration; + sax->getEntity = on_get_entity; + + // Setup libxml2 SAX parsing + xmlInitParser(); + xmlSubstituteEntitiesDefault( 1 ); + // This is used to facilitate entity substitution in the SAX parser + context->entity_doc = xmlNewDoc( _x("1.0") ); + if ( info == 0 ) + xmlcontext = xmlCreateFileParserCtxt( filename ); + else + xmlcontext = xmlCreateMemoryParserCtxt( data, strlen( data ) ); + + // Invalid context - clean up and return NULL + if ( xmlcontext == NULL ) + { + mlt_properties_close( context->producer_map ); + mlt_properties_close( context->destructors ); + mlt_properties_close( context->params ); + free( context ); + free( sax ); + free( filename ); + return NULL; + } + + xmlcontext->sax = sax; + xmlcontext->_private = ( void* )context; + + // Parse + xmlParseDocument( xmlcontext ); + well_formed = xmlcontext->wellFormed; + + // Cleanup after parsing + xmlFreeDoc( context->entity_doc ); + free( sax ); + xmlcontext->sax = NULL; + xmlcontext->_private = NULL; + xmlFreeParserCtxt( xmlcontext ); + xmlMemoryDump( ); // for debugging + + // Get the last producer on the stack + enum service_type type; + mlt_service service = context_pop_service( context, &type ); + if ( well_formed && service != NULL ) + { + // Verify it is a producer service (mlt_type="mlt_producer") + // (producer, playlist, multitrack) + char *type = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "mlt_type" ); + if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 && strcmp( type, "producer" ) != 0 ) ) + service = NULL; + } + +#ifdef DEBUG + xmlDocPtr doc = westley_make_doc( service ); + xmlDocFormatDump( stdout, doc, 1 ); + xmlFreeDoc( doc ); + service = NULL; +#endif + + if ( well_formed && service != NULL ) + { + char *title = mlt_properties_get( context->producer_map, "title" ); + + // Need the complete producer list for various reasons + properties = context->destructors; + + // Now make sure we don't have a reference to the service in the properties + for ( i = mlt_properties_count( properties ) - 1; i >= 1; i -- ) + { + char *name = mlt_properties_get_name( properties, i ); + if ( mlt_properties_get_data( properties, name, NULL ) == service ) + { + mlt_properties_set_data( properties, name, service, 0, NULL, NULL ); + break; + } + } + + // We are done referencing destructor property list + // Set this var to service properties for convenience + properties = MLT_SERVICE_PROPERTIES( service ); + + // Assign the title + mlt_properties_set( properties, "title", title ); + + // Optimise for overlapping producers + mlt_producer_optimise( MLT_PRODUCER( service ) ); + + // Handle deep copies + if ( getenv( "MLT_WESTLEY_DEEP" ) == NULL ) + { + // Now assign additional properties + if ( info == 0 ) + mlt_properties_set( properties, "resource", data ); + + // This tells consumer_westley not to deep copy + mlt_properties_set( properties, "westley", "was here" ); + } + else + { + // Allow the project to be edited + mlt_properties_set( properties, "_westley", "was here" ); + mlt_properties_set_int( properties, "_mlt_service_hidden", 1 ); + } + } + else + { + // Return null if not well formed + service = NULL; + } + + // Clean up + mlt_properties_close( context->producer_map ); + if ( context->params != NULL ) + mlt_properties_close( context->params ); + mlt_properties_close( context->destructors ); + free( context ); + free( filename ); + + return MLT_PRODUCER( service ); +} diff --git a/src/modules/westley/producer_westley.h b/src/modules/westley/producer_westley.h new file mode 100644 index 0000000..0a47677 --- /dev/null +++ b/src/modules/westley/producer_westley.h @@ -0,0 +1,28 @@ +/* + * producer_westley.h -- a libxml2 parser of mlt service networks + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_WESTLEY_H_ +#define _PRODUCER_WESTLEY_H_ + +#include + +extern mlt_producer producer_westley_init( int type, char *data ); + +#endif diff --git a/src/modules/westley/westley.dtd b/src/modules/westley/westley.dtd new file mode 100644 index 0000000..4b926d7 --- /dev/null +++ b/src/modules/westley/westley.dtd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/xine/Makefile b/src/modules/xine/Makefile new file mode 100644 index 0000000..f267209 --- /dev/null +++ b/src/modules/xine/Makefile @@ -0,0 +1,35 @@ +include ../../../config.mak + +TARGET = ../libmltxine$(LIBSUF) + +OBJS = factory.o \ + deinterlace.o \ + cpu_accel.o \ + filter_deinterlace.o + +CFLAGS += -I../../ -DARCH_X86 + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/xine/attributes.h b/src/modules/xine/attributes.h new file mode 100644 index 0000000..87d6dc6 --- /dev/null +++ b/src/modules/xine/attributes.h @@ -0,0 +1,46 @@ +/* + * attributes.h + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * + * mpeg2dec 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 2 of the License, or + * (at your option) any later version. + * + * mpeg2dec 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* use gcc attribs to align critical data structures */ + +#ifndef ATTRIBUTE_H_ +#define ATTRIBUTE_H_ + +#ifdef ATTRIBUTE_ALIGNED_MAX +#define ATTR_ALIGN(align) __attribute__ ((__aligned__ ((ATTRIBUTE_ALIGNED_MAX < align) ? ATTRIBUTE_ALIGNED_MAX : align))) +#else +#define ATTR_ALIGN(align) +#endif + +/* disable GNU __attribute__ extension, when not compiling with GNU C */ +#if defined(__GNUC__) || defined (__ICC) +#ifndef ATTRIBUTE_PACKED +#define ATTRIBUTE_PACKED 1 +#endif +#else +#undef ATTRIBUTE_PACKED +#ifndef __attribute__ +#define __attribute__(x) /**/ +#endif /* __attribute __*/ +#endif + +#endif /* ATTRIBUTE_H_ */ + diff --git a/src/modules/xine/configure b/src/modules/xine/configure new file mode 100755 index 0000000..d321129 --- /dev/null +++ b/src/modules/xine/configure @@ -0,0 +1,18 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + + # Horrible hack + grep mmx /proc/cpuinfo > /dev/null 2>&1 + disable_xine=$? + + if [ "$disable_xine" = "0" ] + then + echo "deinterlace libmltxine$LIBSUF" >> ../filters.dat + else + echo "- MMX Capabalities not found: disabling xine deinterlacing module" + touch ../disable-xine + fi +fi + diff --git a/src/modules/xine/cpu_accel.c b/src/modules/xine/cpu_accel.c new file mode 100644 index 0000000..9228a40 --- /dev/null +++ b/src/modules/xine/cpu_accel.c @@ -0,0 +1,232 @@ +/* + * cpu_accel.c + * Copyright (C) 1999-2001 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * + * mpeg2dec 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 2 of the License, or + * (at your option) any later version. + * + * mpeg2dec 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//#include "config.h" + +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "cpu_accel" +#define LOG_VERBOSE +/* +#define LOG +*/ + +#include "xineutils.h" + +#if defined(ARCH_X86) || defined(ARCH_X86_64) +#if defined __x86_64__ +static uint32_t arch_accel (void) +{ + uint32_t caps; + /* No need to test for this on AMD64, we know what the + platform has. */ + caps = MM_ACCEL_X86_MMX | MM_ACCEL_X86_SSE | MM_ACCEL_X86_MMXEXT | MM_ACCEL_X86_SSE2; + + return caps; +} +#else +static uint32_t arch_accel (void) +{ +#ifndef _MSC_VER + + uint32_t eax, ebx, ecx, edx; + int AMD; + uint32_t caps; + +#ifndef PIC +#define cpuid(op,eax,ebx,ecx,edx) \ + __asm__ ("cpuid" \ + : "=a" (eax), \ + "=b" (ebx), \ + "=c" (ecx), \ + "=d" (edx) \ + : "a" (op) \ + : "cc") +#else /* PIC version : save ebx */ +#define cpuid(op,eax,ebx,ecx,edx) \ + __asm__ ("pushl %%ebx\n\t" \ + "cpuid\n\t" \ + "movl %%ebx,%1\n\t" \ + "popl %%ebx" \ + : "=a" (eax), \ + "=r" (ebx), \ + "=c" (ecx), \ + "=d" (edx) \ + : "a" (op) \ + : "cc") +#endif + + __asm__ ("pushfl\n\t" + "pushfl\n\t" + "popl %0\n\t" + "movl %0,%1\n\t" + "xorl $0x200000,%0\n\t" + "pushl %0\n\t" + "popfl\n\t" + "pushfl\n\t" + "popl %0\n\t" + "popfl" + : "=r" (eax), + "=r" (ebx) + : + : "cc"); + + if (eax == ebx) /* no cpuid */ + return 0; + + cpuid (0x00000000, eax, ebx, ecx, edx); + if (!eax) /* vendor string only */ + return 0; + + AMD = (ebx == 0x68747541) && (ecx == 0x444d4163) && (edx == 0x69746e65); + + cpuid (0x00000001, eax, ebx, ecx, edx); + if (! (edx & 0x00800000)) /* no MMX */ + return 0; + + caps = MM_ACCEL_X86_MMX; + if (edx & 0x02000000) /* SSE - identical to AMD MMX extensions */ + caps |= MM_ACCEL_X86_SSE | MM_ACCEL_X86_MMXEXT; + + if (edx & 0x04000000) /* SSE2 */ + caps |= MM_ACCEL_X86_SSE2; + + cpuid (0x80000000, eax, ebx, ecx, edx); + if (eax < 0x80000001) /* no extended capabilities */ + return caps; + + cpuid (0x80000001, eax, ebx, ecx, edx); + + if (edx & 0x80000000) + caps |= MM_ACCEL_X86_3DNOW; + + if (AMD && (edx & 0x00400000)) /* AMD MMX extensions */ + caps |= MM_ACCEL_X86_MMXEXT; + + return caps; +#else /* _MSC_VER */ + return 0; +#endif +} +#endif /* x86_64 */ + +static jmp_buf sigill_return; + +static void sigill_handler (int n) { + longjmp(sigill_return, 1); +} +#endif /* ARCH_X86 */ + +#if defined (ARCH_PPC) && defined (ENABLE_ALTIVEC) +static sigjmp_buf jmpbuf; +static volatile sig_atomic_t canjump = 0; + +static void sigill_handler (int sig) +{ + if (!canjump) { + signal (sig, SIG_DFL); + raise (sig); + } + + canjump = 0; + siglongjmp (jmpbuf, 1); +} + +static uint32_t arch_accel (void) +{ + signal (SIGILL, sigill_handler); + if (sigsetjmp (jmpbuf, 1)) { + signal (SIGILL, SIG_DFL); + return 0; + } + + canjump = 1; + + __asm__ volatile ("mtspr 256, %0\n\t" + "vand %%v0, %%v0, %%v0" + : + : "r" (-1)); + + signal (SIGILL, SIG_DFL); + return MM_ACCEL_PPC_ALTIVEC; +} +#endif /* ARCH_PPC */ + +uint32_t xine_mm_accel (void) +{ + static int initialized = 0; + static uint32_t accel; + + if (!initialized) { +#if defined (ARCH_X86) || (defined (ARCH_PPC) && defined (ENABLE_ALTIVEC)) + accel = arch_accel (); +#elif defined (HAVE_MLIB) +#ifdef MLIB_LAZYLOAD + void *hndl; + + if ((hndl = dlopen("libmlib.so.2", RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE)) == NULL) { + accel = 0; + } + else { + dlclose(hndl); + accel = MM_ACCEL_MLIB; + } +#else + accel = MM_ACCEL_MLIB; +#endif +#else + accel = 0; +#endif + +#if defined(ARCH_X86) || defined(ARCH_X86_64) +#ifndef _MSC_VER + /* test OS support for SSE */ + if( accel & MM_ACCEL_X86_SSE ) { + void (*old_sigill_handler)(int); + + old_sigill_handler = signal (SIGILL, sigill_handler); + + if (setjmp(sigill_return)) { + lprintf ("OS doesn't support SSE instructions.\n"); + accel &= ~(MM_ACCEL_X86_SSE|MM_ACCEL_X86_SSE2); + } else { + __asm__ volatile ("xorps %xmm0, %xmm0"); + } + + signal (SIGILL, old_sigill_handler); + } +#endif /* _MSC_VER */ +#endif /* ARCH_X86 || ARCH_X86_64 */ + + if(getenv("XINE_NO_ACCEL")) { + accel = 0; + } + + initialized = 1; + } + + return accel; +} diff --git a/src/modules/xine/deinterlace.c b/src/modules/xine/deinterlace.c new file mode 100644 index 0000000..34a88e6 --- /dev/null +++ b/src/modules/xine/deinterlace.c @@ -0,0 +1,860 @@ + /* + * Copyright (C) 2001 the xine project + * + * This file is part of xine, a free video player. + * + * xine 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 2 of the License, or + * (at your option) any later version. + * + * xine 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * Deinterlace routines by Miguel Freitas + * based of DScaler project sources (deinterlace.sourceforge.net) + * + * Currently only available for Xv driver and MMX extensions + * + * small todo list: + * - implement non-MMX versions for all methods + * - support MMX2 instructions + * - move some generic code from xv driver to this file + * - make it also work for yuy2 frames + * + */ + +#include +#include +#include "deinterlace.h" +#include "xineutils.h" + +#define xine_fast_memcpy memcpy +#define xine_fast_memmove memmove + +/* + DeinterlaceFieldBob algorithm + Based on Virtual Dub plugin by Gunnar Thalin + MMX asm version from dscaler project (deinterlace.sourceforge.net) + Linux version for Xine player by Miguel Freitas +*/ +static void deinterlace_bob_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) + int Line; + uint64_t *YVal1; + uint64_t *YVal2; + uint64_t *YVal3; + uint64_t *Dest; + uint8_t* pEvenLines = psrc[0]; + uint8_t* pOddLines = psrc[0]+width; + int LineLength = width; + int SourcePitch = width * 2; + int IsOdd = 1; + long EdgeDetect = 625; + long JaggieThreshold = 73; + + int n; + + uint64_t qwEdgeDetect; + uint64_t qwThreshold; + + static mmx_t YMask = {ub:{0xff,0,0xff,0,0xff,0,0xff,0}}; + static mmx_t Mask = {ub:{0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; + + qwEdgeDetect = EdgeDetect; + qwEdgeDetect += (qwEdgeDetect << 48) + (qwEdgeDetect << 32) + (qwEdgeDetect << 16); + qwThreshold = JaggieThreshold; + qwThreshold += (qwThreshold << 48) + (qwThreshold << 32) + (qwThreshold << 16); + + + // copy first even line no matter what, and the first odd line if we're + // processing an odd field. + xine_fast_memcpy(pdst, pEvenLines, LineLength); + if (IsOdd) + xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); + + height = height / 2; + for (Line = 0; Line < height - 1; ++Line) + { + if (IsOdd) + { + YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); + YVal2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); + } + else + { + YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); + YVal2 = (uint64_t *)(pOddLines + Line * SourcePitch); + YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); + } + + // For ease of reading, the comments below assume that we're operating on an odd + // field (i.e., that bIsOdd is true). The exact same processing is done when we + // operate on an even field, but the roles of the odd and even fields are reversed. + // It's just too cumbersome to explain the algorithm in terms of "the next odd + // line if we're doing an odd field, or the next even line if we're doing an + // even field" etc. So wherever you see "odd" or "even" below, keep in mind that + // half the time this function is called, those words' meanings will invert. + + // Copy the odd line to the overlay verbatim. + xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); + + n = LineLength >> 3; + while( n-- ) + { + movq_m2r (*YVal1++, mm0); + movq_m2r (*YVal2++, mm1); + movq_m2r (*YVal3++, mm2); + + // get intensities in mm3 - 4 + movq_r2r ( mm0, mm3 ); + pand_m2r ( YMask, mm3 ); + movq_r2r ( mm1, mm4 ); + pand_m2r ( YMask, mm4 ); + movq_r2r ( mm2, mm5 ); + pand_m2r ( YMask, mm5 ); + + // get average in mm0 + pand_m2r ( Mask, mm0 ); + pand_m2r ( Mask, mm2 ); + psrlw_i2r ( 01, mm0 ); + psrlw_i2r ( 01, mm2 ); + paddw_r2r ( mm2, mm0 ); + + // work out (O1 - E) * (O2 - E) / 2 - EdgeDetect * (O1 - O2) ^ 2 >> 12 + // result will be in mm6 + + psrlw_i2r ( 01, mm3 ); + psrlw_i2r ( 01, mm4 ); + psrlw_i2r ( 01, mm5 ); + + movq_r2r ( mm3, mm6 ); + psubw_r2r ( mm4, mm6 ); //mm6 = O1 - E + + movq_r2r ( mm5, mm7 ); + psubw_r2r ( mm4, mm7 ); //mm7 = O2 - E + + pmullw_r2r ( mm7, mm6 ); // mm6 = (O1 - E) * (O2 - E) + + movq_r2r ( mm3, mm7 ); + psubw_r2r ( mm5, mm7 ); // mm7 = (O1 - O2) + pmullw_r2r ( mm7, mm7 ); // mm7 = (O1 - O2) ^ 2 + psrlw_i2r ( 12, mm7 ); // mm7 = (O1 - O2) ^ 2 >> 12 + pmullw_m2r ( *&qwEdgeDetect, mm7 );// mm7 = EdgeDetect * (O1 - O2) ^ 2 >> 12 + + psubw_r2r ( mm7, mm6 ); // mm6 is what we want + + pcmpgtw_m2r ( *&qwThreshold, mm6 ); + + movq_r2r ( mm6, mm7 ); + + pand_r2r ( mm6, mm0 ); + + pandn_r2r ( mm1, mm7 ); + + por_r2r ( mm0, mm7 ); + + movq_r2m ( mm7, *Dest++ ); + } + } + + // Copy last odd line if we're processing an even field. + if (! IsOdd) + { + xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, + pOddLines + (height - 1) * SourcePitch, + LineLength); + } + + // clear out the MMX registers ready for doing floating point + // again + emms(); +#endif +} + +/* Deinterlace the latest field, with a tendency to weave rather than bob. + Good for high detail on low-movement scenes. + Seems to produce bad output in general case, need to check if this + is normal or if the code is broken. +*/ +static int deinterlace_weave_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) + + int Line; + uint64_t *YVal1; + uint64_t *YVal2; + uint64_t *YVal3; + uint64_t *YVal4; + uint64_t *Dest; + uint8_t* pEvenLines = psrc[0]; + uint8_t* pOddLines = psrc[0]+width; + uint8_t* pPrevLines; + + int LineLength = width; + int SourcePitch = width * 2; + int IsOdd = 1; + + long TemporalTolerance = 300; + long SpatialTolerance = 600; + long SimilarityThreshold = 25; + + int n; + + uint64_t qwSpatialTolerance; + uint64_t qwTemporalTolerance; + uint64_t qwThreshold; + + static mmx_t YMask = {ub:{0xff,0,0xff,0,0xff,0,0xff,0}}; + static mmx_t Mask = {ub:{0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; + + + // Make sure we have all the data we need. + if ( psrc[0] == NULL || psrc[1] == NULL ) + return 0; + + if (IsOdd) + pPrevLines = psrc[1] + width; + else + pPrevLines = psrc[1]; + + // Since the code uses MMX to process 4 pixels at a time, we need our constants + // to be represented 4 times per quadword. + qwSpatialTolerance = SpatialTolerance; + qwSpatialTolerance += (qwSpatialTolerance << 48) + (qwSpatialTolerance << 32) + (qwSpatialTolerance << 16); + qwTemporalTolerance = TemporalTolerance; + qwTemporalTolerance += (qwTemporalTolerance << 48) + (qwTemporalTolerance << 32) + (qwTemporalTolerance << 16); + qwThreshold = SimilarityThreshold; + qwThreshold += (qwThreshold << 48) + (qwThreshold << 32) + (qwThreshold << 16); + + // copy first even line no matter what, and the first odd line if we're + // processing an even field. + xine_fast_memcpy(pdst, pEvenLines, LineLength); + if (!IsOdd) + xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); + + height = height / 2; + for (Line = 0; Line < height - 1; ++Line) + { + if (IsOdd) + { + YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); + YVal2 = (uint64_t *)(pOddLines + Line * SourcePitch); + YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + YVal4 = (uint64_t *)(pPrevLines + Line * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); + } + else + { + YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); + YVal2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); + YVal4 = (uint64_t *)(pPrevLines + (Line + 1) * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); + } + + // For ease of reading, the comments below assume that we're operating on an odd + // field (i.e., that bIsOdd is true). The exact same processing is done when we + // operate on an even field, but the roles of the odd and even fields are reversed. + // It's just too cumbersome to explain the algorithm in terms of "the next odd + // line if we're doing an odd field, or the next even line if we're doing an + // even field" etc. So wherever you see "odd" or "even" below, keep in mind that + // half the time this function is called, those words' meanings will invert. + + // Copy the even scanline below this one to the overlay buffer, since we'll be + // adapting the current scanline to the even lines surrounding it. The scanline + // above has already been copied by the previous pass through the loop. + xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); + + n = LineLength >> 3; + while( n-- ) + { + movq_m2r ( *YVal1++, mm0 ); // mm0 = E1 + movq_m2r ( *YVal2++, mm1 ); // mm1 = O + movq_m2r ( *YVal3++, mm2 ); // mm2 = E2 + + movq_r2r ( mm0, mm3 ); // mm3 = intensity(E1) + movq_r2r ( mm1, mm4 ); // mm4 = intensity(O) + movq_r2r ( mm2, mm6 ); // mm6 = intensity(E2) + + pand_m2r ( YMask, mm3 ); + pand_m2r ( YMask, mm4 ); + pand_m2r ( YMask, mm6 ); + + // Average E1 and E2 for interpolated bobbing. + // leave result in mm0 + pand_m2r ( Mask, mm0 ); // mm0 = E1 with lower chroma bit stripped off + pand_m2r ( Mask, mm2 ); // mm2 = E2 with lower chroma bit stripped off + psrlw_i2r ( 01, mm0 ); // mm0 = E1 / 2 + psrlw_i2r ( 01, mm2 ); // mm2 = E2 / 2 + paddb_r2r ( mm2, mm0 ); + + // The meat of the work is done here. We want to see whether this pixel is + // close in luminosity to ANY of: its top neighbor, its bottom neighbor, + // or its predecessor. To do this without branching, we use MMX's + // saturation feature, which gives us Z(x) = x if x>=0, or 0 if x<0. + // + // The formula we're computing here is + // Z(ST - (E1 - O) ^ 2) + Z(ST - (E2 - O) ^ 2) + Z(TT - (Oold - O) ^ 2) + // where ST is spatial tolerance and TT is temporal tolerance. The idea + // is that if a pixel is similar to none of its neighbors, the resulting + // value will be pretty low, probably zero. A high value therefore indicates + // that the pixel had a similar neighbor. The pixel in the same position + // in the field before last (Oold) is considered a neighbor since we want + // to be able to display 1-pixel-high horizontal lines. + + movq_m2r ( *&qwSpatialTolerance, mm7 ); + movq_r2r ( mm3, mm5 ); // mm5 = E1 + psubsw_r2r ( mm4, mm5 ); // mm5 = E1 - O + psraw_i2r ( 1, mm5 ); + pmullw_r2r ( mm5, mm5 ); // mm5 = (E1 - O) ^ 2 + psubusw_r2r ( mm5, mm7 ); // mm7 = ST - (E1 - O) ^ 2, or 0 if that's negative + + movq_m2r ( *&qwSpatialTolerance, mm3 ); + movq_r2r ( mm6, mm5 ); // mm5 = E2 + psubsw_r2r ( mm4, mm5 ); // mm5 = E2 - O + psraw_i2r ( 1, mm5 ); + pmullw_r2r ( mm5, mm5 ); // mm5 = (E2 - O) ^ 2 + psubusw_r2r ( mm5, mm3 ); // mm0 = ST - (E2 - O) ^ 2, or 0 if that's negative + paddusw_r2r ( mm3, mm7 ); // mm7 = (ST - (E1 - O) ^ 2) + (ST - (E2 - O) ^ 2) + + movq_m2r ( *&qwTemporalTolerance, mm3 ); + movq_m2r ( *YVal4++, mm5 ); // mm5 = Oold + pand_m2r ( YMask, mm5 ); + psubsw_r2r ( mm4, mm5 ); // mm5 = Oold - O + psraw_i2r ( 1, mm5 ); // XXX + pmullw_r2r ( mm5, mm5 ); // mm5 = (Oold - O) ^ 2 + psubusw_r2r ( mm5, mm3 ); /* mm0 = TT - (Oold - O) ^ 2, or 0 if that's negative */ + paddusw_r2r ( mm3, mm7 ); // mm7 = our magic number + + /* + * Now compare the similarity totals against our threshold. The pcmpgtw + * instruction will populate the target register with a bunch of mask bits, + * filling words where the comparison is true with 1s and ones where it's + * false with 0s. A few ANDs and NOTs and an OR later, we have bobbed + * values for pixels under the similarity threshold and weaved ones for + * pixels over the threshold. + */ + + pcmpgtw_m2r( *&qwThreshold, mm7 ); // mm7 = 0xffff where we're greater than the threshold, 0 elsewhere + movq_r2r ( mm7, mm6 ); // mm6 = 0xffff where we're greater than the threshold, 0 elsewhere + pand_r2r ( mm1, mm7 ); // mm7 = weaved data where we're greater than the threshold, 0 elsewhere + pandn_r2r ( mm0, mm6 ); // mm6 = bobbed data where we're not greater than the threshold, 0 elsewhere + por_r2r ( mm6, mm7 ); // mm7 = bobbed and weaved data + + movq_r2m ( mm7, *Dest++ ); + } + } + + // Copy last odd line if we're processing an odd field. + if (IsOdd) + { + xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, + pOddLines + (height - 1) * SourcePitch, + LineLength); + } + + // clear out the MMX registers ready for doing floating point + // again + emms(); + +#endif + + return 1; +} + + +// This is a simple lightweight DeInterlace method that uses little CPU time +// but gives very good results for low or intermedite motion. (MORE CPU THAN BOB) +// It defers frames by one field, but that does not seem to produce noticeable +// lip sync problems. +// +// The method used is to take either the older or newer weave pixel depending +// upon which give the smaller comb factor, and then clip to avoid large damage +// when wrong. +// +// I'd intended this to be part of a larger more elaborate method added to +// Blended Clip but this give too good results for the CPU to ignore here. +static int deinterlace_greedy_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) + int Line; + int LoopCtr; + uint64_t *L1; // ptr to Line1, of 3 + uint64_t *L2; // ptr to Line2, the weave line + uint64_t *L3; // ptr to Line3 + uint64_t *LP2; // ptr to prev Line2 + uint64_t *Dest; + uint8_t* pEvenLines = psrc[0]; + uint8_t* pOddLines = psrc[0]+width; + uint8_t* pPrevLines; + + static mmx_t ShiftMask = {ub:{0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; + + int LineLength = width; + int SourcePitch = width * 2; + int IsOdd = 1; + long GreedyMaxComb = 15; + static mmx_t MaxComb; + int i; + + if ( psrc[0] == NULL || psrc[1] == NULL ) + return 0; + + if (IsOdd) + pPrevLines = psrc[1] + width; + else + pPrevLines = psrc[1]; + + + for( i = 0; i < 8; i++ ) + MaxComb.ub[i] = GreedyMaxComb; // How badly do we let it weave? 0-255 + + + // copy first even line no matter what, and the first odd line if we're + // processing an EVEN field. (note diff from other deint rtns.) + xine_fast_memcpy(pdst, pEvenLines, LineLength); //DL0 + if (!IsOdd) + xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); //DL1 + + height = height / 2; + for (Line = 0; Line < height - 1; ++Line) + { + LoopCtr = LineLength / 8; // there are LineLength / 8 qwords per line + + if (IsOdd) + { + L1 = (uint64_t *)(pEvenLines + Line * SourcePitch); + L2 = (uint64_t *)(pOddLines + Line * SourcePitch); + L3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + LP2 = (uint64_t *)(pPrevLines + Line * SourcePitch); // prev Odd lines + Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); + } + else + { + L1 = (uint64_t *)(pOddLines + Line * SourcePitch); + L2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + L3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); + LP2 = (uint64_t *)(pPrevLines + (Line + 1) * SourcePitch); //prev even lines + Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); + } + + xine_fast_memcpy((char *)Dest + LineLength, L3, LineLength); + +// For ease of reading, the comments below assume that we're operating on an odd +// field (i.e., that info->IsOdd is true). Assume the obvious for even lines.. + + while( LoopCtr-- ) + { + movq_m2r ( *L1++, mm1 ); + movq_m2r ( *L2++, mm2 ); + movq_m2r ( *L3++, mm3 ); + movq_m2r ( *LP2++, mm0 ); + + // average L1 and L3 leave result in mm4 + movq_r2r ( mm1, mm4 ); // L1 + + pand_m2r ( ShiftMask, mm4 ); + psrlw_i2r ( 01, mm4 ); + movq_r2r ( mm3, mm5 ); // L3 + pand_m2r ( ShiftMask, mm5 ); + psrlw_i2r ( 01, mm5 ); + paddb_r2r ( mm5, mm4 ); // the average, for computing comb + + // get abs value of possible L2 comb + movq_r2r ( mm2, mm7 ); // L2 + psubusb_r2r ( mm4, mm7 ); // L2 - avg + movq_r2r ( mm4, mm5 ); // avg + psubusb_r2r ( mm2, mm5 ); // avg - L2 + por_r2r ( mm7, mm5 ); // abs(avg-L2) + movq_r2r ( mm4, mm6 ); // copy of avg for later + + // get abs value of possible LP2 comb + movq_r2r ( mm0, mm7 ); // LP2 + psubusb_r2r ( mm4, mm7 ); // LP2 - avg + psubusb_r2r ( mm0, mm4 ); // avg - LP2 + por_r2r ( mm7, mm4 ); // abs(avg-LP2) + + // use L2 or LP2 depending upon which makes smaller comb + psubusb_r2r ( mm5, mm4 ); // see if it goes to zero + psubusb_r2r ( mm5, mm5 ); // 0 + pcmpeqb_r2r ( mm5, mm4 ); // if (mm4=0) then FF else 0 + pcmpeqb_r2r ( mm4, mm5 ); // opposite of mm4 + + // if Comb(LP2) <= Comb(L2) then mm4=ff, mm5=0 else mm4=0, mm5 = 55 + pand_r2r ( mm2, mm5 ); // use L2 if mm5 == ff, else 0 + pand_r2r ( mm0, mm4 ); // use LP2 if mm4 = ff, else 0 + por_r2r ( mm5, mm4 ); // may the best win + + // Now lets clip our chosen value to be not outside of the range + // of the high/low range L1-L3 by more than abs(L1-L3) + // This allows some comb but limits the damages and also allows more + // detail than a boring oversmoothed clip. + + movq_r2r ( mm1, mm2 ); // copy L1 + psubusb_r2r ( mm3, mm2 ); // - L3, with saturation + paddusb_r2r ( mm3, mm2 ); // now = Max(L1,L3) + + pcmpeqb_r2r ( mm7, mm7 ); // all ffffffff + psubusb_r2r ( mm1, mm7 ); // - L1 + paddusb_r2r ( mm7, mm3 ); // add, may sat at fff.. + psubusb_r2r ( mm7, mm3 ); // now = Min(L1,L3) + + // allow the value to be above the high or below the low by amt of MaxComb + paddusb_m2r ( MaxComb, mm2 ); // increase max by diff + psubusb_m2r ( MaxComb, mm3 ); // lower min by diff + + psubusb_r2r ( mm3, mm4 ); // best - Min + paddusb_r2r ( mm3, mm4 ); // now = Max(best,Min(L1,L3) + + pcmpeqb_r2r ( mm7, mm7 ); // all ffffffff + psubusb_r2r ( mm4, mm7 ); // - Max(best,Min(best,L3) + paddusb_r2r ( mm7, mm2 ); // add may sat at FFF.. + psubusb_r2r ( mm7, mm2 ); // now = Min( Max(best, Min(L1,L3), L2 )=L2 clipped + + movq_r2m ( mm2, *Dest++ ); // move in our clipped best + + } + } + + /* Copy last odd line if we're processing an Odd field. */ + if (IsOdd) + { + xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, + pOddLines + (height - 1) * SourcePitch, + LineLength); + } + + /* clear out the MMX registers ready for doing floating point again */ + emms(); + +#endif + + return 1; +} + +/* Use one field to interpolate the other (low cpu utilization) + Will lose resolution but does not produce weaving effect + (good for fast moving scenes) also know as "linear interpolation" +*/ +static void deinterlace_onefield_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) + int Line; + uint64_t *YVal1; + uint64_t *YVal3; + uint64_t *Dest; + uint8_t* pEvenLines = psrc[0]; + uint8_t* pOddLines = psrc[0]+width; + int LineLength = width; + int SourcePitch = width * 2; + int IsOdd = 1; + + int n; + + static mmx_t Mask = {ub:{0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; + + /* + * copy first even line no matter what, and the first odd line if we're + * processing an odd field. + */ + + xine_fast_memcpy(pdst, pEvenLines, LineLength); + if (IsOdd) + xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); + + height = height / 2; + for (Line = 0; Line < height - 1; ++Line) + { + if (IsOdd) + { + YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); + YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); + } + else + { + YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); + YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); + Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); + } + + // Copy the odd line to the overlay verbatim. + xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); + + n = LineLength >> 3; + while( n-- ) + { + movq_m2r (*YVal1++, mm0); + movq_m2r (*YVal3++, mm2); + + // get average in mm0 + pand_m2r ( Mask, mm0 ); + pand_m2r ( Mask, mm2 ); + psrlw_i2r ( 01, mm0 ); + psrlw_i2r ( 01, mm2 ); + paddw_r2r ( mm2, mm0 ); + + movq_r2m ( mm0, *Dest++ ); + } + } + + /* Copy last odd line if we're processing an even field. */ + if (! IsOdd) + { + xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, + pOddLines + (height - 1) * SourcePitch, + LineLength); + } + + /* clear out the MMX registers ready for doing floating point + * again + */ + emms(); +#endif +} + +/* Linear Blend filter - does a kind of vertical blurring on the image. + (idea borrowed from mplayer's sources) +*/ +static void deinterlace_linearblend_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) + int Line; + uint64_t *YVal1; + uint64_t *YVal2; + uint64_t *YVal3; + uint64_t *Dest; + int LineLength = width; + + int n; + + /* Copy first line */ + xine_fast_memmove(pdst, psrc[0], LineLength); + + for (Line = 1; Line < height - 1; ++Line) + { + YVal1 = (uint64_t *)(psrc[0] + (Line - 1) * LineLength); + YVal2 = (uint64_t *)(psrc[0] + (Line) * LineLength); + YVal3 = (uint64_t *)(psrc[0] + (Line + 1) * LineLength); + Dest = (uint64_t *)(pdst + Line * LineLength); + + n = LineLength >> 3; + while( n-- ) + { + /* load data from 3 lines */ + movq_m2r (*YVal1++, mm0); + movq_m2r (*YVal2++, mm1); + movq_m2r (*YVal3++, mm2); + + /* expand bytes to words */ + punpckhbw_r2r (mm0, mm3); + punpckhbw_r2r (mm1, mm4); + punpckhbw_r2r (mm2, mm5); + punpcklbw_r2r (mm0, mm0); + punpcklbw_r2r (mm1, mm1); + punpcklbw_r2r (mm2, mm2); + + /* + * deinterlacing: + * deint_line = (line0 + 2*line1 + line2) / 4 + */ + psrlw_i2r (07, mm0); + psrlw_i2r (06, mm1); + psrlw_i2r (07, mm2); + psrlw_i2r (07, mm3); + psrlw_i2r (06, mm4); + psrlw_i2r (07, mm5); + paddw_r2r (mm1, mm0); + paddw_r2r (mm2, mm0); + paddw_r2r (mm4, mm3); + paddw_r2r (mm5, mm3); + psrlw_i2r (03, mm0); + psrlw_i2r (03, mm3); + + /* pack 8 words to 8 bytes in mm0 */ + packuswb_r2r (mm3, mm0); + + movq_r2m ( mm0, *Dest++ ); + } + } + + /* Copy last line */ + xine_fast_memmove(pdst + Line * LineLength, + psrc[0] + Line * LineLength, LineLength); + + /* clear out the MMX registers ready for doing floating point + * again + */ + emms(); +#endif +} + +/* Linear Blend filter - C version contributed by Rogerio Brito. + This algorithm has the same interface as the other functions. + + The destination "screen" (pdst) is constructed from the source + screen (psrc[0]) line by line. + + The i-th line of the destination screen is the average of 3 lines + from the source screen: the (i-1)-th, i-th and (i+1)-th lines, with + the i-th line having weight 2 in the computation. + + Remarks: + * each line on pdst doesn't depend on previous lines; + * due to the way the algorithm is defined, the first & last lines of the + screen aren't deinterlaced. + +*/ +static void deinterlace_linearblend_yuv( uint8_t *pdst, uint8_t *psrc[], + int width, int height ) +{ + register int x, y; + register uint8_t *l0, *l1, *l2, *l3; + + l0 = pdst; /* target line */ + l1 = psrc[0]; /* 1st source line */ + l2 = l1 + width; /* 2nd source line = line that follows l1 */ + l3 = l2 + width; /* 3rd source line = line that follows l2 */ + + /* Copy the first line */ + xine_fast_memcpy(l0, l1, width); + l0 += width; + + for (y = 1; y < height-1; ++y) { + /* computes avg of: l1 + 2*l2 + l3 */ + + for (x = 0; x < width; ++x) { + l0[x] = (l1[x] + (l2[x]<<1) + l3[x]) >> 2; + } + + /* updates the line pointers */ + l1 = l2; l2 = l3; l3 += width; + l0 += width; + } + + /* Copy the last line */ + xine_fast_memcpy(l0, l1, width); +} + +static int check_for_mmx(void) +{ +#if defined(ARCH_X86) || defined(ARCH_X86_64) +static int config_flags = -1; + + if ( config_flags == -1 ) + config_flags = xine_mm_accel(); + if (config_flags & MM_ACCEL_X86_MMX) + return 1; + return 0; +#else + return 0; +#endif +} + +/* generic YUV deinterlacer + pdst -> pointer to destination bitmap + psrc -> array of pointers to source bitmaps ([0] = most recent) + width,height -> dimension for bitmaps + method -> DEINTERLACE_xxx +*/ + +void deinterlace_yuv( uint8_t *pdst, uint8_t *psrc[], + int width, int height, int method ) +{ + switch( method ) { + case DEINTERLACE_NONE: + xine_fast_memcpy(pdst,psrc[0],width*height); + break; + case DEINTERLACE_BOB: + if( check_for_mmx() ) + deinterlace_bob_yuv_mmx(pdst,psrc,width,height); + else /* FIXME: provide an alternative? */ + xine_fast_memcpy(pdst,psrc[0],width*height); + break; + case DEINTERLACE_WEAVE: + if( check_for_mmx() ) + { + if( !deinterlace_weave_yuv_mmx(pdst,psrc,width,height) ) + xine_fast_memcpy(pdst,psrc[0],width*height); + } + else /* FIXME: provide an alternative? */ + xine_fast_memcpy(pdst,psrc[0],width*height); + break; + case DEINTERLACE_GREEDY: + if( check_for_mmx() ) + { + if( !deinterlace_greedy_yuv_mmx(pdst,psrc,width,height) ) + xine_fast_memcpy(pdst,psrc[0],width*height); + } + else /* FIXME: provide an alternative? */ + xine_fast_memcpy(pdst,psrc[0],width*height); + break; + case DEINTERLACE_ONEFIELD: + if( check_for_mmx() ) + deinterlace_onefield_yuv_mmx(pdst,psrc,width,height); + else /* FIXME: provide an alternative? */ + xine_fast_memcpy(pdst,psrc[0],width*height); + break; + case DEINTERLACE_ONEFIELDXV: + lprintf("ONEFIELDXV must be handled by the video driver.\n"); + break; + case DEINTERLACE_LINEARBLEND: + if( check_for_mmx() ) + deinterlace_linearblend_yuv_mmx(pdst,psrc,width,height); + else + deinterlace_linearblend_yuv(pdst,psrc,width,height); + break; + default: + lprintf("unknow method %d.\n",method); + break; + } +} + +int deinterlace_yuv_supported ( int method ) +{ + switch( method ) { + case DEINTERLACE_NONE: + return 1; + case DEINTERLACE_BOB: + case DEINTERLACE_WEAVE: + case DEINTERLACE_GREEDY: + case DEINTERLACE_ONEFIELD: + return check_for_mmx(); + case DEINTERLACE_ONEFIELDXV: + lprintf ("ONEFIELDXV must be handled by the video driver.\n"); + return 0; + case DEINTERLACE_LINEARBLEND: + return 1; + } + + return 0; +} + +char *deinterlace_methods[] = { + "none", + "bob", + "weave", + "greedy", + "onefield", + "onefield_xv", + "linearblend", + NULL +}; + + diff --git a/src/modules/xine/deinterlace.h b/src/modules/xine/deinterlace.h new file mode 100644 index 0000000..8f2ced4 --- /dev/null +++ b/src/modules/xine/deinterlace.h @@ -0,0 +1,47 @@ + /* + * Copyright (C) 2001 the xine project + * + * This file is part of xine, a free video player. + * + * xine 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 2 of the License, or + * (at your option) any later version. + * + * xine 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * Deinterlace routines by Miguel Freitas + * based of DScaler project sources (deinterlace.sourceforge.net) + * + * Currently only available for Xv driver and MMX extensions + * + */ + +#ifndef __DEINTERLACE_H__ +#define __DEINTERLACE_H__ + +//#include "video_out.h" +#include + +int deinterlace_yuv_supported ( int method ); +void deinterlace_yuv( uint8_t *pdst, uint8_t *psrc[], + int width, int height, int method ); + +#define DEINTERLACE_NONE 0 +#define DEINTERLACE_BOB 1 +#define DEINTERLACE_WEAVE 2 +#define DEINTERLACE_GREEDY 3 +#define DEINTERLACE_ONEFIELD 4 +#define DEINTERLACE_ONEFIELDXV 5 +#define DEINTERLACE_LINEARBLEND 6 + +extern char *deinterlace_methods[]; + +#endif diff --git a/src/modules/xine/factory.c b/src/modules/xine/factory.c new file mode 100644 index 0000000..88653c4 --- /dev/null +++ b/src/modules/xine/factory.c @@ -0,0 +1,46 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "filter_deinterlace.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "deinterlace" ) ) + return filter_deinterlace_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + return NULL; +} + diff --git a/src/modules/xine/filter_deinterlace.c b/src/modules/xine/filter_deinterlace.c new file mode 100644 index 0000000..f7883d9 --- /dev/null +++ b/src/modules/xine/filter_deinterlace.c @@ -0,0 +1,106 @@ +/* + * filter_deinterlace.c -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "filter_deinterlace.h" +#include "deinterlace.h" + +#include + +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + int deinterlace = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "consumer_deinterlace" ); + + // Pop the service off the stack + mlt_filter filter = mlt_frame_pop_service( this ); + + // Determine if we need a writable version or not + if ( deinterlace && !writable ) + writable = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ); + + // Get the input image + error = mlt_frame_get_image( this, image, format, width, height, writable ); + + // Check that we want progressive and we aren't already progressive + if ( deinterlace && *format == mlt_image_yuv422 && *image != NULL && !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ) ) + { + // Determine deinterlace method + char *method_str = mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "method" ); + int method = DEINTERLACE_LINEARBLEND; + char *frame_method_str = mlt_properties_get( MLT_FRAME_PROPERTIES( this ), "deinterlace_method" ); + + if ( frame_method_str != NULL ) + method_str = frame_method_str; + + if ( method_str == NULL ) + method = DEINTERLACE_LINEARBLEND; + else if ( strcmp( method_str, "bob" ) == 0 ) + method = DEINTERLACE_BOB; + else if ( strcmp( method_str, "weave" ) == 0 ) + method = DEINTERLACE_BOB; + else if ( strcmp( method_str, "greedy" ) == 0 ) + method = DEINTERLACE_GREEDY; + else if ( strcmp( method_str, "onefield" ) == 0 ) + method = DEINTERLACE_ONEFIELD; + + // Deinterlace the image + deinterlace_yuv( *image, image, *width * 2, *height, method ); + + // Make sure that others know the frame is deinterlaced + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "progressive", 1 ); + } + + return error; +} + +/** Deinterlace filter processing - this should be lazy evaluation here... +*/ + +static mlt_frame deinterlace_process( mlt_filter this, mlt_frame frame ) +{ + // Push this on to the service stack + mlt_frame_push_service( frame, this ); + + // Push the get_image method on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_deinterlace_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = deinterlace_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "method", arg ); + } + return this; +} + diff --git a/src/modules/xine/filter_deinterlace.h b/src/modules/xine/filter_deinterlace.h new file mode 100644 index 0000000..15660c6 --- /dev/null +++ b/src/modules/xine/filter_deinterlace.h @@ -0,0 +1,28 @@ +/* + * filter_deinterlace.h -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _FILTER_DEINTERLACE_H_ +#define _FILTER_DEINTERLACE_H_ + +#include + +extern mlt_filter filter_deinterlace_init( void *arg ); + +#endif diff --git a/src/modules/xine/gpl b/src/modules/xine/gpl new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/xine/xineutils.h b/src/modules/xine/xineutils.h new file mode 100644 index 0000000..a14802e --- /dev/null +++ b/src/modules/xine/xineutils.h @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2000-2004 the xine project + * + * This file is part of xine, a free video player. + * + * xine 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 2 of the License, or + * (at your option) any later version. + * + * xine 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: xineutils.h 194 2004-03-04 16:52:11Z ddennedy $ + * + */ +#ifndef XINEUTILS_H +#define XINEUTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#if HAVE_LIBGEN_H +# include +#endif + +//#ifdef XINE_COMPILE +# include "attributes.h" +//# include "compat.h" +//# include "xmlparser.h" +//# include "xine_buffer.h" +//# include "configfile.h" +//#else +//# include +//# include +//# include +//# include +//# include +//#endif + +//#ifdef HAVE_CONFIG_H +//#include "config.h" +//#endif + +#include +#include + + /* + * debugable mutexes + */ + + typedef struct { + pthread_mutex_t mutex; + char id[80]; + char *locked_by; + } xine_mutex_t; + + int xine_mutex_init (xine_mutex_t *mutex, const pthread_mutexattr_t *mutexattr, + char *id); + + int xine_mutex_lock (xine_mutex_t *mutex, char *who); + int xine_mutex_unlock (xine_mutex_t *mutex, char *who); + int xine_mutex_destroy (xine_mutex_t *mutex); + + + + /* CPU Acceleration */ + +/* + * The type of an value that fits in an MMX register (note that long + * long constant values MUST be suffixed by LL and unsigned long long + * values by ULL, lest they be truncated by the compiler) + */ + +/* generic accelerations */ +#define MM_ACCEL_MLIB 0x00000001 + +/* x86 accelerations */ +#define MM_ACCEL_X86_MMX 0x80000000 +#define MM_ACCEL_X86_3DNOW 0x40000000 +#define MM_ACCEL_X86_MMXEXT 0x20000000 +#define MM_ACCEL_X86_SSE 0x10000000 +#define MM_ACCEL_X86_SSE2 0x08000000 +/* powerpc accelerations */ +#define MM_ACCEL_PPC_ALTIVEC 0x04000000 +/* x86 compat defines */ +#define MM_MMX MM_ACCEL_X86_MMX +#define MM_3DNOW MM_ACCEL_X86_3DNOW +#define MM_MMXEXT MM_ACCEL_X86_MMXEXT +#define MM_SSE MM_ACCEL_X86_SSE +#define MM_SSE2 MM_ACCEL_X86_SSE2 + +uint32_t xine_mm_accel (void); + +#if defined(ARCH_X86) || defined(ARCH_X86_64) + +typedef union { + int64_t q; /* Quadword (64-bit) value */ + uint64_t uq; /* Unsigned Quadword */ + int d[2]; /* 2 Doubleword (32-bit) values */ + unsigned int ud[2]; /* 2 Unsigned Doubleword */ + short w[4]; /* 4 Word (16-bit) values */ + unsigned short uw[4]; /* 4 Unsigned Word */ + char b[8]; /* 8 Byte (8-bit) values */ + unsigned char ub[8]; /* 8 Unsigned Byte */ + float s[2]; /* Single-precision (32-bit) value */ +} ATTR_ALIGN(8) mmx_t; /* On an 8-byte (64-bit) boundary */ + + + +#define mmx_i2r(op,imm,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "i" (imm) ) + +#define mmx_m2r(op,mem,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "m" (mem)) + +#define mmx_r2m(op,reg,mem) \ + __asm__ __volatile__ (#op " %%" #reg ", %0" \ + : "=m" (mem) \ + : /* nothing */ ) + +#define mmx_r2r(op,regs,regd) \ + __asm__ __volatile__ (#op " %" #regs ", %" #regd) + + +#define emms() __asm__ __volatile__ ("emms") + +#define movd_m2r(var,reg) mmx_m2r (movd, var, reg) +#define movd_r2m(reg,var) mmx_r2m (movd, reg, var) +#define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd) + +#define movq_m2r(var,reg) mmx_m2r (movq, var, reg) +#define movq_r2m(reg,var) mmx_r2m (movq, reg, var) +#define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd) + +#define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg) +#define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd) +#define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg) +#define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd) + +#define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg) +#define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd) + +#define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg) +#define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd) +#define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg) +#define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd) +#define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg) +#define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd) + +#define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg) +#define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd) +#define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg) +#define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd) + +#define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg) +#define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd) +#define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg) +#define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd) + +#define pand_m2r(var,reg) mmx_m2r (pand, var, reg) +#define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd) + +#define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg) +#define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd) + +#define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg) +#define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd) +#define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg) +#define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd) +#define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg) +#define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd) + +#define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg) +#define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd) +#define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg) +#define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd) +#define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg) +#define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd) + +#define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg) +#define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd) + +#define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg) +#define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd) + +#define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg) +#define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd) + +#define por_m2r(var,reg) mmx_m2r (por, var, reg) +#define por_r2r(regs,regd) mmx_r2r (por, regs, regd) + +#define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg) +#define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg) +#define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd) +#define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg) +#define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg) +#define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd) +#define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg) +#define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg) +#define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd) + +#define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg) +#define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg) +#define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd) +#define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg) +#define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg) +#define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd) + +#define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg) +#define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg) +#define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd) +#define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg) +#define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg) +#define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd) +#define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg) +#define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg) +#define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd) + +#define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg) +#define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd) +#define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg) +#define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd) +#define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg) +#define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd) + +#define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg) +#define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd) +#define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg) +#define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd) + +#define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg) +#define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd) +#define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg) +#define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd) + +#define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg) +#define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd) +#define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg) +#define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd) +#define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg) +#define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd) + +#define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg) +#define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd) +#define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg) +#define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd) +#define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg) +#define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd) + +#define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg) +#define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd) + + +/* 3DNOW extensions */ + +#define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg) +#define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd) + + +/* AMD MMX extensions - also available in intel SSE */ + + +#define mmx_m2ri(op,mem,reg,imm) \ + __asm__ __volatile__ (#op " %1, %0, %%" #reg \ + : /* nothing */ \ + : "X" (mem), "X" (imm)) +#define mmx_r2ri(op,regs,regd,imm) \ + __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ + : /* nothing */ \ + : "X" (imm) ) + +#define mmx_fetch(mem,hint) \ + __asm__ __volatile__ ("prefetch" #hint " %0" \ + : /* nothing */ \ + : "X" (mem)) + + +#define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg) + +#define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var) + +#define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg) +#define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd) +#define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg) +#define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd) + +#define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm) + +#define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm) + +#define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg) +#define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd) + +#define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg) +#define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd) + +#define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg) +#define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd) + +#define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg) +#define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd) + +#define pmovmskb(mmreg,reg) \ + __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) + +#define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg) +#define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd) + +#define prefetcht0(mem) mmx_fetch (mem, t0) +#define prefetcht1(mem) mmx_fetch (mem, t1) +#define prefetcht2(mem) mmx_fetch (mem, t2) +#define prefetchnta(mem) mmx_fetch (mem, nta) + +#define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg) +#define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd) + +#define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm) +#define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm) + +#define sfence() __asm__ __volatile__ ("sfence\n\t") + +typedef union { + float sf[4]; /* Single-precision (32-bit) value */ +} ATTR_ALIGN(16) sse_t; /* On a 16 byte (128-bit) boundary */ + + +#define sse_i2r(op, imm, reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "X" (imm) ) + +#define sse_m2r(op, mem, reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "X" (mem)) + +#define sse_r2m(op, reg, mem) \ + __asm__ __volatile__ (#op " %%" #reg ", %0" \ + : "=X" (mem) \ + : /* nothing */ ) + +#define sse_r2r(op, regs, regd) \ + __asm__ __volatile__ (#op " %" #regs ", %" #regd) + +#define sse_r2ri(op, regs, regd, imm) \ + __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ + : /* nothing */ \ + : "X" (imm) ) + +#define sse_m2ri(op, mem, reg, subop) \ + __asm__ __volatile__ (#op " %0, %%" #reg ", " #subop \ + : /* nothing */ \ + : "X" (mem)) + + +#define movaps_m2r(var, reg) sse_m2r(movaps, var, reg) +#define movaps_r2m(reg, var) sse_r2m(movaps, reg, var) +#define movaps_r2r(regs, regd) sse_r2r(movaps, regs, regd) + +#define movntps_r2m(xmmreg, var) sse_r2m(movntps, xmmreg, var) + +#define movups_m2r(var, reg) sse_m2r(movups, var, reg) +#define movups_r2m(reg, var) sse_r2m(movups, reg, var) +#define movups_r2r(regs, regd) sse_r2r(movups, regs, regd) + +#define movhlps_r2r(regs, regd) sse_r2r(movhlps, regs, regd) + +#define movlhps_r2r(regs, regd) sse_r2r(movlhps, regs, regd) + +#define movhps_m2r(var, reg) sse_m2r(movhps, var, reg) +#define movhps_r2m(reg, var) sse_r2m(movhps, reg, var) + +#define movlps_m2r(var, reg) sse_m2r(movlps, var, reg) +#define movlps_r2m(reg, var) sse_r2m(movlps, reg, var) + +#define movss_m2r(var, reg) sse_m2r(movss, var, reg) +#define movss_r2m(reg, var) sse_r2m(movss, reg, var) +#define movss_r2r(regs, regd) sse_r2r(movss, regs, regd) + +#define shufps_m2r(var, reg, index) sse_m2ri(shufps, var, reg, index) +#define shufps_r2r(regs, regd, index) sse_r2ri(shufps, regs, regd, index) + +#define cvtpi2ps_m2r(var, xmmreg) sse_m2r(cvtpi2ps, var, xmmreg) +#define cvtpi2ps_r2r(mmreg, xmmreg) sse_r2r(cvtpi2ps, mmreg, xmmreg) + +#define cvtps2pi_m2r(var, mmreg) sse_m2r(cvtps2pi, var, mmreg) +#define cvtps2pi_r2r(xmmreg, mmreg) sse_r2r(cvtps2pi, mmreg, xmmreg) + +#define cvttps2pi_m2r(var, mmreg) sse_m2r(cvttps2pi, var, mmreg) +#define cvttps2pi_r2r(xmmreg, mmreg) sse_r2r(cvttps2pi, mmreg, xmmreg) + +#define cvtsi2ss_m2r(var, xmmreg) sse_m2r(cvtsi2ss, var, xmmreg) +#define cvtsi2ss_r2r(reg, xmmreg) sse_r2r(cvtsi2ss, reg, xmmreg) + +#define cvtss2si_m2r(var, reg) sse_m2r(cvtss2si, var, reg) +#define cvtss2si_r2r(xmmreg, reg) sse_r2r(cvtss2si, xmmreg, reg) + +#define cvttss2si_m2r(var, reg) sse_m2r(cvtss2si, var, reg) +#define cvttss2si_r2r(xmmreg, reg) sse_r2r(cvtss2si, xmmreg, reg) + +#define movmskps(xmmreg, reg) \ + __asm__ __volatile__ ("movmskps %" #xmmreg ", %" #reg) + +#define addps_m2r(var, reg) sse_m2r(addps, var, reg) +#define addps_r2r(regs, regd) sse_r2r(addps, regs, regd) + +#define addss_m2r(var, reg) sse_m2r(addss, var, reg) +#define addss_r2r(regs, regd) sse_r2r(addss, regs, regd) + +#define subps_m2r(var, reg) sse_m2r(subps, var, reg) +#define subps_r2r(regs, regd) sse_r2r(subps, regs, regd) + +#define subss_m2r(var, reg) sse_m2r(subss, var, reg) +#define subss_r2r(regs, regd) sse_r2r(subss, regs, regd) + +#define mulps_m2r(var, reg) sse_m2r(mulps, var, reg) +#define mulps_r2r(regs, regd) sse_r2r(mulps, regs, regd) + +#define mulss_m2r(var, reg) sse_m2r(mulss, var, reg) +#define mulss_r2r(regs, regd) sse_r2r(mulss, regs, regd) + +#define divps_m2r(var, reg) sse_m2r(divps, var, reg) +#define divps_r2r(regs, regd) sse_r2r(divps, regs, regd) + +#define divss_m2r(var, reg) sse_m2r(divss, var, reg) +#define divss_r2r(regs, regd) sse_r2r(divss, regs, regd) + +#define rcpps_m2r(var, reg) sse_m2r(rcpps, var, reg) +#define rcpps_r2r(regs, regd) sse_r2r(rcpps, regs, regd) + +#define rcpss_m2r(var, reg) sse_m2r(rcpss, var, reg) +#define rcpss_r2r(regs, regd) sse_r2r(rcpss, regs, regd) + +#define rsqrtps_m2r(var, reg) sse_m2r(rsqrtps, var, reg) +#define rsqrtps_r2r(regs, regd) sse_r2r(rsqrtps, regs, regd) + +#define rsqrtss_m2r(var, reg) sse_m2r(rsqrtss, var, reg) +#define rsqrtss_r2r(regs, regd) sse_r2r(rsqrtss, regs, regd) + +#define sqrtps_m2r(var, reg) sse_m2r(sqrtps, var, reg) +#define sqrtps_r2r(regs, regd) sse_r2r(sqrtps, regs, regd) + +#define sqrtss_m2r(var, reg) sse_m2r(sqrtss, var, reg) +#define sqrtss_r2r(regs, regd) sse_r2r(sqrtss, regs, regd) + +#define andps_m2r(var, reg) sse_m2r(andps, var, reg) +#define andps_r2r(regs, regd) sse_r2r(andps, regs, regd) + +#define andnps_m2r(var, reg) sse_m2r(andnps, var, reg) +#define andnps_r2r(regs, regd) sse_r2r(andnps, regs, regd) + +#define orps_m2r(var, reg) sse_m2r(orps, var, reg) +#define orps_r2r(regs, regd) sse_r2r(orps, regs, regd) + +#define xorps_m2r(var, reg) sse_m2r(xorps, var, reg) +#define xorps_r2r(regs, regd) sse_r2r(xorps, regs, regd) + +#define maxps_m2r(var, reg) sse_m2r(maxps, var, reg) +#define maxps_r2r(regs, regd) sse_r2r(maxps, regs, regd) + +#define maxss_m2r(var, reg) sse_m2r(maxss, var, reg) +#define maxss_r2r(regs, regd) sse_r2r(maxss, regs, regd) + +#define minps_m2r(var, reg) sse_m2r(minps, var, reg) +#define minps_r2r(regs, regd) sse_r2r(minps, regs, regd) + +#define minss_m2r(var, reg) sse_m2r(minss, var, reg) +#define minss_r2r(regs, regd) sse_r2r(minss, regs, regd) + +#define cmpps_m2r(var, reg, op) sse_m2ri(cmpps, var, reg, op) +#define cmpps_r2r(regs, regd, op) sse_r2ri(cmpps, regs, regd, op) + +#define cmpeqps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 0) +#define cmpeqps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 0) + +#define cmpltps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 1) +#define cmpltps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 1) + +#define cmpleps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 2) +#define cmpleps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 2) + +#define cmpunordps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 3) +#define cmpunordps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 3) + +#define cmpneqps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 4) +#define cmpneqps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 4) + +#define cmpnltps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 5) +#define cmpnltps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 5) + +#define cmpnleps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 6) +#define cmpnleps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 6) + +#define cmpordps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 7) +#define cmpordps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 7) + +#define cmpss_m2r(var, reg, op) sse_m2ri(cmpss, var, reg, op) +#define cmpss_r2r(regs, regd, op) sse_r2ri(cmpss, regs, regd, op) + +#define cmpeqss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 0) +#define cmpeqss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 0) + +#define cmpltss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 1) +#define cmpltss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 1) + +#define cmpless_m2r(var, reg) sse_m2ri(cmpss, var, reg, 2) +#define cmpless_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 2) + +#define cmpunordss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 3) +#define cmpunordss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 3) + +#define cmpneqss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 4) +#define cmpneqss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 4) + +#define cmpnltss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 5) +#define cmpnltss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 5) + +#define cmpnless_m2r(var, reg) sse_m2ri(cmpss, var, reg, 6) +#define cmpnless_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 6) + +#define cmpordss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 7) +#define cmpordss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 7) + +#define comiss_m2r(var, reg) sse_m2r(comiss, var, reg) +#define comiss_r2r(regs, regd) sse_r2r(comiss, regs, regd) + +#define ucomiss_m2r(var, reg) sse_m2r(ucomiss, var, reg) +#define ucomiss_r2r(regs, regd) sse_r2r(ucomiss, regs, regd) + +#define unpcklps_m2r(var, reg) sse_m2r(unpcklps, var, reg) +#define unpcklps_r2r(regs, regd) sse_r2r(unpcklps, regs, regd) + +#define unpckhps_m2r(var, reg) sse_m2r(unpckhps, var, reg) +#define unpckhps_r2r(regs, regd) sse_r2r(unpckhps, regs, regd) + +#define fxrstor(mem) \ + __asm__ __volatile__ ("fxrstor %0" \ + : /* nothing */ \ + : "X" (mem)) + +#define fxsave(mem) \ + __asm__ __volatile__ ("fxsave %0" \ + : /* nothing */ \ + : "X" (mem)) + +#define stmxcsr(mem) \ + __asm__ __volatile__ ("stmxcsr %0" \ + : /* nothing */ \ + : "X" (mem)) + +#define ldmxcsr(mem) \ + __asm__ __volatile__ ("ldmxcsr %0" \ + : /* nothing */ \ + : "X" (mem)) +#endif /*ARCH_X86 */ + + + + /* Optimized/fast memcpy */ + +/* + TODO : fix dll linkage problem for xine_fast_memcpy on win32 + + xine_fast_memcpy dll linkage is screwy here. + declaring as dllimport seems to fix the problem + but causes compiler warning with libxineutils +*/ +#ifdef _MSC_VER +__declspec( dllimport ) extern void *(* xine_fast_memcpy)(void *to, const void *from, size_t len); +#else +extern void *(* xine_fast_memcpy)(void *to, const void *from, size_t len); +#endif + +#ifdef HAVE_XINE_INTERNAL_H +/* Benchmark available memcpy methods */ +void xine_probe_fast_memcpy(xine_t *xine); +#endif + + +/* + * Debug stuff + */ +/* + * profiling (unworkable in non DEBUG isn't defined) + */ +void xine_profiler_init (void); +int xine_profiler_allocate_slot (char *label); +void xine_profiler_start_count (int id); +void xine_profiler_stop_count (int id); +void xine_profiler_print_results (void); + +/* + * Allocate and clean memory size_t 'size', then return the pointer + * to the allocated memory. + */ +#if !defined(__GNUC__) || __GNUC__ < 3 +void *xine_xmalloc(size_t size); +#else +void *xine_xmalloc(size_t size) __attribute__ ((__malloc__)); +#endif + +/* + * Same as above, but memory is aligned to 'alignement'. + * **base is used to return pointer to un-aligned memory, use + * this to free the mem chunk + */ +void *xine_xmalloc_aligned(size_t alignment, size_t size, void **base); + +/* + * Get user home directory. + */ +const char *xine_get_homedir(void); + +/* + * Clean a string (remove spaces and '=' at the begin, + * and '\n', '\r' and spaces at the end. + */ +char *xine_chomp (char *str); + +/* + * A thread-safe usecond sleep + */ +void xine_usec_sleep(unsigned usec); + + + /* + * Some string functions + */ + + +void xine_strdupa(char *dest, char *src); +#define xine_strdupa(d, s) do { \ + (d) = NULL; \ + if((s) != NULL) { \ + (d) = (char *) alloca(strlen((s)) + 1); \ + strcpy((d), (s)); \ + } \ + } while(0) + +/* Shamefully copied from glibc 2.2.3 */ +#ifdef HAVE_STRPBRK +#define xine_strpbrk strpbrk +#else +static inline char *_private_strpbrk(const char *s, const char *accept) { + + while(*s != '\0') { + const char *a = accept; + while(*a != '\0') + if(*a++ == *s) + return(char *) s; + ++s; + } + + return NULL; +} +#define xine_strpbrk _private_strpbrk +#endif + +#if defined HAVE_STRSEP && !defined(_MSC_VER) +#define xine_strsep strsep +#else +static inline char *_private_strsep(char **stringp, const char *delim) { + char *begin, *end; + + begin = *stringp; + if(begin == NULL) + return NULL; + + if(delim[0] == '\0' || delim[1] == '\0') { + char ch = delim[0]; + + if(ch == '\0') + end = NULL; + else { + if(*begin == ch) + end = begin; + else if(*begin == '\0') + end = NULL; + else + end = strchr(begin + 1, ch); + } + } + else + end = xine_strpbrk(begin, delim); + + if(end) { + *end++ = '\0'; + *stringp = end; + } + else + *stringp = NULL; + + return begin; +} +#define xine_strsep _private_strsep +#endif + + +#ifdef HAVE_SETENV +#define xine_setenv setenv +#else +static inline void _private_setenv(const char *name, const char *val, int _xx) { + int len = strlen(name) + strlen(val) + 2; + char env[len]; + + sprintf(env, "%s%c%s", name, '=', val); + putenv(env); +} +#define xine_setenv _private_setenv +#endif + +/* + * Color Conversion Utility Functions + * The following data structures and functions facilitate the conversion + * of RGB images to packed YUV (YUY2) images. There are also functions to + * convert from YUV9 -> YV12. All of the meaty details are written in + * color.c. + */ + +typedef struct yuv_planes_s { + + unsigned char *y; + unsigned char *u; + unsigned char *v; + unsigned int row_width; /* frame width */ + unsigned int row_count; /* frame height */ + +} yuv_planes_t; + +void init_yuv_conversion(void); +void init_yuv_planes(yuv_planes_t *yuv_planes, int width, int height); +void free_yuv_planes(yuv_planes_t *yuv_planes); + +extern void (*yuv444_to_yuy2) + (yuv_planes_t *yuv_planes, unsigned char *yuy2_map, int pitch); +extern void (*yuv9_to_yv12) + (unsigned char *y_src, int y_src_pitch, unsigned char *y_dest, int y_dest_pitch, + unsigned char *u_src, int u_src_pitch, unsigned char *u_dest, int u_dest_pitch, + unsigned char *v_src, int v_src_pitch, unsigned char *v_dest, int v_dest_pitch, + int width, int height); +extern void (*yuv411_to_yv12) + (unsigned char *y_src, int y_src_pitch, unsigned char *y_dest, int y_dest_pitch, + unsigned char *u_src, int u_src_pitch, unsigned char *u_dest, int u_dest_pitch, + unsigned char *v_src, int v_src_pitch, unsigned char *v_dest, int v_dest_pitch, + int width, int height); +extern void (*yv12_to_yuy2) + (unsigned char *y_src, int y_src_pitch, + unsigned char *u_src, int u_src_pitch, + unsigned char *v_src, int v_src_pitch, + unsigned char *yuy2_map, int yuy2_pitch, + int width, int height, int progressive); +extern void (*yuy2_to_yv12) + (unsigned char *yuy2_map, int yuy2_pitch, + unsigned char *y_dst, int y_dst_pitch, + unsigned char *u_dst, int u_dst_pitch, + unsigned char *v_dst, int v_dst_pitch, + int width, int height); + +#define SCALEFACTOR 65536 +#define CENTERSAMPLE 128 + +#define COMPUTE_Y(r, g, b) \ + (unsigned char) \ + ((y_r_table[r] + y_g_table[g] + y_b_table[b]) / SCALEFACTOR) +#define COMPUTE_U(r, g, b) \ + (unsigned char) \ + ((u_r_table[r] + u_g_table[g] + u_b_table[b]) / SCALEFACTOR + CENTERSAMPLE) +#define COMPUTE_V(r, g, b) \ + (unsigned char) \ + ((v_r_table[r] + v_g_table[g] + v_b_table[b]) / SCALEFACTOR + CENTERSAMPLE) + +#define UNPACK_BGR15(packed_pixel, r, g, b) \ + b = (packed_pixel & 0x7C00) >> 7; \ + g = (packed_pixel & 0x03E0) >> 2; \ + r = (packed_pixel & 0x001F) << 3; + +#define UNPACK_BGR16(packed_pixel, r, g, b) \ + b = (packed_pixel & 0xF800) >> 8; \ + g = (packed_pixel & 0x07E0) >> 3; \ + r = (packed_pixel & 0x001F) << 3; + +#define UNPACK_RGB15(packed_pixel, r, g, b) \ + r = (packed_pixel & 0x7C00) >> 7; \ + g = (packed_pixel & 0x03E0) >> 2; \ + b = (packed_pixel & 0x001F) << 3; + +#define UNPACK_RGB16(packed_pixel, r, g, b) \ + r = (packed_pixel & 0xF800) >> 8; \ + g = (packed_pixel & 0x07E0) >> 3; \ + b = (packed_pixel & 0x001F) << 3; + +extern int y_r_table[256]; +extern int y_g_table[256]; +extern int y_b_table[256]; + +extern int u_r_table[256]; +extern int u_g_table[256]; +extern int u_b_table[256]; + +extern int v_r_table[256]; +extern int v_g_table[256]; +extern int v_b_table[256]; + +/* frame copying functions */ +extern void yv12_to_yv12 + (unsigned char *y_src, int y_src_pitch, unsigned char *y_dst, int y_dst_pitch, + unsigned char *u_src, int u_src_pitch, unsigned char *u_dst, int u_dst_pitch, + unsigned char *v_src, int v_src_pitch, unsigned char *v_dst, int v_dst_pitch, + int width, int height); +extern void yuy2_to_yuy2 + (unsigned char *src, int src_pitch, + unsigned char *dst, int dst_pitch, + int width, int height); + +/* print a hexdump of the given data */ +void xine_hexdump (const char *buf, int length); + +/* + * Optimization macros for conditions + * Taken from the FIASCO L4 microkernel sources + */ +#if !defined(__GNUC__) || __GNUC__ < 3 +# define EXPECT_TRUE(x) (x) +# define EXPECT_FALSE(x) (x) +#else +# define EXPECT_TRUE(x) __builtin_expect((x),1) +# define EXPECT_FALSE(x) __builtin_expect((x),0) +#endif + +#ifdef NDEBUG +#define _x_assert(exp) \ + do { \ + if (!(exp)) \ + fprintf(stderr, "assert: %s:%d: %s: Assertion `%s' failed.\n", \ + __FILE__, __LINE__, __XINE_FUNCTION__, #exp); \ + } while(0) +#else +#define _x_assert(exp) \ + do { \ + if (!(exp)) { \ + fprintf(stderr, "assert: %s:%d: %s: Assertion `%s' failed.\n", \ + __FILE__, __LINE__, __XINE_FUNCTION__, #exp); \ + abort(); \ + } \ + } while(0) +#endif + +#define _x_abort() \ + do { \ + fprintf(stderr, "abort: %s:%d: %s: Aborting.\n", \ + __FILE__, __LINE__, __XINE_FUNCTION__); \ + abort(); \ + } while(0) + + +/****** logging with xine **********************************/ + +#ifndef LOG_MODULE + #define LOG_MODULE __FILE__ +#endif /* LOG_MODULE */ + +#define LOG_MODULE_STRING printf("%s: ", LOG_MODULE ); + +#ifdef LOG_VERBOSE + #define LONG_LOG_MODULE_STRING \ + printf("%s: (%s:%d) ", LOG_MODULE, __XINE_FUNCTION__, __LINE__ ); +#else + #define LONG_LOG_MODULE_STRING LOG_MODULE_STRING +#endif /* LOG_VERBOSE */ + +#ifdef LOG + #ifdef __GNUC__ + #define lprintf(fmt, args...) \ + do { \ + LONG_LOG_MODULE_STRING \ + printf(fmt, ##args); \ + } while(0) + #else /* __GNUC__ */ + #ifdef _MSC_VER + #define lprintf(fmtargs) \ + do { \ + LONG_LOG_MODULE_STRING \ + printf("%s", fmtargs); \ + } while(0) + #else /* _MSC_VER */ + #define lprintf(fmt, ...) \ + do { \ + LONG_LOG_MODULE_STRING \ + printf(__VA_ARGS__); \ + } while(0) + #endif /* _MSC_VER */ + #endif /* __GNUC__ */ +#else /* LOG */ + #ifdef __GNUC__ + #define lprintf(fmt, args...) do {} while(0) + #else + #ifdef _MSC_VER + #define lprintf + #else + #define lprintf(...) do {} while(0) + #endif /* _MSC_VER */ + #endif /* __GNUC__ */ +#endif /* LOG */ + +#ifdef __GNUC__ + #define llprintf(cat, fmt, args...) \ + do{ \ + if(cat){ \ + LONG_LOG_MODULE_STRING \ + printf( fmt, ##args ); \ + } \ + }while(0) +#else +#ifdef _MSC_VER + #define llprintf(cat, fmtargs) \ + do{ \ + if(cat){ \ + LONG_LOG_MODULE_STRING \ + printf( "%s", fmtargs ); \ + } \ + }while(0) +#else + #define llprintf(cat, ...) \ + do{ \ + if(cat){ \ + LONG_LOG_MODULE_STRING \ + printf( __VA_ARGS__ ); \ + } \ + }while(0) +#endif /* _MSC_VER */ +#endif /* __GNUC__ */ + +#ifdef __GNUC__ + #define xprintf(xine, verbose, fmt, args...) \ + do { \ + if((xine) && (xine)->verbosity >= verbose){ \ + xine_log(xine, XINE_LOG_TRACE, fmt, ##args); \ + } \ + } while(0) +#else +#ifdef _MSC_VER + #define xprintf(xine, verbose, fmtargs) \ + do { \ + if((xine) && (xine)->verbosity >= verbose){ \ + xine_log(xine, XINE_LOG_TRACE, fmtargs); \ + } \ + } while(0) +#else + #define xprintf(xine, verbose, ...) \ + do { \ + if((xine) && (xine)->verbosity >= verbose){ \ + xine_log(xine, XINE_LOG_TRACE, __VA_ARGS__); \ + } \ + } while(0) +#endif /* _MSC_VER */ +#endif /* __GNUC__ */ + +/* time measuring macros for profiling tasks */ + +#ifdef DEBUG +# define XINE_PROFILE(function) \ + do { \ + struct timeval current_time; \ + double dtime; \ + gettimeofday(¤t_time, NULL); \ + dtime = -(current_time.tv_sec + (current_time.tv_usec / 1000000.0)); \ + function; \ + gettimeofday(¤t_time, NULL); \ + dtime += current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ + printf("%s: (%s:%d) took %lf seconds\n", \ + LOG_MODULE, __XINE_FUNCTION__, __LINE__, dtime); \ + } while(0) +# define XINE_PROFILE_ACCUMULATE(function) \ + do { \ + struct timeval current_time; \ + static double dtime = 0; \ + gettimeofday(¤t_time, NULL); \ + dtime -= current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ + function; \ + gettimeofday(¤t_time, NULL); \ + dtime += current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ + printf("%s: (%s:%d) took %lf seconds\n", \ + LOG_MODULE, __XINE_FUNCTION__, __LINE__, dtime); \ + } while(0) +#else +# define XINE_PROFILE(function) function +# define XINE_PROFILE_ACCUMULATE(function) function +#endif /* LOG */ + + +/******** double chained lists with builtin iterator *******/ + +typedef struct xine_node_s { + + struct xine_node_s *next, *prev; + + void *content; + + int priority; + +} xine_node_t; + + +typedef struct { + + xine_node_t *first, *last, *cur; + +} xine_list_t; + + + +xine_list_t *xine_list_new (void); + + +/** + * dispose the whole list. + * note: disposes _only_ the list structure, content must be free()d elsewhere + */ +void xine_list_free(xine_list_t *l); + + +/** + * returns: Boolean + */ +int xine_list_is_empty (xine_list_t *l); + +/** + * return content of first entry in list. + */ +void *xine_list_first_content (xine_list_t *l); + +/** + * return next content in list. + */ +void *xine_list_next_content (xine_list_t *l); + +/** + * Return last content of list. + */ +void *xine_list_last_content (xine_list_t *l); + +/** + * Return previous content of list. + */ +void *xine_list_prev_content (xine_list_t *l); + +/** + * Append content to list, sorted by decreasing priority. + */ +void xine_list_append_priority_content (xine_list_t *l, void *content, int priority); + +/** + * Append content to list. + */ +void xine_list_append_content (xine_list_t *l, void *content); + +/** + * Insert content in list. + */ +void xine_list_insert_content (xine_list_t *l, void *content); + +/** + * Remove current content in list. + * note: removes only the list entry; content must be free()d elsewhere. + */ +void xine_list_delete_current (xine_list_t *l); + +#ifndef HAVE_BASENAME +/* + * get base name + */ +char *basename (char const *name); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tests/Makefile b/src/tests/Makefile new file mode 100644 index 0000000..ac2b4a3 --- /dev/null +++ b/src/tests/Makefile @@ -0,0 +1,40 @@ +include ../../config.mak + +TARGET = dan charlie pango pixbuf dissolve luma + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../framework -L../modules -lmlt -lmltdv -lmltsdl + +all: $(TARGET) + +hello: hello.o + $(CC) hello.o -o $@ -L../framework -L../modules -lmlt + +pango: pango.o + $(CC) pango.o -o $@ $(LDFLAGS) + +pixbuf: pixbuf.o + $(CC) pixbuf.o -o $@ $(LDFLAGS) + +dissolve: dissolve.o + $(CC) dissolve.o -o $@ $(LDFLAGS) + +luma: luma.o + $(CC) luma.o -o $@ $(LDFLAGS) + +dan: dan.o + $(CC) dan.o -o $@ $(LDFLAGS) + +charlie: charlie.o io.o + $(CC) charlie.o io.o -o $@ $(LDFLAGS) + +clean: + rm -f dan.o io.o charlie.o dan charlie + +depend: dan.c charlie.c io.c + $(CC) -MM $(CFLAGS) $^ 1>.depend + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/tests/charlie.c b/src/tests/charlie.c new file mode 100644 index 0000000..7130b1d --- /dev/null +++ b/src/tests/charlie.c @@ -0,0 +1,193 @@ +#include +#include +#include + +#include + +#include "io.h" + +mlt_producer create_producer( char *file ) +{ + mlt_producer result = NULL; + + // 1st Line preferences + if ( strstr( file, ".inigo" ) ) + { + char *args[ 2 ] = { file, NULL }; + result = mlt_factory_producer( "inigo", args ); + } + else if ( strstr( file, ".mpg" ) ) + result = mlt_factory_producer( "mcmpeg", file ); + else if ( strstr( file, ".mpeg" ) ) + result = mlt_factory_producer( "mcmpeg", file ); + else if ( strstr( file, ".dat" ) ) + result = mlt_factory_producer( "mcmpeg", file ); + else if ( strstr( file, ".dv" ) ) + result = mlt_factory_producer( "mcdv", file ); + else if ( strstr( file, ".dif" ) ) + result = mlt_factory_producer( "mcdv", file ); + else if ( strstr( file, ".jpg" ) ) + result = mlt_factory_producer( "pixbuf", file ); + else if ( strstr( file, ".JPG" ) ) + result = mlt_factory_producer( "pixbuf", file ); + else if ( strstr( file, ".jpeg" ) ) + result = mlt_factory_producer( "pixbuf", file ); + else if ( strstr( file, ".png" ) ) + result = mlt_factory_producer( "pixbuf", file ); + + // 2nd Line fallbacks + if ( result == NULL && strstr( file, ".dv" ) ) + result = mlt_factory_producer( "libdv", file ); + else if ( result == NULL && strstr( file, ".dif" ) ) + result = mlt_factory_producer( "libdv", file ); + + return result; +} + +void transport_action( mlt_producer producer, char *value ) +{ + mlt_properties properties = mlt_producer_properties( producer ); + + switch( value[ 0 ] ) + { + case 'q': + mlt_properties_set_int( properties, "done", 1 ); + break; + case '0': + mlt_producer_set_speed( producer, 1 ); + mlt_producer_seek( producer, 0 ); + break; + case '1': + mlt_producer_set_speed( producer, -5 ); + break; + case '2': + mlt_producer_set_speed( producer, -2.5 ); + break; + case '3': + mlt_producer_set_speed( producer, -1 ); + break; + case '4': + mlt_producer_set_speed( producer, -0.5 ); + break; + case '5': + mlt_producer_set_speed( producer, 0 ); + break; + case '6': + mlt_producer_set_speed( producer, 0.5 ); + break; + case '7': + mlt_producer_set_speed( producer, 1 ); + break; + case '8': + mlt_producer_set_speed( producer, 2.5 ); + break; + case '9': + mlt_producer_set_speed( producer, 5 ); + break; + } +} + +mlt_consumer create_consumer( char *id, mlt_producer producer ) +{ + char *arg = strchr( id, ':' ); + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_consumer consumer = mlt_factory_consumer( id, arg ); + if ( consumer != NULL ) + { + mlt_properties properties = mlt_consumer_properties( consumer ); + mlt_properties_set_data( properties, "transport_callback", transport_action, 0, NULL, NULL ); + mlt_properties_set_data( properties, "transport_producer", producer, 0, NULL, NULL ); + } + return consumer; +} + +void track_service( mlt_field field, void *service, mlt_destructor destructor ) +{ + mlt_properties properties = mlt_field_properties( field ); + int registered = mlt_properties_get_int( properties, "registered" ); + char *key = mlt_properties_get( properties, "registered" ); + mlt_properties_set_data( properties, key, service, 0, destructor, NULL ); + mlt_properties_set_int( properties, "registered", ++ registered ); +} + +void set_properties( mlt_service service, char *namevalue ) +{ + mlt_properties properties = mlt_service_properties( service ); + mlt_properties_parse( properties, namevalue ); +} + +void transport( mlt_producer producer ) +{ + mlt_properties properties = mlt_producer_properties( producer ); + + term_init( ); + fprintf( stderr, "Press 'q' to continue\n" ); + while( mlt_properties_get_int( properties, "done" ) == 0 ) + { + int value = term_read( ); + if ( value != -1 ) + transport_action( producer, ( char * )&value ); + } +} + +int main( int argc, char **argv ) +{ + int i; + mlt_service service = NULL; + mlt_consumer consumer = NULL; + mlt_producer producer = NULL; + mlt_playlist playlist = NULL; + + // Construct the factory + mlt_factory_init( getenv( "MLT_REPOSITORY" ) ); + + // Set up containers + playlist = mlt_playlist_init( ); + + // Parse the arguments + for ( i = 1; i < argc; i ++ ) + { + if ( !strcmp( argv[ i ], "-consumer" ) ) + { + consumer = create_consumer( argv[ ++ i ], mlt_playlist_producer( playlist ) ); + if ( consumer != NULL ) + service = mlt_consumer_service( consumer ); + } + else if ( !strstr( argv[ i ], "=" ) ) + { + if ( producer != NULL ) + mlt_playlist_append( playlist, producer ); + producer = create_producer( argv[ i ] ); + if ( producer != NULL ) + service = mlt_producer_service( producer ); + } + else + { + set_properties( service, argv[ i ] ); + } + } + + // If we have no consumer, default to sdl + if ( consumer == NULL ) + consumer = create_consumer( "sdl", mlt_playlist_producer( playlist ) ); + + // Connect producer to playlist + if ( producer != NULL ) + mlt_playlist_append( playlist, producer ); + + // Connect consumer to playlist + mlt_consumer_connect( consumer, mlt_playlist_service( playlist ) ); + + // Transport functionality + transport( mlt_playlist_producer( playlist ) ); + + // Close the services + mlt_consumer_close( consumer ); + mlt_playlist_close( playlist ); + + // Close the factory + mlt_factory_close( ); + + return 0; +} diff --git a/src/tests/clock16ntsc.pgm b/src/tests/clock16ntsc.pgm new file mode 100644 index 0000000000000000000000000000000000000000..61e64ad3fb834f1c08414c1d8419182318a6ac0c GIT binary patch literal 691217 zcmeFadE9PCRp-l0IN<{c2_chhnjsJ(c0{q;J72xm))tj!5M?kbLJR~D5CQ@nZ9qT* zfe3~eF^WM1(rPP?cXqgn!&R9Q2qBn20wEzGAt4i-`>eO#^{%z5)>^fycJ2Kr3LgL0mVA>&xuANnAIx>nq~Ag#OX#ja|31>kf9^$*#ND^)+^Von8A^*WI?S zyR+*a?7Anr?!~Tqv+F+Wx-Yv9Vb}fGwNG4!vg`ir5{vddKwJ+L*J0v1TwD(l*Ae16 zGP@p}T}NftL$d4W;yR|djxDa^lm{cH1jsChlixgPFbkEmQH zRa>W^9=&eH!?i0h8)lg&o>J^ ze}&=s7KZ0r8J@q&@O&G?^X&}JcQ8EP$?$v^!}He|p1-~cd}eT74H=&AZ}VBe`GEq? zhY2_zF5vti0q50ChUX(Co-;TfmBD#+o8!5F^DzaSR}&?k6*wQS!ucB%IIpI3JR5L6 z!G!a_HsSnG2hI<3;r#FloF7qz^NA5S1DoOU_3R9=Y$xZ3c4ueKVQoB`&Gr18(siBCQ&V)W=U9j3&@%LVgkDdA-gLv! z?>CryzUL@DbMo@^ysX_Aou2!$vqtCRRktyx*|6!nTfD}W+*tkb@sB44*Z!6?4n9w}8~yHie0m;ho)0uSw>W2HGkku0b=BA$P0`Z` z-D{%f&Ctu&33EQz9LFdJ9jy^gGaO?ogu9Q=JY!3{v4nfBndiRBV840ZTXdeoYapY6 z&v0!9qrDVuo9N9Ybfiv*xtTt5LhqIY&3ia313iY%cd-tRw*HMVeq*G4w)woi^xWq* zO7FZE%!X!~`P>LPBaO??MCbHpgwgBt95$gpW%k>(b=!^awJQldqSMarFdmBt>sHowuvXV(0Dk2XGA-5Z?GJQ>IAv)#MVTO0Ja>^I@X%xux&fchBuz&wH89Q+l0iY(6p2>5LqV19~^`nP+UppQ~l(I-RY=Eax>oFO{PEJML@~y_p%hBy`L2bl@a;5jn7Nijd*QPx@Sq}_FXACpCfK#N?oueJGbxSo`=pgHjnTciP9&X zWbt_uQuH$29QwH&^JnPMgdUTi?Jm1m7c{RC)(uD7864~6GvC|Y%ICe5p3A!6y6N2G zH6G@kvB7h4Ij+%pRB}FP>$3SgDn(E0inbZuo}n){&Q97CLZjKBPd`J?-^H<{;iz|T z=zRVfJ0VFwA4#td)^`t1>E4Lg4ZAitPkPQPgL&UZo}G_TPD6_DIoh=`B0Klc*|W|i zkLAp=boTOdBKH#2!ttKNvK-41y04Ri$ecz#pY!wV#yE~xg0>7tTYmnkaf+SZ!LfVz z%;{{Oi^TanrZy<;##Hy*=Qj2(otI`a1ZY1$k7M+d6m4hd%bYVvEk^6z9OE-|N$B}4 zv~(OJ95heRqYOvgK(CL_ZQXU;T+CG)^g8Q2_k57fntkri&dbm_Sq=I#!sjtA`XHg_ z=(L-|aqtAq`Fx`|3wlg~)(y1I=R4UM(E1MV>3*Zm=TY{Vr{@QXGoPb&L(=(3_q6Bf zZX;qgwEcqGew5v3HhemdbKeu9sXJGCJx9BVo+Cr|_S)rj!fB3Uxdc5Y zpKoWU^y?jV%kp`S>LBN{Z8tV0J?|DeYneIC?Y88cC=GlbN9g)#(SC~FJ%q+Rg#2?i z6Wabgl_?3j-oe2Qv|b?`$!C3ccfGr=&gXG{dFD@fzG`s@sbw=tz_ zV^ituJLgfmP;w&6dn7Y{MhG3L7uHgAKcPSA{9>V%qW3zXWrB|P*~JoctKn$tv1_Ri zQiolie{S&`dVa3+nfKQ5gw6ML^Ym=j2FJMPscvKM(ph#k=XXAx=TFXw)5vB1x%rH? zQ}lsCX#Y%T={TZ&c5@`?x`CdD&v$1R?N6!kS?}H$Z=X4zN2h1MuWq{ADEA94m7Oi? z{7~~31$%3PKAXoRXPJ|&JI-Z3jxf4zI_68!?S!_@h@RTZ!R-c)dn`xILi-Ln->VWO zbbEsCHypZwUJsx3-GeQB=GnNlIvC;e66ra28+G$s(s@cxU5nYU=sdnAIF-)wgd_dV z;<226^rJbGLop>=IctqP8$b8a%^y-(*6UPCh2x_k82 zC7GPxMHs!l6m1i_wN^MjLzfAcpU@}t7{|f+eEsznp}B)@OVGOE*mOQGWuG~p z?b@I`QM8xOt!|^wJnv>Y`)saz4LvK%S&PSVKGE6B&xzF$LQhZ8{nbL7(QPJr2||zW zK&PeAcUj++FB=<|`t0qbEIkZsVww=q*o#&|u*61uVvVJEMvVMoT z89#HQVckb^bdAw-RSQRD=$6y%wDX~f%s85#&@w@LRl=Sw4(_1a612`|y$8BhAtXNQ zCveaS{XPD9B%fP*>nyur*9Lpjvrp&pB+)!QmvzD2MrUc6<(*8*`CX08k9?%gV?L|X zIG;r-)8|K5m&If`KZ4M;{olQw;~=4t)6)|)H_%`DQk~HCU4(rGTFcM#^10T%q4z%P z>6xD@x{mZLv-5bjQPNr4Ex2@5(9X`1$MP=cvAnO*nNwIF=M>iOiOGBfe2x-2c238f zDZ0mVw0Y=tWa!p9q2Fm2CA9sk-ki{NK7aYkMCca7QL7LVpYKqAVKyG4a(0W`8>sL%$=T>GSBnSxu>5_@>t$U8s~S*(3ulijuW5bj9zz&?jiJi-5jOm zK(oO=>y{%Ybeo07?3_NMeE#AWIib0O?&tGOHxZ$A!%;KPntxuN&t#wPdtdU;l%F|$ zDL>C)H|)+0&ppfZydJl)ES>G-{P2fM9`n1L#`1m{HlK8oB(l6CJ}=AYDK6S(^eE2} z@8;+u^yoTaAE7y)V|~!NgQon<`F!)uL})S`aX#y3j8cVg&iqV#CORK-NFSeD?M6>} zZs~e%P0xMic~j`jX`GKGjq|%@=qzbm-bp@>WOU7QOmWc%3Ek^B>?+}y1Wox_=X0$> zNcnj@pUH0A^Paxni1V4!^Pz`Qe&+dl3ZHp;9#a|I4RmhZi(+TzoXGq>=P|$E3^oI; zOFlfIS~w)7bK%*iKf`5EVAf1Pz# z&(Ss0F-MASHPQN>m8COu8=*bN5v>w(2h9zKwbNTS(DfdU9s|8BpNY=*xz98{bAIb~ zV?F74s@qsscD6b;HqUEF3hQ?|iOboOw=QI{{%J8;&L1Q+>@tj03hS0*3Za)y&@G08 z_#8LT>*6!fxz@cw>A7y7TkS?HJ$scwzi*@FHgdC3o>Nzv4eht9v@_~j%*IGM^PF6c zHzzqKHp8Dfm-(z@v30k*7_EDb-H@SsEwp`}ot2>5y6mPJ4l>YGAzVM7se7YlpL^{F z-__0C#)BSY@wwMM%k+GZ&XUhQo9jCS^{gys%Z#kg5S=4@23A{C=JRzz*UnK|(sSs9 z*3XEZ*K*W)?fhP7={PncLDzaXsK-vL5NKX5|^XRXEV>wNNJDD^vt3%pPx!-fB(0gqBoP! zbqig~(7lAVJE8ebl@SR#Za6lD&vV*mO3x2?0OjW?c7yYIp7dO=3zj`~3jgXXj>of?VdG#9?p5S!|7`GTKYgHlaW935(I|v>bIpx77*z9LJOdt)Dd-HymW3 z*UM+x-93iSWH)sCOmybIXEiQ8dzC?%o~OEvy+P;ANX|%L|C4i>#vYgXGmFc7zQ$<3 zTDVk-*1I{zT8_;lbbo?g#&EQDKx@CBLgzoDf1Z!eWH;88p67HM>!Pz&6O=B4-<39l zX6B}InUc#HpUM0wF*zS`4trzHVtrgA^pw76J4IU=y7l}djnP_1bfi)^mC);U9JK^J z+HmXzKI`fEu)`=n^L;51_dLdJ)OwzC^St-ztoJt4Y+3!wXG#)lGn~KP_?uQ&PGx!T zahaa!RF<79Ek)WxEqu+4EPDt|dGp6Tz2G16a_C7Cf zH#nbr-Nu1P=iQW?fz5D99&2~RVQ(a9tl!PY=)Fv6f454M&{L{}^_|}2peaF9561{T z-?8%>SKbMB)A`)KH--4zXE(;BXU}c;<~gqm=GnR2EjYF+II3fVEJiu<=u8<|ALTi@ zyw`GaKIdg+{w$F>{izYTob$*`&vYvD*-av}Mw_=dgCq`Rk22n_*nC zSf3#Zdn3+bYrIK});-7i3GH`s#4WUa9!DFY^#mQS5w<31?+lJj;Il^OTJMJ5S*O$a zh$A?kt-iW-xs4^V^HOxy^D@omEH3Xg8s~E!k?C2^X1M?G>e7i^&U$2~XBL-f{ve^t zQ#tYs-9qS*j-%cO9Z%5xhGQ8%$GYq2J##wCT`8Q;aknwmJZ~bM zOPfJ6UUnu9Yon6I`Ap7X?VkASk2E?bKJO()&ugOR?B$q;&`Tv~%|PFz{F--bh0yEZ zC{N&M^Uofi+pB{bpKEqwp7h+(^IUTqdDliSom=*zv}R{waX#v2bNyrujm7z#Ph@(k zvl;U<@Hw8MV`p_xTDC`ij_&X17{zGKbF7chy%w4iI#MNUNzh{qhj+^8R6dv8&(y!M zB%jG{^rvTTp5r^ZWo1yN=iNhR-rX$6Ig{zWpU!zsF7I;^m-j?pe`K>+@_8MM_6gnJ z7d*wv#)aoe#9+?6bM#a|@%%IC~>IJ>!!pjQ0AXEk;}WR>s!~=O(nQ6WSJ< zC+N?AzKziQHwmdqNDi73H1U~!jc^nB?Cl=(_)PwyozIk>>-$osRR%q`A+z&w*>7{_ zJ$1R+7)fWnpKcTATqb2XQ|9Dy#?Q<6w9aOo&&xCVKqho;N8y+{At!VjpRc=)2(9t? zh8r|O$GV`sQ#i^B;i&vv^4aUIn^GO5{7ih-?Z!Om*)tn+&{_LcK`lGC&{?_+en%c7 zAK6|*s{+!TB(Zi^vN)eX`ud+SHrsq2&1lTZ_UAl|wlZ|<2}=DYx}KqPLi>*6s;i{s zuru^1$3cANzv=BK=-tleSamSk`Al>kpPqaBHhj0ylbx5Ob53Nsx7j?k&p9V?dG9kB zpT^nznAHU&wy(0sPM`4%2R^gej8A+9!s=;Hzy6Q95IrTzr%_1Kja!m@({`Fz)1NND^8m9oFCtPl3_d5nD~KI@%zt?rrA z`RJoLpFOv+S#%y{o%c?1PGnAhB8C0WlE(Us$L93RA~VhVjP6U(7NITA5w8_)HlbS` z$J7K}=I5J~9_X)p1qodm=;ioacGs2F!I*s}cGJ$3sqQ(Fp8M$BYM%3s=dsyYr!)7= zd`uoAAK7%)@-ofwl&p=*oSe`2c^RL!=4NEH&t?8pCvrYpC-g@@N-VZUOEOwd(R-25 z>q*d_;VAh`zvGP+!ZG=|JiV@u&tvSy`1H)P^D)Q7({rtBV-%f7)C6-TIq~_QS67Y8{JGBLe0FazdP<6(o6xdOXj|xrB!f}rXL{!Ixz9aYmBBu@Ab_<(8TBVF1t3vfd<;n&v)L52|8XMwDWTxpWA!u zG(OLrp5=}dN$2B_mwe89>UzvZl+I(Uv(4rybS_z(&x~aAH+>UO*uH?w{NhR38|Qi1 zy07PGWU=)*p3zpdaJ-3LUxuFIIP@wZ5qhNI!2Epct(c&Z&$r!%gwFlOQhY9}gJpWA zQ$^R6o^7|W7wB9jW}LC}^Ax*5 z>ABul$Ge`#yNylF&dB2Y(`NJTNzTMzZ-h+FKRJiJv1GAzS7)@9o^jqIwAU9ssw-Oa z9Ggezn1!x8j?oFaG#oacTRJ$}{YDR;iNVO{nBAa#gPPlT(1SESPpJ&{c5SSi&fGNf zaZY8tx9MEwWt!nBxg6&?xx9}g=W)3inVoZ)-+Nr<&nzm-`FR=LlcMMG9A$>S{PH|S zxAt<#3@tl3>;x@$tMCl16I!nlMjbTmBlHq9RtUdR^gyHESccDbcU`19Sn|2uZfJBq z@<`%yq%zp&Hl%mXcMFd3&RRcRi*-g8m!IpP^Lp6)&8v&^SQ~Q=dt=FB>uxHemrBub z%W>IdWr`j_X#Z4>D4{LKq4W6*Um!w{Nzk~<`=*^;DmI@>1D*TlJBr_k?a$A3K1b{` zC2Nn*Bhz!tZFuH+H`DpRVsnJgz-*7p{Fy~%IX@?(*OQ`4LPt8HYZ-bSgeE>;do2-K zOVF(~!WP4U1{(QncR)|)v)#SX$7g@vAni*Tot|yCA-gsXG&+|igU^%}BcG`!=kZPh zxQv&UnYB62VQ(y1tlv!~^v6EtvwZ9x;;hfgw{_`8rRK{ zTaFCSpNSLNt`l-X6Q8fSMkn+X$Kllo?F5ZI(3`+#yE=&J`OrhLI#}{~irt{}+*%pr z>3KOimu!CMLrXf3s0muxnR+wIQAuQ)$yuD=Zwi}%)4*rAG!|QP9+&wuoyu~y#%S!X zvp&5XJ&Ms&Qgm4>to3sEgzoF)s1v&8I9d~Qi{U^%-&*WZK?9xdP#NzxFh3)oZTpOL zj_pj*YJ*yOCOVIE8=iTdnw|UU%=0phcT;kXS&e#L#uYp-=i@}@^ryvRZJu-38%rAN zcheYskkHi4!80^>99E~@s08hu#9{N(V_g}V6S|(D`7g(kD*>FmQBxli_VtS@OB86M5dXX#rgf_u^DKc^LcX_Z5fUI4iSilKvu8KjYlCDrboU%d&z#P( zYeV+bjW-)pvh%p)OeEGuIgRE0X0qAhb7HoYn#*~fmF+Rl%htG-qsKGa>bZ*(T018? z(r-7HhvtsM{&hmC6V@C@Yl1FogmuG#1{(9Ty@$~6;OOOZPUk}oLH~>v1M@TT8Rz%y zi`Wgn?|EK2`{&fjp1OL^M(LePI#2Z)o0goHb{gDrX!)7uN)}r)HkbJmi_3g|E=JE; zE$mCtOAxxvag0sSc8w4X#~cRwZmX*`Kj(bT>AY`WoX=y@bG_?%gn9PqT>j?kI_XS? zIUi|x8Ruk9F7L)9XRP$qJ~?^o>h#M)#PG=_z^)p+Eg; z*>4x`<)9iNjCh9bC3L@owi9%};fPlV=ioEaxwOxio*(c4tPYlZUN$|)>w@c~^DDd)Cu4-;=`AGdIu6(Yf>*@s16h&X(86^D^CIO4dF(iOcbBU^5U}a@f1G zh+NKd65C^9u{D~)=($t0Rx6B|=+;`H%+PYbO0*kV&(P$c`Cb)r9IXkutPvugzx-t+ zG$!cWaO4Jhckp?h^xRM9cwJCt=WqN*X`bt5qong()|qIWk2ET$xsu0nrbg$8ajv-x zd|vk$v|>9nvk#BR<+GB-){M<%{=_0P&CkQ=B~x@+D_n-qZH{AXg035moX`0$5w3&J z`Q8+&0ixf?^E2`p-NpkSh<3wE&+!vCTKhJ5diJ_D+UPveI&Xe*-W)aqrvGv4DoO0k zSWM36If?BtvDg~vfK^{?;WU#}15-OsIj z#$1i*`2j_3ur)oCc|PhW;xnb^Y35mWJTH-*`QAVpw`AwsWzfA%=bXm!emORiuc(g- zAZHWA=Yx#qyp9t(dV*3tMf?4BQ4byI&f zjm{5wNG(0{t_`~`I4_-}HNh4-Q&P@HHl1Zs&hMtv8Ebw1XFV;`iYz9Zw;G>e&L(pC zBxkX8Z*!SH(aB7+KBGMwZ4vt8ALm@=W1Z2Oi{8zIZg(7!1dWE{#^Nk!B(&snTZM2u zpRMX(&S&Iyi`|%#o+&%q=RC*E^E9*3-?5?5xwV&W@37fKvHHN`b1S3&DR{CHC$T*y z8tbFw7#%0H<~d3ey_p%B2u+ToH$m5HgfTwj9&i7Q(ei|T+i&Rkd8&OzyAi1kM$nemCv zWj@=>=*>&fd9AS5L)+cZy5*=@Xphikf^Ih)ett$i-*#IfwBEt7d3-KwgI;=$yN!r> z-ivfTFxh;{_EqO|Ym&}cY~3S??N3f)ee5xMr0IyK=swS}B%!D6Rq;+sLPBGLZZ#af zf$rzCe)hB9U56Hu!Ua=a|{>f4eH*jgt2}N6ZE#W*TYPxh*-{ zY@SjNtamv3o*AE#O|Ffch^#z9Hp6F(&zs9=kI?hi3pEqn=Q-veH1Fil6Er`!zu$3O zzkMN}zx1V?(AZ^{*9i3lji=bjJu1uaIX@XG%4f72k@T#cSEu!D=yc|%@N+uLbLzah zpubnJM(0>fP-Ane*RbiFR{;6FZ#9-?vw~k|ijz5w<9tplZMTs>v2t@hZ|7%xqGxBC z?PYYU>6ntD>n3_Vgl=~nWr9XNUw=IkdK#Z^-Ts{`$!F}~kR8wwztNJPNAbBWJrkYv zzKvG5u_T=-FVje)^Qh#^S)7me7Mp?4|KI95$mo*Pyo-a6HAZVGI^sEMwZah|x}Kql z(3GIH^EkXdJ0vvnxivxSdkE*@v%WjU@7}2G>&En4?i(!Kb9;JTiq5sqHq4Rp!{OCvfbgo$qpULSdlDGb;0N9*D4qKmz&iLnil+LY&evd}O z{?y4#vtt=eRF)%+(XkZW>N(;WdVWH;BDaGM`5>)8}qyv`1*KVu-0Z|CD6LyX6Rd`Q?()_6hzG zLhE%xavWD*O_l>QbfnL&H$nGT2M!SKv!Fqb0QWm=W9c79x`#EZ! zW0?$1gpN6mdV(HdIJ^qsI6lkn4QV&_ZU1JsZ=dt@eE8vLpL0H!>DhN1L}zP93U?bL z%tpRXaHQ9utSmYa-BT&lEk~_RSX$_^O4#e5b;BVIbe+$4-kA)v z-Cw8WXRo_X@_Cxw$mx9Kk;rE+J=ePGB6UIAZ20@Qm#6dIXR}3MYo3$2ozFn&1Buaj zCkNh3Hsf84(e)JVdyeG^9dR7FgN`L={Tw@KI5Yz-JD`{0^HjTmbbj!Ik8yDTnV0EqES-JhypG&#G1!_f*$gxP@DHWUsL!-eT6$;uPRq^t-10dZnJhn%$?|je z61vxMv?gf%91hPwFTrP79YhkR{H)uJT5Yf;J!^d%74b`HHgQ2v{`Jf?>K=KhP+UwEdpEfI+@e#KWtxhCg&^m8lzinv}7~hm5lE39CK!9 zy^|wGXxnkLCFr=}(D*EOsPyD#*=z$Qhr{J&$`{P z+;dK6stmTfjYwT^IXcgkoDZ@&k^He+SDnvkMb6;b{oZCYRS5Zrr)L~TQglw}+(gG~ zh5THOI-$uzUv(9EXtW&OZbIziK*teF(6UBYHypNs?&Y(#J0<03tvV=s>!jTnm!6|; zV?H|PH9@k?I4ZMql+LV|5?d}cDKI2^C^L*)9a~ouy z*GK2kY_`wUSRb1WwOmXibz`6HY$h(_sEyA(lwLYX_cMA-#gLO3$Ge@-w&NI`pp)Tf z;qzVM7gT)zobq$xvt1qZ?6c%^v^JQ#=hAKXbdGdw^qGxXb{q?m2uDy=;a?LXS+)n&Gew zbeW&;R8HV%<+EKKjN6TVoxMz@#_PG%g}30*ruNt);>gpT&wl@=Na-R7WW zjZiZjT8~{a(7c1A)&bqeXWKq|cEeB4SQ#w)>M%WT7M;;G<7b4;Ejt8dUZy*X&QpBz z-e5BjI?);b+$2iN#H`;*9&2~YFq)DxA0e0dXPwc0ie8G)SS2J2jR~55+dD>R>7cb5 zp>3c!pKrUZOwi>Cc2oF_Eatt>X)aP5wECW>x(zbVBeU}euR(d4MoT1T|Fokzn+vUM zOvH2Z=+vCDbIIraEM4p8kk6w@TBov{)u=4z*2id0W*l!Kp{bL@s}mxjF+tO*{nA3~ zebCVa-EN@y4wdPA*6g#!XFWaRY5bU;b2>ldA$EGEo@cEtxK27x>DHj6EJs#uKKW!X zIWNQJIGIm5rIpRZ-pB}(bNShmr6q-HcS};bkI|Ot_~=J<*TK^>j*-jsStoSvIpQXo zcSP5FImmK&gr4d+q6xaiaFiV!k^L!UeUNw8rL0`0=Y9LM-VMp;*7RJu4WG`|ZowY! zEVDEE4E&6E4a&jS0HuIN}L9 z)d*vTW2)b<=zPc_$v<0s*3$D~hb2DC+F&g`V|G6JXykL*x1nX{O{a4Un;&hSsrqIv;vy>fYe?+3T$9P0uBr_1)a$Huy;!k?bsYam(cV@P}h&#?L%EmtKSN zGL1AkOP4`+R&t(?&4yggrsHg0A~iquvERCVSwz<6=cROv(UIPclFD*senz9);Gg6- zXp}QL-?uVfhSv9dmzJZK&|ZSJYlO*gte?-E&eCqkeS>}J8ME^-#~`15x3LVJb2guN zVua2bo2?xvyPeI;k~sld z-q&wm$w6y1LT)&G1KpON%hQqK`I+-svl|ijoL2^Ix3La7V`9e7oX!z8Kk88$ohd8x z(PpxFuGEZvqU{qiGyn8iY&|`28EsVzM>4uUMe~m67DBhx3DH9PjzcDBtwuP-aMbIA zWBHuy#>n)H*_nPLMKjOyWM`x@e)iCLI-5^+IyJmLjWHc`Q8u@w<~AbZYEEQ1{=3y> zQMfk07bxA6qgxow4F`_NZ{R5R9G6}y86Ec=a}c`KaZFFpQN!W&KS%jo+GlO&Am_7} zo~^z*OwZ^x#?aY5Kh*QiNaggIv$^ydWSVI-ozAv#p2Fr#E33CrGN01eJBPGw`IIy+ z?|{$gs?pebJ}0GPPMUiSeO&qtJ~NKd%cW@Bb8tdmemSLRz1xl~hnJy|(3a!StAx=6 zT^f$L`OHs#w)kAD4MyBE?&!|x?3rh6KZ;%z%Y4RWw0&BK?mDy_jdp{75}VT~Cv=PF*bG8@I|*$ET~E;E860Gw_5Fjo z-YT&$1BWg5S*xX_IAVPE zx;OltDcn6DCibO7(sS-M`pq--JI_yN%**sSH961Wn^nD>O~%;VLT32gsn*MCYA zvUcy&*m~MVY2R@0r;#MBaaqolWTu&RM$em~s&vE;l?8f-?9PQeeLTBGP_a$fEJx60bIj@J!F*3JR0b99DJ56i`TTi}i zb!j}-=PVBU^P5a*B62xemeH*#TK03qYK6-YT5}wI2|AtApBv~JpYOhPk>5za4V&lZ zKKso1?4@U`GKlFJ-Nrn0_G*H)iXbIs97U6}&gPTNF1miFkws+XHpb?b)VwsAfzB;_ zPSo~kY(1Ta(j$^|NoJZ^FQao8?WO3kgf2TdYIQ=r6I!bhmI*r52tC7*^ZEAMfzafi z@7nqW6*?cO#%Hg4quiOo?M9;W;fJT*jl^ep=7x07mfJu&({66RF4&fx=cY5IWg6{e zHm`@weSBVu(%fix4qb(5RT(>+&l^!_dkHP8gi{i<-UCe)!X7^7-5Z{LE<5Y&^ep@8 zTHVH6be7h+)oYY&*3Z=##b$q3prSCcsR*0r^cj{#uFaQB<}-i0b;WqBukvVYJ)48l z)01>bW}0bbbhKi~yY14rmZGKSunBF|3duujghoF9%fGY|S~?CpLAM!>+&~kbli{Ea z=!oB_@i}5QxZ615h-5bspJjTkxsB31Ydv)}v%&l6;$CA^a^4I!N636i!(PDF)LbL5 z^_+9q8Ye#EpE`kKvlff(`4&ohj)Om|Q(E?Mly}7Be6&7B$5V8~M7I*U*2$swaU?>w zCTQJokb!RFbAGxg@j0&!mZ$Pt`I+;X(;0tfP^M?A=eeKGe5c?%bS{%KcNsX|J#3Dr z=4Hriu~?f&GV{+EkM&hHjjbo+DP3oDZ6`}kX1X82=!oeUm7;ae(MxEo6Z#ffa~x@} zck1G(C+OTj6Q5HLG<9(J`I-1^cW>zSIZo#zkBp~hyDk{b&JjAdcnxyRIO?Z!yKkQ6 zG$Oe<%I3`@bD}lpvK;IDjm=stw&&MD=`uU>8DerinupPK(;-uI)N?FJXw7j%613jK zksD~L5KiH9-o26YIo-$k9H+Bg7hHA=Z1p}^k_ck)j{1pm-|xe zzPdKIQPu_N93;&Vv5R=Q1MQQE44y|I?V>%?G?G#-nw062(*~vkbLiEtma_F6QiO`WM zp}x=CHXLN2se=P{5Nh>7&2LbCCO+r(d1QLVt~zuZXr9N=*{%u7ehtayM?R9MoJKjD zwO);Nvw3}~*(b36G|pgs<>R)l8kh6uF(#L*Y#Lin#!|Y*Xx~VeWTqLN(HfyY@{t&$ zYoTWH&H)E#ubON9&v8R&jKYt_M+-6;E>?aH9uw=s^+ zR!z{e&XUeOY}S9(RX5IaAXE&WC^ z9Q*g*?QXQc8~jE)pLM(8)A^{Q5}##yuDOjl=fmm#hrcj!goR6k4y0pdycw^o=#|K zp_AkAtAu)&9T|=~pYPZ@g9AG3=mfhOpMCqB_ip&LLEgEs7wF9EfN4CP&HY^(quAV% zntcXqPooUh)=3iUcOTlh;ykui#;BaHvUse|kEHav8Qo{2C8OIt2YKi*8QQbZyh`XL z=yt;)JM5Mut+VtRW0G@QZti9CK{6AocksM3u;?3|&-oj> z*J*6cMk#IQXL{mOn!AmBoTq2HkLTzJqgzsRtLGSDIV7Qzg^pAS^8~FK4$DBd@tJm~ zKz^pZgH#!@u`1xE zW@h4UbhLxzVi4uAzAi@Pe3ivxeSRFJeMWn|9mM2(G=CYaYske^ohF%-wea~%f!=m%Q`d3&NEc>1N>D-o__ZFMy zB(vryVslX%m+J~FXA?So5(^(Q^|zKQ~FQ6WSJ9 zC$ycQWeAw zBs6wIdxW+ehwh;BE;~rjlFzr?0)&PhXt{?FIyj>F+2gZjH}cwGBt1*F5jD@4o%`r) zdySfNjwENzIBy=CeuhqjjyR6oK~sXJ8X+WT*<&a75JH6z^Rv!pxw~8Ev$W4t8|+Wd z(rsAgIrq+fcAlHgrPUabn+1%so9S$xi_DR9tgX>AaLX(*nSWM6Tv<@kH$IayxPGtm zSf7nh+Oixp?@>B89CV+v8Sm#}bbE@{d!o^Ev=Um@2@|2Y}1{(QH zh9l3m8bRKK75k}Ydvv`bd@9oeSt)14tuBh`0U#a-96iTQl#6+>w>-6c{-g(S`CZL z-~7!2#^UB6nK_|z7Hju^vwa0YTv=AqH#(CuxPGtkSf87p(v+O@5hiE(*~@6(bc`i5 zoo<)!`j(bM)(L$Jop*6)3A$!DIG@RIzz!8HKLel3?mFOeX`eN_LFt*Fh-7s=C$}+` z&eXA?dkvnPPdt%lXIT-fIpm!xwt)4gOf-fae>$#ZzM!gfN}9S0?7t43HF4y+LB z9d;g{%kB-}b7`Nc^O-v9^7QP}8BgI)ZlgUrx6qlhS)P2fNo+ny=0tIe!QMP_IsXiT zxU#I|Z)`>~xPGVcSf5)LrIE_=Gsfte>G0}>yf0e!9P1!7_1RH^mNmlCaA1dB>apX7 z13T<$d@j2;&^}AMLAAkJde(Lfrt~b$Gj=@J&4xzjh}ZCvb8a=3%+1r-yeXNvCneK6 z9)D}kaxUlh6#xex$EaMcs*_o+(3xD$PN#HFl9p`7yFNxwHywUI2YTq5<&fRbew{F{ z5?TqmG#p@{?FwNFpP@Ra+h=JvwDjy(2D#g4qqDXbh0F%gnR|_K$vN7o(e5-nHrK0w zF*e)PzVeCYD*QQOY|p61-pKQu$M$^?#m=&lzp)v~;QC$3V|}JhY29+rtW9aM8*+qH zmY;Jox+g_XAvEu_)B8AL30m*5;|3Z#>}viQyX%n85xZfhXI>dheH-he^OWQ~jm?>0 zX15-hPdTN==Fw#K8LU6;;W54w;cj$|&136{1K{AJ7?sOabu#l6I+M%Usg$-EZTEIa zI;Xo4jFzT@*9&_HU3Nt49$L%L9-*m9=p|^|aE#=0*}Ea_2D#@vJ#)9g>3rOAnt8U& zMwy+p2>;CP|Wl^|3AB3^9tR!$`hBLT6E_tla z%tvXNpXqK>7)_qTGtt-)?Gak*RG_ z_A7(jJj=S^G&&QT6c!q{9^5;!tL46ct#9_us9QhI%i_WL=e5IWTfb;m&o`r2zDLxbaxT^x|0 zp+;CU9P#{Y*=I=4_utx^0{I#F47I^XdbZ5-Japz>gFEM?*es&5xUqBdh}2xx`Scl& z!P>LU<}#xCueYu^g(E9X1cx3(sGP5bB>|F2H zh|;+yH|t#*0>|veCUdO{SWnG5g|%6YzTVt=$&BQUeI`ERpAq`T*Mx9vF3K4kxhD$O zM>&zT`*|oW9S7cxVRXCcpcFkXp?$|;C+NE2u=$L;2cvujy8(1g`5E|3y9UWUTa`gx z7fc<`Q95fiLGl_s$$6b@_EYov$h@1ljPyn_=bv#3*H@Yd4n2r)S+1$GnO102&gW{B z)?75s&5@+b(>kP&wi(@%qFXYw)yd&k31t@tBxr6p>U_R)bq2KFU)RfLup2Tx6P+LO z5a6@#HkPKdG|qFg*-p(%k=aYf+Ou9V*UWL`Ig+;TGv{)7U*~UhMF_{{BFW&$9Z|S8 z%89Jq&p~O-&hj&oq;p0~LVxt5t&H|kbUUF_Cr8b3$SNTuXt~oH9ke90-oa7w8BVV& z`HUxT@STHbH}v#OeRbqEB6P-IN6Am$$BsHQ8)bH;Vk4Fx1@8<=7&8D z>8x4jh}URK&Sjs*2sTek%^HQZIh(@elikl{B(+Ur`$>(z@#g^`8%rdEBX`7Mf0Pqh zyZ0%LZbSQQq4ab{PfyWSt#CS_p^rmP&@sbNp1>i`-;ntk&fc*3EKfy3y8-UmPtRq? z^Bi=xlJjzG?n})z)2varoGa<;&GeBu;wii}NYinj|8D!TDI9qcC$YcMfN)?sLT0|k zXEQz3sGQIBQhI%i?n%)Wq48u6xnm{uL+cruEHw3T0HLD^y6l0L9d;I<%j%$KH;~S$ zHVAwM_gr%uT2GyyoiRB-{Nb3L$!qAz+3uvP8Rsdv*V(EVJ9N-? zg2q$q$Z*gJ9K>h%^+C$dlF!=C!MyVsJ2#NdiO*5Dk-9cwbVfGoJGgt2b8eg?Y!-@n z=f)!QDb5KRyMxSL7Op=Vmy**r5sk}HguN|mq9pd$84#{c*U2nb)aZ=QODg9x9;G$U zk>;i)>HNeFSu>OsL(6nTT{NcX9zy4JLbA~4IJ^W6yHvaiq0eX8{ajWD^V%S$XW3V$ zrDw}+a603!3g+G!vvZWrJUM$-V}3UGr{>aJXfvL>kk5J&bDh23im_ZSS**?czpJZ- zzwxy`fn!grKsFXg3fJzC!nMzw$@#sZ_AobtCDld5uVNmR6(2=4d6*O3m|JX$z|eg>xkH#k6H*EUF$PA^0WeEXp++k6LRXZ|@QM|0B= zP0>i`{QM-j?>l+u1Zd*(l~)p>$wKRnLr>7saFBtH^O?FgqFwe>lbwfiW2}HI*HTSOU`|4*2&yk19#&J&RxR2Iz zG}Q~KqXQX@DO&d&bwX2}P*_4_R;VDZO!{QzPa~B6yT8lRv%I&n zbW4&(F7wY8Mq6DSzUi2U(BL@APUz$~WQ~vvht6j@fuqdNiO*CWB>ODwhL)b|ZiAn+ z!M$^>V*|6ZPv<#vv!0rF7n$um>^~WmmVLHPdHNmOSB=7v=TQR3)_D|;JgY!idCW=d zk0YFxRL<|_qO{LwyYp^pikAI$u?#IOG&&C4sUjUT)d;D_E*a>&gCp(m_W4Zqne2vE z8$`Q-=^4)3(9Cm;&R7#f>x^tZ@kFFEIp;`nmYo_kry(Ts_Dvg`J<}}nv34huv31v$ zn0@-%Pseb13U|GCk;(is!r%Cs7=fd!A~d$wC?Kv(`D~`A%Tro&(mX+@@jQ%32u1nRyWS=#=k)QW$rDx4NTXe3~1d+~5 zvUyZ$)>1N`lLWTzTF7iCV(-ZaE_->m{~7VOHu|f-YUOVG`UrtzYw9$%R}>(wOw{O% z&zGfiBuV$>Xy0^LDO%PFV}zDfZr+2CmmWHF3&zjxv(lfY?emdt~1KC_l&b(7& zYHrrp+@6{_g|)H8Vm`kcxZF=(`^_4O_2+*ju6Fjey%D2uWR=a~+LH>Fi^s^}+GmZ^ zGCRvVD@j|s4ogbclCGkI-~#5)j(5&@w?&mtCq6Cd0uEG2@(Yog- zYlV3~bV+Ei(6t0@84lTDr}LS2Z*V@R-VLe^M$&T&oom(^=}h&&h;bglW<53clG$Q0 zpWkdQ_p;Z1uZ6$0wHcDl+gAFvtdCGQvc@K{|HQ>|@o;WVTUol7($aSH=IEM>PM%{N zp{a`_C1`Fqq=EMHv*dHK&&h63=QDNIS?O8U1*vPJ?5IoLS<)H%HPCAyoAv#HmerWf zW}VF6@+}sdc|I=3oWS~cPA*SP%Qjv6p42$o``iSw^Pq*jZEGVGuCMWFY(Fhf>`ZJD zrRzz$ozb4@&{MQ!qO}ZNc5+aLran7z&?!Mn!+{mToX>Y(UH4d>E}C`^BAe`6Xx#l&r7C-8f3eaozn*Di}qi`VdzbCk__C9ov3UIpyQ%$&jcI7VijzV_@C zE?cR$XTBxz_O3-D%g-QsJC?N&IJ&mZ;o1s|#s0hl=Il|O(|(%fT$bY~rQ4FU<)eww zrRm61w5%3#6P)41G2U;Uk4&bGWKS*+c4fVbn7D1Tel z)ks`>?h{#mn!(t-tyB7M|JLKQBs0J3rF7nZ$CLCFMr$d$m(b*(;g@4mpIvg$GC`x^ z`07`qd`A16dh3wSxqGJctkF5S4d|((s$h-Ix!1tttg-pzlZnor)2Qd>IGf9=AI<1F z8E5CM%+)jF>g~744H4@jJ>n!$WO)zH<>YN@&>C#Hq zk~C-ZKmRjPTJMg=9G#mEjnH^bG?-{BL-Src=!KRRIynw4L8o0Rx7>2;tyCez9o}F6 zI`_}_zBg3|(LPJNk*8-_8RXfyh0d~H1CukdnV%M_CFeMsOQSJ@%t&DCGeTzDSZGgr zx!lfQ{f$1-M&I)2TYvVK*)<8h%`eCJ+p?<0V*k0vWbIjoWAk<%PHS1ZPHCB>r!g8$ zM`E<^Ip|~#v>ZTa-9jhFp(SYS;Q&4Zp>MnG_S^5cqlM3yo)0_h@WUVUpwziReRZCB zj%DXW=a%Fg$<4LYJU^L7b9uc~j?%SnU7fqp6&aqh+h*WSc%g;Dk+nXF{ihm>y*U%i z*@GIV+takh=yKO#o}^nDUANI%iq1Vpl+fu!J1s$D7xY)YLIxW641~VxuCINq%+J)l zap<9FH&W*_&>8rg+_UdCplidTbLw@L);ZDHPR_O5EO!M;Hb+vkZkowmJBGzHPI8tc|@DnvO-N4^V;pv&& zhSs$ap>r%bk7RS1n%l^{9GBNc=?OYn;aDA0WWA9l> zV|&JgGkeg-Y0hREmy~Wv(%g2mGn!WmbI$?Y99D*=-73;?XbCzu(A+-*pK*6L_>IJ8 zZa4Dutho*Bct*3KW#?p_d2+@xL$O-}lQXjUWVMTq=Vp)1bCJ20m`ncZGfPvshq$$O zdYL-qy&W7Eg9hMETH1-=@N0Dn$5wbewx8Kt&Yv(Gn?sM&R+=U<^HD3Md6Mp9w3VXC zL<6A{qxqgf-pRoo2UZEMzdrq*3MA;iO;gT4yuD-H>fsSqKVM5*A5AdRYFS8 zrQyI1JB`mNJyU)LJ|DK~t4p1AM;@8_>QZ`6ZiCEoZZ>Ey3elOfxt5$Yt6}A4$>yBQ z+KF?L$arUy*-y+Rfwh_4OyxeV_P%A}xBB2O{$c`ThrQYX;qWUp4%eUCJhq?MWX_*3 zAgjZ5Xlv`y%=YXv-qM(AXr(Q#M_8Vm;*=sKUlK0|s=`5E~9pq;*F zOwT!;VLu8t8w=MCfhm9O$UziEG;P=Nzye&%dR_OG?{2wD@=rj zbCW1TBcZWM$h$buaJ2I|rDxrRC;o3uFHOX*mWrm7+4XgaMUxejTgb4JS)y($!Lb#vqy z8VOB~Ba)!0{~7u>WOp6-4bEps&zjpvbwMpV>(-gmIVI;-HhZagJ~Eda&S&CWj&fM8 zvnkv5q@AwqZ*=2z@{1uDhZc39cEBr*APy||SzLRn6WN-z*v#h{l9i!FX?=GKWoR7b zS=y#FC25<{(njkkx=!eHCWq!YQi8S&N6Ba2TgT}PySjnTc$Vm~Tffeoe)~1m1>uZ3 z=%~ZJC_rcO8hrnD>ejHV2A+0Ax%uC)i?cbe0_JR%emULgHp5HIIfe84d8yn+UTbwb zUt8apjMZUZ5JGR~q5=4A{)zyAo0imgY(KY{tj}{ITVn;!J5v^?wKR=vrcaO3y6xy? zbUH%`7!95SyE$^pfrKWs#m7rT)6Kx4Z1!0K0*720kBi47v^K zsk7*udkxu1mnY|5HrL6#J}%1)9J#Y7+cMwE*VgsJa606R{yTPcqPO$4X53DEp$Wy+ z#WsumCpwY!S&Pkleh13RU<#)trCXA8?m9}-p?MCU(7NL&6EyGOAU@}IgQsWQ)1BxH zd_L~DWS;%(OmwbW4Uf%sY93GKJRi&ZXj+bNSgvWIufDdGu`TO|V06frFcho9PS6f} zZ6bi1UatbUVS!I%`>9T4ZI%;RA1ip?nbJ8ONz*o^{Uq%%IyW7VqV)_-XOH5qUtN9m zHP?Lp^KfE6Wa#Uz143hd#tI?sP|^5IJ5zY)hIAV?ouzdqufeTGOK!H<+(%~HWJnI@ zcQGzY;@0nU&cxc zdr)<7-*)eF%FpCB5}#9chV+c*?~~bpembB|~`=Ut@4!`UoIQ-Bfvi(fwayd&()n)?n)(lti&ieWuP>x8~~D?xvLD?>wq)(o`9XQ~cLyMdKKZl1Z>$ZLW?XD>Oo zuvu0CWopjJyd;6lh%q>awS8QZdQGEOIbK^V3j3;*1_X2b1(FAhVudtn&J?n0Z$ z){~OS+KfhLK4+0y<8J$At7JMMrT zgpi+s&#AjE*=MvHGCd!=)wQ8zXKtOrYd~@?>w$^R$v0znvu2x#%^sOcm!T(S$zpz2 z<1#Y0_8B8^zADPv$hvN%cE30br^COf5yQ>PCqj1U>s=@gyySp5@W^7aK3{S%lp#Q`$<>C8N_0Ltr$fXs8yF=SaH>JwjuGPKM*w)hS6DpHuJizO8+ObYdO$ zJX25IH+~~G8y1~QtD&>Go|>g+j**#@n2*O$Ij3#xKEm3_nr^Iiy*M19vwmSSaEH7w z6uT3?ECAn*B>^ZKOLQjdvpJW``;yN%6Xmp*rhQ6dl1{dRGn#C4o}x8Er&Bq2hQ=tr<^O$g4Z-cOFPVVaL9ce;xbljD;=r`dWo@=(vz$RLm!BG`%RDVP zUCYv#q@#?MDLR>G%Fvw9&fGd9 zogq2L*{u5vkIZo{Pp2}cZDd@hZGA--TAN>*0hcpx+(-BIOm6nv|Rl{_mlFZSG(d0RjiH7r`Kl2$ld6XyUFMJ_& zabOQeGSJXrhyFS78SFFI4X6!bdOmtYnLB5)8l`VW+l)q|PUhTYlw{@^ znZ_QM>sdL%VZNqLTz^F~TAN>(37iAJ)#phB4V45x40``*yKv2P#RjR&pH+`z8q zW4G&qGCL>hoO=yS&T%$dHbas*#^otg=De+secsldHsiJFg@Mo<{#8w=?OOc*Y+bXX zbkV`A zWTuQvBb&>$tXwBCuBb8Bd)|cCrp3bnI^YYtusZCe#OKrzpNR$lU`_mvbj{j zu{))aIiJhv%r>a@FdbrwfT(9pvH zdsHYtr|vr7bE*wudOmvFZKU6>;&kR-13Kw2H=n%qYumgQn4Ct=W=hRO=5bv1sf@&} zeR{<8pEV&iym$aQ2Y$scpbq|`8kwWdXM*YiUpooI4KJG*4nJ_=Ts+LloZoXY%dy7k zzxkV-)G|-&oW>pxNokGIV52ETr@e*fIV7P|FElugw9^|5N6BZh8_7K%wWv~=O%+vGLEdY>`}SKVtnp%*LoU8Y!SRf zx^4l`4*e?{z}vkv3}$s<0c2+)XLEip`Al~jsXb0>ltxD5uV``GA#*e_npX=iTX_yn z=+AELRsqKW2|CpXp~nu+NP_y{Jyxe9LH7pOXQ&OP^bBs}*lqKSbWZ(rK<88u^%rZO_u|FoFv&4)1?SU3TngI-+(n~VJ=Q@K0p{h@H3@yohl z9C}d&acI(kbM`1_v%DuZ%TY;c$!RN1qnSoZmq}V#n-vNXGY`>`RWjUI~IlDtSri)Y}`sV%R9+uylde!rD>hgd6I@*Ea0Qj zbYO}G6U{R;5c=wsg~kM(3R=ain0T>X+&avR5Q)de}7(QBmS3_Z>z zo0Ds{$jmJ>Cv$|$R#x^CGCb9}Yt4pWTUpcr(aEo@9gYk91OA7~H8)q6`~D%A9sDI7 zK(4-`z_>bP;8{#dI?FqxGylvCoEkHB4_za!(bh>aNX40oWFk(aEH9y1Z8(|2IR`Ef#{uSqO%+! zpXpPQnkQ=HbdA!Gq)SG_S)@ql%P)s+yDP5*$AJkN49BgvCO+Tuo=_c3b_3EgxQ*kE zBRZ#k4M@)1YG5r8ax)nXnVGf3EIme^mo+NeNm-|FKI_1?@@N8J1Fz14!^OUI5Kf1G z&HtTUo5RaZ`LFxlNYXi@ zaev{Zm!gTj;tJUFod^wG95>!5`Fy|o-T(fSo{v5D_~Vn=c-U4=5c)NM&goPQ>eE0r z^VFPd29lW)Gv{)h%2SwJfv)j50MxD}ive@UUo;$-i+^E;=g!+rW9fSSJzenaekllM za~VUjGgKkHJ&APYpB||xPa~&GN|W!v9E}x2IE^%=Xs8tep}|6fHpj_qb2-n-7L)TloZlP3b*8&O>%24sjkA8q z;+S09U)TxcA+N26qU-xxyHVWqq6y^c0|(PPk1SquM&nqrnliP;=~O?2EG?5Xb#(xv z!Ep(_>=RiV#_OsVqbM3WX`qE7|-FoXCckJJP zuY1Aora*c=`e>*N0-d2I2wnr72J8w;HjkMCLB|tCyhe$iE{1j7HCK z#T8dx`MJ-1{`1#ecjJw>+yZ>Q_r2lFXGqV-9*cCwNe0E`a1N{3niN?#S;7!r5FVz-->=#Lj8WSxu=r zk(xLyDGj|H$moxM9Qrvv^(i3q)mKA;zWL_cZ{NTFKKD8F(8CUU@Pm&zCeay^bL!Pd zY=-W+_1=_PKSTdE`;Bn{U?tkao=~D zV6HqcQN8`3B(|hAu^LIOa~dm$iP0bX7^G+*^i@|~bIozu3xvYC?^5_94*S`4HzPs&8%G}?i0HV6TDwa5Wz=cA3q-faJ{;TYW5FWj@R zJ7E5Y^^v&mtqP`#$2PZfVsl<2trM$>)TwJ4yfpMl|I*w@ zdBqi<``mTc-EzzR{g9rIJ@$kX9{zAh&X0Laa?Ou@EZB^cms45>DkGCQk#io~EEX^g z-z*Hrj=ydfVC#VSCr-xmuK0pIh1NCxkE!#ywnD-6&Q#8ABsS6-SPi6xL=BtOfL}D|fX3k}zGBTO-SYvS%yd5AM^vVGPs{`5n;!Jq8bg=+4(1fRH)(Ke_wAzyX22ga0E3{TG-S#4vR{!;1f9OMh|MwsM@D*3=-+#ghk9o{v9}8Sg=OjZuPArxb-T`K1 z(EWc}2VXq+UtscofkFR=d+=)x{ukJY{{m;7^*iVv&>pR8uKL@Wp^MC3G|A*|t{{p)P3iW~w^(U+AkN^0O{^$??@Pi-x{ont+-@EwY_rCYS3(r0Gth3HIr z@dtnK`@jGDz7IJ4J>T>1|NVEbE}(VdH8DGp`@6nNpj+GoG2XCj7h>YzKY3061r8AG zZU4_)5J`LVuQIS2p5DQ(GTni{JXz<@x||Z-+SJ3;f3d& zclOz5o%P}upLW`*r#|gzPkG7{pZJ6){J;+YrT@c!_}GAI~`rnHot=tmgR|cnudOZ0%D3I(#!6&og}C?BF^07li%`>`C+-VE?0<06KAT z0b6%MCo#-&8cQOloSggxQW^3xn2bMIU5U)U`@4zFzx~_4^;^IBn-^Yq!3FPl$2sS` z{`F^_b>^8r{nMwP{*ym>>Z#9o#*h5S5B<=SpZvrp{>T5Al60apayljIL~3HSq_xb~ ziP%1~+fug&^%=0cwyzAq=-{H_zrX>8vJL;AI}x6fUw2{aJhVBSp5!#Ph>S@&W#vR= z;4&vO7>)n(UoO7*10Q(b``-KB_q^xb?*=-bciy?@o_+Q&{K8phz2X%we(@P+oOar? zo&|({+S4FKBcoH2CQ2voft*HCb5`fH)_Gma*)n^3OzwHMHt4SPFD_8FCnq)>SiAP0 zHyM?)e1U`5;G-Ie;kia*?ktkMfP{?KmcH2f0~|)q<&>GhXFzIBY<~ayAvs@k5zzTp zfAyX3eEZwq`qnqS>2Z#9s=F^_`!#^w;Eh!CY8aZ8( zTC%#Nwax1`W{*qV9^uyt)Q$hkHxcl=7J!TW!)F2IOkWy=Y+zZ0!)c99<2)gkOs1@i zX_<2w6EoNhAhTq1O3p~<^Uwd~Uq1KTx4h+zZ+z`*&pPX8fA*y>ebI}~IOFuwpZ)Aa z=%+vZM}Fj~PyL}EddgFt^rYlEp6~={>E=yc;UO=^(((}-g%Io&p!JNZvZ~O@|7=p*^6KNf)_mRd8eKB zlRt@srWBnR4MzG0S65DHo70@sC95r3`}uk*vzJNTHvM-ACU@~aa|5v1@lRL~iHrVK z&2Vjap^d|7wNK;HS)`PlGnpr4PGybD;4nyaxDu=9w>f$xr_@BpA3vHlQd=NGEbBFz%D)L|40|~ zR_7NaY5gK5*GAD~?X9!fy6lr(jL5bA(>vfe^p!>NxAwoR0jJ$ddpI1Uv6Ya4%4m}L zC(p{9%g~FFE~xy$1$`MMnISbJn?LY@_rE{U`91I9bOyKa_P4+7ZEt?_FaBcU^Q&I< z@|XY2&%EeGkf49+r=IhiXFco3fBe)_fAmM6@eJ@B;G)5FJUL@DCFw-z?bk2S$xf#6%Sm#9NU;DLR{ncOjm3O}Lmw)+}e(7y*d&^ti^rknw;dQV3&;L2` z`DHHy!vP8U^wUd1^At@v8jLhKX`j+OPv@L2Gqs+oQ_{v1?S0k>U9N~wxM$7!h}`=( z&w|axzH~!TxR!s`WROn&f)ON!@2FFlui*UE#*)Q!ms44$WzOX?G4srv$c(8Oj0Uk8 ztwu`DC7s{?_H)mD>s#OaX36I>&jiDf5;Qmtu+YhJJoA}Pe>#|G@EmA5G)8mZk!NX2 z(~{E>QhThfnGj@nN#oc|506{k=_Tl9ytf!~oBpD;fN)KJ>12Ek`+`v%PT#{>8yjmB zmUBd5IVzc)Q(5P7o|sbwkdwL0&56yaBABf6yWjomzkcC`ke$(OKzcssoU_jc`<(Lg zt6%-IKYQkxU^pm2OF}<8Srm2R=`={ZYkCM|`rnYQ#%GFU; z1G8gv)>pOgw}0(s5O~bLq6x2)7q1DGv;4Kg@Hy~{#*!GmEk4qfr^k>wX00(@Pvdo>ct45T-yB2DP5hS0a9(}EETEj_O9w)8 zhOZjW;q;A?v++BN!E&B6xEyO#_S3S?Wsl4$HS27a$@w>aBW34zz3YMtz&t}`Fr{a( z8&DlYKELvnFMs*VUiLFvhU59qf8O(+`&@7wP$wkIfrLgEoq9UJbP%J-NF$>?N^|QG z;k1>heFGA=)+2ch)NUv5)HgPfxpVxRJdV$T(ylL^0h+UX_TbROS8ao#xI?d!|A(A#Dc*bG)9(fOj)mD3sOg2`gezpZ6#@o(!HFv?MJPy$dfiPxMgIsxA(~y@=pJjeon6IO%=83LG5kF( zN!VWN@mG5$`K#SoOosWbqvNtawm15*5AHD}m+j1qWY%*tS`A9h=ru|@C-V$$_)1fgMZ3k5j z`zWpFX**HZN!`NgK5I=0dkT^3?~bDEJl``foxk%tXG7=EFJ2Fa)8Fdzwf(8i+}f;8 z;BwYuGCa%l#m+4g7&}90GHjQjWoAsx$Y$6R2v^#({ejg5of^=q0aimMXP?fgGMLgc z+Ktq`k^J+2`cJ^;R3U^O4(Ni$1Wk_PSzEn!oY2qMGSSj=Btj?G0j8s!(cDYtS-MQq z?VKJ>YA_*0-riB2x>HxoL)($xI)>asF*@MO)=6UcyW4qNd!t5P`*}%UYo?dVvAq02 zQT?-QM(#6^%$S-Jn=f9SsG+kNk~8-j)U)xfcfo!^NYCf3y6PxBqul`eoVx3vK1h5< z!vXsU!9hQ7)n^9@I$3Cs(0YnaHd-OZVsovzsAm!^Ziur<8qzMd1^*BLu!UgvY97mi_X9HYeZ*o z8>upg=^5(M?0mhJnfkezn{9T zw>lA;{^ZP^GLG?f#Hx8YJO0~qa(BVC>7~seG5jm~Xd8K}&Ru(jMqYca#$dq*i6+xjm;OWtaGXfVseKe@W{ z44wMzu%Dw$(Y&jpmZLeNxs|R_TC*O#oQ`GcmX1AQ^#~7wj2}Vb_;-w_>U`hWgj#p; z=5RRuOCntDU1QTW_Qd9|{$v`J^|TC#u5;NYGx>}%HEX#!w;Jf2F*yUB-@UqiV|At7 zC>EU|Jws>Rn^(OX&|3%gS@Vat+RxEsJ2<7`*AD4g z{p=yTzOT4o_hEUZ^Ov+gAvb#$PtM+$6S!rJtR3+gXYHt!^AUF1H@nC+9&Q?m;os9s z+UR>R?s}_q^4hZzCVN>K!gCgrcWz5A`-vI)=inEo@PglKO;_3tNIL?N%xIf=Zcf!e znVgZ%DLJQ(jhxQdw}I{%P82=o9N_btR=pd@=hwW3`wi%@JM+wBps_~y{4EFl+~=O2 zS?H3`&)BIIrVLFT9o%$CMq`I`X{0%$Ws=UbbZMsbG;QbUn)v{Ndvuo1TKLS@j$kzZ zj!_hD`<`iJ9rLz<;H~LRBFlMz&I<8m*VDK+OtqsC?}Ib(MI z_0nItmC+T)dw{vjc`BEs%dj&uCv%L=)JK=t4Ewh+IbXDGoy+WuT^mprJa4CSgVJ;2GuUV7-mv)0 z4M&-vpS^u1$B(Zrv>bFcha_~KqNR7(&Z$8A;&w z?~2p5edXq{c}nJOd)H=c>l2T?*0U&+`>CuOW^fySP+UM`exb=opJ+4Svoso()8NKA zSq+(-p_{Hw=kvGrq?G9y>~pdk7N5azBm)hG19y5qZ?%(9Iu5K8`h=!xVM@`OjqYJ| zElX?8qmR>@MrxqDqn*Xt8|&tC8&RjOZso4M-lA@8ejJtUwA{jFKQr?h zASW}W=2kY#9zQ<{5Sk%Gx0E8LESjpMUP>p#S;RuYT3m4i#yj zli`pF`m~*1=+wy}35_S*#Zold=#tT7q`B?zl62{%+bHel=@_S5GxfAoJs+!q`Qz!^ z_l+8X{kdiNJe9Al@7sLsc~WDq{X9Q zAp4y2Idwoog;4fzBtAoi#wsCN=oq2>6g`d6d6JG&+D_ABINeU_>8zeBS*L{FN7s4Y zS|6WVX*yzk4|lzHZ06RUv@khB<#AlDk+~%`=e0m|8oXB{ch0%j(CB=@c3m*-NqNWC zi5r-nQ||`$)`8t9^K;H;G#t2-F!34psyut!LPMRfmZ5n^hh{orjJA_>Kc)LQ9Zl3d zq#nWQd1<{l`5ON}05N-HejdTs_V4iM+V`wRUhnyMD*Ig4dYDu9T%nj(6WlPTvGmN6 z%(c|q%4SN=7qOb4P3QBs&9m;FvA53RGj(u4g;4T2ooAO4G&+uCp=*SO^EqrnW5sYf zqa&2oorg#1TAq$`dVQpxo7J0@u>1MkPSP=N^f9;gmdD)svowyBc7b2be86s+(wz6(LQ5-{`sFL|C~DPl7W^rLY|~=1*Gp94Ujbt}cW$>KUg*yi&pP~CX&Sy%{(s6)=PTkPF+b&fKpRwAt z;uBi-bWn<>njv*a^Qxh=9kOy*CTYz}TUpvq)Ac;vlBoMK_0)bn$@sZBjo-f%r*%H} zadg~UeZ;N5bJ}S?_WzA;PGP6w0W7tf+bcxMmo$-_n?Ag%ijBbPa>Tq8-?i@^e zQ?NSd=V!}7pT51@JN0pZ z=bY0Sk~23OlFq5;Id>a8JyUO8jL+C(M>RrB(AZ~}9ET)y>g14?g9x2G2d@^U-G$iE z(aPw!?EpcYTWQ^R*jc(p>4^7e<@6L%k7o7KwC?W^nabJt`w^~ASrcJyea-r*>=|au zWB{sr$Q(<}8k?~ih-{WtBPD0sI_Gpw`%x}fnGH_o)VHChXRUjq%+GKNH1$9yKGQxG zbkHe5ljD$eLLxNm`_>69JqI%SDcjeRcdnh1;r4INLg5fAhVZ#lG+Yg+W|&4<;ZR|8 zJEg~PdRbDBXZ7;5j`KRs*VdI&SQ~w}&fD0Exv5;Y7+yz)-7jZxxt^JGGPmSri_Ly= zmNh|cHh|7l7d(H)`McbPJ)WRwzA3Ek!?h>leWj zr?=ZW97d0%^prFm%hTVnB_$n;G62BqDw`24C@<$UH{&{!i(37YR!0mmWhgs@*l%g~9?+;iwD+A|%xkCr*Q z&S=edSXtVmbUjT+IlVVX4aDXQU2=^6+T(5g*%Z>o-;K~Vx@H+F$GGg9j8>P?LuQ$p zd6$OoG;*tfbpEaF3w}v3x6V`()Uq?sId>a&dggWmyPtV~9h|_y`OFRUi?+`il@6L5 zhqTbRU!{%ER*Kdth8CmON9px)dU~dw!+@l8y(FR2-&JQYUlC{O)Rj@*#?~!GonVLgnJR>2IIu>@PfLOXO?@2TINAyAyXY~D?)4qE@$e}< zGEMh#dPJt4FI5Bk=VEoqHd9WbtS zcR;sC*@Xu)$_DZ6DQlm2ny0==@Zpa*uY}u}x>`r%^07UwrXSG4KOe4J_I0Ip@44nEL5T?~MBe z$vgv{d1a8NXS5q+pQ$=n<1-m(*=0uwnyQ4StsF;gp{0k;Er&+vyjobAj#ft3Y)8H? z92h;C(vdVBuOcRbhtZrZjMLa(NnD+0G=1o=)~t2Zk@y-6EqO|wAGaobm=(kN+D)w*>TsFqSwJ_ z&v=ZXbZegOt0ksz?I-1yr<|#Cjq9xT2;2UwjkWRB>!EVoV?^?@@0jNyv!9!Hnwz%< zn$sYwQA^HII^(ZbakFv$Dmx>c$!+k?jpUwFdPci}_8Gf3%KVIc#tI=CXl^*H1RWVgyQbV8Rdno_iGI(X+D>}BCQ4a*!oElE$KbSzCT$?1`qdfv(+v0tNgJA?B# z;*@P$Ii1OUS-FMEF)oiLv*t6Vq-O3ks8hpZGbU&0HR@&~LT7Lrl%9EQP}+@NK6ArC z37U4QpyOyIbn3U0gtk0~W;%KpT~a!)8|t>hveG`KN2Td@PESeHV@bUftFa>_r)}iE zjk9g5M^JeQF2~61Ic9if^~_l|BSvOFHA~wZXLD{fkj*kV^Nx+&Y)CqD^X#SP)LEBm zgUN27ea7lw>aN55jK2_zJ5;2BPKLuy&}Ee{IS$!tms<|r5v>y%Jx4S}bJOAH=m?{i zrF1k+FU{%Eq+Wv65gx;uHp;fG9>e5bD!1k37?*2_c@&w;)I5UCEy=k?XZS^K_)TuO z((iJovo*{q=c|Bb2A#-nP_PR8z+^n&=mYk*6;5{3?bSC#q>6zPS zSsldPDRn+41DzWV>7XG&`;LP;Inu80I-${Xlqq`C89mi_eBbJ_`=#?IwT@}+d8!Nc z|JhPAjIAZ3Jbje&xs|f5>(@`^dAK|!F@xpMI-0Gn=16KD&*t1YV{(>sPG>tOvr(t> z`B`O<`s$!_gWPjzHN#{m za~tjD=)7uJ+Kv%Px<=``ncjR(kI&RV^QnZM+g$*^M==?`2fC2^E@#`d`sjnfYo(DZ zmu;Cxkhx|wJm0KW1F2V|OwMiAnVSvS@eDgsG&)0N@Rxq++|>nihV-2GZY2B6`TUyI z1@>9X&z#S^#}4>RU3Sp~O%{3tp~*$-jNUmmdTa0`cCDH@#3egBrZL*C9WG7j9zP8U zyFW|go3WhSx~hlOz|VQE&7g8?T8`~4N$9R%dG)xD%(~CeQu8P_`&OftoUNLmPUo6= zMmi&(-?F;iyt<$^SlZ{*U03oMPe{u7oEwgG5(iJvc7}!%IiiG?-5hA5TRn%JqVt+z zZ;rN{^xTwgu^#K=^gNlmB~_1R^QKUFPA=D7#yZHH8;xjg=4{rI^PF_%eH-95^z@wD z4PG7e`J6hSQwK+?5E7rsa6pZ)mZ0%m=)~t#C-gIPl+d*lJ(kfew&Mp@mwtwae~!oF zw=ZxS^(%MK-o56ZBXgLGr+l`i<>-1pRgd8FNG8K~_4x~YOO+MwWKd+RmemvUXfnrA zbB)axvlBJA(}=S<_ZreV^O|5fWuwf_a!1Pfih1UAF4Hsbts}e9!)NNTlZFEww4I>I zLg$?vTBWc?Xun#>cdtxgbkuhABq=Jl^LewVJa1m! z95RogEEYTFB_tP41&LM(as>y_8;x)0s|)Cy*9Vh-CO*?CNuJ@b z6ZC1~M2_4-r~N82Lq`d%rD(lgC{y%0814Cvaap>R(*1dQ6sPCQ)Db?9W-@#q+KApy zS6G-2<0d0UX21Stcg@+Ib2^zLsX6z}(rHB4Ox+r#bDo}^sV-Q$jXXUApHt@s)&|LL zkbUOebvB==LP!Q$8V)N#mz{QGq30pAo}z8jF(;$#Bt4bVQRlHFr#F?nxXbAQZuyHOsOHtcia7Q z`9{v?&7|@YTpmT{J~rnk>1*fdr_=T2uTjC>#o6@%alKz$_^aFS$&~YN?w;f@CG%Xmje2d6dh5V$Xng*;)rI@J!EX?sse?lr=&}o% z42QJPaONmEj@&}m35~Tv+eDM+;Eetuc0Gk%TP@K$e>^F>>OQ)K(Y;A}-ISila~L4F33JZU5CN1?6_ zjm~%$zeZ10paqwO{FGKedny2Wv z=_qT4(W;?UH=LK!ap%#Vrl)awy`&z`=3b-B-*e&XK5Qd%jLS=tS+mXUY_^SaJUNf1 z^Z7enb*07>8^VzBpf`P6X4w<0Ial{ENPv)=*O`fAIMf;{>Sw@dA z9`jOqlQ_>&&cODuC6z% zu4JF7djtFH$UmoFAO!y`^E2>SH_#=Y&)EJ|Lh9nk6LhlBypu!o&^1DrJ<&Wva~Hi1 zM)%l`I-?`Kcr97Fr-lf1MXN>_otO2%>33y)u#eA@uKjl|dfkVuTppR2eb4M=W-B!> z!Dh>9_;kkPoOe6NymQLViO!nau+y_`H;~V-TV1Q>jqTkj(mzW+%MK3UGZ<(z9Q6cU zBlNSjuAkVt;ACjHer)SHb^H3!t?QZF*Xn1mwy*SSSVZWW=ZF(J@4Sn-4n0TPjGmUH zW0Y>=G*Gwwit^ZAH=WJ$8<5m3eD3FP{-%rG%EMMNM;&t{G1tiKv03lX(8;VBjn>>e zM{-89@$1`HIzhDT*+^BvlFsBdWMwdto}ssn=jS#)%MOkhpJjqReWywosT2ByZZ*+; zDS9lUZA!yA9rk6P;DJxJkL##>Kt_&V?Tm&uZ2m@{NXkyXtMfT$>y-NndfmsZT#hH^ zxyjs?n)}%7S&hErT=N>(@2t_8^BK+&P1#wabJ@9}?M#8*I^;9i4W6IV>7v-bQRZjh zv)n^y=jXDEBTvu~3#}6xcNLa|rWE}n?E2xI>#6KwO?RsyYr9(wcbe|D22U!ky!TG) z>wx{?Pf)M&_YHBzCNw-j7Tcc{Ja0da6B<_bQF@a&ZS#2whvmC2cB>EPGTbrpQrFyCg|tw{HluX zICz~<5;|FEJeLD2g^>*1N@zbtm!>1?qpcim+m7jsj(X`zxg|B1zPZL`X`H7g=aF>I-3F!SbGNSaYgWl^U~Q21ZlK+0$y7gJ05Vcx<+kv+p&=)A_v2ZA8*D_SR{9 zmfaih>sEPwM#JIx4ZRE6PtXy^A$#rW8JY-9CYm#PM?|j%OhvDT+(t_motutWj@B7H zK1oM>$9$CTPt!|sdJ2#6-3oTAk9)X0M`EsL=02ZckvVEKT61%2a`t+iv10@K>2kA? z&OwsinVaW`+rZy~?Mu(t{p|6%%|O3!`}b6yzxA83)WxAE=(L+qa~z`y-It=*&FGr% z7)$BLKmPj~uihFWycg~2sI9XTH8QsSvx?p7g88`|P0Ult+-@}b*_>Ac=Sk!yzZ=bxX)O4t(O0kCV0~>9LeP2B)*5w)0ty!-gNsNX(IBmVHK4YNj@UwbRhpjID;nIm>iT zw};xyh9x_%bpGIS<3{e)<@8MN1}Xa-!{@pMjdz4)g0?sgOz8DIVd~oP9y$|RGtsRn zx@Pp(iK+Y1ULP$pdVZFkou=dR^gsNA&SwL5)t|I+*_)V;L1tfSZe_FGYQW@7Zv>@2 zT??Icw~;r4TzaOv8*zSvKIM%KN8S;x4!Z2wtqB^QCd3vRH#zzV4O2AS7UF#&PSJXf zCJQL@u@%!Fr~dbk_EQ&^-IoGi^7PoB3h;+2^16jvc;-BO4AnK|{yUPw2W4O=oCG z=(rS3nGUKS9nEMxN#6`g_i(!8vjMt>pUmTOY-Vov89bTkO`tV5_gD=sIm=#SJe}qA zjCX_lcF^xP=re?p;b=+FI76?uCM65aXK0zwbmI;a8mDM^Us#)t>u0pbOSc%0B~JIH z=@w2WJ{zEG_(?mL$0g>a@|>E>(qFOEeEO2_H6Mk|d~&vXjTkz^`!+s2x1P(*VCkOm zZV=lI-af-c`%a>ppWA*@e%XT#B(ps|Ja5Lz?QZ7Ete9U~bn+m5&-?W6SZIcP~;~9d)VioXiH^ z3v%Wex{WeDbGt!oH{i{6*ly^4gXVK>ptIq~J3=}^!!03iq4yxPFGcs{=y=<4zfszf zr%mt`e$>uoe`2<~jD_BbzVhE5HvgTS`Wde4C)ByXv22FPnf4k|pKcVL@7;UziU(Mz6(a7vi&9rZhU^BKF-sBuX=lZ-LzOBxio+Z0MZwLAQbIs>^LzoS8 zPSB5^=f~%H>@+_*&-tLr`L-nOtO~Z!afF5@n)e+2jHX&0^v-Yuqa%~Fm(qJVkHu}* z=L|&%6JPdlxg|00L1x)Ck7RSC)!>q|>@{*@!^C$dKRT4(lq2W zfnesNTQV;jLRM+~z1L~@*(@dJ*>vX3GfdC-&Xe6n>7F^ef$eiUpK*f&8))8eNDf+e z98rX}H10f&j@opLVl;oV($bx#?!JScEnR4BU?W<7Z7NEGADv1YjnmupSo1UkWAO2O zE|18}T61pk&{9J~A~V;ZvpWq7o8^5V?=|e!S<24kUht!Be!mTTN($F{meO-RH<;}P z?$%-ZT(|4${zjdjIX+W{LrTy;bUBVh=(1~9x9#d1qG6W<61vTEl&!m((dUP*6#qoH zj`57PTj_aOx<5?=5En1|xIErvST}$cGG7~;dy_L|ojr7>TN_@tv8HEgH%QryUOr13 z!jjMBj?kW!PU??b#UeK3ZmUOp+c+=~gqn z1n|~0T|oKtb`LK567x(lOSzdd8hyE0>(P~L#>v^@HKOP&nP=E~{y=rlWqPLgygYXh zZwGPv8RzHa34}O5b9~MQx-=X;2|D#SdI+7*tau46dk#HCAD7XV&7p_U{wzJ0(wWb_ zTy9IuUNX;QGw+&tqv2t5i`6LU`~{l7p!qYH&oA>CnEDQ+dMj9(jg*}eow0egyA3)$ zQ+6ZU=T<)B26WyKW&>R&=%1V?w>Xv#2iy^2!x3Sj^UX=wa`*@>^*OW@U7L=Pj6PbD zo=a&@nqK(aOJ?^0lkGn&EoINhP3HXV;*zzIU9*&$kHhA^Yj z(eqL?*XlT3tHj=`)XQjFk{+FTL$47_XKbD=Zeyi0 z@6&r+{j+I5oO$b8Nl!hA+WGaQd!o~J_bnQB5`E1`Q*^pP2Ty_9a{^U=s0 zu?3XLJT5oaY%aSRC7Y$>4Eq|e>kR3fy+%ZKPIRW+#s`dhrqeTTH@NL!K0V0s88@IU zd@ftiE1%aLAw_7|gvO`6OA9?GLu1e3P0>eVw7xgACh22SnoiSRKDTmtoM*O?xi2;Q z+59DOqXy-hEo?67jFj`~4V2D6IiFr$r1R-T*3PYT_Lygj+kojgH=oztVD8pY_W75; zeC;$iKfipQmrnEIdD4DR<}+`gSHof15lRU<5&ApZJi1Nl+SO;iBP@rH(3X9n-A1?N z=ziN_rL^3SF2DW9hG{HJZ~qN9J?wmLC9}scufNSES$m~f=Jk8cHOsk~Z)tG5K$^`I zo&OIpe=X*}i@|h;Y{tpCjn1Eb`pGtbxXj1f(4FU$o* zpKmsq<r1Kxaq#H!r>5R=Yjy>3MBFYj&gL^I|u0d!v=l<%Y2A*g?ag zC1{J|NL^^&Lg(`;n&q%%XqnJ$DVjDNN6XQ6MjxBf6rbC@x!wns&e@)v zIj=FE&bYVHmY&O#DW%<@+I1E_^9_#DKzj`bPSCXDNEUhyq4|@A%PB*Ay6}HhQ$D|t zO3^bI9br4{lrFuq_P#+&pZ|;3d0;*tm&{Q+t6Burq8Tk4~+@k zpQ7!IrfqcV&TuZHy-B*&czDco;`3ZGk1);ssd*1J*U7nLvz(kUonc?&bI{nZ)44t^ z$h9^Soh7%yol4={Gwg1Z_W6y=b75EeZ1EdBpUVxQWT1II!{>S@!{JTPafHT<{w5lD zKS!CNYtLcZ7y454I7VCchV~?VEK2uqxy5GeMP@BGx3o04o(9#_ptgZEn@1;SxD(7L zxwCg}r?ccXEbe(t&ydf{$-y-}ulBj*b8gr1enaCkZJ<}f!L^|I9&{o!-V&xBhweD? zp3sw_+f4NF7%iPN)N{0r(bq@mHa`11bIbqG+H&}NTR&rLYUW!SJ~mTy)~!aFoO`{- zcsgUZL8WKdtb@Bj$mjFH?zc)lzkZ%yo+oVw*V8F>K9>y+ozL2iP)g9raqLIvKF>j= zXuIinfN9=IIa+44PHA{=H2?Z5j?p}H`F*RG=A4I@&(}p}Ej3FBxq1|v$3A$}dST{MMGqg+d_EGHEvfmM+1#F-IqTd*=gjBz7XFgX$!)}>XUOL_&vU+5^lYEY z_6E)8^#tK^esJ|0WdpiA;|=+|cI=?x@Fi%kmNtQs)95$O7B*|iS@If@bmm(d zTzXzlbSFBux@VZ4rS0I_-dOW<51+jTnrhi$2b~hM*5jzV&?5;w!gJ7s_L`0!AFUf{ zE2BM|!88N9@HX5VZJU2PV(K*s>@Xd{u?cjcV_9tlFaab)hed-&YSXodF5iJqA#dGv_ zIv!wK9iwyfQIfRJOJ_c}ae4V)xEH6tvo_~!WY$yjJT_B3I*QHyp`m!@m=X54z zR(gi^nLeGe-u^84=QTgm4Le(Yrp~F*2|76rt_fXtp<4(|KL<-D^bu0@O=EORlD1NM z;qwv6e1Ef9-Uwo^!CU8OI& z`KftCZl=!$(z$s|a<wt4(KKi=lC^ZaO= zA8zx5ZPq8p@`V2P%cSQMMrUZ;=8#SF_!Qk@qk9=0Yoxu5?#kduGwNVMzNVQ&V5!xJB703XnYa{-|LJU8>6ywJDn}*d3~Z4 zm7eYG4U6BX`MfslYzCS#9C$}4JLuH2;}f*iaa<>%z57DRM$gI7BN^R7X^PJl5|_Uj zL+1N|&EDjU>HH@&dS}CGHZq-g^IZ1o{OOtEGrV~t%5T8@Y~wR^4+l=r*l;8Vt@Z5c z9*33C;6UNOT@DnUUR;h8o?d`Mg^z$kg~geb(sGn0+TuCJrReKo^l0Bv^10-$<^32o z|J^Y1T^h@`Uy4IMmg@ZB-y7%5& zi_cWE4yR{Zd!ye!^ZA)SMTq%K8;&wTr=Fe1abycUp3r5AMvucfzroMMq6lqI(YK8L z3NhvF{`LEX&n0y&?|o#pyXKp~W@;bUpPaGRz}DGIXSq|C>0G;w@NC|rFaM<}QIgV&TOQ#A;LhGJ`Vs!NY@Y4t1Am^W% zd4L`6X|wC+UsL9*>im;&_-C=y-w+s9KB}plKe4d5{Ed&z|6?A5&^Y~MoYU}b1Fh!+ zEo|13^L5fWrDxo%!@EJsZrr)^=6TAS`Kk15>DTdogKuz1h9fFLXU8F1Xu1t;vmBVv za+{-#(EFumZD*)Aq^+)FtnF9;o|z0^mF$(@`^X%fnvc)s*5rI_I@jiz+YGj+XUcBa zwu7yF<{KQkflhpW{PA^5Xid;{k0TTMQic9^xeE=O95V=AZwsv{`g(G-mC|)umOfEp zGx0Sf^ZHumasGB3nQca6A2xf8vz5)g)_H{4$a}%;HZJ1%cgZ|^-G-c=t#*UE8wuMD z`Fsk_&prcfF&t?}ILC1ulh6?4$VSG6FSC3 z(}a#o(Q>QfxQzCt`W zyNyKWjdrZalk#{brIT|q9xNYgVA04dW`Kzd>%{Y zYtGG{eV{ElNA3i%cjmHlADwHrVciTSIw$u`r)O+8qWR3ui?ke5z!y z!*w-!le49-p?QrNbf(O6-VD}mqc=U%c4I$2<1>U71Dy;<+7ar6PB->T30iX;dlK5l z=m*K6PJrJsy4)SoYeEm7ua(U$+dz)ZJ;}ND8po$|PS53iNZCA>rv|z7+-o;*d!sEs zQw?YfpXCmR&Sxn>_gZMa$$>L8zJJu4p>2d7o1(8RM|+)g?K`Y|_ORJ%G%TCI-rOv) zImT*G&bci)%Z&|u2U4Hepw0{C)<(|Gna;J_$b2r{M#*P-H<;|k-Mep{XTA4Xvd{5+ z_8aJAI3g0X)p5`k8a6q`5W3Ao(}ec9=vLFAHSeN)w8wSyF+c&8G2Hp?i{A}Sf-LbRtIo;SF zlc1sFh$r;J4;Lc(A>Bk%E}GOF5BfaEC`K!n-krZzFTUG;@K5F`ZHE4S^&K*w$FNzy zflj|!|I5?xx9iJi1Nq#{v6;3SG@a`c-2Gl-6rE-B9Fv}H-HpWOH@AT|*Wv9TZr4eC z#trD_wt4nE>j@Q_&$NL~h9f2DpKP-}FE$$vzQu7f2;Jj3;!^Zz(_!m%ESNSU`t(Dc z%J>&w`3lG9F=V!-=6#%om(53~GwjsStqnh&>&>9oJ(ua3wj20t%6L9|40Pi26Hjo2 zE^o7|6SQ?pNOz%OhW_7ZY$sM^53OhD5rl5p7v2wyrucl_Y;H5oUOIn4Oj~j;t#jGf zXv@y6bf(;M?KbT0xlGR`pDlJHhR;&Nj^=afK(`u>`Zl|s1nsrZ`0i29rm&6Bl;?yuN` zcCgiNSolnLpc9`*r&-n7AGXGrHBw^66(_urS#rKDzEnVyr~pzSk0+nxCQ>Nfg*5c_AE&pifO z=W|4Y)*T1GCydO{?SvkmqGi(&!)QCDPomj8*f-bj7C!sP>`BeXU~}9)uqQd|UFXtl zz~^xPvXWXx&^E2JBvl(c=;gAw^-V$nr z&i6t?#{u_*F&Vl%vvRWtedHX?G1|jtyVID*=2=#QPR_@sGi+@@w~@1R51skuhBrMY zyOH?3cI#4lPTN7+Zyc4+b<0j_anO9G&wC$_&{h-e^Bnu6Xxq-v?mB!%x~8;^&vr6f zjfN#RYYk_a%|}Vjwb$@k=eF$3(V2D|`KD*8^(<`$dG}oI2HWg&;`3|UjLpw!Lul(j zKXK{U@i%eABxtYWz$Xel9$L@PHWS_7=UC3Oe1jeSzPn!2aU@3Te7hM#<-7p#2>VDM8CUJ5P^; zcN|A0^qdrZQy4At`Tk*ZzjJQuYm_^|vh8fo&R#leZo{@2j7!hSZj^k+_Bpld@a;(H z>4UU?mia6o}lTb-FLxQy3l(Q+U_|bU38!6h|AHDj9&RXhRt7^M!qFfyD9WwUVt=&ZSo`1H)#4UNxqdt)Cy>!(yQpL-H?l;gk_dXDAjCp4d;B^xcZ?jlmO zb!X^dv}B}xr?u_5*@R~02Iu)~_G|>H$YcFX>?9!Q%d_hhR@uFQ0KGHaAZDf zEe@EVEf%`94Xt@-3!$yMLb}hv5xOl!%ckR+7;WRTmCQEVte*?ile6SBTG%`yIoDpJ zH#;>P|1gHFc+# z;G(*Z=$q#i+7^Bud`1Wj)VVS=_f4!kGy5xRWJ zN}ZwO34N>-eFR3!e2!+bB{?q}KWSzq=lh$^*7ThDoKAJu=~=fM+IDd7{LCAU`e|1q z6ZB0Y^c>G|y*YZ_8uEO;No?kmbHCTH?F4JHQ3U7H+xYCvnP-@ue+x!>k`?7PGMz2y zS+^VI+3vmkv)-|@7>?ZHNVh>#J3?qU=q(|%&=$vmE%b3SG)L&UDO$JD`{n4bewF6) z5!ozTjY#JlpPc8>*|Haebf(=#M0$>}8?*SF8XWcwVa;dBaO9SqM(FQ@`H#zd4~(DC z=n=A~H=Z+vZI*-D71Ab}V>F|pALL!M)ViCKqDT1XlFxQBTX%sLr=gwOZfCPUImgpk zKZ_EToqOo)-waae*<&}_?X%Y3c$dZFI&+&rpf-L> zxsBYb8)-M<>~p%mE}qX813fZ9TO0>}XOiqVj!Nhj&(V{jqfCcAN9&AEd_F3hkD8q0 z=!~uN@(#B)tep*!$Bf3qu7oDx0Ixn4Rw-J$^>+Z&N@R{Eb z`V2?j5!xN}7{?Kvp>2J;eiI#^qWevUZDSabqi-Uc@9XP%+kh)U0r z-KhC|=Q3|@Q{MdC+TM8SG~WH7-r%VDY%?5oLTk5i*!YYc2i_ANKSO)_91@{r7pV_qdJd^lY&k`gYKJIwh9RwSgXGIHD3XCNv7rkKzQa zb?wS~IqEJ)PlooHXx?+|%jmBg=WFKqDj1E=$71uWM$UDN`1U=Gm+!9*8k#za?0d^LytQI*)T3RP#AKFD27Cr{~&kwAknL?34)qobKrN@Oix9 z=tHAP!Zhj(Yl@!85|n{k%z(@5X7 z-mCG~)2z>=qW@jG8Pv@38qsvdHzDEl4EKUwI>)Ey%;$8gXx-hY?Zz8#a8HJv!{?l! z%QGqxpYth|oS@V5V(I*>og{=U4u68ij^o&bK7!|%X`?-iruZDoW_=r|8Ru53!QHK~ z(0Ue~wOcmozO$U2B|7tNBeyn|SGl=CyA8TmC*N6Tvl|vZCpz=n!Lj+d=CjX0XFl5v z2Yn+nBs6S7@4qLsd1$H;O=oB?q4!DA7Sj=(qf0(t7n^4!=Sb^Zo)pySZ12?Zbmq)6 z>}`xp&lbB;+vhQSE}ub|{RZFQkoerw;h+I!x(%3~e*@+o80l$N^0|~u=bWA`cB8h>e0zg?`k>UW^W;g2sv-3P5Z=#~V1 zQwZJSIeJp`Y}3IpI-1Q9+d$cB%t_96>paJ7*!O}_ZbR!mTk~_>-H_}?&F9xH^XfLQ zT;>bE3omdtjQaQ-Z8-D<4Y!2XOX&VxAy4SmZDD_k9-pIad|sZ{ zYMIoPlm5y*x-o2?={4#;UB7pZpfl$-;AU`Sdd_x3;xpggsM~cOztP6$QHF!QE2$?z zW5*#`XkXVZ&T{NU=p&?PyNxb$w9aP>mzUp2OithRu{k<9drk>jy~dH~?CotpI@9Ty zI+sE<>)v_i?aS2pxwadY?O-&Y>k}$+~LTEBho?J5;P_>e7?8rILbZYI1lY1 zG}pM}Q#AL)mF08ksViEFjxrr>jF$O4lg&NJIj-qUdyON}x!*j)%^*zAU@>?vZw9sA zv&`q3&N)3xXH)8S-M)PG8fcBrdV=P9cJYp5Z$kTag*>4p&mlAV8_MWsz-^|(&uE^{ zM`H7=X92bOfUxFpjl*NAWq5 z&YE%7*xZ_&y&FMKa-K)$*1aH4XU=Uzy61NY`+TX^z2&pdGx#huIHCueZF~o_Us5@4;r>O z_ylct9I}Phn;fl#rp|r)GIX?wK2D03S{)0YXR~>}bM_|Zn@?xht7~=7G3hzm4QV@= z+jVjIxxc}o8)*3xDTO6y6DIh%`w`_X50Cb z(N?1^IeY2+899FIMs}9WbDZ1oq-Xh=DO7rlHaiLc@M*3 zOVBZn<7N=L)pPWw=+TU}@j0FUlmCwGYDBYn#7?k{&Sj^rbQ`}fhJQ*5rnAj$!1PR? zN`a>awcViAZcu!F{W6#G^Dke!OzJ;h@chbk@tHOpuw@4mw2jdGt^FPgT@t$76Y3`l zM`Y+o6Wx=dbsKFty<)Y|WscVQEHgQMtFyV!YDCkSzMGEr8rDAD7&^z8XK$|#rf10K zcfr`6IC%H2ty`DVbGFa9y^;OK2tIoo9QF+%Z8-EDp^eY23EFF+M-zIyiIxd{+!QVI zInh`CJD$zAbWRZ(XJ_21!|6Fc#cFw_KgkN78RY0pr{`$9LGd}I=WL(z?MVH8BlB6h zgF|a@Xs1+;l%Q`ap+|WRJw=Z(9Xy|9Cg*Q;I{#H2`pK^8(=UYKo+G$?{%2;cH#^(S zGw(Jmn?a7xAUwZIrRN^IA?N3QKG!$cW&cd^IiKP^wHndM*=L=PWj0#ryl;BG+u=7Z^7*xE zb(5oq(4I3Z$0GDB(~YD4HT9DNBI6WZQ{?zPY}2<_b!j_@22DcWW_q>bUqXPL?A zTML`@ec-6%%vGL z@48b+gf%p30iX;H;>TmeGc7oSX^`~qh&5H-yVU@QgV*-8Z@1$ zJCLYGU0Zh6S{pK*ajOo~xy5bx&ZT(L^EmsQPY;gdv;B;U&gW4HdZgp1n;gfq9IYn$ zI4K(2XpPSnI;+P6Pe0%~bm?=cODEBpIU;!exc{6W?KSqJbG+N|HaB2;_V77B)hgOP zkL0tx15F#~5rzXNX#1A1*KyQ@uDf>eT@G$j$a`ps(AScpGovLwTiCp(b8bn_{e8Nl z(s^8ZhTV;PsvFXoZa&{t>_(4$rf%Pe%Fp&QDjow34F^onKL+y{njeArA($Va`97NV z9!HFY9zke7qrWN4zXX6lG_-co;`L$v(Nt1gZBI^pCHuov$i4B`OFy(TY~0q zgvJhfY=W*Wv~5r5AvBkwnF#$58=?P|&(JcV=cH&$rvvl(C~THCf-T9Jv(9s}b4_P& zYonxdtJ{c6&(J=D@ci~AJfB|V{M@n~q}p}z?HihZZsD`GA*2m-REHxXLCa@V{Ep+6 z(5nHNV>&XQZETL%2g*HXnaxM1bA1oeY&u(eby4marf2$O3T-#++d=vCpq8JlH*nDT zxu4Il!*PTJT{@1NLFoB?jwsI|r|86I8=c2D>0~q4D#*>n&kzp^uoMM-W=`9OD_i z@Od7awd5R6XJ2EZud~rcXRmpVaT~Vu%(0hzi`CHgff}8q6WlYr z#`V(~yA8Y<9G{+byV1KH9Ls0gaO7J?X+G-)8uHn1IA$d17{_tFgznoGS{VHe)6`D_ zx9~Z_YG`bZPtIPiagB6tbsKzohR^D@q-SV1bUwccChLuh+&I0U{Kl(bUb&3*ek6N- zrupnK&{2jXB0DdzM3A(n>wmsqe4BblT@t(t*qIo`B*xc`&Wje?9HGFMn>uD5fD`=r} z?_My*ZSbcCIrOO*GA{E zRVTZRqo!xwZqR&w4NTV07x{d8A^T^I&)Ny^2tM~2ju8pEB=mQy8P&BLm!W5y=(#SM z;k&-D3}@q8X(pyLyC@0PI5LQ_qSenNBSzIj6L z(++fJdbf3W85>OGfdBSRQr50_zVqnq~XAXmK?OaB`h7sC<}cJgzhoXI-w&{bmnsu zoBNv1tzP3M(OEOkvfDUPdWL+?yFr+qY5PpKH|WzTk$ywZ&-xjaK0aFwhbKYDI}Y2P z@cIee+vn&fbmDUqoi(o^H=SGQ%q3@TC)jT`D7W!3G0V-Nc+&l$`LBGFXqlZ$I=5~H zaeB6$OM%Vj7`ri-&-4v;tql%nIDQHS2#%*X!@;*Wz~fX-FRd+(v5up*(6@w6xBNe- zj(*b}@1hqzN3nTia&GB5&!ux)r>@sL%XDsY&ppjLi`{_vncfbL%FpBY3=Q=06SUuP z)P$}MtDHWtombgsQz-2UkLx*XY>sfwV>W`k*SJnP&vF~q^xU%>gm&W&u+P`WXZ;Le zAD{Uhp_HIUI*zu}Dn}r6Tbn~BwB|X+xo8WYxsC?>d>^lIU3Av>f?hhy>G@tU@K856 z(tUaf4t1Yi!gJl+^I$D@gX1%OHbvhK_WS47{0#XVWuSS(5#ykvw}f?qj&>ZrdpY>F z-8B=sG#whBA%nTs8k>8Pv*b07OlM1LBie1)H-qESGxQsJw+`B8ozJg;$){61`$3)0 zz6~KX(ASorBOJ#qp_7GP{;DGS^aHPpmif#P8GgPOo&7t(FGS7l}2F%l!p>Kiq=V#kp(AaQ{a?pB$ zt_l4eV(Lv{y(hGH?KsQ95t?W8L&EUSlpRlKPm1RF%n=!W-b-ic_M>R8antD><2G=5 zw(SPrW}XRa$fUEVmd6IV@c5zpCN;}*S&0(lXEnk|H#az%zQ%3ABZ6e&mZHq^ZG7#ozp9=^W5h) zTGKP;Gu#c1wj0nsL%;DV7@(iY+mYb&yXpHkpnq=XGu#mR6EwWZ&blLPPtYZy|D%}i zRa0J>blrp=vn!0~bJ$XJ&1a6t@bg?=fHf14CY_o(wU=k+g?zob9rX)gJOPD3@+Be zULB^hwOPlf=N`N9#v8QXcnu7Q&##Wl&!hR=VmP84beW(d2u(=n@7OH#9+pEQbo;3l zJwsbOhnAutpLrtZZ;r`k?^zW3?)^n}5MtvKLTsEqss zF^NjhJzYEcL}A>fa1^0+M)!8^5}z}HrC0YOovE8P+Ucx!o_pyW7>H*8 z=eIaI!}bQ9o}qn)+rd5f{LC{wgoZmpcn^owaNq=uZ{onW*~upfM1@`{q0HS0a*YjNcJ85beO|Dpv)^sVX9i_DOLn6rJ&&*(H;a<+)TGL?LHSK?kvjD>D-&1v3bU) zQY1R#=0;0;h9?It-MTjz`)uJeeR`1VZ_rQhhW!oBZ}jEo7Q7 zk$b_~JombdsPsI?Zg}}jx9i~T8y3F-`OF!P+~2_Y+27%S8$v6evEjfw!np~0wBw+= zc2ORB4?^pG4sVK<_O>PoPLLy3{ zQDa3!!HQ^9AoMZ}GcW~an99)f?z7J>YpuQ3+H0S4-YLZMJ`eA&ymSA!_wn)IkqF=IB{RKm6ejefX@)gy(X@b4B&JQhlycpR1MU8u7VSc&-zk1?+P@ z`z&Oh8<=NN@hmQ$8;j?r;#oqTrQ}&gp5^GdnLM|kX9apzqUTohtOCz!^sE8TTJ)>~ z&wB9O2A&Pzxg9)rfagx|+y$Py!SfsN{MJ493eUa4vlV!@2G2I&xes{m3!ZJkvmJQu z2cB8z*&aMQfCv52tR2y_6MA+=&o1cM6+OG5XLs`KL7qLyvln^xE}nggXW!!4k9qcI zo&(rtHuD@PJO>HS9PybeJ_jq$JoP!G@*G-y4l_N6yGOwDWtY!#Kb7Z-?Q4L(at962 zSIyD@ef5qqps(3U0`#>z^MJl?7aq_HcI5zl{caqf7w%pH`i4D9Krh;p0($XY6wo*B zO#yw=J`~Ud&r6o>hXGCSybR%a`OUL2pb?&LL3mz)@VpY?`Bs4ERjcPgKm$ClL3myZ z@VpM-c|E}MZ2-?30G@9Lc)kPR`A&f6y8xc=26+C>X2Ivn0iL%9c-}$b^VJB?*C0G! zi|~9M!t(-z=iLaNcPDthf#7)&!Sh}Po^LGhd{cqveHlD2W$?U=#q)9o&$9(Q-y-07 zg^1^sBA#zm@VrXJ^Xdwo*HrPm)_~`Ahpp52+;RKz|62j{ zvS(sKFaJYAXawk6j)#O^@oY%wmCu2MzV*3~(2&ooR*8H*5fZwY&o9b^zT?H2&>NA@ zp9bUeXTbRUSuj5TGZ>$@1LO1kAbBC3xBoYh&u7B%8PRzsMCY9mop(WW-WAh%w=WVt ze+l!M(0NZv=e@o{`CQO>pOVh|ey!m1*BLs0gQfE~IXch&7R%>v3v@nPr1N(~I-jG^ z`MWBe&#ln;dsRAr-$3W{IyQHYG(Y#BU;Y~RQ+cjh|eFCCTNk*AITH+hL6@K=sQ222|WYz^UHF)0qA@m zK%1;dPMWkj^{qi1_>l#OG5HpZ^N+`L8jb37y}B>Ac6A37_9W z`21E%=e^%n@cC~FKEJ)7^L`AS-^tSXT`Zm7&C>Zj0-fJ0(D{9H-Y3%e{R*8=Q|SDG zc^^>e{J}#$SfTTW4*gJ-&L2MP!#$hpeD+iHzf}Nz`4yjYKk9SknF^qjXo7|VG)_3ahX@S-jppa`Gojc2V48&Et_w1uN8}q1 z&+&7n^ETVG@)^?ke#Zjx{yv57LFhSpV<2XJqqZu>?w0VL?ct(6y5)KIH8BU;w^EuOb z>#e8aGbAsh^Y+H{8Pa*D!RZF3^X@czZsv1AXC~WV=*&&e`*VC|=`1AA0-X;M=sZWH z^IVb5?hA#@?yCx&52@1mPy?M0!*o9U@Jlb%6OQ<89nrrdG@YV11)=f$yqr$ZaDcWa z95CPz6X-SQj4;p7>t^7DaLeE`oSt_;r-NvE#_0y)Gs-sf?3tVmj-H-40(!+kDB*ws2XZ)89Si}D@{L14!0|giyFnVx&xp@g!YLc^S#dWS_9%^@{s!(?pG*}3PlBlPdPpX%dmj*8Dw zvpJm0QL{P3phI>!u9;Dj<9goXcw(7y4E8uk2z}Gj2%vAmd|pB_=)&SafWG-zgwPPs zXo9vn9IKy)2n_%Y1CAMosDzn>CVO!#>RPvC1V3q`qJ=R_Gf9*{ptCH@HWBV$bQ}JjRv%V&wrk;r!?~UmB8Ql%j|DJI^XZrfW5CV@%a=uKO;UP zcLUK`PdDDEPS4HeMnUH(y^R^QHpJw4u(MIgohLCiFq7fC$mj0&W&c_Q^ktV@Yz{Sw z9_4a~M}?Bhacz^yvB2zcI6`wK2L<#Ei-vg|WQ5iyXx-wt`P*=UzGaZZvFcng;rM_UizhkzEJ*4Csu~jqU$0%s1d) zK|1efbvOP1P0x>0r{^c&bc2{1PaXJN4Xp#p4}Jh0PQ|(GdY+6+I_@?(C#B9<0$w%%;O+?4mK2LQ*xM}&E>3m<&ezx!#(pf$oMAI`m8{8SWb%@UN zUP{y1Af@wO1)u5kTqGOJx!`_`x53c)fX&U?7~^bAVQgR;m+v5*``248qc=98OFmC$ z+KnUhW|43-&CeTzJKp(z%ALXu@2ThKncy>=p5^p;sQHYu4Qker>AC7XQ#$Ws^6JKT z8|z#3^%-Ak|*eVfAB8QkTk{ooauZY%k6GxZ=3;U<9`LGgJSarr1OrS7x}EX8#sF= zts9Nz2BC9dJ*pU?5R`)p&_9C|ay zV3&i9Ih@JCx*XTBog52BHF8)yj>T^+ZhI4tquJs>SG}>tftxsPc@NB>SG?C~L4za0 z)gQ=&UenmZfea1+=zKmX862CG&w{-H=!{MWQThy7?o%d+&+$b71itM@U z+js|)ZE$@XoVU@^wb9tZ0F{YWr z^>T>K9MiROT*r2D*i8=B<0u+AOdbc#I7l1ElCR?tnoQ8&M1WRrdP6?rrlgf(4~OD# zfDyVa;n?t_4CujpzA#VFe7~sRZycTPcMJ3LBeTDe-MZ~e&2^B^kl|>0mfelxkXwh` z4Kh9B){QoE>aOC-UDuZJvpYH|@&@FtD)+y+1!`jd11bD zN<1iZUr3DJ^n_+aJ40g<1@xw{IN%B4N_@{7CL93JV0@nFjyG@E@%hH)$Y)4rn_Fi- z8zgR>#^yvxCFR~ch+uzOXC5xU5r$ySw-76%%k(Gj6?Mz~UTI3S~PRHn=S*T-!#A zwIS_C8Pc?&IveWrJb|$R24v}_`yWc2oh^L!DTl3{Lk&7yH;4LY%^bsathBit*Rk7G z#+V#sA4lnN)VHe8HV(Q|1!o-C;=ub3Wzou0pcXwBFV8#WNA&xdUGe16rXud2<^ z-sY&bbJV&y+@2M6(~7ob#km~fmX)i;9V>0E9HUK+B8DF5agdNh&p3*WD(H|94bbK$ zj+O7rfWGzph|mDgAmA8rhcKTHZU8q_?)ckG=UD7*tQM!T#(<8(rRreyHS)=!Og+g2)+i#XGdt$Q^B+U zS`9hemuko{c*}}(PT1PYVK+GnJhOcq+S&-{_zT8sS;wbF0Hq=ksk~ zk2h~{Ovz`y9VydUx}8$zGq{-|ZcTx7#ybZgpRMi&o}O{GvByYrqsTS}o($UCHu&4z zLb4(5M-h!WbvKHdI&UOngK{~(@8;+JF(KNpZKX!&F^ra7jxCwc^pud<97{qD)q_TS z*1D1u3pxiJ?$tC22R}b=zI?8q4uZ+JIeiwVXLBo(#%Gc}lg^ErS2q=%4`ADnxaoOI zrSmj4U1Tx$JJQ+B(A&A6>a$U74pF$1T@Gh*2)!ILWpZ4$7 zb2yvhHCoc4+Z;oiIV6{3rc4fQw@SHLWr)W?x2hCv94O)#YH^Sz4glye4#(OLWkS~z zXu;sffCe4?4SwC9-8((`es`76ugK?vfX{V%1ME$K?lYkCEI?<< zXFo-sVKF*P?Hn4SFW0*{G@GNnm&5H>Ig7hhIM(F2{!2{CQEXPZ!P3V;V(7x-Kp98T z#WB?4fD`n00HEawy2-IycYY=`pFqzHpA($j3zpAp`$ncSw4a63!DBOhTTch2^cm85 z7u343E83W%P0wg{-g8f~ErrmTHf|K&#=cJ@jT=ul-5BJ&4X$mY#oBnbz~<-7m8^~D z4s?DN<;{ zXL?GA@a*280ouKR8MM8N1K`=coSx5X!5x*&iO+odMhl-ao#n0F@NcB~S#md&=~>;E zg87V-XSyqeW*bHFTujd;o%i3r%r-c0W9+%0K&y)Y_2sRO<`;ZFeV=#=^us9 z)A<}KqP1=gt(jvwmt$-v2WxVaw+Z*4B1bVn?}bO`8*6PGXoOy>SRBg_fEEYYrvgUk z6$fQPk8wCq3r7w(z#SEU=gpJPm*o5zJ}ioKJg zG&xF-qr9t9@8i%tj!G8?((E9gm!q5Bq=_Q~da48(?omk*hhT7If#ddkK$vf+Y`pn7 z(;0S)3ibxro091)_SOw>H&Ev@Zr#B9QdDz;WY4s5V;XP6+P2YRZAeWULwYu}mW|QI zh7e}tkE_0h=kpJa_(4x+E#K%TYqQT$xm-BX<`9F9_FfL&<>2?L*gH8&lVhOAfk)`Y zdd9Kj?Qn#~7RR!8LO`qgRNw>+wy8)C$Ewrv5h2>7k`+72flhozcf8XP;q4#GfZk|) zetEt>nDb}AXWrg;b)Jre(?MZt3OpO!$>wgLjVWk)9${{f?0Fh*!`imdWNiq^^Prv$ zCEL*VqSQJzEIU!$K9uV7kt045+1&k>^NswCvpK2_9rbN1V>Ycgm*Y$c(SuwLPUX0U zb2(fm2eV&=+pbcY9AzU%-Q!sNRSf9D;-GCD5YS7OY8D3?p<&zTEoTEjk2oVlEgT?% z&H=~zA7nu1gd^eEy{Pc{f;>TI1De~PV$Yv5o#E~7%x92pfXykH&ggUyG;hGaXgwRm z?gpBkQR@c2)s0UEiMdhPl|qwegSSy`N!h5@hHx$@I~&?&l;<6yIUCPEw6+!H#5^Jk zuTDDLVr-lY*=*%=M{0x4QS~|07`k#&sM#Ftx4v0CyASOy2e)0NG&zb8dLhlAZzx*( zF`uc$L3gSkJi9mYDIvl$N;qt1gl)IH0iJ=u!JiP?`OG)iZ9aT9q#MY6Zg)4(^t`*q zXM8qDvW@1G!7b%nPrGb2)xT=$Lh2j{_50 z=Ciel0|e0SB`o6L2@NiJ3q7NGe&+eicW`j|#?10LryIQe%%{&ny0Jr^pPQ#=w9yJp z&&aH^)4A2Ft9u)zwZXM*Y;31)Q_xwMm%+!?OvC>Hq;h`k`Mj;h<@lYYpQFBQ#qC;A z9_MmwFEd)+vf^BhS^N||bFCcQc9qiPC>uG-%__|v2LkkxeIcP6EDjKHz$OmpaG3ZE zH>t1*G}@z*0*;(-RQdcs?u5{sZ~T(GpOXE$oNr9ZXRtM;na^lr3gRRI6!n?rUvuDnumIb18pW4Svku9f3BF6Xej9NcylCgXS_O*v>I2jg*&kRwjc z{qLfU0|Ht}IPe5b_Njmf8cxvl`FR!Cq*8C;NO)dfGdS}6d`IPmx1MigI&ZaAzTaKp z^JlsI8O+aT z!R<&HZ*54<#&-`^dN#yt<9ml_&PJ_cqrMZx=xb0a=l4CIJ){5ffBv$LXp7Hr*{j`; z`V6-@v}TS8T@EIQ=3I^ie@%1f(&KQq3Exx@n#9nH-hyZ7#c#!c*4sD+TO4qLeoqGU zidKgML>zg5=36*2p9O;>9T4(6RNBu6hwwSmd0W=rfb`9L7SiWa!2JArU~fq72HKbc z>AV|i-9Xbb-j?zfHG8f#ZqQ9BmcEVB+juvVZH%`zL}x=xHnhzsBaICZVel^p_!^na zaDI-I{-1wwKh?+RbLfQrH{IvZx;bXv<={+?Yp=DL9HqzM2u&I}Ch$0L#_@GHLR&2k zvQH%gy3yf4n^ZEOfn%2w=$gTi@a$gX33^Pv0r<>rPvP>7%;&1T0rsWJ1wfU*qH> z3SUF#v!A1@eD40Bep;yF*?*}7(G{CRa_wBoAwFEj(S62c9OF!mGUF&4ImiI*-r@;* zQDv(N!n1n=`8>?x0C;ZXGfFrreCAIGv%xWwe9q~{)?4fOhMmuOdfp!FO@Z_E7(T=4 z8D$%T&5hPz10f&3pM(BJ;1@d_{a5#9! zE+-uMgfR2DYCxxe!+JiL=jY7lOy_O3vChvReFn|XEqn&q#!jZ}c~^WSc?n-86jJQ8v_zW$$W-B0x~`W)&Q?Q9Ok z=g@49A1#At8T8xa#0cXb?aTv;Fu%!7F*}#m#%w0r;H(W{KMI>X4{$a_pHAP4 zqIYc68#YY728OWk?xRP3^#1;VJT6~P<#W{9Ih@P!Z}MrOb2&aIAzE(caJ?L#=MM|7 z{sNaokKM0Qc5;*^2lY5y3QabvEcyyQCoK9nPz-$&YU98=Rfxp_%956U3jkW4pz8_8 zxtY*;e&!QseviuL$>&VxZU0a5H-3+spFz68H*Y*T^I1qYjsw#(+L$6v&t`LjWEw8gjI>RxUe_f|@0b$_- z$Ywu3-hHi=w1qL+@O7Rq;aFl=V9Ik*R|1NZM61mD8|MinzK~Wxw1GjMyi{ly4cKR=%w2k0>d$EN3Vrt|iE z^TwHZe$I5>$>MIn)(x~T1=4vBv@HeEc`rOY?~Tok+NP9gybZ2vqsiJ}vyCCn#_)~} zL&HXOcDDH%-qxUWzW@C%cF!fRbU%j2`5d-}j!M*_`y8&D<27>9am8!poI|oXoXc?v zmvdbGdiTRTc9)~<$Zl0(8Rj2tMDHHh53M=SL(yKQi%|vp0^-`3A@5 z{4MOQ1Jg6SIViasQtO7*+)x`g$jM;M+ZfiiVYN2+rj6&!<(oF7-6-;A6m2hxz7?g` zurZyl@q#0PuR#gju{k~#K6igJ&1s=}X=QMazkp}=F*iYXpD_cp`-r)&;yx-f4(^<= z2%+5vn9pS&M|t0SCQb=q!ZF6;$b9Ak^(f z@%%jIbTCiP+q2C`Al(4Hbz{y3A)jSB>$_6O^h~_Ey0`H=I(Lk&(-~wJ%1g{*?p@?^ z{C&xN-B0zI#^_+n_<53>)4Mp5ue%S4ZvsK-MALa&v0Xk)VeXu+`x?+ z#M{`HG;S1prrw67ZNqA9@J$;NIvZlk#@a(QW24rvF_o{u*cv6FBbVWCo6%uu=rH*l zO3)_7^3?E(&()29@9ZaZEUV>z;f0C-3hr`|Bc|w;9k~ zm-FR3KZDc3+`LhBHvpZ{%@k9*A)6a`R|@tvi1obh*N9hF^ENCyQmod7(6rIyY@9n! z>eg;UsH3VD3#^-5`j#@uQZQIH)n?rIrTn^pfayXOY4JD$l zJ+;iCEhY!!agfa_RgVLo621+N(726*>{J0E$1-4XEPq#)IHV>Hv`r4}nqq(7G&)D3k^=(jZV@hk|eQff~H*HAH z#`MNUtzo0?YkcGgi?1Qt8idauyZ^`F+>Bp4pTlZ&RN`op&!J7xgSt7?W)5zMzRKRq z!R=SEm>ksOsBc!meH@iG4msoa1_1QZ5fgO9;ovu^3{N=j_(>$Rt-(&6pVRgfl2wo&d#FMJ($V#T{VdIe^9}*|GJ_?SDO4q+0K6< zr5y6TZx+w)L%YjSPS7@!qkc|ELTL52H=Lh`b#dhRx%Hm6-QgI{=e*-;JfB^^Lzvv?xtzR>8*6FGmnY1nM!^OQ!1Iz~I6LwN?<9IS8WT#i}X6wU8g;arXhO%8gS za1S^`FWM6m8uxMFttxUGhhlMn5t_H`RveTG-N5J6!r_j~!{d&|!{ZLY!{b&-geRU4 zZrBjd2lMnc4J~^ne8%3!QsQk;YvX_esJC%*!RIm7 z2JdXR1wAaE<;^H^$4152$RuW7)qM@iHWZVs;YMm#KjR1I@Sk#C<~}hS9hIn~=5y%V zR)*Oet<4;Emt!iEgZ6RIyDE#GObCrn37-m64&rfuwXsV<7YDRBOidgK&^3o+HNQzE z0$OcJ0tQC{^v0Q=Bc1PSy`A!}X?|v#H`w$U^lpH318z(Kts7_?EZmm@&5bXkbVD;Y zaN`E?HVB=|4Y1VP_*OyZG1i8#8--=_Jki)VtDU4kn16Nt1(aR?$6<#cxG`UJUt+ zQ|JK}2N)_N4iFs@T~Irw%g(ao$!47jPm*A(SKI?{HoN<|LT}vBszneDLdrm zjZ-*2gPSR*WXHT>M%0V|Z*vN4=q==5y%n9NMlGb=5wa=E^uQCXx)I*GrTpp#}h%i zfy@n*ZK#`4$jRW-$@EP7HmJ8T)Y=f5HrV7@?AZ`b1&0|M)rO5q>U{DMbzh^=)+hs$w5Nsg)oEeUaJ`g8lmASA;7bH z2~P>zEW3nf_abM|`U&A`=9agO&pClE`F!V{8-vg7_6Eo2Oy`|IFH+{SFg@=k@fpw= zoebhT-I}-2dNN48jUw4N(AKrV8g)X`hLCK`6X-n9*vMlt|Efag8L>5FKHCZHZm$@g zN)T;u?uHWja%IyBJ3_k;xsyWPw%c@s#(cJW9FWkE&yvLf0uG^Tl;d-)CrL{<;`w0C zH>Trrd3$i{t)u^}&CgTwnVp`6dxI7_4>dRN^h{>wB6;3?=q#~W+lw;X*r>7Dva70+ zW~8r6Cc{tq>Df=v_qq5!wkg`u(P3=pP{wGzokQETa>e%26z#e>*vIa2IFqC3bE2NJxO#H`b^vcACKfcBmu; zx@CTDu*svg0q(~OO3!$yVACSM}~SbITi4M^txb>_2Y^dm2Rq><5vtt<70 zj?q4c5Om1h97;0>GenPZIV{^%XeS3XIdCIK#p3`O$AlI~IwVBfR3e~x2Rb<%Ha@RU zEq3vIFwf7~uuFJu$~PE3e@dR8Bb~S9x<${31m;c$<99H92I)rC-2nFn;l31;xq-50 z+_=%=ZG3}R&(zwOJ)3$PHfw`x+Bjz}XKj2}NH)Zl4S~&v9;zA}e4Y_LJ^ygs*qE+e zgRwPgeD;jq$VSH)pJPZj$HsFxOr0DghAvDF6hkk13rsn%$AP!1z%~xW;s7Bu(4fJ_ z*jwI{0DVgupkV_2{y0IeIxQU$rkqK5 zN4=ASY2-j5#|V!DW*lG7DfCicaV-002K4f8WkMs1BLO;ZQxSHlBtQd)BaP6J&uf!o zSMoV0&>NG_kG|=1&v0LgWNs+CQt+J= zoILMGybUrtQ)^@PZ0c>4T^q+2Y@Rdc+040MOV7p$Larh2MbSDoPE;r7TE9l!*Ra_d zp;?2YG5PF84|q`|a{1eqb4+zws07jeEA6yU2szxxQV!kP%)#i;?1mMZLR&gH$N+sk z!n1pW2IvNl1K`=c%zSROI1--S3wDBz1N5y4&uM<<42~^<&uaQSE6&flTbI*~Oy`|< z&V1(G4M6AJp;;%H8z|c#Te>wq>nDTsTyS=gJeSr6L+3dRotvBufzC5xY*aF5gRh|l z&G|dRVCC%t-BWTIezG|+dKjT)MzaTnGvIR2?*8kpqx-Mg2n`dC?lrut0wQSl5)vBn zSt4|vpQWxOozI-Zk;Z5JgfJ-_G2h4ubZOX)J0C3bjdFh0_#Ek6+nU1hInx=Q4T9+z zm~|F9+r2tGJ?nHnpx9wWt-39b&g$e`-G?Hk8Q_adVC|KHWAs(W=I((sE?+yN!{l^K zV{}})oi;^lKF1K7V-lC6(Cvnp9M|uL3Ek*%06@!a9MIwr`FuboG_p9-2o3mbbU54r zdw4(#N0Ojp!l4=*3DEI`F!A|8@rH`r|6HCA4&?J?F~OLK&tPK;;4^65F!33g8z_6m zn^MMk8?U9MQY}_%DiLcn&S#UUsJYn)J={t9>?OR z06;G$TUEdaz2xb6h8DXxGN9oZ;qejB4Gu?&pd+8xqzM}AQHg+74330nrNM3xpFbV_ z=QKZmHl7cr`T1Ys?ZHUrSt;EB^E01>c~70q*G ztHqJ765eul1ax*dtoOV@3rA8oV!*Nf2XTN-0d&!kRPIoT`NmzT0lGdvM>=n<+#a;> zInfz3BPBkY-3>53^Q{}L=7!d|fj6aKZ=!Hr-bmXced=7ft|4l&+bL!^B9LC;W-(0xg|;Fb7??tu6)*~ zXWrca({tn5Ae^4z?7R=z0;`=2602^;&IOrVLu}t~bv6__t2+19KO!GOV&#C)4rO(^Nd?QWBO!}O^)A^j!4KO`}jVZiY*Y4G6*#=5Bn%g!k z*2Xxau9eM)DqB&sTw}{%GvMS{b7yBgdK-A&K^bI~LGPsFhLdn%)&*3hB*9NM-OdDn_!b1*^2 z7?)#ulSAv{ARY$@Iba({o5gYSvm&5xNe)MQ!m;N0k z?kpNO#^lcmpG$iq?M;bigFED%uxFEYoo29L>cfaVr!Iyj&zQ{FM7HAF+3a3=&1S} z@)X@@b2yjdbtR&m%W+DXb6oxU;-K*A(&f1JRGM-OFgehLLcCc;_Bcoz2M9S}8%Lg? zVcRIOI3l270{!0TL9gT-jyOOww+KNCM@Tr-vT-}(5@7eJ)_1AEqUG_dmHZ}HwK4T z8>O=`ceJx1b!-eZHb(mzhRoU8t-;tDbv}2TzUVXV$M8&Qbm&RP8S;6dwryoXo1;Lq zZNtiVlcVrBz6?(aiN^tAXt-4cS{yKg7P~l-2c4TZvcsWI(5udkfL{GQCE*Yg=nX%L zgf8aipGHCt&o?5Sw@K|tk#!fM@p-L>%H>Z()K? ze4gfvFyP6%VZaYu1Or9FQh!spE3$Y&3U2(k@v*C6CGoSqS# z_tmCnoII1+c~IAe^<0o^5M^@>_o3j^&B&P`lVp&O43qO{Tf@%hEsfD)$l<<_7~SUE zafId=O^yluTgvFxP7X|HI6vQD-g^ZijwXvE1sovZ;3nu)lce?}$q97KH_8sXnc%a0 zJ0+$Y+bQ-&$!BJIu57f**)y1)K_?Q(Hqb6BiO$%ov(uTjZ4Bw!DCx`@bsU@L30tcq zHqVT)K~P-0-85{T@1Bms(&L;ClhrYm(NWvEqWK(^b`BQN&gEb?t&DRy%Ka*CM2o*jb5eY5j8&l5D z20mXJ^XD`_r@bkO&uMFl%I9m-{LHp)06y2v4O`=e=4~v))AIq?+nC1MV0$)LKJ(5- zyRo6|L|K2hW^B}ajVWyn(5z9;%IK3jA9!bt&mFg|PKT|fqu$Y>8y!`jLl~o5Z4SPf z!?_$xGe@~&#kF!2y&Tl#sC9DajT}~w1GaIPERLkvE&sy=XxPM&0G;rh_`H(YrLwx% zq_XzJNaz?quRmEjAx!!6i{k`6UH+W-{27`*$N4$ZSvws}*!D(&F84JbMRBN(9OZQ99LdBoy$=(IrK&j z)#Cs$H1s%N8wYQ3EPH1HG`vUn?wE1pZ7M4o9gda+I?c}?ON0)9o}SN<&bgbv;Pa~^ zg=6}>ec8NmN}QilE7Fj3qs`oajT^|@*!S&_&)BLXvorQKrm;4xJsbRHln)-lI~%gG zq3uMe_!>o!fj_9XYfS2Dcu0TzfgkV5-2WGHSbIIC(^2o}s5NxBjVt0K`yBGF6~3E8 zZ00DU=wU7g)5(FVq3LcFY;xeuD$wIt{8a#Gd`bx0IMPiO-r_*}R3e~32CYrdz~P8@ zRK6bxo#yBB(*zywsDyw+F*pLC$NL*`eolOTWb`*EpCg@TrMrVE-AG$gBAs{4Zk_IK z12DsC1Z|j@k@;$tB7NJp(=me^yx9(A_5mp4|t- zTn>6pi16&*;1v3LvRg$yCzSXM+c*-Q-HV#Vk??Hf^R4NOFa^+-1Ud%LWk=GC&(D$0 zrClfUIe$~~S$8*voejcl19}@nPX={wW7utO>$xCH=ShtX{SG%Cl*@P3RKxv&NM!T{ z!(rhg%I5C*bh58GZGR?uR_3%TY5q7VL^==wi1DQtjXby=YHJXxPUAx2nK44!Bb#Ptd4~ zBOMYZJgXBla5&;6m2`{Hz~`#Lu@U)P*c*w@W%I^Waeij_%()x6btCUf;mwUjAl-oG z25j6wn^F*;mm)r6Zv$H!vu9&(qrM@<*0iyT;j=4dVWwxdXoo!&l((X+6Zu^6H872f zcN?mFwsmVXyBgB`>}Tj>etit&u=aXNr=wJ zW|xBq(KVA}!Q%;`p8!*ig-?WlRy__7a)=&B3OUe5l{7(Ti-T+8$lFw6Q-5|iR-F(5 z9TSeSg(Kz9b%Ucd-}r}nhbQj0iv39OehST>>HM7RjWgnu9QmBm=d?8?(s@UtyTPR! zd3uJO8-UJI;|B6J_Jxfbnzw|V?*1Ca=tP< z*L;n7yN1oysP<}vh{Ks0+11ea>>2&CU%yP_GW>2zr=w%JgfJ)^L;a0N=X?Ko;B(xbBDQZxe2#RUmG-7YKBsSzTgOb#rzW>9Z%pA@ zH>&1_ls&_219=>KQU>z;eLC-PaEp0_zB%+HUA z{zi?@aeCgqY~Dy)Qxcz(yOHP&%;(DV+-7dT#tr0cpkxDYNzvOj>ej}z&c^c&;hhbs zV?%D(sQ4O1jsZWY+ZwiJ4Xdlcm>M=dcbqoj+5g#e=Y=(+L&3BAN=`X0Rkp3LLbUsk z<1@c!rNFcMfP~QQEr~eX+uDt9%x7wHKt3;A2vcZ$-@86Rqqd~PXVhkA%AiX=-&!~v zh6Fkqc4U5*JNj`0hn3GX-H`3)I3-6u$LTruil%fUxpnRCMou^Qvq8)BjFM+`GKih$ z>CXknI2&x{>^^Ki6I436rtfMp~d4!5j67b;{43-yDAfoyp6DQIAQ|5x^V2Id}FA=F_h1Adfu++-k^Mr z+|IpqX?kXErsPgheR`e=I`2>Rq+mW{=efw852CwYOKYQ~Gjk@`WNb)mK1|tHrO~9DkP)Qk?jp`+q+uqj8I&Ec9kb|{Yu zhq)Z%Opb-S!x3IroL$@~E|Kk!qm)1tv zw81(XZ0;-=8v>u@h7HZv7~igOyNKlch1Jyvtr{|q(Kl{UP^2;c+c_OZtHb7WSXw&9 z86C3EA#Yn5Z*vTDIT(}Uaqz}UlgR->jzv$-Gc??)B3c|zk2ADjao7@$=fsR7@ws$3 zVgemnIM$scP0&RGZ8kV8`E!lW(cXx3o>lBkN#CTcDRF*Ibly3o&v|1C*Shhglx~1+ zDRpxLoeU21Hgs!)IvaE5FxEyfJzJd(X)lV@v2mVwCaC%vcv2?srm-~|Tn$8D?afc; zyPpTShvzc<)HxkStHb7WST?U1j1Do5uJ|1M6#W|Kb1;u+a}-0gt(Swk9JT!_c9TQy z7sYy^ZOH_!w4M{jx`^Q8FVp0PiAn;1fS#doSHYv zttqcB_?+C00n;jH-P(9BnVjd$q1MLxne8a2G16`s zyBu!6%D>|z8oL~Y$pJ+Ubl=<7$ALVK?Ad)i0UF%(wq?+&!x8yhY*N`W`5aFN)AVd{ zH}b|5u5}~tOUdTOlfd+xJ2znC#&~ao>_{QW^YI0pskKq|Y&`eivS)+ejM8Xqh?Db4 zV(MJmhVp_+=4`PwCU-R)pIE z6t=n?*yKQXMtpAfI1--Si{X6EUHx%>2DemV1YO)Byl3#);%>B0&w$QgmlbT>fZ4_{ zZv&qT9(W+;b3x}h^jxsWH4bJb=SfZlMLH{YxJiytysgmLWNQ=$f5Hb^j6uJ{Z0_F4 zGc)(iJ%!P1$l*Wa8O;&e>f3E9Lgx&6rY(*XaHKBim~Y6A9p&@7b=ZO46!~-P-YC-N zqPZ^4&ymkD+t?|&8!3Bc%;((4pORQ~*5@$pWF$?BNO=`a`_auO}}bExec45ICOR#=yVAiA}c19x)Z zMh=_DQE%gDusDi)geHe0-cdQEPoQ0`f`8nhJbOpQEv@j6t2*$H&ENRVZ<_p#i^J`d zOL~7p~5SD`OKsnd0&d%+>kbXM^91!W$cM!-ni@=xFMhch~qy(?NnhwO96+g9v02iMHuT#i#{5>0zKoXPP9oJ3QX z1GjQulLPNoS%@1splSD(oN^$KBdO46r%DQ;D;7sNLFa8M(Si;R$Ewp}1f3j?=-4&! zIo=?=^W#0C<%a$`pIhy^Xm3P5({y9U!rh2;-X*6Ssda;!p405S=bqWzcxz5KK(+xl zrP#a;+_r(OjROzFZ5!0upw7lzle57Z8*Iylv=!yU!sM*_8b!>UKd9LnmOUsoSHsk* z5%`Qu4W7{b{2c%Lt55kKBc-iY$AnIYa%89(9nR+v8akZM!9H%+3iI&Y9D>b35N&cf zkjkN%9C)`1+^j-8jtuBU`Kk)CIMPlPp^GDF98rU2n>gYGeNH?jj2X1Z=g`8jF6>dk z`7@oLBcJcMV+5a%?%V5(d_FdC`1b>!i{_0Y-6(rElDm=a4d#6*6>}rgd2e8DfRjO6 z-v(~mAlAl#xNU=48`RmDJC|A;th2%7&Mn4<(y%em*QnYW`J##N%I0dATQwY?3sb|w z=bqC>Jo`Vho)}JZ2VB9k|4NLam4*%$&+bD8&;Fw_<(TfI5aHRqfwxtzyUuEIfC2gj zfM>wx#ZsSLo}g0~N5Zq()-RtCN_?h0{WH(!wrs` zsZ2N0^t_vu&v|+V*#_D(2>ERDHpW>S!;LzQ&ODpthK+j;n@a|3pHMEx_cLL2OyhLu zMu*nWA@_4gK8I`PU>?rq;F>vxxEy*bht=ee`Z#!x1MO5XP0(x;M@%?k0$uiSM2Ewa zKqrGEc8sDM-tNHLKe)e6_csJSSL}@{pRW${v*Kp_&|^G6`;05j~E4O8B%mLvt3#vS-GO z1ME{tBXsPtiwTF~a5V8bCeXLPNHgrl^O<%dQTw^Z=OW!G-Hp_`!A#G&bK}c7-N@7P z-r3xMn^Gii;~S#4G0xhs>_!=FYzWE5`G-r!hUROGw>8YJMm5an|Da-O5JF$@Aop|B zQ5=`?XDwdGbXG^brK4(e2vJA7&(UOa6c-CkE(cOMhMFA2;{cmg7D10A9TTE9jtJM!T`2WtjcHvC z=V{PR4M1Z0@{b%=rv4SEF_hzVT*GA|RLAw_= zLPraF`SKV+M?RNLNp179t%C#KAe?%Bo{G!f4k?G4|hKekG-wKI~|4% zEcK?2+UAw2(IK4{9?MVB(zX?D*9zCoVQJ=|tsE}pI1Ul|5AY0qow8j8_Kw0v4$#34``LoFYPDjet$Rb z*Wvt`+#dX_+Km+UrbsutMRy}@Oo`L;ZmD%6(U~(hzLL_7>}|ko!{TjdZ5vwG25s6H z=4^0#QNDjD*RdfrYz*`@)Lf(1tzmIB?5!Ftrbh5Icslod_Kg1XUv@v{$6Fo7z7Ct! zQE%#~IUSnOAvbiWJ_k2Nv%6NxO)Ek(2O6SXE63}}6pdOr>?ViY$N@L2WRC;2al9=N z`fuV4%~~9BgibdJqs4K{dlI1opaY&)wj~_vPv=eu37^>pj-hA7s( zP|b}*XV@m18#gT82C_D0u=*08u5)>aMYX~69#dZO_l;AzzO+%eis(*NXs8XmjXVe9IsH+9sU4!3tjd}O0T z>gSNQtu)viH0P-9Sivp_-LE2dazKyc`s<;|0UJ5;IU({mBA{=~9!GBDND0R`BcOrB z5klz1=kN4@UP&B|Rp*9;BWzMx^Zk%;AcG?&(D;P#_8<3z#s&wzK{yegd3z%^Z#*ib z8)0iooSqAJLuuVe(=)d%g)=w6rWBAoLvQ05z^WT+ZP2ETy0dWtlWTA-8=SErHEf)8 zxMXac98p~S9%pMs-5}w_QI6>!(Bl3Ca(#Yp!d4eW9!gHb`$tj241u_wjf_U(VPZWPo;WQN-cj;EbbY+qDxq^Z5p$ zF)0(e?K3AEvaaNPWiZ>R9~Gv;$8-4J^>BA-+CteAEAWRT4^K=ur# zXW(r>Iv;@eym9Hw`E+cqA=q?6s^LB^Cg$|PJ)O-Rtr;}?kDS%f;B|Pb!?cCPW_65p zIy9q0Zs?GF4!)hkYIBs$9440o9u!`KU5@K^frN&g9AvjjnxI=ej@-tPcdDos2brMb zHWeoW_@{6<*jwIlj|w>Qjt452dA$l6+pKHqoJuo4LfwFLo+($ohz=NgEcyw&%r#yZ4RcF0}avGFcGEFvK0+>3CV~ z4$D}lqhfRjQHL`+n1|oC;<`CrQ)bbu%|U_=+RH&*4t>9hyj=yF9I%nY;BlmoBW4_$ z#j*TdG2;jWbm|&yOE^*shfg?628UOmC*kuekzMzyKHngByF>Fv{FLzd4S~<6Qa;D& zIo(Ss%?)l>N^ac9-Uhsr@(z$a!@dpZZ49+G-cPopoOUqn*gFh$IcmKe&g3`?#vHhn!)9`{cpQ1FiqYbT6ZE&k1buctL9eJe9DKro5@=#@1jP>C zP`T@Zp3t)Y%+Alpz}qRtz^D~{EcZu#sY~4uvQc81!$)0PQ zQY_vE-jM>kHjY0CZ%Dz`M&WFb$$8$q(%CQ@8|>sP`5Gr5A^93(Y>nw$4ZEpP^l2at zmv3Hrl>6z)+`{KDNjDuD8ir{9C$$;6`(BMX+?NWT-B+5?A&k)CX(5AW_fa`Pb0>vm zGe_%5A-S&tV`$OjNFlU)k%uIS`;{h|pyQojtoq=wS&*pFgizgB?4PKr<(V+zoHef9B@rp?nt7 z4Ktt9^t?ypGc!GdTPfMA+mh(a85=yC51*LMERxfQ0*=wgn;)AyLjU)@`q|j}U+;C8 z%?@i@hsElsIURaS#}K1K@i`bobGuf`ZVqR2kjK!=0T4aJ?Mp7 zv}$q0gkyF{I6@PL=y0H0-l>HH-BH;De8%?1Wj%|-{2ZD$2%l;126Z=L>qay;()7GX z*}0)NZUAosOwY@}^lY;>R-oazXxbptGwsfn72zMX^Hwc@%t$m5zh9*q&*=5mZSIa0`x_o^&@N+k4+OdAK-sS>WL zM2n-`r=mL?t4;{F2vGvP_QW30eZaB)#QYuYHBjUw3? zV{AyihP(}BxUEs|)o`sERWi?S^E6lnOCNemgJ*Nk=U@2ezyHktG*H@m9md8EYg>n< zsiWp}=q(+x(V;eUFj0r-bC59Fxg4)0h`z#gbD)QDIZ(`jT@KvJK}-&Cp^!H@Y92@4 zs&Z3q;|L)K*r*b&5=M)IHgUvlDl6X?ENIk|ls2h^hy(H&-|^n?QP`2h8PL`9L9{&u zo(|skzKXpO_ND|rhpj0jeWvb4+?WzuH`2b8m~O;uV^7B1$k}twHulYp8^GHDv$M_G zcrT)}-n3zIHj3QYZfu+``5HN8Mz1tmLvPloyBfAu4ZEpf=+gk029`#h&wtT`QeONTQ$gq9A;=urAOiloEV%|W-UIF|!8bGQvFUx0|V znj8zh2mrkRc5>u%!df2(@Hj%qp>}b^j3b25`~)3$sf2)IRX;%E7LIl2hY8vb(44_B zgwI@iUAR5?NXTbwZ{X9xI8z5c3+{$^HW>G%q|S|$J?o7dPfOV|?`^=g4aM3(bSBQm zTx@MTmz)ZoP&gahUKF-tgUK}p_!{DgpuPvC?rPXtHLRw_08c|^GW@3JGW>5FpS!X<2~gN-?wd=4@|`?qw0E?tiD-Z$j4{hSc;dBK9*$&vYN_c&q(ZLv6PXM~u~ zt3%HycS1Pxe74&gn9r^0Mln6_8Tp*DjX@`aL#&N9XQRk9Ms;imlk=b(-0A3#eSr@C z*f)ku!~KaVZFYYIref1eNMQXnBr|+Hla$^(7%f|OZG^t`(qe>mA6OWTA`btC`gY?9 zo%vjEOo|!wV&;^vBy_ziDKy#j0S9Rs#f}}v=k@Dh0v!Zs)X{ItpXK?P<8vf2mp+qp zBe)y2dnu95dno1xmu;lk8MJLkJFKiu-Sl*}8yh^GQI>(;h?DbZlg`3sE16r_tkbys z7BU&Wc3wyK)9~0EJFIOTb*p2H(;*ujQbR|J&tb7S*k%rdXuM-Z+phvUIiyC8n#Zwt zuSn>;RRy$h#0)xYR9UuvB(!eX6+Ik&gif1Od;*R4sJO#@kZ{2K`F5GlNA-r?Z-09) z_uSh*yf4pUr%Z=EkBpJ@1vwjl^fRZzG)x z?!Uj_Z6H3w4JpLhKzy$CY|zapV~h>a*FZ?lZ)VQcFq#@BKDT)qiltFRlaUoDR98Lu%--`y6JQ1C7zT%i&r%z=jpst^nX;bG*~`g_}~j4@{p3r^pk;RhbffyUPm?Vs9POl zoQ`&*L+s}$f{wDAqqI2~m*W&TL~9#XoXK%&zEcQIjs<@W2>r&Ka-c?zY}&mgy3l#A zN-X057RTFrLNgYJdPXQX9N4k*5y!d@mnVdr!BP5kMn1p1oS%bThq^ZcpK62*8_b#D>4$U1hSIJf z<{IN{4U4N`H8om14U?ro`26<|c0UjC52dm6-gzBOZil_G!`9YOw>ri+9jej6#L>LZ z!EIY{Hb>FUamDBGX<@CI!|rmxP7Y{tAdlle<4K|5afFZqSR7xA=Y(m3{zgATFAE!0 z;y#s|X%h!(vcnF?>bOazPdLtlj$N3ae^@5ahww@Hi8{c_?h8_nzCrkr0Xzh;~GFg^fd(@BKWRub+@Ep) z)<1(}9>V7#l-_a}EfM;XOIjFxPb74lpJj_fKOb49z10185_bfK+6z0Ay$=rYP^U4V! zmv^+89rnf!TU$rX>hMm-HnJTp_jC+0I*Kux+qP2fT5-)B+v6#kHgo8`9J0&NW^&}s zD!GrN^f>Ak$MOSuLf=fAIB>!-wgT%o<_T+@!JQxACb>L|HVW74+mo9v)0xQYh#DEI*zWT9b>Hy z*V7?9s?ouW(W1{$jM3$;723@~n>n1z@fetMT>V&>azK*rcQ|^-E^bok6X^bg@FX}vi}|zIz|qR*a60%erM(eucH^xn z*xfj*pPz+vBbpnf_Z-a)u5lx8O5wZ>scl2DHgMC1?rhM_DBnATIvdTKM&6@{?z{{MCQI~b35#f9Tu-+oYkSV zbVx>r)X>5C99%m`xogE_a{xqRmqTvlsGA(f<9Kr-boMynDdF3a#}Pd{VsR{cXAfwx ziNjCO*x^`p8l0f}1e)woDGlh4b%xy?e~Tt)VAxswI?3J$r-QF9PX|N#%(@#S-5{+S z(cIYM%^}^0of~hB({r4i_sN@5bZRB|P8dB7uu~-*6E2l34%Ebv zwyF3CnsYcv3x^+|p~11?NAQly%<(y}80}5zKf^aug86)0;B#;{LT6pvmqN{rCl%Rq z+Le;B4c*(2+BPI>gEVcRT%+o2kOm~y*kC5-lbAC>X&cHgTSITwFqs+#GPigdt(HdN zXxRB2D6Ozq`l`+BFyPt$NiFB-zOOkQy44}X9R6eJypV}G+($(aJ=o?zcrIKHfM@p- zR71O$*@eFL+PcY+A`W)1O5}6)IN~W`&EnvjILIxPet?cWNig96d~Pu~#?Q}idftBf zKHre}Os3~udp^f~DRFwHCxd(KRZP$OL_X&`DV(>_dM-GnvoXfl$Uz2vC8QeeV`XxF z09&S!KAg(bXz?_-(?0DpjmG77A>LqqNhH=j-+U=8pBG-FY{!xaeTiUn*ce@QbKsN0 z(zgQo8dz0;C5T9n2W*8aj%{`5feFw>g~4F$*$U?&W~{Rq7^3 zZsgEB4$#ICGw6N$8G0${;z$+;PB;$g0UaDW#^DHjK9sqm;*SU?$~P|Z`SZozf4;=u z?iTG0#Ak!M5%?VUrAX$6(zubP=cQ?S&ep~O2V`&KX256B+CY5PoQj)hN*DF^Fu#8bksRV7*+H4nOI;^@x^p93xE za6;%E4z7iR>EQ4c9F6(&Kly&7Pj&vYbUL_QZ*QF8vy6X*cL!0rp}8BOb)%MU#OZnO zXl~?9DaqSNbcP#Xv$p|vq*$ztbLK*8;j^42Y3AAX~8S{<4y-vEFBBdMK_sttEM7 zjnudilV^5MO77dp-p0GZ>};_%h_gZ3_dkH>{J}%0vr%?ze7KmL1z#g38R)lUYiP|H z<6MntJ&jgN!{}%ba{s@FxSxlH2Xk0|*KT*Tw02k}=}yE2hSsbx&egCqYS=xEHcO+)(MWv$ zlZUvUNap^xPGs)eGIg$f(sF889iok1N6qRGqG~!$wg$17d2SpI=J|H8sdyDYw z-_Wx{6mpPWjxpziX@GVwV#bj-t3*C8S`nS0=>5aEtaSlg+ixBd8?OOhXu#L~K z{ke61hJ5DSI?LH0H8)bWQQYZH$unr;&wU%&+W@okftGVYWYm$K4Kz8sH-yfImc~Xg zIlB)SIt%R@LgGAwu0}P{aDQO;G|JdQ`y>&!=>;b9P(F9et`WKX{+_~Up3u|!96F(C zcM@bYwC(Z)ExC4?&?TR5NG5biXq&}>ExUexUV(Zzf?`*3?D&Mk#~XKQ{>HR??$Zt2 z`Hb%kTC?XkJ*R9V?dax`=gpVSIWqU3UHF>| zf5T15mCu^pjux}S*4RsBt+rA<>9a2XJzjMXxbD%Mrc5~>>9MI)}tsHfe zfmrjSU7h4{Jdd9o}k@n-9LBdgO%ouGd-V?yAjfjurVc^4SuobGx46k9BvI3jT>otF1?L!q->*RZNU8~ z-zJZy~}^|lV(>KNj5@J0vU(BXWJSJ5=u^>dJ?&E~jD+_BVe?q7j&;#b@S9JC>+-xQB8M<|xyAh`6(7F+(XJT&9JKZtcNR1n1 z--g}VfcsJ8rj65(vw@9`L#oC`*|5R+8vHhtc3VSpHEOLI7E{C4r_p9<3~@A&p<(57 zp!EOydH2)&w7MP5W=G}Vu)c*ww>m0L2YY158y#Xp2SIe{bI?sIjLiWK3!TgH`8-6s z9V_60y&T!)sF@tOkt1$aS@e~7P8j<*Xd6diarm2rX`f0zL7$B#=ySSL!oG)N)w%VA zBN!b1ib~Em!u)(eM`*tPna`j5ZvJ6>M(zgNx)G*l$=ryI8;ZA)X6M|t!CM>0gIohS z8*`zxfqOPiK&OIqFA8I96la2LzeWr)!0&cjLvuB1tr~Sx!|G|YSQN zGoSl^P@W6wpO}Vd|3?)(hp%gW9dZ`keIq*^ES~*`{CQy!bGQ!(p8Z=gLRW1LfM@?Q z^I6-l5;Kk==Y)~Z)>Fc8(R&$8IBvcf@|im$^a00e=y1>kTDw8Garun0XSQ`iGB@J% z9J3A0+o+uj0y?AZD0XLq;B)V8gpDbI&!KfA zq#I;gipb|BVScuH8@X$v-Pv&e7mTtEyccC{(Xp|v;Pd)ocILOC@ViiE!qu40(`d0Y z)NLmmw`+!mOzDT+`(gKvAl7~l^E=w?4!hZ5Y3!(PVbQD(+3Ap4I(ValYv>Ss4m3tn zo5Q&rkAhRQvpK*c_i_-IBg7n5lcVh8h#tp{q>Uq49M1?_z55IrTO1zHEBrQ~4y2>Vh(x*?exv2i1M z8~T=%+_sUsHjuRej5@{HKy0RaQNB;^qLd9Atgivm=J-;yHH6uDxT{e&HEewvEtW>R zqhT^MSU!7rKFmEDm+^PQ{Ejxe!`|BA&5lYaUGq9Lt3!4=q?Qi5(NX#w<+c^w=5Wm% zugi#5U5=!3G?*OWx(evy5Im05#t|x^1E1qY6<~4P@*Z)5RuT^0;OKX#I9Oqt>y(G;9=;Gv{lx+8V=Mjk>8}_B50T1Af)+XfzlagwOx* zQ1{c3xqmQ&>+ihX(d>7$*&Wt>EWWX$#^_HuvqO0_t3!4=q|Ga$(NQKHXVCLP=X0RP z*&Jxsie_`jF2@j)L-jb~T@{pZm@JNdg8ojIL9Z+vj@94u35P!-^Z^IB2PXn@Y_&cz@7+7-5SGPjhd-p^E8~Lanws8fW_DCjz+Vg5%~P4 z5A{Eh%kg)e$lPDd{JEj^%&>xI|Fwc=|CMHSv^yP46wS8zQ$AbTIbh0h#T9vi?p{eJ zg=v6xFRUg;P@mpITFA)9be!|t}*I>#;>97Fg_?Pu+7N_nq4OwYUYH&gm_gEKdzTPcyx(W{Hu z#!S&!WAmYfPeT$%aDyfYFTO9ov;Q`QtZcCtD>4SUd2_fJ!Y2dKr8&y8{%^O#C6Sc_a z-rYd08`#_ke2%+PXyZoVZ6rQ(Z5uUf9fN*yg~^92S=&cXGH1`f+iK?1IPlgk~E#qG|W!eugeQ4j*!0i(}a{ z`-~&-`B~ir-8XTR4#%n!x&eB1ze$B8(Cbgecf2#7!wo`z#alcd^xISN`5@pkwKx3U z6ifOHr{~@J)(zI&2-!2;lp=c@T-yd`ZNR1tY<09c9iq`eP77I|15eRp*9zXWLR=2e%mH_-i{n#xO&J*co*~M>50T7yiBf8Aj>m{*D&6!`9ki z*~Fr6VUewlR;PnAI=F@ow{ZnMwRVnT&k8s!lzKU`%aJ=d9HIXs5&DaP&|gX^$A9)I z2kqk!+c^4+;~U6>_JHp9sodh5I98Mn2T7pUp64BCA3^*1x!!L3vYvK+u*DX)U+Wu8>D4JH#W%RZ1Xkvc8wRZ zmb2_?XssH~)0qDf<#wRO)390^t&YYpLxZ(5hVi*)^&mWlf7E_*sGSnh@2XLU|AV^O zAqE{4tE1KFuo)c)&;AV>q1_vNRygjYFq<6QIbq~;-mGFfB@7z$l9JE*8KK8>(bG>) z2y+L=7(P?GuA?#JbN}AX=RSMJ<_7WV_Nko=a%~%&wE=FU%ms8tyHT`L!J4sAOwOZE z1Q|sme59QRDrELwSv-wA8tbphlRo_ua=d4JTPHF97jSOoUk~T>rl<5hjnSCUmn$|$ z-L{*S(Acx%EDo!~K^?n%LO3;_slCCc&*i;AXm0GjJLEGqH%PXTtY_KV$Q!I?p3Y=) zc5f-2nN;IQCe?5sv6J%y?rCf`qL};{&t2=MJd@FFD==X@j2&w$T;%CU5 ze)v}14xOJ@ zkXTb zqcOzLNbMOieU)zl)E@311H{!`)k!?h8laV+~L@}S8Cje0ne!?EW3G2vKuen)6* zaNPc5VSc`_OE`Y}+k4$B_ak-lGiN_%yDsw?q#KX!_+0dEz}Ah&dp;v`BV-%F+z8o5 zY}|NS;4|&ph~7rtk)m50(ry&!Y~Z~p&qrs1v|+>IYjCy(->fmj)fnq(G*}w?xgYp; zh@mmi&+vTyvxm8#NM!uGiemK-TKOFRzV+mg8XVGli`h{-Go04yC}IwA>k5gYnSCoD zh;FbsV#d+^ju}VyJMx)p<%lV?BXoGuZ9+6b)2%ApDWS&a6)W-tO&t!9K>H)YZi2=q zggD>m8|kOoe$K{1uf17^6AJeZZbI+CxYA!ZYI^3 z@H|kNn%!4*PeXSz^0$D#)tBUe&-50|!Obr*k@33=E>Hn%`Z7|t^>-VkB_@}j&org^ zoTK}+h&lWRRz_1ompidAp*7!Lon5~t2&?W zNISf{{Mp85?sPEUO#$hKpP%)!LC9y6ZS37o&s!RuOJ8F=n}=I;Q?a>Taj%J*UYg)WVCZRX2mI*->{NfIikq{8aaZ=u`o1p6dp%^O1O7V=-%U4!dV>s zJ(ZMj@D4}Vq|zB2hsHZ9tijDf?`{oV6XXfoC?xXmjzqr)|H zpvUUV3Y|b8l{t#*L6X$2}?0+lXx&nzg|< zZ6Id@oC@N-D7a(eJd$jjPtF9(ehsc&<426=EV&wTtA_J5=Koa8HEN!Q&C+ObG};Y~ z;eG~kGcbwycahHd_s?D$eg<-SnBmdpcUYS{%w~reN^5N$txX-A(;*lgDC(d-2i>*; zZ4T#hyfz;dl4g!LL|?_Z94#hCY~%=iNpJ1zIBtAfH$(Sr9IVAbC+Obccwe-jSDn@Y z8YdjjW_vp1DYFQS4ub;#G6vy5xkAKB_&!Lvs3b1S{r=R2HcH;S~fuLjExQ2uyOj~ z*w`qyp;&DV$<>fsHO6@w7E7bW(P%X^yqy8Jo0unN*3ICFj6cm>rmueXcm8K6rTY$# zM#H0RJB!ursJC|18argK!#8z&noFelrVh^OD2D)=haA1f(eG4&7RT~$b$~`q9C?ENZg)+^^Equ&$t@iC z4xtz9d`FU;Z{+QR(XPACBclE6`TTp`?UatkT>1>$jpMvq=iLo>Z&1vh`^F98Z3H^U zEh*93m_0js8`0W0{-C%aB{yw6XKuDO@@|w9<{@VT(3uz;XmVzJ4U#%rZ4JrQkXto0 zQ=`_WVY4(`lLiQrwU@1i#&A1BaWgQ9`FE7c^!-3Qr+?NMcKAP^nR7!m>hNERL$v=w z2s+fRjv|T{&kM_r4m3i$Hz?$AZvdX%OXx#ij_!TO6uSGJ259#pyBrpiBYGUMkt1jv zH}rgt9!H-+yBXR)@RX3ZIOrLn4>(|5iIp0v>^XHP!*nZA;2X#K{ z=5yb=0kdcAWH8xoLxUHbAc7UIIF+#s*0>+*?9t_Wm}LYKV8Y z(J*)#*~8FYX+Xx`p(8%S8}po8{2@svhR$0Cn=yU0cLo@vKPiA~{J>6N`7e)lkHq8R^Kq8P5W~aU9T)z_ zI8B@Ej=I^QH+FcdV=I1=maGnb`wHiDxQ>qd;yKzG9q3_v4lqWmHb)wxIhO;qas=6q znjF_RcpT8;Kz#Q5R8SL#8=+|rN0&h77LL3}CC<;GgCjO@boujxs9z_{&zHpf+0W0H z#eBo_Id4tL?#8u#e)jGL;B#qigk33t&wb;D)!T?&8!OWAoSQZTX9G;nim`$COnr^p zj-dS-WxEDvYqYu=&eNEGmoPimJPnJbVRkfH4UM6GhTYAO$b9tH;irYq0oda#k0FLf zi{D|hJL;_+dSi#|bx2l6lhc6^T{d)Jp98mZ$To-DvyvX@MT*Cxp*U33S?|f*okj=NG1qB*14!XyQM6KBw&| z$lmbhgB_ncI?qabQ~bN^ZhWDepFz3-S~tY(xi>fdtM}@R-bUPza(1FKXxd2GhUjd7 z=~**23SWcF&ZVuvHEXoG8Y4XoEz5A<*&U5GLnA=MI4I{oYj!gVJ_n(!o15cP%*nyWGte)~%4bd*L1ZRlWp4md@FZVqU3z-ErC z(vB5pa=bp~9I=-}HaTb`2j_A07Kffehb9hRmE`%%I2_)v`%rSA-4Wq!*x-nK{zObT z;Qj87&$t~ao(^vNm)-eb}DiB|I@?$Ph>LuZN#zoXJagn_B|~veuuX^%FBi56QkK792|P9<5OJPAz2-w(?PBn z){PG5bAXL2c-xBW<^Vni+_aKy4%f@^`FL7*^%oMMLo0`Da`25D*yFhIzq%2+VsW?$ z8f{Z?BlNA=;ph^M+``dyaQwixaD)U}Xy5?-8;Q?HM}H&oxwJQYx{OVX-u<+fLdHjbVPqNH^o*f8PH<9@Ez%c$>Z$bZ8j< zQOgwF|E)2Rc7KLoEWS~O=uut=XWj94zzLrHTNHEnHzxSC zeY)Y@=j_(ybOTJ!ppPGV8+)TW-S}jXdK+!$g1JEy(0QP-LD=lx7IbF2HMq%{CA0rf zNSmvkMkUYiU#H2q@&X<5Ro~>0qUBYZe+RyxEau+-G(1f>7XNrSr?+fMb2D`R*|dz_ zn1s%~NohY}DCNL}#-?5GK`&lhdK?|0sb!b>95U$OK=-#);vK@ZYolWqPYC1udClN_)z_Gw%`%y5T}O2` zSAd+puEH4nK?{xZ-wwoa`ez)6rOzUte|l&CBcj*##c<1`-SDutci7zyWt7$$JLI+w z-s<3-4vW!28#=Jhf!jIYt`)auW&1cqlV*-EMCV?P(&f-i4&2AFcrSEQ#gEY0gI>0O zN9g6f#nB}k2X=(+5@^8ZgX0A4`J7ugx&#{L8+nIHXK*m*gXs3)#W3GUd`^2)y7}4j znYbIC&kMTw8SuH^meO}_^yWt2xDhs`1U@hI=|Q+?cUatx zn%SW>cF0>;c&meRI)s)E6m?LeqvCUR-t3Uh4FAHJ9n8a99h}o)G&(@kq4^we*9x&YLKN*>j$m_Smm``S zdB4haWV=c-IeZK)dmONhBWKXd`+X|i1pS^kL9fgXhZ~^tCKZFhVVIxe^z7}8SH=0c z<8w?mW}VW_&*^ON)GmGY)3a~g@Y8c)ZuGlSP~!&YZN#<>$=Z0|oM>&Z&PJM?6=MS= z8-=eyQsXwGh(P%L=+Wm}iZieDzNIa&WM;=SRFZg8o8A0ly zmPf1MVf8yK%^mta7Ok;EYU}V;M-fPKPDk0&L5&XD&{6R@@)Z5KlyjVkY>t?71ec@S zuR=RHdXochR!JU*4>@QCjk-8;6Gt~eefE=o8Ypu0UPvcaMGb)3D?(fNMI zy7BqTF2H2HuHslIm;Fb`5S1ir{K6 z=Yh`CnEw(sbFO+C>eOs=H0*|kw=*94r#V43yc*+X7`zP1Vd=fZkW z*&wVAG8>Kga30-`Jg|=Uqwm zyhoRAKyw2&ZuH4>u+NlZ&F&+X%>F|q z(Qsd>Y<6F!qdxToIOeOoG|;&ClXRFj;DyB6A)l8Vu6?qlaC&1=S_nG)$1Rkeg3;3w z`s%a;Yg$5Qiz8ch{)n)@M;LqTx@+EPlZs?;jOBAmH-mhWI?1Uk>CCYiHXuP?Mq}pt@utU6 z%fmY!=4smMcU0{Ty|qJY?2x<;&g!r^9kinZ8y&LGq1YTThh}!HM3*D(SK&>Lv{|L^ z9Nn{Xp?i6a=tVevt z?HY`&QOwRKNT!C?r%|&sOpb=#&}jEFhPxR$C{ zk6?H_*v4tA-%+pLeH&$=&d0 zgT8gcpACMgt5mfjaRz)x$EBO?2T8u)4^_U3Toa!?nZ9icw@(BKRx?xDIk098#nq* zDbU-%TT(=8BV-#DXCvK4fgKxgCkpm8=r$DEu2I?=R#(F{YRvxxn>ov#M$OW&I2u+% zqt(tB?q*EjWq3Y+>S+IyV9dPu=_mY8Ba^v5PoBpohb7d(@%X#8Q^Xd-qsi__XN2Vo zy|u%e9c(DgdmWtBVR1TiqoeRS@)VuAIb1Ubv^iqV5qddXD+h5oVk-wWIdCJ#4PWj; zj^5)~?6<0*HV$`HrLZ`<3Hod_K?jFpb>5`HCLDMCRB*p@cL3Vc51K4`O5CvQae zxl1>8=$bbk?dE6XZusfhw{H09`3Y%y2Av!JWU$|qg1n88Jony4&Dtn;qvXvf&pRYL z8?a;JL@+rMUjw;xbz7r!HTYJIA)bb7(@^r}x})L!jEDS5xXvvOj{Ssj-1 zLKJfpMh84COn7!LQpka94vWjdpA&NTz56~6>T%?ogxTWg5@<(g_rwI+H6?*F!su|w z3AFCl!ERC7T-V{bV|C1)vAe;Y4T9;}H*Ua_LC)K#oeP$Cxl^uTGB!}=OneQJYB0?j zWv@n?siAoq3Y$%ihJnlBPYU^>@PW2J393+!+}gAlZPv2Hu83ZH;md zO6h8}m>RODp<5bNN5kr81Uuv2l|eauJ=D#Z!psOp#x^2?)h`I2(_42;8-JRyO#i_+ z)1%$;XfZr2en-vi&{{j>#tz==uvi^sO9wVOa6^alIgX8UG~BiVcdZCEhnG2`%R%?6 zV3VV76_u3B0uzL*0z=W`^Qqs6ZxPCLoJ1e)^L1L&4klew^viZh6=Z4~yS1#_fiZ zH|Xggn{IUOMrz$4=EhsQbiur-L~T)G-ut46D*p<5cx(NMRY%!HpY)Xf-gW*D3df?)Nnh-K!B;Cg(b5zED& zk1;*kEstP${C)il(c*X1nmgpy4ymz&-@;g zg}dRW=dN{wm>WKO_Kh3Iy@U2 zcpFO1)?i(Y;yjQyHRL`G&C;kj8ny=o>kF#Bqaxe3SV zk1=K2-%ZEk{{N&%<@{U4Z~e=ue2?L#N2}#wH9WlCap7-NTUxa{`e{$0iFta=&(pDT`r&CqCR&luunjPo** zkulK8pj3avebP?`^Ca_g2KY3#$8gglSRTgM!)|!g{SMvkkXt+W#*T38i{cLNbR2`z z4%*Z~osPojNKpszIYQ9E*&ME!@zg*&D=trtU_!F{NwW@cUBItwG

MrAI`Dn?pY<`nE}#rP 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%0A`V=8l|hp;$CoH<;29B{YFP1@uD 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 literal 0 HcmV?d00001 diff --git a/src/tests/dan.c b/src/tests/dan.c new file mode 100644 index 0000000..3488768 --- /dev/null +++ b/src/tests/dan.c @@ -0,0 +1,111 @@ + +#include +#include "../modules/dv/producer_libdv.h" +#include "../modules/dv/consumer_libdv.h" +//#include "../modules/sdl/consumer_sdl.h" + +#include + +int main( int argc, char **argv ) +{ + char temp[ 132 ]; + char *file1 = NULL; + char *file2 = NULL; + +// mlt_factory_init( "../modules" ); + mlt_pool_init( ); + + if ( argc >= 2 ) + file1 = argv[ 1 ]; + if ( argc >= 3 ) + file2 = argv[ 2 ]; + + // Start the consumer... + //int vstd = mlt_video_standard_ntsc; + //mlt_consumer consumer = mlt_factory_consumer( "bluefish", &vstd ); + //mlt_consumer consumer = mlt_factory_consumer( "sdl", NULL ); + mlt_consumer consumer = consumer_libdv_init( NULL ); + + // Create the producer(s) + //mlt_producer dv1 = mlt_factory_producer( "libdv", file1 ); + mlt_producer dv1 = producer_libdv_init( file1 ); + //mlt_producer_set_in_and_out( dv1, 0, 5 ); + + mlt_producer dv2;// = mlt_factory_producer( "libdv", file2 ); + //mlt_producer_set_in_and_out( dv2, 10.0, 30.0 ); + +#if 1 + // Connect the consumer to the producer + mlt_consumer_connect( consumer, mlt_producer_service( dv1 ) ); + + // Do stuff until we're told otherwise... + mlt_consumer_start( consumer ); + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + mlt_consumer_stop( consumer ); + mlt_consumer_close( consumer ); + mlt_pool_close( ); + return 0; +#endif + + //mlt_producer dv1 = producer_pixbuf_init( file1 ); + //mlt_producer dv2 = producer_libdv_init( file2 ); + //mlt_producer dv2 = mlt_factory_producer( "pixbuf", file2 ); +#if 0 + mlt_producer title = mlt_factory_producer( "pango", NULL ); //"Mutton Lettuce Tomato" ); + mlt_properties_set_int( mlt_producer_properties( title ), "video_standard", mlt_video_standard_ntsc ); + mlt_properties_set( mlt_producer_properties( title ), "font", "Sans Bold 36" ); + mlt_properties_set( mlt_producer_properties( title ), "text", "Mutton Lettuce\nTomato" ); + mlt_properties_set_int( mlt_producer_properties( title ), "bgcolor", 0x0000007f ); + mlt_properties_set_int( mlt_producer_properties( title ), "pad", 8 ); + mlt_properties_set_int( mlt_producer_properties( title ), "align", 1 ); + mlt_properties_set_int( mlt_producer_properties( title ), "x", 20 ); + mlt_properties_set_int( mlt_producer_properties( title ), "y", 40 ); +#endif + + mlt_playlist playlist1 = mlt_playlist_init(); + mlt_playlist_append( playlist1, dv1 ); + mlt_playlist_blank( playlist1, 1.0 ); + + mlt_playlist playlist2 = mlt_playlist_init(); + mlt_playlist_blank( playlist2, 3.0 ); + mlt_playlist_append( playlist2, dv2 ); + + // Register producers(s) with a multitrack object + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist1 ), 0 ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist2 ), 1 ); + + // Create a filter and associate it to track 0 + //mlt_filter filter = mlt_factory_filter( "deinterlace", NULL ); + //mlt_filter_connect( filter, mlt_multitrack_service( multitrack ), 0 ); + //mlt_filter_set_in_and_out( filter, 0, 1000 ); + + // Define a transition + mlt_transition transition = mlt_factory_transition( "luma", NULL ); + mlt_transition_connect( transition, mlt_multitrack_service( multitrack ), 0, 1 ); + mlt_transition_set_in_and_out( transition, 3.0, 5.0 ); + //mlt_properties_set( mlt_transition_properties( transition ), "filename", "clock.pgm" ); + mlt_properties_set_double( mlt_transition_properties( transition ), "softness", 0.1 ); + + // Buy a tractor and connect it to the filter + mlt_tractor tractor = mlt_tractor_init( ); + mlt_tractor_connect( tractor, mlt_transition_service( transition ) ); + + // Connect the tractor to the consumer + mlt_consumer_connect( consumer, mlt_tractor_service( tractor ) ); + + // Do stuff until we're told otherwise... + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + + // Close everything... + mlt_consumer_close( consumer ); + mlt_tractor_close( tractor ); + mlt_transition_close( transition ); + mlt_multitrack_close( multitrack ); + mlt_producer_close( dv1 ); + mlt_producer_close( dv2 ); + + return 0; +} diff --git a/src/tests/dissolve.c b/src/tests/dissolve.c new file mode 100644 index 0000000..d9ce2d7 --- /dev/null +++ b/src/tests/dissolve.c @@ -0,0 +1,71 @@ + +#include + +#include + +int main( int argc, char **argv ) +{ + char temp[ 132 ]; + char *file1 = NULL; + char *file2 = NULL; + + mlt_factory_init( "../modules" ); + + if ( argc < 3 ) + { + fprintf( stderr, "usage: dissolve file1.mpeg file2.mpeg\n" ); + return 1; + } + else + { + file1 = argv[ 1 ]; + file2 = argv[ 2 ]; + } + + // Start the consumer... + mlt_consumer consumer = mlt_factory_consumer( "sdl", "PAL" ); + + // Create the producer(s) + mlt_producer dv1 = mlt_factory_producer( "mcmpeg", file1 ); + mlt_producer dv2 = mlt_factory_producer( "mcmpeg", file2 ); + + mlt_playlist playlist1 = mlt_playlist_init(); + mlt_playlist_append_io( playlist1, dv1, 0.0, 5.0 ); + + mlt_playlist playlist2 = mlt_playlist_init(); + mlt_playlist_blank( playlist2, 2.9 ); + mlt_playlist_append( playlist2, dv2 ); + + // Register producers(s) with a multitrack object + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist1 ), 0 ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist2 ), 1 ); + + // Define a transition + mlt_transition transition = mlt_factory_transition( "luma", NULL ); + mlt_transition_connect( transition, mlt_multitrack_service( multitrack ), 0, 1 ); + mlt_transition_set_in_and_out( transition, 3.0, 5.0 ); + + // Buy a tractor and connect it to the filter + mlt_tractor tractor = mlt_tractor_init( ); + mlt_tractor_connect( tractor, mlt_transition_service( transition ) ); + + // Connect the tractor to the consumer + mlt_consumer_connect( consumer, mlt_tractor_service( tractor ) ); + + // Do stuff until we're told otherwise... + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + + // Close everything... + mlt_consumer_close( consumer ); + mlt_tractor_close( tractor ); + mlt_transition_close( transition ); + mlt_multitrack_close( multitrack ); + mlt_playlist_close( playlist1 ); + mlt_playlist_close( playlist2 ); + mlt_producer_close( dv1 ); + mlt_producer_close( dv2 ); + + return 0; +} diff --git a/src/tests/hello.c b/src/tests/hello.c new file mode 100644 index 0000000..4914304 --- /dev/null +++ b/src/tests/hello.c @@ -0,0 +1,136 @@ +#include +#include +#include + +mlt_producer create_playlist( int argc, char **argv ) +{ + // We're creating a playlist here + mlt_playlist playlist = mlt_playlist_init( ); + + // We need the playlist properties to ensure clean up + mlt_properties properties = mlt_playlist_properties( playlist ); + + // Loop through each of the arguments + int i = 0; + for ( i = 1; i < argc; i ++ ) + { + // Definie the unique key + char key[ 256 ]; + + // Create the producer + mlt_producer producer = mlt_factory_producer( NULL, argv[ i ] ); + + // Add it to the playlist + mlt_playlist_append( playlist, producer ); + + // Create a unique key for this producer + sprintf( key, "producer%d", i ); + + // Now we need to ensure the producers are destroyed + mlt_properties_set_data( properties, key, producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + + // Return the playlist as a producer + return mlt_playlist_producer( playlist ); +} + +mlt_producer create_tracks( int argc, char **argv ) +{ + // Create the field + mlt_field field = mlt_field_init( ); + + // Obtain the multitrack + mlt_multitrack multitrack = mlt_field_multitrack( field ); + + // Obtain the tractor + mlt_tractor tractor = mlt_field_tractor( field ); + + // Obtain a composite transition + mlt_transition transition = mlt_factory_transition( "composite", "10%,10%:15%x15%" ); + + // Create track 0 + mlt_producer track0 = create_playlist( argc, argv ); + + // Get the length of track0 + mlt_position length = mlt_producer_get_playtime( track0 ); + + // Create the watermark track + mlt_producer track1 = mlt_factory_producer( "fezzik", "pango:" ); + + // Get the properties of track1 + mlt_properties properties = mlt_producer_properties( track1 ); + + // Set the properties + mlt_properties_set( properties, "text", "Hello\nWorld" ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + mlt_properties_set_position( properties, "length", length ); + + // Now set the properties on the transition + properties = mlt_transition_properties( transition ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + + // Add our tracks to the multitrack + mlt_multitrack_connect( multitrack, track0, 0 ); + mlt_multitrack_connect( multitrack, track1, 1 ); + + // Now plant the transition + mlt_field_plant_transition( field, transition, 0, 1 ); + + // Now set the properties on the transition + properties = mlt_tractor_properties( tractor ); + + // Ensure clean up and set properties correctly + mlt_properties_set_data( properties, "multitrack", multitrack, 0, ( mlt_destructor )mlt_multitrack_close, NULL ); + mlt_properties_set_data( properties, "field", field, 0, ( mlt_destructor )mlt_field_close, NULL ); + mlt_properties_set_data( properties, "track0", track0, 0, ( mlt_destructor )mlt_producer_close, NULL ); + mlt_properties_set_data( properties, "track1", track1, 0, ( mlt_destructor )mlt_producer_close, NULL ); + mlt_properties_set_data( properties, "transition", transition, 0, ( mlt_destructor )mlt_transition_close, NULL ); + mlt_properties_set_position( properties, "length", length ); + mlt_properties_set_position( properties, "out", length - 1 ); + + // Return the tractor + return mlt_tractor_producer( tractor ); +} + +int main( int argc, char **argv ) +{ + // Initialise the factory + if ( mlt_factory_init( NULL ) == 0 ) + { + // Create the default consumer + mlt_consumer hello = mlt_factory_consumer( NULL, NULL ); + + // Create a producer using the default normalising selecter + mlt_producer world = create_tracks( argc, argv ); + + // Connect the producer to the consumer + mlt_consumer_connect( hello, mlt_producer_service( world ) ); + + // Start the consumer + mlt_consumer_start( hello ); + + // Wait for the consumer to terminate + while( !mlt_consumer_is_stopped( hello ) ) + sleep( 1 ); + + // Close the consumer + mlt_consumer_close( hello ); + + // Close the producer + mlt_producer_close( world ); + + // Close the factory + mlt_factory_close( ); + } + else + { + // Report an error during initialisation + fprintf( stderr, "Unable to locate factory modules\n" ); + } + + // End of program + return 0; +} + diff --git a/src/tests/io.c b/src/tests/io.c new file mode 100644 index 0000000..fc40238 --- /dev/null +++ b/src/tests/io.c @@ -0,0 +1,208 @@ +/* + * io.c -- dv1394d client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "io.h" + +char *chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +char *trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +char *strip_quotes( char *input ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, '\"' ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == '\"' ) + strcpy( input, input + 1 ); + } + return input; +} + +char *get_string( char *output, int maxlength, char *use ) +{ + char *value = NULL; + strcpy( output, use ); + if ( trim( chomp( fgets( output, maxlength, stdin ) ) ) != NULL ) + { + if ( !strcmp( output, "" ) ) + strcpy( output, use ); + value = output; + } + return value; +} + +int *get_int( int *output, int use ) +{ + int *value = NULL; + char temp[ 132 ]; + *output = use; + if ( trim( chomp( fgets( temp, 132, stdin ) ) ) != NULL ) + { + if ( strcmp( temp, "" ) ) + *output = atoi( temp ); + value = output; + } + return value; +} + +/** This stores the previous settings +*/ + +static struct termios oldtty; +static int mode = 0; + +/** This is called automatically on application exit to restore the + previous tty settings. +*/ + +void term_exit(void) +{ + if ( mode == 1 ) + { + tcsetattr( 0, TCSANOW, &oldtty ); + mode = 0; + } +} + +/** Init terminal so that we can grab keys without blocking. +*/ + +void term_init( ) +{ + struct termios tty; + + tcgetattr( 0, &tty ); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[ VMIN ] = 1; + tty.c_cc[ VTIME ] = 0; + + tcsetattr( 0, TCSANOW, &tty ); + + mode = 1; + + atexit( term_exit ); +} + +/** Check for a keypress without blocking infinitely. + Returns: ASCII value of keypress or -1 if no keypress detected. +*/ + +int term_read( ) +{ + int n = 1; + unsigned char ch; + struct timeval tv; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds ); + tv.tv_sec = 0; + tv.tv_usec = 250; + n = select( 1, &rfds, NULL, NULL, &tv ); + if (n > 0) + { + n = read( 0, &ch, 1 ); + tcflush( 0, TCIFLUSH ); + if (n == 1) + return ch; + return n; + } + return -1; +} + +char get_keypress( ) +{ + char value = '\0'; + int pressed = 0; + + fflush( stdout ); + + term_init( ); + while ( ( pressed = term_read( ) ) == -1 ) ; + term_exit( ); + + value = (char)pressed; + + return value; +} + +void wait_for_any_key( char *message ) +{ + if ( message == NULL ) + printf( "Press any key to continue: " ); + else + printf( "%s", message ); + + get_keypress( ); + + printf( "\n\n" ); +} + +void beep( ) +{ + printf( "%c", 7 ); + fflush( stdout ); +} diff --git a/src/tests/io.h b/src/tests/io.h new file mode 100644 index 0000000..2255215 --- /dev/null +++ b/src/tests/io.h @@ -0,0 +1,45 @@ +/* + * io.h -- dv1394d client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * 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 2 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_IO_H_ +#define _DEMO_IO_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern char *chomp( char * ); +extern char *trim( char * ); +extern char *strip_quotes( char * ); +extern char *get_string( char *, int, char * ); +extern int *get_int( int *, int ); +extern void term_init( ); +extern int term_read( ); +extern void term_exit( ); +extern char get_keypress( ); +extern void wait_for_any_key( char * ); +extern void beep( ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tests/luma.c b/src/tests/luma.c new file mode 100644 index 0000000..dabb878 --- /dev/null +++ b/src/tests/luma.c @@ -0,0 +1,75 @@ + +#include + +#include + +int main( int argc, char **argv ) +{ + char temp[ 132 ]; + char *file1 = NULL; + char *file2 = NULL; + char *wipe = NULL; + + mlt_factory_init( "../modules" ); + + if ( argc < 4 ) + { + fprintf( stderr, "usage: luma file1.mpeg file2.mpeg wipe.pgm\n" ); + return 1; + } + else + { + file1 = argv[ 1 ]; + file2 = argv[ 2 ]; + wipe = argv[ 3 ]; + } + + // Start the consumer... + mlt_consumer consumer = mlt_factory_consumer( "bluefish", "NTSC" ); + + // Create the producer(s) + mlt_producer dv1 = mlt_factory_producer( "mcmpeg", file1 ); + mlt_producer dv2 = mlt_factory_producer( "mcmpeg", file2 ); + + mlt_playlist playlist1 = mlt_playlist_init(); + mlt_playlist_append_io( playlist1, dv1, 0.0, 5.0 ); + + mlt_playlist playlist2 = mlt_playlist_init(); + mlt_playlist_blank( playlist2, 2.9 ); + mlt_playlist_append( playlist2, dv2 ); + + // Register producers(s) with a multitrack object + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist1 ), 0 ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( playlist2 ), 1 ); + + // Define a transition + mlt_transition transition = mlt_factory_transition( "luma", wipe ); + mlt_properties_set( mlt_transition_properties( transition ), "filename", wipe ); + mlt_properties_set_double( mlt_transition_properties( transition ), "softness", 0.1 ); + mlt_transition_connect( transition, mlt_multitrack_service( multitrack ), 0, 1 ); + mlt_transition_set_in_and_out( transition, 3.0, 5.0 ); + + // Buy a tractor and connect it to the filter + mlt_tractor tractor = mlt_tractor_init( ); + mlt_tractor_connect( tractor, mlt_transition_service( transition ) ); + + // Connect the tractor to the consumer + mlt_consumer_connect( consumer, mlt_tractor_service( tractor ) ); + + // Do stuff until we're told otherwise... + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + + // Close everything... + mlt_consumer_close( consumer ); + mlt_tractor_close( tractor ); + mlt_transition_close( transition ); + mlt_multitrack_close( multitrack ); + mlt_playlist_close( playlist1 ); + mlt_playlist_close( playlist2 ); + mlt_producer_close( dv1 ); + mlt_producer_close( dv2 ); + + return 0; +} diff --git a/src/tests/pango.c b/src/tests/pango.c new file mode 100644 index 0000000..fb5e19a --- /dev/null +++ b/src/tests/pango.c @@ -0,0 +1,77 @@ + +#include + +#include + +int main( int argc, char **argv ) +{ + char temp[ 132 ]; + char *file1 = NULL; + char *text = NULL; + + mlt_factory_init( "../modules" ); + + if ( argc < 3 ) + { + fprintf( stderr, "usage: pango file.mpeg text_to_display\n" ); + return 1; + } + else + { + file1 = argv[ 1 ]; + text = argv[ 2 ]; + } + + // Start the consumer... + mlt_consumer consumer = mlt_factory_consumer( "bluefish", "NTSC" ); + + // Create the producer(s) + mlt_playlist pl1 = mlt_playlist_init(); + mlt_producer dv1 = mlt_factory_producer( "mcmpeg", file1 ); + mlt_playlist_append( pl1, dv1 ); + + mlt_playlist pl2 = mlt_playlist_init(); + mlt_producer title = mlt_factory_producer( "pango", NULL ); //"Mutton Lettuce Tomato" ); + mlt_playlist_append( pl2, title ); + mlt_properties_set( mlt_producer_properties( title ), "font", "Sans Bold 36" ); + mlt_properties_set( mlt_producer_properties( title ), "text", text ); + mlt_properties_set_int( mlt_producer_properties( title ), "bgcolor", 0x0000007f ); + mlt_properties_set_int( mlt_producer_properties( title ), "pad", 8 ); + mlt_properties_set_int( mlt_producer_properties( title ), "align", 1 ); + mlt_properties_set_int( mlt_producer_properties( title ), "x", 200 ); + mlt_properties_set_int( mlt_producer_properties( title ), "y", 40 ); + mlt_properties_set_double( mlt_producer_properties( title ), "mix", 0.8 ); + + // Register producers(s) with a multitrack object + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( pl1 ), 0 ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( pl2 ), 1 ); + + // Define a transition + mlt_transition transition = mlt_factory_transition( "composite", NULL ); + mlt_transition_connect( transition, mlt_multitrack_service( multitrack ), 0, 1 ); + mlt_transition_set_in_and_out( transition, 0.0, 9999.0 ); + + // Buy a tractor and connect it to the filter + mlt_tractor tractor = mlt_tractor_init( ); + mlt_tractor_connect( tractor, mlt_transition_service( transition ) ); + + // Connect the tractor to the consumer + mlt_consumer_connect( consumer, mlt_tractor_service( tractor ) ); + + // Do stuff until we're told otherwise... + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + + // Close everything... + mlt_consumer_close( consumer ); + mlt_tractor_close( tractor ); + mlt_transition_close( transition ); + mlt_multitrack_close( multitrack ); + mlt_playlist_close( pl1 ); + mlt_playlist_close( pl2 ); + mlt_producer_close( dv1 ); + mlt_producer_close( title ); + + return 0; +} diff --git a/src/tests/pixbuf.c b/src/tests/pixbuf.c new file mode 100644 index 0000000..fd0f0b6 --- /dev/null +++ b/src/tests/pixbuf.c @@ -0,0 +1,72 @@ + +#include + +#include + +int main( int argc, char **argv ) +{ + char temp[ 132 ]; + char *file1 = NULL; + char *file2 = NULL; + + mlt_factory_init( "../modules" ); + + if ( argc < 3 ) + { + fprintf( stderr, "usage: pixbuf file.mpeg file.{png,jpg,etc}\n" ); + return 1; + } + else + { + file1 = argv[ 1 ]; + file2 = argv[ 2 ]; + } + + // Start the consumer... + mlt_consumer consumer = mlt_factory_consumer( "sdl", "PAL" ); + + // Create the producer(s) + mlt_playlist pl1 = mlt_playlist_init(); + mlt_producer dv1 = mlt_factory_producer( "mcmpeg", file1 ); + mlt_playlist_append( pl1, dv1 ); + + mlt_playlist pl2 = mlt_playlist_init(); + mlt_producer overlay = mlt_factory_producer( "pixbuf", file2 ); + mlt_playlist_append( pl2, overlay ); + mlt_properties_set_int( mlt_producer_properties( overlay ), "x", 600 ); + mlt_properties_set_int( mlt_producer_properties( overlay ), "y", 460 ); + mlt_properties_set_double( mlt_producer_properties( overlay ), "mix", 0.8 ); + + // Register producers(s) with a multitrack object + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( pl1 ), 0 ); + mlt_multitrack_connect( multitrack, mlt_playlist_producer( pl2 ), 1 ); + + // Define a transition + mlt_transition transition = mlt_factory_transition( "composite", NULL ); + mlt_transition_connect( transition, mlt_multitrack_service( multitrack ), 0, 1 ); + mlt_transition_set_in_and_out( transition, 0.0, 9999.0 ); + + // Buy a tractor and connect it to the filter + mlt_tractor tractor = mlt_tractor_init( ); + mlt_tractor_connect( tractor, mlt_transition_service( transition ) ); + + // Connect the tractor to the consumer + mlt_consumer_connect( consumer, mlt_tractor_service( tractor ) ); + + // Do stuff until we're told otherwise... + fprintf( stderr, "Press return to continue\n" ); + fgets( temp, 132, stdin ); + + // Close everything... + mlt_consumer_close( consumer ); + mlt_tractor_close( tractor ); + mlt_transition_close( transition ); + mlt_multitrack_close( multitrack ); + mlt_playlist_close( pl1 ); + mlt_playlist_close( pl2 ); + mlt_producer_close( dv1 ); + mlt_producer_close( overlay ); + + return 0; +} diff --git a/src/tests/setenv b/src/tests/setenv new file mode 100644 index 0000000..44cee49 --- /dev/null +++ b/src/tests/setenv @@ -0,0 +1,7 @@ +export MLT_REPOSITORY=`pwd`/../modules + +export LD_LIBRARY_PATH=`pwd`/../framework:\ +`pwd`/../modules/bluefish:\ +`pwd`/../../../bluefish/lib:\ +`pwd`/../../../mpeg_sdk_demo/bin:\ +`pwd`/../../../dv_sdk diff --git a/src/tests/test.png b/src/tests/test.png new file mode 100644 index 0000000000000000000000000000000000000000..b3fca64519876231ae9a060fc194a0ae48244fd8 GIT binary patch literal 1352 zcmV-O1-JT%P)Q2Ue;Z@vP-uW*q~F{#Qkvx3{iucMMBURf(ro( z64WDz%3wsI1WiZ?IwmqfE=>#|LBSY)C;}3QD2(82BPa;o3Yo5hZNR#99qZa2f1I_H ztzGq(@2_*d%lmxq_4@>AL{Ystz*E3%(8@tLfC8W$@PYO#i2cA(x63(@PM;VBR0zBZ zsDLq@BESzc0Gr${XE4` z1|ntJ0SGg&TT#7n-^VOm1LP#zVloOEUoK*PO)iZ~?3fIKZ+`3pz?LmDX}oDv8-aU) zg>j&wdadZGpWHfwAkV6w>%po_8W!8RaAkmwZr!a5X4uFyCr$Q5SsnWjit2q9*fZMd z9E-%}x?(0zFayy1bCC8MVV<6D1t1cIGrt5mcsjs$=YoX7S~4|NZkO}C?5JM08kjs9 zFcj7}?&(9Ruwb_tC@nHlV3Wzp&@Gb$l-iBdRA*89h+Zx?15rA=BZ+GM_Ia94Nx-DU z(X0%KbuZZY^sTXc{HBAN>MR7%+aKlCO;@>aMekmAo1O=n2FE>pM54M?)+~wIN386A z!@<`ZO3BHLyL%FV1ezl@Qc-H=;5*}ad`cE~jWtnOW@f!=XG>i%7PB-03LpgRJkrgK zm8~@Hzs_%eh2nF2kNN2eMq|GY&|??@B#T+_$(pf@D>UNoi*oREFSDj)W6PES@O6cG zzQISAKSH)eVoO3yTQ2rfx8)jxL$RwUH8HWo#NqD)3BJ?ZZddsf3AC#Ki)PvwS7_vV zPlQ>kTG{mZ3g)eCC{RK3XAoU0l3r}8U_Op9YZb^ z?EobOdXH}F3^OpOpR~&#;idQ5Is3;g0LqGtY+F%+HA@-+eW)sv4_+%Ctr4^j0XYY7 zvscfk`zD&Hz9%ETYG6=f>HAk{KCgF|3B^V}tSceIBt}3_pAVuXtq6#5WJmQffO&tm z3NmN96{A5=qhxd9Y#-eLJ>Zcj9P${1up%f)^|VT~g#0ouC4e0HKI z1xwRFEb)1o{1R{k=;`PVvwm+E02wC1j`|YH9k<(7IHFO%^%|`mg83iL;5+;<%SgDg_rhe<@N zjeW(caw9`Q-F)B%2Tu0h0Ud9@ed)`Ro)sFm@%;ckPgB@Yz03nV1IVX- z3Q~}3pt8)2#Vk?#s1;cf6pb= zFFs*IeR9kQEtgscSoCgtywFof2zc1-a<(K6j;NZUx5y5_3^1j_LjBx4${i-c5sh7k z{d{z&n{Z@QSSK*Y?Q)(RK9RIc71cWmv@b#AgWfOAMnO12IuO9Oz-+h6c{ADMXzx-P zSOlyB%F|JXKpPSh.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) $(NAME) + +install: all + install -m 755 $(TARGET) $(DESTDIR)$(libdir) + ln -sf $(TARGET) $(DESTDIR)$(libdir)/$(NAME) + mkdir -p "$(DESTDIR)$(prefix)/include/mlt/valerie" + install -m 644 $(INCS) "$(DESTDIR)$(prefix)/include/mlt/valerie" + +uninstall: + rm -f "$(DESTDIR)$(libdir)/$(TARGET)" + rm -f "$(DESTDIR)$(libdir)/$(NAME)" + rm -rf "$(DESTDIR)$(prefix)/include/mlt/valerie" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/valerie/configure b/src/valerie/configure new file mode 100755 index 0000000..462e2de --- /dev/null +++ b/src/valerie/configure @@ -0,0 +1,2 @@ +#!/bin/sh +echo "valerie -I$prefix/include/mlt -D_REENTRANT -L$libdir -lvalerie" >> ../../packages.dat diff --git a/src/valerie/valerie.c b/src/valerie/valerie.c new file mode 100644 index 0000000..5256f7f --- /dev/null +++ b/src/valerie/valerie.c @@ -0,0 +1,969 @@ +/* + * valerie.c -- High Level Client API for miracle + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include +#include +#include + +/* Application header files */ +#include "valerie.h" +#include "valerie_tokeniser.h" +#include "valerie_util.h" + +/** Initialise the valerie structure. +*/ + +valerie valerie_init( valerie_parser parser ) +{ + valerie this = malloc( sizeof( valerie_t ) ); + if ( this != NULL ) + { + memset( this, 0, sizeof( valerie_t ) ); + this->parser = parser; + } + return this; +} + +/** Set the response structure associated to the last command. +*/ + +static void valerie_set_last_response( valerie this, valerie_response response ) +{ + if ( this != NULL ) + { + if ( this->last_response != NULL ) + valerie_response_close( this->last_response ); + this->last_response = response; + } +} + +/** Connect to the parser. +*/ + +valerie_error_code valerie_connect( valerie this ) +{ + valerie_error_code error = valerie_server_unavailable; + valerie_response response = valerie_parser_connect( this->parser ); + if ( response != NULL ) + { + valerie_set_last_response( this, response ); + if ( valerie_response_get_error_code( response ) == 100 ) + error = valerie_ok; + } + return error; +} + +/** Interpret a non-context sensitive error code. +*/ + +static valerie_error_code valerie_get_error_code( valerie this, valerie_response response ) +{ + valerie_error_code error = valerie_server_unavailable; + switch( valerie_response_get_error_code( response ) ) + { + case -1: + error = valerie_server_unavailable; + break; + case -2: + error = valerie_no_response; + break; + case 200: + case 201: + case 202: + error = valerie_ok; + break; + case 400: + error = valerie_invalid_command; + break; + case 401: + error = valerie_server_timeout; + break; + case 402: + error = valerie_missing_argument; + break; + case 403: + error = valerie_unit_unavailable; + break; + case 404: + error = valerie_invalid_file; + break; + default: + case 500: + error = valerie_unknown_error; + break; + } + return error; +} + +/** Execute a command. +*/ + +valerie_error_code valerie_execute( valerie this, size_t size, char *format, ... ) +{ + valerie_error_code error = valerie_server_unavailable; + char *command = malloc( size ); + if ( this != NULL && command != NULL ) + { + va_list list; + va_start( list, format ); + if ( vsnprintf( command, size, format, list ) != 0 ) + { + valerie_response response = valerie_parser_execute( this->parser, command ); + valerie_set_last_response( this, response ); + error = valerie_get_error_code( this, response ); + } + else + { + error = valerie_invalid_command; + } + va_end( list ); + } + else + { + error = valerie_malloc_failed; + } + free( command ); + return error; +} + +/** Execute a command. +*/ + +valerie_error_code valerie_receive( valerie this, char *doc, size_t size, char *format, ... ) +{ + valerie_error_code error = valerie_server_unavailable; + char *command = malloc( size ); + if ( this != NULL && command != NULL ) + { + va_list list; + va_start( list, format ); + if ( vsnprintf( command, size, format, list ) != 0 ) + { + valerie_response response = valerie_parser_received( this->parser, command, doc ); + valerie_set_last_response( this, response ); + error = valerie_get_error_code( this, response ); + } + else + { + error = valerie_invalid_command; + } + va_end( list ); + } + else + { + error = valerie_malloc_failed; + } + free( command ); + return error; +} + +/** Execute a command. +*/ + +valerie_error_code valerie_push( valerie this, mlt_service service, size_t size, char *format, ... ) +{ + valerie_error_code error = valerie_server_unavailable; + char *command = malloc( size ); + if ( this != NULL && command != NULL ) + { + va_list list; + va_start( list, format ); + if ( vsnprintf( command, size, format, list ) != 0 ) + { + valerie_response response = valerie_parser_push( this->parser, command, service ); + valerie_set_last_response( this, response ); + error = valerie_get_error_code( this, response ); + } + else + { + error = valerie_invalid_command; + } + va_end( list ); + } + else + { + error = valerie_malloc_failed; + } + free( command ); + return error; +} + +/** Set a global property. +*/ + +valerie_error_code valerie_set( valerie this, char *property, char *value ) +{ + return valerie_execute( this, 1024, "SET %s=%s", property, value ); +} + +/** Get a global property. +*/ + +valerie_error_code valerie_get( valerie this, char *property, char *value, int length ) +{ + valerie_error_code error = valerie_execute( this, 1024, "GET %s", property ); + if ( error == valerie_ok ) + { + valerie_response response = valerie_get_last_response( this ); + strncpy( value, valerie_response_get_line( response, 1 ), length ); + } + return error; +} + +/** Run a script. +*/ + +valerie_error_code valerie_run( valerie this, char *file ) +{ + return valerie_execute( this, 10240, "RUN \"%s\"", file ); +} + +/** Add a unit. +*/ + +valerie_error_code valerie_unit_add( valerie this, char *guid, int *unit ) +{ + valerie_error_code error = valerie_execute( this, 1024, "UADD %s", guid ); + if ( error == valerie_ok ) + { + int length = valerie_response_count( this->last_response ); + char *line = valerie_response_get_line( this->last_response, length - 1 ); + if ( line == NULL || sscanf( line, "U%d", unit ) != 1 ) + error = valerie_unit_creation_failed; + } + else + { + if ( error == valerie_unknown_error ) + error = valerie_unit_creation_failed; + } + return error; +} + +/** Load a file on the specified unit. +*/ + +valerie_error_code valerie_unit_load( valerie this, int unit, char *file ) +{ + return valerie_execute( this, 10240, "LOAD U%d \"%s\"", unit, file ); +} + +static void valerie_interpret_clip_offset( char *output, valerie_clip_offset offset, int clip ) +{ + switch( offset ) + { + case valerie_absolute: + sprintf( output, "%d", clip ); + break; + case valerie_relative: + if ( clip < 0 ) + sprintf( output, "%d", clip ); + else + sprintf( output, "+%d", clip ); + break; + } +} + +/** Load a file on the specified unit with the specified in/out points. +*/ + +valerie_error_code valerie_unit_load_clipped( valerie this, int unit, char *file, int32_t in, int32_t out ) +{ + return valerie_execute( this, 10240, "LOAD U%d \"%s\" %d %d", unit, file, in, out ); +} + +/** Load a file on the specified unit at the end of the current pump. +*/ + +valerie_error_code valerie_unit_load_back( valerie this, int unit, char *file ) +{ + return valerie_execute( this, 10240, "LOAD U%d \"!%s\"", unit, file ); +} + +/** Load a file on the specified unit at the end of the pump with the specified in/out points. +*/ + +valerie_error_code valerie_unit_load_back_clipped( valerie this, int unit, char *file, int32_t in, int32_t out ) +{ + return valerie_execute( this, 10240, "LOAD U%d \"!%s\" %d %d", unit, file, in, out ); +} + +/** Append a file on the specified unit. +*/ + +valerie_error_code valerie_unit_append( valerie this, int unit, char *file, int32_t in, int32_t out ) +{ + return valerie_execute( this, 10240, "APND U%d \"%s\" %d %d", unit, file, in, out ); +} + +/** Push a service on to a unit. +*/ + +valerie_error_code valerie_unit_receive( valerie this, int unit, char *command, char *doc ) +{ + return valerie_receive( this, doc, 10240, "PUSH U%d %s", unit, command ); +} + +/** Push a service on to a unit. +*/ + +valerie_error_code valerie_unit_push( valerie this, int unit, char *command, mlt_service service ) +{ + return valerie_push( this, service, 10240, "PUSH U%d %s", unit, command ); +} + +/** Clean the unit - this function removes all but the currently playing clip. +*/ + +valerie_error_code valerie_unit_clean( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "CLEAN U%d", unit ); +} + +/** Clear the unit - this function removes all clips. +*/ + +valerie_error_code valerie_unit_clear( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "CLEAR U%d", unit ); +} + +/** Wipe the unit - this function removes all clips before the current one. +*/ + +valerie_error_code valerie_unit_wipe( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "WIPE U%d", unit ); +} + +/** Move clips on the units playlist. +*/ + +valerie_error_code valerie_unit_clip_move( valerie this, int unit, valerie_clip_offset src_offset, int src, valerie_clip_offset dest_offset, int dest ) +{ + char temp1[ 100 ]; + char temp2[ 100 ]; + valerie_interpret_clip_offset( temp1, src_offset, src ); + valerie_interpret_clip_offset( temp2, dest_offset, dest ); + return valerie_execute( this, 1024, "MOVE U%d %s %s", unit, temp1, temp2 ); +} + +/** Remove clip at the specified position. +*/ + +valerie_error_code valerie_unit_clip_remove( valerie this, int unit, valerie_clip_offset offset, int clip ) +{ + char temp[ 100 ]; + valerie_interpret_clip_offset( temp, offset, clip ); + return valerie_execute( this, 1024, "REMOVE U%d %s", unit, temp ); +} + +/** Remove the currently playing clip. +*/ + +valerie_error_code valerie_unit_remove_current_clip( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "REMOVE U%d", unit ); +} + +/** Insert clip at the specified position. +*/ + +valerie_error_code valerie_unit_clip_insert( valerie this, int unit, valerie_clip_offset offset, int clip, char *file, int32_t in, int32_t out ) +{ + char temp[ 100 ]; + valerie_interpret_clip_offset( temp, offset, clip ); + return valerie_execute( this, 1024, "INSERT U%d \"%s\" %s %d %d", unit, file, temp, in, out ); +} + +/** Play the unit at normal speed. +*/ + +valerie_error_code valerie_unit_play( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "PLAY U%d 1000", unit ); +} + +/** Play the unit at specified speed. +*/ + +valerie_error_code valerie_unit_play_at_speed( valerie this, int unit, int speed ) +{ + return valerie_execute( this, 10240, "PLAY U%d %d", unit, speed ); +} + +/** Stop playback on the specified unit. +*/ + +valerie_error_code valerie_unit_stop( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "STOP U%d", unit ); +} + +/** Pause playback on the specified unit. +*/ + +valerie_error_code valerie_unit_pause( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "PAUSE U%d", unit ); +} + +/** Rewind the specified unit. +*/ + +valerie_error_code valerie_unit_rewind( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "REW U%d", unit ); +} + +/** Fast forward the specified unit. +*/ + +valerie_error_code valerie_unit_fast_forward( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "FF U%d", unit ); +} + +/** Step by the number of frames on the specified unit. +*/ + +valerie_error_code valerie_unit_step( valerie this, int unit, int32_t step ) +{ + return valerie_execute( this, 1024, "STEP U%d %d", unit, step ); +} + +/** Goto the specified frame on the specified unit. +*/ + +valerie_error_code valerie_unit_goto( valerie this, int unit, int32_t position ) +{ + return valerie_execute( this, 1024, "GOTO U%d %d", unit, position ); +} + +/** Goto the specified frame in the clip on the specified unit. +*/ + +valerie_error_code valerie_unit_clip_goto( valerie this, int unit, valerie_clip_offset offset, int clip, int32_t position ) +{ + char temp[ 100 ]; + valerie_interpret_clip_offset( temp, offset, clip ); + return valerie_execute( this, 1024, "GOTO U%d %d %s", unit, position, temp ); +} + +/** Set the in point of the loaded file on the specified unit. +*/ + +valerie_error_code valerie_unit_set_in( valerie this, int unit, int32_t in ) +{ + return valerie_execute( this, 1024, "SIN U%d %d", unit, in ); +} + +/** Set the in point of the clip on the specified unit. +*/ + +valerie_error_code valerie_unit_clip_set_in( valerie this, int unit, valerie_clip_offset offset, int clip, int32_t in ) +{ + char temp[ 100 ]; + valerie_interpret_clip_offset( temp, offset, clip ); + return valerie_execute( this, 1024, "SIN U%d %d %s", unit, in, temp ); +} + +/** Set the out point of the loaded file on the specified unit. +*/ + +valerie_error_code valerie_unit_set_out( valerie this, int unit, int32_t out ) +{ + return valerie_execute( this, 1024, "SOUT U%d %d", unit, out ); +} + +/** Set the out point of the clip on the specified unit. +*/ + +valerie_error_code valerie_unit_clip_set_out( valerie this, int unit, valerie_clip_offset offset, int clip, int32_t in ) +{ + char temp[ 100 ]; + valerie_interpret_clip_offset( temp, offset, clip ); + return valerie_execute( this, 1024, "SOUT U%d %d %s", unit, in, temp ); +} + +/** Clear the in point of the loaded file on the specified unit. +*/ + +valerie_error_code valerie_unit_clear_in( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "SIN U%d -1", unit ); +} + +/** Clear the out point of the loaded file on the specified unit. +*/ + +valerie_error_code valerie_unit_clear_out( valerie this, int unit ) +{ + return valerie_execute( this, 1024, "SOUT U%d -1", unit ); +} + +/** Clear the in and out points on the loaded file on the specified unit. +*/ + +valerie_error_code valerie_unit_clear_in_out( valerie this, int unit ) +{ + valerie_error_code error = valerie_unit_clear_out( this, unit ); + if ( error == valerie_ok ) + error = valerie_unit_clear_in( this, unit ); + return error; +} + +/** Set a unit configuration property. +*/ + +valerie_error_code valerie_unit_set( valerie this, int unit, char *name, char *value ) +{ + return valerie_execute( this, 1024, "USET U%d %s=%s", unit, name, value ); +} + +/** Get a unit configuration property. +*/ + +valerie_error_code valerie_unit_get( valerie this, int unit, char *name ) +{ + return valerie_execute( this, 1024, "UGET U%d %s", unit, name ); +} + +/** Get a units status. +*/ + +valerie_error_code valerie_unit_status( valerie this, int unit, valerie_status status ) +{ + valerie_error_code error = valerie_execute( this, 1024, "USTA U%d", unit ); + int error_code = valerie_response_get_error_code( this->last_response ); + + memset( status, 0, sizeof( valerie_status_t ) ); + status->unit = unit; + if ( error_code == 202 && valerie_response_count( this->last_response ) == 2 ) + valerie_status_parse( status, valerie_response_get_line( this->last_response, 1 ) ); + else if ( error_code == 403 ) + status->status = unit_undefined; + + return error; +} + +/** Transfer the current settings of unit src to unit dest. +*/ + +valerie_error_code valerie_unit_transfer( valerie this, int src, int dest ) +{ + return valerie_execute( this, 1024, "XFER U%d U%d", src, dest ); +} + +/** Obtain the parsers notifier. +*/ + +valerie_notifier valerie_get_notifier( valerie this ) +{ + if ( this != NULL ) + return valerie_parser_get_notifier( this->parser ); + else + return NULL; +} + +/** List the contents of the specified directory. +*/ + +valerie_dir valerie_dir_init( valerie this, char *directory ) +{ + valerie_dir dir = malloc( sizeof( valerie_dir_t ) ); + if ( dir != NULL ) + { + memset( dir, 0, sizeof( valerie_dir_t ) ); + dir->directory = strdup( directory ); + dir->response = valerie_parser_executef( this->parser, "CLS \"%s\"", directory ); + } + return dir; +} + +/** Return the error code associated to the dir. +*/ + +valerie_error_code valerie_dir_get_error_code( valerie_dir dir ) +{ + if ( dir != NULL ) + return valerie_get_error_code( NULL, dir->response ); + else + return valerie_malloc_failed; +} + +/** Get a particular file entry in the directory. +*/ + +valerie_error_code valerie_dir_get( valerie_dir dir, int index, valerie_dir_entry entry ) +{ + valerie_error_code error = valerie_ok; + memset( entry, 0, sizeof( valerie_dir_entry_t ) ); + if ( index < valerie_dir_count( dir ) ) + { + char *line = valerie_response_get_line( dir->response, index + 1 ); + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + valerie_tokeniser_parse_new( tokeniser, line, " " ); + + if ( valerie_tokeniser_count( tokeniser ) > 0 ) + { + valerie_util_strip( valerie_tokeniser_get_string( tokeniser, 0 ), '\"' ); + strcpy( entry->full, dir->directory ); + if ( entry->full[ strlen( entry->full ) - 1 ] != '/' ) + strcat( entry->full, "/" ); + strcpy( entry->name, valerie_tokeniser_get_string( tokeniser, 0 ) ); + strcat( entry->full, entry->name ); + + switch ( valerie_tokeniser_count( tokeniser ) ) + { + case 1: + entry->dir = 1; + break; + case 2: + entry->size = strtoull( valerie_tokeniser_get_string( tokeniser, 1 ), NULL, 10 ); + break; + default: + error = valerie_invalid_file; + break; + } + } + valerie_tokeniser_close( tokeniser ); + } + return error; +} + +/** Get the number of entries in the directory +*/ + +int valerie_dir_count( valerie_dir dir ) +{ + if ( dir != NULL && valerie_response_count( dir->response ) >= 2 ) + return valerie_response_count( dir->response ) - 2; + else + return -1; +} + +/** Close the directory structure. +*/ + +void valerie_dir_close( valerie_dir dir ) +{ + if ( dir != NULL ) + { + free( dir->directory ); + valerie_response_close( dir->response ); + free( dir ); + } +} + +/** List the playlist of the specified unit. +*/ + +valerie_list valerie_list_init( valerie this, int unit ) +{ + valerie_list list = calloc( 1, sizeof( valerie_list_t ) ); + if ( list != NULL ) + { + list->response = valerie_parser_executef( this->parser, "LIST U%d", unit ); + if ( valerie_response_count( list->response ) >= 2 ) + list->generation = atoi( valerie_response_get_line( list->response, 1 ) ); + } + return list; +} + +/** Return the error code associated to the list. +*/ + +valerie_error_code valerie_list_get_error_code( valerie_list list ) +{ + if ( list != NULL ) + return valerie_get_error_code( NULL, list->response ); + else + return valerie_malloc_failed; +} + +/** Get a particular file entry in the list. +*/ + +valerie_error_code valerie_list_get( valerie_list list, int index, valerie_list_entry entry ) +{ + valerie_error_code error = valerie_ok; + memset( entry, 0, sizeof( valerie_list_entry_t ) ); + if ( index < valerie_list_count( list ) ) + { + char *line = valerie_response_get_line( list->response, index + 2 ); + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + valerie_tokeniser_parse_new( tokeniser, line, " " ); + + if ( valerie_tokeniser_count( tokeniser ) > 0 ) + { + entry->clip = atoi( valerie_tokeniser_get_string( tokeniser, 0 ) ); + valerie_util_strip( valerie_tokeniser_get_string( tokeniser, 1 ), '\"' ); + strcpy( entry->full, valerie_tokeniser_get_string( tokeniser, 1 ) ); + entry->in = atol( valerie_tokeniser_get_string( tokeniser, 2 ) ); + entry->out = atol( valerie_tokeniser_get_string( tokeniser, 3 ) ); + entry->max = atol( valerie_tokeniser_get_string( tokeniser, 4 ) ); + entry->size = atol( valerie_tokeniser_get_string( tokeniser, 5 ) ); + entry->fps = atof( valerie_tokeniser_get_string( tokeniser, 6 ) ); + } + valerie_tokeniser_close( tokeniser ); + } + return error; +} + +/** Get the number of entries in the list +*/ + +int valerie_list_count( valerie_list list ) +{ + if ( list != NULL && valerie_response_count( list->response ) >= 3 ) + return valerie_response_count( list->response ) - 3; + else + return -1; +} + +/** Close the list structure. +*/ + +void valerie_list_close( valerie_list list ) +{ + if ( list != NULL ) + { + valerie_response_close( list->response ); + free( list ); + } +} + +/** List the currently connected nodes. +*/ + +valerie_nodes valerie_nodes_init( valerie this ) +{ + valerie_nodes nodes = malloc( sizeof( valerie_nodes_t ) ); + if ( nodes != NULL ) + { + memset( nodes, 0, sizeof( valerie_nodes_t ) ); + nodes->response = valerie_parser_executef( this->parser, "NLS" ); + } + return nodes; +} + +/** Return the error code associated to the nodes list. +*/ + +valerie_error_code valerie_nodes_get_error_code( valerie_nodes nodes ) +{ + if ( nodes != NULL ) + return valerie_get_error_code( NULL, nodes->response ); + else + return valerie_malloc_failed; +} + +/** Get a particular node entry. +*/ + +valerie_error_code valerie_nodes_get( valerie_nodes nodes, int index, valerie_node_entry entry ) +{ + valerie_error_code error = valerie_ok; + memset( entry, 0, sizeof( valerie_node_entry_t ) ); + if ( index < valerie_nodes_count( nodes ) ) + { + char *line = valerie_response_get_line( nodes->response, index + 1 ); + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + valerie_tokeniser_parse_new( tokeniser, line, " " ); + + if ( valerie_tokeniser_count( tokeniser ) == 3 ) + { + entry->node = atoi( valerie_tokeniser_get_string( tokeniser, 0 ) ); + strncpy( entry->guid, valerie_tokeniser_get_string( tokeniser, 1 ), sizeof( entry->guid ) ); + valerie_util_strip( valerie_tokeniser_get_string( tokeniser, 2 ), '\"' ); + strncpy( entry->name, valerie_tokeniser_get_string( tokeniser, 2 ), sizeof( entry->name ) ); + } + + valerie_tokeniser_close( tokeniser ); + } + return error; +} + +/** Get the number of nodes +*/ + +int valerie_nodes_count( valerie_nodes nodes ) +{ + if ( nodes != NULL && valerie_response_count( nodes->response ) >= 2 ) + return valerie_response_count( nodes->response ) - 2; + else + return -1; +} + +/** Close the nodes structure. +*/ + +void valerie_nodes_close( valerie_nodes nodes ) +{ + if ( nodes != NULL ) + { + valerie_response_close( nodes->response ); + free( nodes ); + } +} + +/** List the currently defined units. +*/ + +valerie_units valerie_units_init( valerie this ) +{ + valerie_units units = malloc( sizeof( valerie_units_t ) ); + if ( units != NULL ) + { + memset( units, 0, sizeof( valerie_units_t ) ); + units->response = valerie_parser_executef( this->parser, "ULS" ); + } + return units; +} + +/** Return the error code associated to the nodes list. +*/ + +valerie_error_code valerie_units_get_error_code( valerie_units units ) +{ + if ( units != NULL ) + return valerie_get_error_code( NULL, units->response ); + else + return valerie_malloc_failed; +} + +/** Get a particular unit entry. +*/ + +valerie_error_code valerie_units_get( valerie_units units, int index, valerie_unit_entry entry ) +{ + valerie_error_code error = valerie_ok; + memset( entry, 0, sizeof( valerie_unit_entry_t ) ); + if ( index < valerie_units_count( units ) ) + { + char *line = valerie_response_get_line( units->response, index + 1 ); + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + valerie_tokeniser_parse_new( tokeniser, line, " " ); + + if ( valerie_tokeniser_count( tokeniser ) == 4 ) + { + entry->unit = atoi( valerie_tokeniser_get_string( tokeniser, 0 ) + 1 ); + entry->node = atoi( valerie_tokeniser_get_string( tokeniser, 1 ) ); + strncpy( entry->guid, valerie_tokeniser_get_string( tokeniser, 2 ), sizeof( entry->guid ) ); + entry->online = atoi( valerie_tokeniser_get_string( tokeniser, 3 ) ); + } + + valerie_tokeniser_close( tokeniser ); + } + return error; +} + +/** Get the number of units +*/ + +int valerie_units_count( valerie_units units ) +{ + if ( units != NULL && valerie_response_count( units->response ) >= 2 ) + return valerie_response_count( units->response ) - 2; + else + return -1; +} + +/** Close the units structure. +*/ + +void valerie_units_close( valerie_units units ) +{ + if ( units != NULL ) + { + valerie_response_close( units->response ); + free( units ); + } +} + +/** Get the response of the last command executed. +*/ + +valerie_response valerie_get_last_response( valerie this ) +{ + return this->last_response; +} + +/** Obtain a printable message associated to the error code provided. +*/ + +char *valerie_error_description( valerie_error_code error ) +{ + char *msg = "Unrecognised error"; + switch( error ) + { + case valerie_ok: + msg = "OK"; + break; + case valerie_malloc_failed: + msg = "Memory allocation error"; + break; + case valerie_unknown_error: + msg = "Unknown error"; + break; + case valerie_no_response: + msg = "No response obtained"; + break; + case valerie_invalid_command: + msg = "Invalid command"; + break; + case valerie_server_timeout: + msg = "Communications with server timed out"; + break; + case valerie_missing_argument: + msg = "Missing argument"; + break; + case valerie_server_unavailable: + msg = "Unable to communicate with server"; + break; + case valerie_unit_creation_failed: + msg = "Unit creation failed"; + break; + case valerie_unit_unavailable: + msg = "Unit unavailable"; + break; + case valerie_invalid_file: + msg = "Invalid file"; + break; + case valerie_invalid_position: + msg = "Invalid position"; + break; + } + return msg; +} + +/** Close the valerie structure. +*/ + +void valerie_close( valerie this ) +{ + if ( this != NULL ) + { + valerie_set_last_response( this, NULL ); + free( this ); + } +} diff --git a/src/valerie/valerie.h b/src/valerie/valerie.h new file mode 100644 index 0000000..f359226 --- /dev/null +++ b/src/valerie/valerie.h @@ -0,0 +1,264 @@ +/* + * valerie.h -- High Level Client API for miracle + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_H_ +#define _VALERIE_H_ + +/* System header files */ +#include + +/* MLT Header files. */ +#include + +/* Application header files */ +#include "valerie_parser.h" +#include "valerie_status.h" +#include "valerie_notifier.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Client error conditions +*/ + +typedef enum +{ + valerie_ok = 0, + valerie_malloc_failed, + valerie_unknown_error, + valerie_no_response, + valerie_invalid_command, + valerie_server_timeout, + valerie_missing_argument, + valerie_server_unavailable, + valerie_unit_creation_failed, + valerie_unit_unavailable, + valerie_invalid_file, + valerie_invalid_position +} +valerie_error_code; + +/** Clip index specification. +*/ + +typedef enum +{ + valerie_absolute = 0, + valerie_relative +} +valerie_clip_offset; + +/** Client structure. +*/ + +typedef struct +{ + valerie_parser parser; + valerie_response last_response; +} +*valerie, valerie_t; + +/** Client API. +*/ + +extern valerie valerie_init( valerie_parser ); + +/* Connect to the valerie parser instance */ +extern valerie_error_code valerie_connect( valerie ); + +/* Global functions */ +extern valerie_error_code valerie_set( valerie, char *, char * ); +extern valerie_error_code valerie_get( valerie, char *, char *, int ); +extern valerie_error_code valerie_run( valerie, char * ); + +/* Unit functions */ +extern valerie_error_code valerie_unit_add( valerie, char *, int * ); +extern valerie_error_code valerie_unit_load( valerie, int, char * ); +extern valerie_error_code valerie_unit_load_clipped( valerie, int, char *, int32_t, int32_t ); +extern valerie_error_code valerie_unit_load_back( valerie, int, char * ); +extern valerie_error_code valerie_unit_load_back_clipped( valerie, int, char *, int32_t, int32_t ); +extern valerie_error_code valerie_unit_append( valerie, int, char *, int32_t, int32_t ); +extern valerie_error_code valerie_unit_receive( valerie, int, char *, char * ); +extern valerie_error_code valerie_unit_push( valerie, int, char *, mlt_service ); +extern valerie_error_code valerie_unit_clean( valerie, int ); +extern valerie_error_code valerie_unit_wipe( valerie, int ); +extern valerie_error_code valerie_unit_clear( valerie, int ); +extern valerie_error_code valerie_unit_clip_move( valerie, int, valerie_clip_offset, int, valerie_clip_offset, int ); +extern valerie_error_code valerie_unit_clip_remove( valerie, int, valerie_clip_offset, int ); +extern valerie_error_code valerie_unit_remove_current_clip( valerie, int ); +extern valerie_error_code valerie_unit_clip_insert( valerie, int, valerie_clip_offset, int, char *, int32_t, int32_t ); +extern valerie_error_code valerie_unit_play( valerie, int ); +extern valerie_error_code valerie_unit_play_at_speed( valerie, int, int ); +extern valerie_error_code valerie_unit_stop( valerie, int ); +extern valerie_error_code valerie_unit_pause( valerie, int ); +extern valerie_error_code valerie_unit_rewind( valerie, int ); +extern valerie_error_code valerie_unit_fast_forward( valerie, int ); +extern valerie_error_code valerie_unit_step( valerie, int, int32_t ); +extern valerie_error_code valerie_unit_goto( valerie, int, int32_t ); +extern valerie_error_code valerie_unit_clip_goto( valerie, int, valerie_clip_offset, int, int32_t ); +extern valerie_error_code valerie_unit_clip_set_in( valerie, int, valerie_clip_offset, int, int32_t ); +extern valerie_error_code valerie_unit_clip_set_out( valerie, int, valerie_clip_offset, int, int32_t ); +extern valerie_error_code valerie_unit_set_in( valerie, int, int32_t ); +extern valerie_error_code valerie_unit_set_out( valerie, int, int32_t ); +extern valerie_error_code valerie_unit_clear_in( valerie, int ); +extern valerie_error_code valerie_unit_clear_out( valerie, int ); +extern valerie_error_code valerie_unit_clear_in_out( valerie, int ); +extern valerie_error_code valerie_unit_set( valerie, int, char *, char * ); +extern valerie_error_code valerie_unit_get( valerie, int, char * ); +extern valerie_error_code valerie_unit_status( valerie, int, valerie_status ); +extern valerie_error_code valerie_unit_transfer( valerie, int, int ); + +/* Notifier functionality. */ +extern valerie_notifier valerie_get_notifier( valerie ); + +/** Structure for the directory. +*/ + +typedef struct +{ + char *directory; + valerie_response response; +} +*valerie_dir, valerie_dir_t; + +/** Directory entry structure. +*/ + +typedef struct +{ + int dir; + char name[ NAME_MAX ]; + char full[ PATH_MAX + NAME_MAX ]; + unsigned long long size; +} +*valerie_dir_entry, valerie_dir_entry_t; + +/* Directory reading. */ +extern valerie_dir valerie_dir_init( valerie, char * ); +extern valerie_error_code valerie_dir_get_error_code( valerie_dir ); +extern valerie_error_code valerie_dir_get( valerie_dir, int, valerie_dir_entry ); +extern int valerie_dir_count( valerie_dir ); +extern void valerie_dir_close( valerie_dir ); + +/** Structure for the list. +*/ + +typedef struct +{ + int generation; + valerie_response response; +} +*valerie_list, valerie_list_t; + +/** List entry structure. +*/ + +typedef struct +{ + int clip; + char full[ PATH_MAX + NAME_MAX ]; + int32_t in; + int32_t out; + int32_t max; + int32_t size; + int32_t fps; +} +*valerie_list_entry, valerie_list_entry_t; + +/* List reading. */ +extern valerie_list valerie_list_init( valerie, int ); +extern valerie_error_code valerie_list_get_error_code( valerie_list ); +extern valerie_error_code valerie_list_get( valerie_list, int, valerie_list_entry ); +extern int valerie_list_count( valerie_list ); +extern void valerie_list_close( valerie_list ); + +/** Structure for nodes. +*/ + +typedef struct +{ + valerie_response response; +} +*valerie_nodes, valerie_nodes_t; + +/** Node entry structure. +*/ + +typedef struct +{ + int node; + char guid[ 17 ]; + char name[ 1024 ]; +} +*valerie_node_entry, valerie_node_entry_t; + +/* Node reading. */ +extern valerie_nodes valerie_nodes_init( valerie ); +extern valerie_error_code valerie_nodes_get_error_code( valerie_nodes ); +extern valerie_error_code valerie_nodes_get( valerie_nodes, int, valerie_node_entry ); +extern int valerie_nodes_count( valerie_nodes ); +extern void valerie_nodes_close( valerie_nodes ); + +/** Structure for units. +*/ + +typedef struct +{ + valerie_response response; +} +*valerie_units, valerie_units_t; + +/** Unit entry structure. +*/ + +typedef struct +{ + int unit; + int node; + char guid[ 512 ]; + int online; +} +*valerie_unit_entry, valerie_unit_entry_t; + +/* Unit reading. */ +extern valerie_units valerie_units_init( valerie ); +extern valerie_error_code valerie_units_get_error_code( valerie_units ); +extern valerie_error_code valerie_units_get( valerie_units, int, valerie_unit_entry ); +extern int valerie_units_count( valerie_units ); +extern void valerie_units_close( valerie_units ); + +/* Miscellaenous functions */ +extern valerie_response valerie_get_last_response( valerie ); +extern char *valerie_error_description( valerie_error_code ); + +/* Courtesy functions. */ +extern valerie_error_code valerie_execute( valerie, size_t, char *, ... ); +extern valerie_error_code valerie_push( valerie, mlt_service, size_t, char *, ... ); + +/* Close function. */ +extern void valerie_close( valerie ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_notifier.c b/src/valerie/valerie_notifier.c new file mode 100644 index 0000000..7a10bdd --- /dev/null +++ b/src/valerie/valerie_notifier.c @@ -0,0 +1,126 @@ +/* + * valerie_notifier.c -- Unit Status Notifier Handling + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include + +/* Application header files */ +#include "valerie_notifier.h" + +/** Notifier initialisation. +*/ + +valerie_notifier valerie_notifier_init( ) +{ + valerie_notifier this = calloc( 1, sizeof( valerie_notifier_t ) ); + if ( this != NULL ) + { + int index = 0; + pthread_mutex_init( &this->mutex, NULL ); + pthread_cond_init( &this->cond, NULL ); + for ( index = 0; index < MAX_UNITS; index ++ ) + this->store[ index ].unit = index; + } + return this; +} + +/** Get a stored status for the specified unit. +*/ + +void valerie_notifier_get( valerie_notifier this, valerie_status status, int unit ) +{ + pthread_mutex_lock( &this->mutex ); + if ( unit >= 0 && unit < MAX_UNITS ) + valerie_status_copy( status, &this->store[ unit ] ); + else + memset( status, 0, sizeof( valerie_status_t ) ); + status->unit = unit; + status->dummy = time( NULL ); + pthread_mutex_unlock( &this->mutex ); +} + +/** Wait on a new status. +*/ + +int valerie_notifier_wait( valerie_notifier this, valerie_status status ) +{ + struct timeval now; + struct timespec timeout; + int error = 0; + + memset( status, 0, sizeof( valerie_status_t ) ); + gettimeofday( &now, NULL ); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; + pthread_mutex_lock( &this->mutex ); + pthread_cond_timedwait( &this->cond, &this->mutex, &timeout ); + valerie_status_copy( status, &this->last ); + pthread_mutex_unlock( &this->mutex ); + + return error; +} + +/** Put a new status. +*/ + +void valerie_notifier_put( valerie_notifier this, valerie_status status ) +{ + pthread_mutex_lock( &this->mutex ); + valerie_status_copy( &this->store[ status->unit ], status ); + valerie_status_copy( &this->last, status ); + pthread_mutex_unlock( &this->mutex ); + pthread_cond_broadcast( &this->cond ); +} + +/** Communicate a disconnected status for all units to all waiting. +*/ + +void valerie_notifier_disconnected( valerie_notifier notifier ) +{ + int unit = 0; + valerie_status_t status; + for ( unit = 0; unit < MAX_UNITS; unit ++ ) + { + valerie_notifier_get( notifier, &status, unit ); + status.status = unit_disconnected; + valerie_notifier_put( notifier, &status ); + } +} + +/** Close the notifier - note that all access must be stopped before we call this. +*/ + +void valerie_notifier_close( valerie_notifier this ) +{ + if ( this != NULL ) + { + pthread_mutex_destroy( &this->mutex ); + pthread_cond_destroy( &this->cond ); + free( this ); + } +} diff --git a/src/valerie/valerie_notifier.h b/src/valerie/valerie_notifier.h new file mode 100644 index 0000000..816e728 --- /dev/null +++ b/src/valerie/valerie_notifier.h @@ -0,0 +1,60 @@ +/* + * valerie_notifier.h -- Unit Status Notifier Handling + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_NOTIFIER_H_ +#define _VALERIE_NOTIFIER_H_ + +/* System header files */ +#include + +/* Application header files */ +#include "valerie_status.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define MAX_UNITS 16 + +/** Status notifier definition. +*/ + +typedef struct +{ + pthread_mutex_t mutex; + pthread_cond_t cond; + valerie_status_t last; + valerie_status_t store[ MAX_UNITS ]; +} +*valerie_notifier, valerie_notifier_t; + +extern valerie_notifier valerie_notifier_init( ); +extern void valerie_notifier_get( valerie_notifier, valerie_status, int ); +extern int valerie_notifier_wait( valerie_notifier, valerie_status ); +extern void valerie_notifier_put( valerie_notifier, valerie_status ); +extern void valerie_notifier_disconnected( valerie_notifier ); +extern void valerie_notifier_close( valerie_notifier ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_parser.c b/src/valerie/valerie_parser.c new file mode 100644 index 0000000..c2d1b33 --- /dev/null +++ b/src/valerie/valerie_parser.c @@ -0,0 +1,147 @@ +/* + * valerie_parser.c -- Valerie Parser for Miracle + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include +#include +#include + +/* Application header files */ +#include "valerie_parser.h" +#include "valerie_util.h" + +/** Connect to the parser. +*/ + +valerie_response valerie_parser_connect( valerie_parser parser ) +{ + return parser->connect( parser->real ); +} + +/** Execute a command via the parser. +*/ + +valerie_response valerie_parser_execute( valerie_parser parser, char *command ) +{ + return parser->execute( parser->real, command ); +} + +/** Push a service via the parser. +*/ + +valerie_response valerie_parser_received( valerie_parser parser, char *command, char *doc ) +{ + return parser->received != NULL ? parser->received( parser->real, command, doc ) : NULL; +} + +/** Push a service via the parser. +*/ + +valerie_response valerie_parser_push( valerie_parser parser, char *command, mlt_service service ) +{ + return parser->push( parser->real, command, service ); +} + +/** Execute a formatted command via the parser. +*/ + +valerie_response valerie_parser_executef( valerie_parser parser, char *format, ... ) +{ + char *command = malloc( 10240 ); + valerie_response response = NULL; + if ( command != NULL ) + { + va_list list; + va_start( list, format ); + if ( vsnprintf( command, 10240, format, list ) != 0 ) + response = valerie_parser_execute( parser, command ); + va_end( list ); + free( command ); + } + return response; +} + +/** Execute the contents of a file. Note the special case valerie_response returned. +*/ + +valerie_response valerie_parser_run( valerie_parser parser, char *filename ) +{ + valerie_response response = valerie_response_init( ); + if ( response != NULL ) + { + FILE *file = fopen( filename, "r" ); + if ( file != NULL ) + { + char command[ 1024 ]; + valerie_response_set_error( response, 201, "OK" ); + while ( valerie_response_get_error_code( response ) == 201 && fgets( command, 1024, file ) ) + { + valerie_util_trim( valerie_util_chomp( command ) ); + if ( strcmp( command, "" ) && command[ 0 ] != '#' ) + { + valerie_response temp = NULL; + valerie_response_printf( response, 1024, "%s\n", command ); + temp = valerie_parser_execute( parser, command ); + if ( temp != NULL ) + { + int index = 0; + for ( index = 0; index < valerie_response_count( temp ); index ++ ) + valerie_response_printf( response, 10240, "%s\n", valerie_response_get_line( temp, index ) ); + valerie_response_close( temp ); + } + else + { + valerie_response_set_error( response, 500, "Batch execution failed" ); + } + } + } + fclose( file ); + } + else + { + valerie_response_set_error( response, 404, "File not found." ); + } + } + return response; +} + +/** Get the notifier associated to the parser. +*/ + +valerie_notifier valerie_parser_get_notifier( valerie_parser parser ) +{ + if ( parser->notifier == NULL ) + parser->notifier = valerie_notifier_init( ); + return parser->notifier; +} + +/** Close the parser. +*/ + +void valerie_parser_close( valerie_parser parser ) +{ + if ( parser != NULL ) + { + parser->close( parser->real ); + valerie_notifier_close( parser->notifier ); + free( parser ); + } +} diff --git a/src/valerie/valerie_parser.h b/src/valerie/valerie_parser.h new file mode 100644 index 0000000..0a80387 --- /dev/null +++ b/src/valerie/valerie_parser.h @@ -0,0 +1,76 @@ +/* + * valerie_parser.h -- Valerie Parser for Miracle Server + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_PARSER_H_ +#define _VALERIE_PARSER_H_ + +/* MLT Header files */ +#include + +/* Application header files */ +#include "valerie_response.h" +#include "valerie_notifier.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Callbacks to define the parser. +*/ + +typedef valerie_response (*parser_connect)( void * ); +typedef valerie_response (*parser_execute)( void *, char * ); +typedef valerie_response (*parser_received)( void *, char *, char * ); +typedef valerie_response (*parser_push)( void *, char *, mlt_service ); +typedef void (*parser_close)( void * ); + +/** Structure for the valerie parser. +*/ + +typedef struct +{ + parser_connect connect; + parser_execute execute; + parser_push push; + parser_received received; + parser_close close; + void *real; + valerie_notifier notifier; +} +*valerie_parser, valerie_parser_t; + +/** API for the parser - note that no constructor is defined here. +*/ + +extern valerie_response valerie_parser_connect( valerie_parser ); +extern valerie_response valerie_parser_push( valerie_parser, char *, mlt_service ); +extern valerie_response valerie_parser_received( valerie_parser, char *, char * ); +extern valerie_response valerie_parser_execute( valerie_parser, char * ); +extern valerie_response valerie_parser_executef( valerie_parser, char *, ... ); +extern valerie_response valerie_parser_run( valerie_parser, char * ); +extern valerie_notifier valerie_parser_get_notifier( valerie_parser ); +extern void valerie_parser_close( valerie_parser ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_remote.c b/src/valerie/valerie_remote.c new file mode 100644 index 0000000..50eb4ae --- /dev/null +++ b/src/valerie/valerie_remote.c @@ -0,0 +1,309 @@ +/* + * valerie_remote.c -- Remote Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include +#include "valerie_remote.h" +#include "valerie_socket.h" +#include "valerie_tokeniser.h" +#include "valerie_util.h" + +/** Private valerie_remote structure. +*/ + +typedef struct +{ + int terminated; + char *server; + int port; + valerie_socket socket; + valerie_socket status; + pthread_t thread; + valerie_parser parser; + pthread_mutex_t mutex; + int connected; +} +*valerie_remote, valerie_remote_t; + +/** Forward declarations. +*/ + +static valerie_response valerie_remote_connect( valerie_remote ); +static valerie_response valerie_remote_execute( valerie_remote, char * ); +static valerie_response valerie_remote_receive( valerie_remote, char *, char * ); +static valerie_response valerie_remote_push( valerie_remote, char *, mlt_service ); +static void valerie_remote_close( valerie_remote ); +static int valerie_remote_read_response( valerie_socket, valerie_response ); + +/** DV Parser constructor. +*/ + +valerie_parser valerie_parser_init_remote( char *server, int port ) +{ + valerie_parser parser = calloc( 1, sizeof( valerie_parser_t ) ); + valerie_remote remote = calloc( 1, sizeof( valerie_remote_t ) ); + + if ( parser != NULL ) + { + parser->connect = (parser_connect)valerie_remote_connect; + parser->execute = (parser_execute)valerie_remote_execute; + parser->push = (parser_push)valerie_remote_push; + parser->received = (parser_received)valerie_remote_receive; + parser->close = (parser_close)valerie_remote_close; + parser->real = remote; + + if ( remote != NULL ) + { + remote->parser = parser; + remote->server = strdup( server ); + remote->port = port; + pthread_mutex_init( &remote->mutex, NULL ); + } + } + return parser; +} + +/** Thread for receiving and distributing the status information. +*/ + +static void *valerie_remote_status_thread( void *arg ) +{ + valerie_remote remote = arg; + char temp[ 10240 ]; + int length = 0; + int offset = 0; + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + valerie_notifier notifier = valerie_parser_get_notifier( remote->parser ); + valerie_status_t status; + int index = 0; + + valerie_socket_write_data( remote->status, "STATUS\r\n", 8 ); + + while ( !remote->terminated && + ( length = valerie_socket_read_data( remote->status, temp + offset, sizeof( temp ) ) ) >= 0 ) + { + if ( strchr( temp, '\n' ) == NULL ) + { + offset = length; + continue; + } + offset = 0; + valerie_tokeniser_parse_new( tokeniser, temp, "\n" ); + for ( index = 0; index < valerie_tokeniser_count( tokeniser ); index ++ ) + { + char *line = valerie_tokeniser_get_string( tokeniser, index ); + if ( line[ strlen( line ) - 1 ] == '\r' ) + { + valerie_util_chomp( line ); + valerie_status_parse( &status, line ); + valerie_notifier_put( notifier, &status ); + } + else + { + strcpy( temp, line ); + offset = strlen( temp ); + } + } + } + + valerie_notifier_disconnected( notifier ); + valerie_tokeniser_close( tokeniser ); + remote->terminated = 1; + + return NULL; +} + +/** Forward reference. +*/ + +static void valerie_remote_disconnect( valerie_remote remote ); + +/** Connect to the server. +*/ + +static valerie_response valerie_remote_connect( valerie_remote remote ) +{ + valerie_response response = NULL; + + valerie_remote_disconnect( remote ); + + if ( !remote->connected ) + { + signal( SIGPIPE, SIG_IGN ); + + remote->socket = valerie_socket_init( remote->server, remote->port ); + remote->status = valerie_socket_init( remote->server, remote->port ); + + if ( valerie_socket_connect( remote->socket ) == 0 ) + { + response = valerie_response_init( ); + valerie_remote_read_response( remote->socket, response ); + } + + if ( response != NULL && valerie_socket_connect( remote->status ) == 0 ) + { + valerie_response status_response = valerie_response_init( ); + valerie_remote_read_response( remote->status, status_response ); + if ( valerie_response_get_error_code( status_response ) == 100 ) + pthread_create( &remote->thread, NULL, valerie_remote_status_thread, remote ); + valerie_response_close( status_response ); + remote->connected = 1; + } + } + + return response; +} + +/** Execute the command. +*/ + +static valerie_response valerie_remote_execute( valerie_remote remote, char *command ) +{ + valerie_response response = NULL; + pthread_mutex_lock( &remote->mutex ); + if ( valerie_socket_write_data( remote->socket, command, strlen( command ) ) == strlen( command ) ) + { + response = valerie_response_init( ); + valerie_socket_write_data( remote->socket, "\r\n", 2 ); + valerie_remote_read_response( remote->socket, response ); + } + pthread_mutex_unlock( &remote->mutex ); + return response; +} + +/** Push a westley document to the server. +*/ + +static valerie_response valerie_remote_receive( valerie_remote remote, char *command, char *buffer ) +{ + valerie_response response = NULL; + pthread_mutex_lock( &remote->mutex ); + if ( valerie_socket_write_data( remote->socket, command, strlen( command ) ) == strlen( command ) ) + { + char temp[ 20 ]; + int length = strlen( buffer ); + response = valerie_response_init( ); + valerie_socket_write_data( remote->socket, "\r\n", 2 ); + sprintf( temp, "%d", length ); + valerie_socket_write_data( remote->socket, temp, strlen( temp ) ); + valerie_socket_write_data( remote->socket, "\r\n", 2 ); + valerie_socket_write_data( remote->socket, buffer, length ); + valerie_socket_write_data( remote->socket, "\r\n", 2 ); + valerie_remote_read_response( remote->socket, response ); + } + pthread_mutex_unlock( &remote->mutex ); + return response; +} + +/** Push a producer to the server. +*/ + +static valerie_response valerie_remote_push( valerie_remote remote, char *command, mlt_service service ) +{ + valerie_response response = NULL; + if ( service != NULL ) + { + mlt_consumer consumer = mlt_factory_consumer( "westley", "buffer" ); + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + char *buffer = NULL; + // Temporary hack + mlt_properties_set( properties, "store", "nle_" ); + mlt_consumer_connect( consumer, service ); + mlt_consumer_start( consumer ); + buffer = mlt_properties_get( properties, "buffer" ); + response = valerie_remote_receive( remote, command, buffer ); + mlt_consumer_close( consumer ); + } + return response; +} + +/** Disconnect. +*/ + +static void valerie_remote_disconnect( valerie_remote remote ) +{ + if ( remote != NULL && remote->terminated ) + { + if ( remote->connected ) + pthread_join( remote->thread, NULL ); + valerie_socket_close( remote->status ); + valerie_socket_close( remote->socket ); + remote->connected = 0; + remote->terminated = 0; + } +} + +/** Close the parser. +*/ + +static void valerie_remote_close( valerie_remote remote ) +{ + if ( remote != NULL ) + { + remote->terminated = 1; + valerie_remote_disconnect( remote ); + pthread_mutex_destroy( &remote->mutex ); + free( remote->server ); + free( remote ); + } +} + +/** Read response. +*/ + +static int valerie_remote_read_response( valerie_socket socket, valerie_response response ) +{ + char temp[ 10240 ]; + int length; + int terminated = 0; + + while ( !terminated && ( length = valerie_socket_read_data( socket, temp, 10240 ) ) >= 0 ) + { + int position = 0; + temp[ length ] = '\0'; + valerie_response_write( response, temp, length ); + position = valerie_response_count( response ) - 1; + if ( position < 0 || temp[ strlen( temp ) - 1 ] != '\n' ) + continue; + switch( valerie_response_get_error_code( response ) ) + { + case 201: + case 500: + terminated = !strcmp( valerie_response_get_line( response, position ), "" ); + break; + case 202: + terminated = valerie_response_count( response ) >= 2; + break; + default: + terminated = 1; + break; + } + } + + return 0; +} diff --git a/src/valerie/valerie_remote.h b/src/valerie/valerie_remote.h new file mode 100644 index 0000000..7ddf9d7 --- /dev/null +++ b/src/valerie/valerie_remote.h @@ -0,0 +1,41 @@ +/* + * valerie_remote.h -- Remote Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_REMOTE_H_ +#define _VALERIE_REMOTE_H_ + +/* Application header files */ +#include "valerie_parser.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Remote parser API. +*/ + +extern valerie_parser valerie_parser_init_remote( char *, int ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_response.c b/src/valerie/valerie_response.c new file mode 100644 index 0000000..47a820d --- /dev/null +++ b/src/valerie/valerie_response.c @@ -0,0 +1,244 @@ +/* + * valerie_response.c -- Response + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include +#include +#include + +/* Application header files */ +#include "valerie_response.h" + +/** Construct a new dv response. +*/ + +valerie_response valerie_response_init( ) +{ + valerie_response response = malloc( sizeof( valerie_response_t ) ); + if ( response != NULL ) + memset( response, 0, sizeof( valerie_response_t ) ); + return response; +} + +/** Clone a dv response +*/ + +valerie_response valerie_response_clone( valerie_response response ) +{ + valerie_response clone = valerie_response_init( ); + if ( clone != NULL && response != NULL ) + { + int index = 0; + for ( index = 0; index < valerie_response_count( response ); index ++ ) + { + char *line = valerie_response_get_line( response, index ); + valerie_response_printf( clone, strlen( line ) + 2, "%s\n", line ); + } + } + return clone; +} + +/** Get the error code associated to the response. +*/ + +int valerie_response_get_error_code( valerie_response response ) +{ + int error_code = -1; + if ( response != NULL ) + { + if ( response->count > 0 ) + { + if ( sscanf( response->array[ 0 ], "%d", &error_code ) != 1 ) + error_code = 0; + } + else + { + error_code = -2; + } + } + return error_code; +} + +/** Get the error description associated to the response. +*/ + +char *valerie_response_get_error_string( valerie_response response ) +{ + char *error_string = "No message specified"; + if ( response->count > 0 ) + { + char *ptr = strchr( response->array[ 0 ], ' ' ) ; + if ( ptr != NULL ) + error_string = ptr + 1; + } + return error_string; +} + +/** Get a line of text at the given index. Note that the text itself is + terminated only with a NUL char and it is the responsibility of the + the user of the returned data to use a LF or CR/LF as appropriate. +*/ + +char *valerie_response_get_line( valerie_response response, int index ) +{ + if ( index < response->count ) + return response->array[ index ]; + else + return NULL; +} + +/** Return the number of lines of text in the response. +*/ + +int valerie_response_count( valerie_response response ) +{ + if ( response != NULL ) + return response->count; + else + return 0; +} + +/** Set the error and description associated to the response. +*/ + +void valerie_response_set_error( valerie_response response, int error_code, char *error_string ) +{ + if ( response->count == 0 ) + { + valerie_response_printf( response, 10240, "%d %s\n", error_code, error_string ); + } + else + { + char temp[ 10240 ]; + int length = sprintf( temp, "%d %s", error_code, error_string ); + response->array[ 0 ] = realloc( response->array[ 0 ], length + 1 ); + strcpy( response->array[ 0 ], temp ); + } +} + +/** Write formatted text to the response. +*/ + +int valerie_response_printf( valerie_response response, size_t size, char *format, ... ) +{ + int length = 0; + char *text = malloc( size ); + if ( text != NULL ) + { + va_list list; + va_start( list, format ); + length = vsnprintf( text, size, format, list ); + if ( length != 0 ) + valerie_response_write( response, text, length ); + va_end( list ); + free( text ); + } + return length; +} + +/** Write text to the reponse. +*/ + +int valerie_response_write( valerie_response response, char *text, int size ) +{ + int ret = 0; + char *ptr = text; + + while ( size > 0 ) + { + int index = response->count - 1; + char *lf = strchr( ptr, '\n' ); + int length_of_string = 0; + + /* Make sure we have space in the dynamic array. */ + if ( !response->append && response->count >= response->size - 1 ) + { + response->size += 50; + response->array = realloc( response->array, response->size * sizeof( char * ) ); + } + + /* Make sure the array is valid, or we're really in trouble */ + if ( response->array == NULL ) + { + ret = 0; + break; + } + + /* Now, if we're appending to the previous write (ie: if it wasn't + terminated by a LF), then use the index calculated above, otherwise + go to the next one and ensure it's NULLed. */ + + if ( !response->append ) + { + response->array[ ++ index ] = NULL; + response->count ++; + } + else + { + length_of_string = strlen( response->array[ index ] ); + } + + /* Now we need to know how to handle the current ptr with respect to lf. */ + /* TODO: tidy up and error check... sigh... tested for many, many 1000s of lines */ + + if ( lf == NULL ) + { + response->array[ index ] = realloc( response->array[ index ], length_of_string + size + 1 ); + memcpy( response->array[ index ] + length_of_string, ptr, size ); + response->array[ index ][ length_of_string + size ] = '\0'; + if ( ( length_of_string + size ) > 0 && response->array[ index ][ length_of_string + size - 1 ] == '\r' ) + response->array[ index ][ length_of_string + size - 1 ] = '\0'; + size = 0; + ret += size; + response->append = 1; + } + else + { + int chars = lf - ptr; + response->array[ index ] = realloc( response->array[ index ], length_of_string + chars + 1 ); + memcpy( response->array[ index ] + length_of_string, ptr, chars ); + response->array[ index ][ length_of_string + chars ] = '\0'; + if ( ( length_of_string + chars ) > 0 && response->array[ index ][ length_of_string + chars - 1 ] == '\r' ) + response->array[ index ][ length_of_string + chars - 1 ] = '\0'; + ptr = ptr + chars + 1; + size -= ( chars + 1 ); + response->append = 0; + ret += chars + 1; + } + } + + return ret; +} + +/** Close the response. +*/ + +void valerie_response_close( valerie_response response ) +{ + if ( response != NULL ) + { + int index = 0; + for ( index = 0; index < response->count; index ++ ) + free( response->array[ index ] ); + free( response->array ); + free( response ); + } +} diff --git a/src/valerie/valerie_response.h b/src/valerie/valerie_response.h new file mode 100644 index 0000000..765c632 --- /dev/null +++ b/src/valerie/valerie_response.h @@ -0,0 +1,61 @@ +/* + * valerie_response.h -- Response + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_RESPONSE_H_ +#define _VALERIE_RESPONSE_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Structure for the response +*/ + +typedef struct +{ + char **array; + int size; + int count; + int append; +} +*valerie_response, valerie_response_t; + +/** API for accessing the response structure. +*/ + +extern valerie_response valerie_response_init( ); +extern valerie_response valerie_response_clone( valerie_response ); +extern int valerie_response_get_error_code( valerie_response ); +extern char *valerie_response_get_error_string( valerie_response ); +extern char *valerie_response_get_line( valerie_response, int ); +extern int valerie_response_count( valerie_response ); +extern void valerie_response_set_error( valerie_response, int, char * ); +extern int valerie_response_printf( valerie_response, size_t, char *, ... ); +extern int valerie_response_write( valerie_response, char *, int ); +extern void valerie_response_close( valerie_response ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_socket.c b/src/valerie/valerie_socket.c new file mode 100644 index 0000000..3ce761e --- /dev/null +++ b/src/valerie/valerie_socket.c @@ -0,0 +1,177 @@ +/* + * valerie_socket.c -- Client Socket + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "valerie_socket.h" + +/** Initialise the socket. +*/ + +valerie_socket valerie_socket_init( char *server, int port ) +{ + valerie_socket socket = malloc( sizeof( valerie_socket_t ) ); + if ( socket != NULL ) + { + memset( socket, 0, sizeof( valerie_socket_t ) ); + socket->fd = -1; + socket->server = strdup( server ); + socket->port = port; + } + return socket; +} + +/** Connect to the server. +*/ + +int valerie_socket_connect( valerie_socket connection ) +{ + int ret = 0; + struct hostent *host; + struct sockaddr_in sock; + + if ( connection->server != NULL ) + { + host = gethostbyname( connection->server ); + + memset( &sock, 0, sizeof( struct sockaddr_in ) ); + memcpy( &sock.sin_addr, host->h_addr, host->h_length ); + sock.sin_family = host->h_addrtype; + sock.sin_port = htons( connection->port ); + + if ( ( connection->fd = socket( AF_INET, SOCK_STREAM, 0 ) ) != -1 ) + ret = connect( connection->fd, (const struct sockaddr *)&sock, sizeof( struct sockaddr_in ) ); + else + ret = -1; + } + + return ret; +} + +/** Convenience constructor for a connected file descriptor. +*/ + +valerie_socket valerie_socket_init_fd( int fd ) +{ + valerie_socket socket = malloc( sizeof( valerie_socket_t ) ); + if ( socket != NULL ) + { + memset( socket, 0, sizeof( valerie_socket_t ) ); + socket->fd = fd; + socket->no_close = 1; + } + return socket; +} + +/** Read an arbitrarily formatted block of data from the server. +*/ + +int valerie_socket_read_data( valerie_socket socket, char *data, int length ) +{ + struct timeval tv = { 1, 0 }; + fd_set rfds; + int used = 0; + + data[ 0 ] = '\0'; + + FD_ZERO( &rfds ); + FD_SET( socket->fd, &rfds ); + + if ( select( socket->fd + 1, &rfds, NULL, NULL, &tv ) ) + { + used = read( socket->fd, data, length - 1 ); + if ( used > 0 ) + data[ used ] = '\0'; + else + used = -1; + } + + return used; +} + +/** Write an arbitrarily formatted block of data to the server. +*/ + +int valerie_socket_write_data( valerie_socket socket, char *data, int length ) +{ + int used = 0; + + while ( used >=0 && used < length ) + { + struct timeval tv = { 1, 0 }; + fd_set rfds; + fd_set wfds; + fd_set efds; + + FD_ZERO( &rfds ); + FD_SET( socket->fd, &rfds ); + FD_ZERO( &wfds ); + FD_SET( socket->fd, &wfds ); + FD_ZERO( &efds ); + FD_SET( socket->fd, &efds ); + + errno = 0; + + if ( select( socket->fd + 1, &rfds, &wfds, &efds, &tv ) ) + { + if ( errno != 0 || FD_ISSET( socket->fd, &efds ) || FD_ISSET( socket->fd, &rfds ) ) + { + used = -1; + } + else if ( FD_ISSET( socket->fd, &wfds ) ) + { + int inc = write( socket->fd, data + used, length - used ); + if ( inc > 0 ) + used += inc; + else + used = -1; + } + } + } + + return used; +} + +/** Close the socket. +*/ + +void valerie_socket_close( valerie_socket socket ) +{ + if ( socket->fd > 0 && !socket->no_close ) + close( socket->fd ); + free( socket->server ); + free( socket ); +} diff --git a/src/valerie/valerie_socket.h b/src/valerie/valerie_socket.h new file mode 100644 index 0000000..293faca --- /dev/null +++ b/src/valerie/valerie_socket.h @@ -0,0 +1,56 @@ +/* + * valerie_socket.h -- Client Socket + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _VALERIE_SOCKET_H_ +#define _VALERIE_SOCKET_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Structure for socket. +*/ + +typedef struct +{ + char *server; + int port; + int fd; + int no_close; +} +*valerie_socket, valerie_socket_t; + +/** Remote parser API. +*/ + +extern valerie_socket valerie_socket_init( char *, int ); +extern int valerie_socket_connect( valerie_socket ); +extern valerie_socket valerie_socket_init_fd( int ); +extern int valerie_socket_read_data( valerie_socket, char *, int ); +extern int valerie_socket_write_data( valerie_socket, char *, int ); +extern void valerie_socket_close( valerie_socket ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_status.c b/src/valerie/valerie_status.c new file mode 100644 index 0000000..fecd138 --- /dev/null +++ b/src/valerie/valerie_status.c @@ -0,0 +1,160 @@ +/* + * valerie_status.c -- Unit Status Handling + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include +#include + +/* Application header files */ +#include "valerie_status.h" +#include "valerie_tokeniser.h" +#include "valerie_util.h" + +/** Parse a unit status string. +*/ + +void valerie_status_parse( valerie_status status, char *text ) +{ + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + if ( valerie_tokeniser_parse_new( tokeniser, text, " " ) == 17 ) + { + status->unit = atoi( valerie_tokeniser_get_string( tokeniser, 0 ) ); + strncpy( status->clip, valerie_util_strip( valerie_tokeniser_get_string( tokeniser, 2 ), '\"' ), sizeof( status->clip ) ); + status->position = atol( valerie_tokeniser_get_string( tokeniser, 3 ) ); + status->speed = atoi( valerie_tokeniser_get_string( tokeniser, 4 ) ); + status->fps = atof( valerie_tokeniser_get_string( tokeniser, 5 ) ); + status->in = atol( valerie_tokeniser_get_string( tokeniser, 6 ) ); + status->out = atol( valerie_tokeniser_get_string( tokeniser, 7 ) ); + status->length = atol( valerie_tokeniser_get_string( tokeniser, 8 ) ); + + strncpy( status->tail_clip, valerie_util_strip( valerie_tokeniser_get_string( tokeniser, 9 ), '\"' ), sizeof( status->tail_clip ) ); + status->tail_position = atol( valerie_tokeniser_get_string( tokeniser, 10 ) ); + status->tail_in = atol( valerie_tokeniser_get_string( tokeniser, 11 ) ); + status->tail_out = atol( valerie_tokeniser_get_string( tokeniser, 12 ) ); + status->tail_length = atol( valerie_tokeniser_get_string( tokeniser, 13 ) ); + status->seek_flag = atoi( valerie_tokeniser_get_string( tokeniser, 14 ) ); + status->generation = atoi( valerie_tokeniser_get_string( tokeniser, 15 ) ); + status->clip_index = atoi( valerie_tokeniser_get_string( tokeniser, 16 ) ); + + if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "unknown" ) ) + status->status = unit_unknown; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "undefined" ) ) + status->status = unit_undefined; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "offline" ) ) + status->status = unit_offline; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "not_loaded" ) ) + status->status = unit_not_loaded; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "stopped" ) ) + status->status = unit_stopped; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "paused" ) ) + status->status = unit_paused; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "playing" ) ) + status->status = unit_playing; + else if ( !strcmp( valerie_tokeniser_get_string( tokeniser, 1 ), "disconnected" ) ) + status->status = unit_disconnected; + } + else + { + memset( status, 0, sizeof( valerie_status_t ) ); + fprintf( stderr, "Status thread changed?\n" ); + } + valerie_tokeniser_close( tokeniser ); +} + +/** Serialise a status into a string. +*/ + +char *valerie_status_serialise( valerie_status status, char *text, int length ) +{ + char *status_string = NULL; + + switch( status->status ) + { + case unit_undefined: + status_string = "undefined"; + break; + + case unit_offline: + status_string = "offline"; + break; + + case unit_not_loaded: + status_string = "not_loaded"; + break; + + case unit_stopped: + status_string = "stopped"; + break; + + case unit_playing: + status_string = "playing"; + break; + + case unit_unknown: + status_string = "unknown"; + break; + + case unit_paused: + status_string = "paused"; + break; + + case unit_disconnected: + status_string = "disconnected"; + break; + } + + snprintf( text, length, "%d %s \"%s\" %d %d %.2f %d %d %d \"%s\" %d %d %d %d %d %d %d\r\n", + status->unit, + status_string, + status->clip, + status->position, + status->speed, + status->fps, + status->in, + status->out, + status->length, + status->tail_clip, + status->tail_position, + status->tail_in, + status->tail_out, + status->tail_length, + status->seek_flag, + status->generation, + status->clip_index ); + + return text; +} + +/** Compare two status codes for changes. +*/ + +int valerie_status_compare( valerie_status status1, valerie_status status2 ) +{ + return memcmp( status1, status2, sizeof( valerie_status_t ) ); +} + +/** Copy status code info from dest to src. +*/ + +valerie_status valerie_status_copy( valerie_status dest, valerie_status src ) +{ + return memcpy( dest, src, sizeof( valerie_status_t ) ); +} diff --git a/src/valerie/valerie_status.h b/src/valerie/valerie_status.h new file mode 100644 index 0000000..295c242 --- /dev/null +++ b/src/valerie/valerie_status.h @@ -0,0 +1,85 @@ +/* + * valerie_status.h -- Unit Status Handling + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_STATUS_H_ +#define _VALERIE_STATUS_H_ + +#include "stdint.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Status codes +*/ + +typedef enum +{ + unit_unknown = 0, + unit_undefined, + unit_offline, + unit_not_loaded, + unit_stopped, + unit_playing, + unit_paused, + unit_disconnected +} +unit_status; + +/** Status structure. +*/ + +typedef struct +{ + int unit; + unit_status status; + char clip[ 2048 ]; + int32_t position; + int speed; + double fps; + int32_t in; + int32_t out; + int32_t length; + char tail_clip[ 2048 ]; + int32_t tail_position; + int32_t tail_in; + int32_t tail_out; + int32_t tail_length; + int seek_flag; + int generation; + int clip_index; + int dummy; +} +*valerie_status, valerie_status_t; + +/** DV1394 Status API +*/ + +extern void valerie_status_parse( valerie_status, char * ); +extern char *valerie_status_serialise( valerie_status, char *, int ); +extern int valerie_status_compare( valerie_status, valerie_status ); +extern valerie_status valerie_status_copy( valerie_status, valerie_status ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_tokeniser.c b/src/valerie/valerie_tokeniser.c new file mode 100644 index 0000000..c69e42d --- /dev/null +++ b/src/valerie/valerie_tokeniser.c @@ -0,0 +1,172 @@ +/* + * valerie_tokeniser.c -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include + +/* Application header files */ +#include "valerie_tokeniser.h" + +/** Initialise a tokeniser. +*/ + +valerie_tokeniser valerie_tokeniser_init( ) +{ + valerie_tokeniser tokeniser = malloc( sizeof( valerie_tokeniser_t ) ); + if ( tokeniser != NULL ) + memset( tokeniser, 0, sizeof( valerie_tokeniser_t ) ); + return tokeniser; +} + +/** Clear the tokeniser. +*/ + +static void valerie_tokeniser_clear( valerie_tokeniser tokeniser ) +{ + int index = 0; + for ( index = 0; index < tokeniser->count; index ++ ) + free( tokeniser->tokens[ index ] ); + tokeniser->count = 0; + free( tokeniser->input ); + tokeniser->input = NULL; +} + +/** Append a string to the tokeniser. +*/ + +static int valerie_tokeniser_append( valerie_tokeniser tokeniser, char *token ) +{ + int error = 0; + + if ( tokeniser->count == tokeniser->size ) + { + tokeniser->size += 20; + tokeniser->tokens = realloc( tokeniser->tokens, tokeniser->size * sizeof( char * ) ); + } + + if ( tokeniser->tokens != NULL ) + { + tokeniser->tokens[ tokeniser->count ++ ] = strdup( token ); + } + else + { + tokeniser->count = 0; + error = -1; + } + return error; +} + +/** Parse a string by splitting on the delimiter provided. +*/ + +int valerie_tokeniser_parse_new( valerie_tokeniser tokeniser, char *string, char *delimiter ) +{ + int count = 0; + int length = strlen( string ); + int delimiter_size = strlen( delimiter ); + int index = 0; + char *token = strdup( string ); + + valerie_tokeniser_clear( tokeniser ); + tokeniser->input = strdup( string ); + strcpy( token, "" ); + + for ( index = 0; index < length; ) + { + char *start = string + index; + char *end = strstr( start, delimiter ); + + if ( end == NULL ) + { + strcat( token, start ); + valerie_tokeniser_append( tokeniser, token ); + index = length; + count ++; + } + else if ( start != end ) + { + strncat( token, start, end - start ); + index += end - start; + if ( token[ 0 ] != '\"' || ( token[ 0 ] == '\"' && token[ strlen( token ) - 1 ] == '\"' ) ) + { + valerie_tokeniser_append( tokeniser, token ); + strcpy( token, "" ); + count ++; + } + else while ( strncmp( string + index, delimiter, delimiter_size ) == 0 ) + { + strncat( token, delimiter, delimiter_size ); + index += delimiter_size; + } + } + else + { + index += strlen( delimiter ); + } + } + + /* Special case - malformed string condition */ + if ( !strcmp( token, "" ) ) + { + count = 0 - ( count - 1 ); + valerie_tokeniser_append( tokeniser, token ); + } + + free( token ); + return count; +} + +/** Get the original input. +*/ + +char *valerie_tokeniser_get_input( valerie_tokeniser tokeniser ) +{ + return tokeniser->input; +} + +/** Get the number of tokens. +*/ + +int valerie_tokeniser_count( valerie_tokeniser tokeniser ) +{ + return tokeniser->count; +} + +/** Get a token as a string. +*/ + +char *valerie_tokeniser_get_string( valerie_tokeniser tokeniser, int index ) +{ + if ( index < tokeniser->count ) + return tokeniser->tokens[ index ]; + else + return NULL; +} + +/** Close the tokeniser. +*/ + +void valerie_tokeniser_close( valerie_tokeniser tokeniser ) +{ + valerie_tokeniser_clear( tokeniser ); + free( tokeniser->tokens ); + free( tokeniser ); +} diff --git a/src/valerie/valerie_tokeniser.h b/src/valerie/valerie_tokeniser.h new file mode 100644 index 0000000..02764dd --- /dev/null +++ b/src/valerie/valerie_tokeniser.h @@ -0,0 +1,55 @@ +/* + * valerie_tokeniser.h -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_TOKENISER_H_ +#define _VALERIE_TOKENISER_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Structure for tokeniser. +*/ + +typedef struct +{ + char *input; + char **tokens; + int count; + int size; +} +*valerie_tokeniser, valerie_tokeniser_t; + +/** Remote parser API. +*/ + +extern valerie_tokeniser valerie_tokeniser_init( ); +extern int valerie_tokeniser_parse_new( valerie_tokeniser, char *, char * ); +extern char *valerie_tokeniser_get_input( valerie_tokeniser ); +extern int valerie_tokeniser_count( valerie_tokeniser ); +extern char *valerie_tokeniser_get_string( valerie_tokeniser, int ); +extern void valerie_tokeniser_close( valerie_tokeniser ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/valerie/valerie_util.c b/src/valerie/valerie_util.c new file mode 100644 index 0000000..0a350c2 --- /dev/null +++ b/src/valerie/valerie_util.c @@ -0,0 +1,77 @@ +/* + * valerie_util.c -- General Purpose Client Utilities + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include + +/* Application header files */ +#include "valerie_util.h" + +/** Remove LF or CR/LF terminations from the input string. +*/ + +char *valerie_util_chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +/** Remove leading and trailing spaces from the input string. +*/ + +char *valerie_util_trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +/** Strip the specified string of leading and trailing 'value' (ie: "). +*/ + +char *valerie_util_strip( char *input, char value ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, value ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == value ) + strcpy( input, input + 1 ); + } + return input; +} diff --git a/src/valerie/valerie_util.h b/src/valerie/valerie_util.h new file mode 100644 index 0000000..5854f10 --- /dev/null +++ b/src/valerie/valerie_util.h @@ -0,0 +1,37 @@ +/* + * valerie_util.h -- General Purpose Client Utilities + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _VALERIE_UTIL_H_ +#define _VALERIE_UTIL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern char *valerie_util_chomp( char * ); +extern char *valerie_util_trim( char * ); +extern char *valerie_util_strip( char *, char ); + +#ifdef __cplusplus +} +#endif + +#endif