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