/* gap_story_render_processor.c
 *
 *
 *  GAP storyboard rendering processor.
 *
 *  GAP video encoder tool procedures for STORYBOARD file based video encoding
 *  This module is the Storyboard processor that reads instructions from the storyboard
 *  file, fetches input frames, and renders composite video frames
 *  according to the instructions in the storyboard file.
 *
 *  The storyboard processor is typically used to:
 *   - check storyboard syntax and deliver information (number of total frames)
 *     for the master encoder dialog.
 *   - render the composite video frame at specified master frame number
 *     (is called as frame fetching utility by all encoders)
 *
 *   The storyboard processor provides fuctionality to mix audiodata,
 *   this is usually done in the master encoder dialog (before starting the selected encoder)
 *
 * Copyright (C) 2006 Wolfgang Hofer <hof@gimp.org>
 *
 *
 * This program is free software; you can 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, see
 * <http://www.gnu.org/licenses/>.
 */

/*
 * 2006.06.25  hof  - created (moved stuff from the former gap_gve_story modules to this  new module)
 *                  - new features:
 *                       use shadow tracks, (implicite) generated by the new overlap attribute
 *                       normal track numbers are         1, 3, 5, 7
 *                       corresponding shadow tracks are  0, 2, 4, 6   (shadow = normal -1)
 *                  - support video frame flipping (hor/vertical)
 *
 */

#include <config.h>

/* SYTEM (UNIX) includes */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <math.h>
#include <errno.h>

#include <dirent.h>


#include <glib/gstdio.h>



/* GIMP includes */
#include "gtk/gtk.h"
#include "gap-intl.h"
#include "libgimp/gimp.h"


#include "gap_libgapbase.h"
#include "gap_libgimpgap.h"
#include "gap_lib_common_defs.h"
#include "gap_audio_util.h"
#include "gap_audio_wav.h"
#include "gap_story_file.h"
#include "gap_layer_copy.h"
#include "gap_story_main.h"
#include "gap_story_render_audio.h"
#include "gap_story_render_processor.h"
#include "gap_fmac_name.h"
#include "gap_frame_fetcher.h"
#include "gap_image.h"
#include "gap_accel_char.h"
#include "gap_mov_exec.h"


/* data for the storyboard proceesor frame fetching
 */
typedef struct GapStbFetchData {   /* nick: gfd */
  gint32        comp_image_id;
  gint32        tmp_image_id;
  gint32        layer_id;

  gchar  *framename;
  gdouble rotate;
  gdouble opacity;
  gdouble scale_x;
  gdouble scale_y;
  gdouble move_x;
  gdouble move_y;
  GapStoryRenderFrameRangeElem *frn_elem;

  gint32        localframe_index;
  gint32        local_stepcount;
  gdouble       localframe_tween_rest;
  gboolean      keep_proportions;
  gboolean      fit_width;
  gboolean      fit_height;
  GapStoryRenderFrameType   frn_type;
  const char      *trak_filtermacro_file;   /* dont g_free this ! */

  gdouble          red_f;
  gdouble          green_f;
  gdouble          blue_f;
  gdouble          alpha_f;
  const char      *movepath_file_xml;      /* dont g_free this ! */
  gdouble          movepath_framePhase;


  /* performance stuff for bypass render engine (where possible) */
  GapStoryFetchResult *gapStoryFetchResult;
  gboolean             isRgb888Result;        /* TRUE use rgb888 buffer to bypass convert to gimp drawable */

}  GapStbFetchData;


/* data for video frame prefetch using multiple threads
 * (used optional depending on gimprc configuration)
 */
typedef struct VideoPrefetchData   /* vpre */
{
  t_GVA_Handle        *gvahand;
  gint32               prefetchFrameNumber;
  gint32               targetFrameNumber;
  gboolean             isPlayingBackwards;
  gboolean             isPrefetchThreadRunning;
  GMutex              *mutex;
  GCond               *targetFrameReadyCond;
  GCond               *prefetchDoneCond;

} VideoPrefetchData;


/* Workaround:
 * calls to g_mutex_free (mutex) often (but not each time) FAIL when closing videohandle.
 * even after checks that no prefetch thread is still using the mutex.
 *
 * To aviod those type of CRASH a workaround was implemented that puts unused mutex
 * in a mutex pool for reuse instead of removing the mutex,
 * to aviod the call to g_mutex_free and to avoid allocating too many mutex resources.
 *
 *
 * GThread-ERROR **: file gthread-posix.c: line 171 (g_mutex_free_posix_impl):
 *   error 'Device or Ressource is busy' during 'pthread_mutex_destroy ((pthread_mutex_t *) mutex)'
 */
typedef struct StbMutexPool   /* mutxp */
{
  GMutex      *mutex;
  gboolean     isFree;
  void        *next;

} StbMutexPool;



/*************************************************************
 *         STORYBOARD FUNCTIONS                              *
 *************************************************************
 */

#define MAX_IMG_CACHE_ELEMENTS 6
#define MULTITHREAD_PREFETCH_AMOUNT 3
/* SMALL_FCACHE_SIZE_AT_FORWARD_READ is set 2 frames greater than MULTITHREAD_PREFETCH_AMOUNT
 * With smaller fcache the prefetched frames are often overwritten by the prefetch thread
 * before the main thread can read them from the fcache. This triggers many unwanted seek operations
 * with significant loss of performance when multithreaded processing is activated.
 *
 */
#define SMALL_FCACHE_SIZE_AT_FORWARD_READ  (MULTITHREAD_PREFETCH_AMOUNT + 2)


#define GVAHAND_HOLDER_RANK_MIN_LEVEL        0
#define GVAHAND_HOLDER_RANK_NO_BENEFIT_LEVEL 1
#define GVAHAND_HOLDER_RANK_2                2
#define GVAHAND_HOLDER_RANK_3                3
#define GVAHAND_HOLDER_RANK_4                4
#define GVAHAND_HOLDER_RANK_MAX_LEVEL        5

#define GAP_VIDEO_STORYBOARD_PRESCALE_ENABLE_DOWNSCALE_CHAIN "video-storyboard-prescale-enable-downscale-chain"


extern int gap_debug;  /* 1 == print debug infos , 0 dont print debug infos */

static GThreadPool         *prefetchThreadPool = NULL;
static StbMutexPool        *mutexPool = NULL;

static gint32 global_monitor_image_id = -1;
static gint32 global_monitor_display_id = -1;


static GMutex * p_pooled_g_mutex_new();
static void     p_pooled_g_mutex_free(GMutex *mutex);


static void     p_debug_print_render_section_names(GapStoryRenderVidHandle *vidhand);
static void     p_frame_backup_save(  char *key
                  , gint32 image_id
                  , gint32 layer_id
                  , gint32  master_frame_nr
                  , gboolean multilayer
                  );
static void     p_debug_dup_image(gint32 image_id);
static void     p_encoding_monitor(  char *key
                  , gint32 image_id
                  , gint32 layer_id
                  , gint32  master_frame_nr
                  );
static GapStoryRenderErrors * p_new_stb_error(void);
static void     p_init_stb_error(GapStoryRenderErrors *sterr);
static void     p_free_stb_error(GapStoryRenderErrors *sterr);
static void     p_set_stb_error(GapStoryRenderErrors *sterr, char *errtext);
static void     p_refresh_min_max_vid_tracknumbers(GapStoryRenderVidHandle *vidhand);
static void     p_select_section_by_name(GapStoryRenderVidHandle *vidhand
                  , const char *section_name);

static char*    p_fetch_framename   (GapStoryRenderFrameRangeElem *frn_list
                            , gint32 master_frame_nr                   /* starts at 1 */
                            , gint32 track
                            , GapStbFetchData *gfd        /* out: result structure of the fetch */
                            );
static void   p_calculate_frames_to_handle(GapStoryRenderFrameRangeElem *frn_elem);

static GapStoryRenderFrameRangeElem *  p_new_framerange_element(
                           GapStoryRenderFrameType  frn_type
                          ,gint32 track
                          ,const char *basename       /* basename or full imagename  for frn_type GAP_FRN_IMAGE */
                          ,const char *ext            /* NULL for frn_type GAP_FRN_IMAGE */
                          ,gint32  frame_from   /* IN: range start */
                          ,gint32  frame_to     /* IN: range end */
                          ,const char *storyboard_file  /* IN: NULL if no storyboard file is used */
                          ,const char *preferred_decoder  /* IN: NULL if no preferred_decoder is specified */
                          ,const char *filtermacro_file  /* IN: NULL, or name of the macro file */
                          ,GapStoryRenderFrameRangeElem *frn_list /* NULL or list of already known ranges */
                          ,GapStoryRenderErrors *sterr          /* element to store Error/Warning report */
                          ,gint32 seltrack      /* IN: select videotrack number 1 upto 99 for GAP_FRN_MOVIE */
                          ,gint32 exact_seek    /* IN: 0 fast seek, 1 exact seek (only for GVA Videoreads) */
                          ,gdouble delace    /* IN: 0.0 no deinterlace, 1.0-1.99 odd 2.0-2.99 even rows (only for GVA Videoreads) */
                          ,gdouble step_density    /* IN:  1==normal stepsize 1:1   0.5 == each frame twice, 2.0 only every 2nd frame */
                          ,gint32 flip_request            /* 0 NONE, 1 flip horizontal, 2 flip vertical, 3 rotate 180degree */
                          ,const char   *mask_name        /* reference to layer mask definition */
                          ,gdouble mask_stepsize          /* stepsize for the layer mask */
                          ,GapStoryMaskAnchormode mask_anchor  /* how to apply the layer mask */
                          ,gboolean mask_disable
                          ,gint32 fmac_total_steps
                          ,gint32 fmac_accel
                          ,const char *colormask_file  /* IN: NULL, or name of the colormask parameter file */
                          );
static void       p_add_frn_list(GapStoryRenderVidHandle *vidhand, GapStoryRenderFrameRangeElem *frn_elem);
static void       p_step_all_vtrack_attributes(gint32 track, gint32 frames_to_handle
                       ,GapStoryRenderVTrackArray *vtarr
                      );
static void       p_set_vtrack_attributes(GapStoryRenderFrameRangeElem *frn_elem
                       ,GapStoryRenderVTrackArray *vtarr
                      );


static void       p_vidclip_add_as_is(GapStoryRenderFrameRangeElem *frn_elem
                      ,GapStoryRenderVidHandle *vidhand
                      ,GapStoryRenderVTrackArray *vtarr
                      );
static void       p_vidclip_shadow_add_silence(gint32 shadow_track
                      ,gint32 fill_shadow_frames
                      ,GapStoryRenderVidHandle *vidhand
                      ,GapStoryRenderVTrackArray *vtarr
                      ,const char *storyboard_file
                      );
static void       p_recalculate_range_part(GapStoryRenderFrameRangeElem *frn_elem
                      , gint32 lost_frames
                      , gint32 rest_frames
                      );
static void       p_copy_vattr_values(gint32 src_track
                      , gint32 dst_track
                      , GapStoryRenderVTrackArray *vtarr
                      );
static void       p_vidclip_split_and_add_frn_list(GapStoryRenderFrameRangeElem *frn_elem,
                      GapStoryRenderVidHandle *vidhand,
                      GapStoryRenderVTrackArray *vtarr,
                      const char *storyboard_file
                      );
static void       p_vidclip_add(GapStoryRenderFrameRangeElem *frn_elem
                      ,GapStoryRenderVidHandle *vidhand
                      ,GapStoryRenderVTrackArray *vtarr
                      ,const char *storyboard_file
                      ,gboolean first_of_group
                      );

static void       p_clear_vattr_array(GapStoryRenderVTrackArray *vtarr);

static gboolean   p_fmt_string_has_framenumber_format(const char *fmt_string);
static gboolean   p_fmt_string_has_videobasename_format(const char *fmt_string);
static void       p_storyboard_analyze(GapStoryBoard *stb
                      , gint32 *mainsection_frame_count
                      , GapStoryRenderVidHandle *vidhand
                      );
static GapStoryRenderFrameRangeElem *  p_framerange_list_from_storyboard(
                           const char *storyboard_file
                          ,gint32 *frame_count
                          ,GapStoryRenderVidHandle *vidhand
                          ,GapStoryBoard *stb_mem_ptr
                          );
static void       p_free_framerange_list(GapStoryRenderFrameRangeElem * frn_list);

static GapStoryRenderSection * p_new_render_section(const char *section_name);
static void       p_append_render_section_to_vidhand(GapStoryRenderVidHandle *vidhand
                          , GapStoryRenderSection *new_render_section);

static void       p_open_mask_vidhand(GapStoryElem *stb_elem
                          , GapStoryRenderMaskDefElem *maskdef_elem);
static void       p_copy_mask_definitions_to_vidhand(GapStoryBoard *stb_ptr
                          , GapStoryRenderVidHandle *vidhand);
static void       p_free_mask_definitions(GapStoryRenderVidHandle *vidhand);
static GapStoryRenderMaskDefElem * p_find_maskdef_by_name(GapStoryRenderVidHandle *vidhand, const char *mask_name);
static gint32     p_mask_fetcher(GapStoryRenderVidHandle *vidhand
                    , const char *mask_name
                    , gint32 master_frame_nr
                    , gint32 mask_width
                    , gint32 mask_height
                    , gint32 *layer_id               /* OUT: Id of the only layer in the composite image */
                    , gboolean *was_last_maskframe   /* OUT: true if this was the last maskframe */
                    , gboolean makeGrayFlattened     /* IN   true flatten and convert to GRAY, false keep color and alpha channel */
                    );
static void       p_fetch_and_add_layermask(GapStoryRenderVidHandle *vidhand
                    , GapStoryRenderFrameRangeElem *frn_elem
                    , gint32 local_stepcount
                    , gint32 image_id
                    , gint32 layer_id
                    , GapStoryMaskAnchormode mask_anchor
                    );

static GapStoryRenderVidHandle * p_open_video_handle_private(    gboolean ignore_audio
                      , gboolean ignore_video
                      , gboolean create_audio_tmp_files
                      , gdouble  *progress_ptr
                      , char *status_msg
                      , gint32 status_msg_len
                      , const char *storyboard_file
                      , const char *basename
                      , const char *ext
                      , gint32  frame_from
                      , gint32  frame_to
                      , gint32 *frame_count   /* output total frame_count , or 0 on failure */
                      , gboolean do_gimp_progress
                      , GapLibTypeInputRange input_mode
                      , const char *imagename
                      , const char *preferred_decoder
                      , gint32 seltrack
                      , gint32 exact_seek
                      , gdouble delace
                      , gboolean compensate_framerange
                      , GapStoryBoard *stb_mem_ptr
                      , char *util_sox
                      , char *util_sox_options
                      );
static gint32     p_exec_filtermacro(gint32 image_id
                         , gint32 layer_id
                         , const char *filtermacro_file
                         , const char *filtermacro_file_to
                         , gdouble current_step
                         , gint32 total_steps
                         , gint accelerationCharacteristic
                         );
static gboolean   p_transform_operate_on_full_layer(GapStoryCalcAttr *calculated
                         , gint32 comp_image_id, gint32 tmp_image_id
                         , GapStoryRenderFrameRangeElem *frn_elem
                         );
static void       p_transform_rotate_layer_at(gint32 image_id, gint32 layer_id, gdouble rotate
                         , gint image_offset_x, gint image_offset_y);
static gint32     p_transform_with_movepath_processing( gint32 comp_image_id
                         , gint32 tmp_image_id
                         , gint32 layer_id
                         , gboolean keep_proportions
                         , gboolean fit_width
                         , gboolean fit_height
                         , gdouble rotate     /* rotation in degree */
                         , gdouble opacity    /* 0.0 upto 1.0 */
                         , gdouble scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                         , gdouble move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         , const char *movepath_file_xml
                         , gdouble movepath_framePhase
                         );
static void       p_transform_postprocessing(gint32 new_layer_id
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gdouble opacity
                         , gint32 local_stepcount
                         , gint32 layermode
                         );
static gint32     p_transform_and_add_layer( gint32 comp_image_id
                         , gint32 tmp_image_id
                         , gint32 layer_id
                         , gboolean keep_proportions
                         , gboolean fit_width
                         , gboolean fit_height
                         , gdouble rotate     /* rotation in degree */
                         , gdouble opacity    /* 0.0 upto 1.0 */
                         , gdouble scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                         , gdouble move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                         , const char *filtermacro_file
                         , gint32 flip_request
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         , const char *movepath_file_xml
                         , gdouble movepath_framePhase
                         );
static gint32     p_prepare_RGB_image(gint32 image_id);

static void       p_limit_open_videohandles(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 currently_open_videohandles
                      , gint32 l_max_open_videohandles
                      );

static gint32     p_calculateGvahandHolderRank(GapStoryRenderFrameRangeElem *frn_elem
                            , gint32 videoFrameNrToBeReadNext
                            , gint32 track);

static t_GVA_Handle * p_try_to_steal_gvahand(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , GapStoryRenderFrameRangeElem *requesting_frn_elem
                      );
static void       p_split_delace_value(gdouble delace
                      , gdouble localframe_tween_rest
                      , gint32 *deinterlace_ptr
                      , gdouble *threshold_ptr);
static void       p_conditional_delace_drawable(GapStbFetchData *gfd, gint32 drawable_id);

static void       p_init_gfd(GapStbFetchData *gfd);

static gboolean   p_is_larger_image_variant_expected(GapStbFetchData *gfdCurrent
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 vid_width
                      , gint32 vid_height
                      , gint32 originalWidth
                      , gint32 originalHeight
                      , gint32 currentPrescaleWidth
                      , gint32 currentPrescaleHeight
                      );

static gint32     p_prescale_image_size_handling(GapStbFetchData *gfdCurrent
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 vid_width
                      , gint32 vid_height
                      );

static void       p_stb_render_image_or_animimage(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 vid_width, gint32 vid_height);
static gboolean   p_is_another_clip_playing_the_same_video_backwards(GapStoryRenderFrameRangeElem *frn_elem_ref);
static void       p_check_and_open_video_handle(GapStoryRenderFrameRangeElem *frn_elem
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , const gchar *videofile
                      );


#define NEAR_FRAME_DISTANCE 36

static void    p_initOptionalMulitprocessorSupport(GapStoryRenderVidHandle *vidhand);

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static void    p_call_GVA_close(t_GVA_Handle *gvahand);
static void    p_get_gapStoryFetchResult_from_fcacheFetchResult(GapStbFetchData *gfd, GVA_fcache_fetch_result *fcacheFetchResult);
static void    p_stb_render_movie_single_processor(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32  vid_width
                      , gint32  vid_height);
static gboolean p_call_GVA_get_next_frame_andSendReadySignal(VideoPrefetchData *vpre, gint32 targetFrameNumber);
static void    p_videoPrefetchWorkerThreadFunction (VideoPrefetchData *vpre);
static gint32  p_getPredictedNextFramenr(gint32 targetFrameNr, GapStoryRenderFrameRangeElem *frn_elem);
static void    p_stb_render_movie_multiprocessor(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32  vid_width
                      , gint32  vid_height);

#endif

static void       p_stb_render_movie(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32  vid_width, gint32  vid_height);
static void       p_stb_render_section(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32  vid_width, gint32  vid_height
                      , const char *section_name);
static gboolean    p_check_next_composite_frame_includes_same_image(GapStbFetchData *gfdCurrent
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr);
static void       p_stb_render_frame_images(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr, gint32 vid_width, gint32 vid_height);
static void       p_stb_render_composite_image_postprocessing(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32  vid_width, gint32  vid_height
                      , char *filtermacro_file
                      , const char *section_name
                      );
static void       p_stb_render_result_monitoring(GapStbFetchData *gfd, gint32 master_frame_nr);


static void       p_paste_logo_pattern(gint32 drawable_id
                      , gint32 logo_pattern_id
                      , gint32 offsetX
                      , gint32 offsetY
                      );

static char*      p_get_insert_area_filename(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand);
static void       p_do_insert_area_processing(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand);
static gint32     p_prepare_GRAY_image(gint32 image_id);
static char*      p_get_insert_alpha_filename(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand);
static void       p_do_insert_alpha_processing(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand);

static gboolean  p_isFiltermacroActive(const char *filtermacro_file);
static gboolean  p_story_render_bypass_where_possible(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr  /* starts at 1 */
                    , gint32  vid_width       /* desired Video Width in pixels */
                    , gint32  vid_height      /* desired Video Height in pixels */
                    , gboolean enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                    , GapStoryFetchResult      *gapStoryFetchResult
                    );

static gint32    p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr       /* starts at 1 */
                    , gint32  vid_width            /* desired Video Width in pixels */
                    , gint32  vid_height           /* desired Video Height in pixels */
                    , char *filtermacro_file       /* NULL if no filtermacro is used */
                    , gint32 *layer_id             /* output: Id of the only layer in the composite image */
                    , const char *section_name     /* NULL for main section */
                    , gboolean enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                    , GapStoryFetchResult      *gapStoryFetchResult
                    );


/* --------------------------
 * p_pooled_g_mutex_new
 * --------------------------
 */
static GMutex *
p_pooled_g_mutex_new()
{
  StbMutexPool *mutexp;

  for(mutexp = mutexPool; mutexp != NULL; mutexp=mutexp->next)
  {
    if(mutexp->isFree == TRUE)
    {
      mutexp->isFree = FALSE;
      if(gap_debug)
      {
        printf("p_pooled_g_mutex_new: recycle mutex:%ld\n"
          ,(long)mutexp->mutex
          );
      }
      return(mutexp->mutex);
    }
  }

  mutexp  = g_new(StbMutexPool, 1);
  mutexp->isFree = FALSE;
  mutexp->mutex = g_mutex_new();
  mutexp->next = mutexPool;
  mutexPool = mutexp;

  if(gap_debug)
  {
    printf("p_pooled_g_mutex_new: allocated new mutex:%ld\n"
       ,(long)mutexp->mutex
       );
  }

  return(mutexp->mutex);

}  /* end p_pooled_g_mutex_new */

/* --------------------------
 * p_pooled_g_mutex_free
 * --------------------------
 */
static void
p_pooled_g_mutex_free(GMutex       *mutex)
{
  StbMutexPool *mutexp;

  for(mutexp = mutexPool; mutexp != NULL; mutexp=mutexp->next)
  {
    if(mutexp->mutex == mutex)
    {
      mutexp->isFree = TRUE;
      if(gap_debug)
      {
        printf("p_pooled_g_mutex_free: disposed mutex:%ld for later reuse\n"
           ,(long)mutexp->mutex
           );
      }
      return;
    }
  }

  printf("p_pooled_g_mutex_free: ** ERROR mutex:%ld not found in pool\n"
           ,(long)mutex
           );

} /* end p_pooled_g_mutex_free */

/* ----------------------------------------------------
 * gap_story_render_debug_print_maskdef_elem
 * ----------------------------------------------------
 * print all List elements for the given track
 * (negative track number will print all elements)
 */
void
gap_story_render_debug_print_maskdef_elem(GapStoryRenderMaskDefElem *maskdef_elem, gint l_idx)
{
  if(maskdef_elem)
  {
      printf("\n  ===== maskdef_elem start ============ \n" );

      printf("  [%d] record_type       : %d\n", (int)l_idx, (int)maskdef_elem->record_type );
      printf("  [%d] mask_name         : ", (int)l_idx); if(maskdef_elem->mask_name) { printf("%s\n", maskdef_elem->mask_name );} else { printf ("(null)\n"); }
      printf("  [%d] mask_vidhand      : %ld\n", (int)l_idx, (long)maskdef_elem->mask_vidhand );
      printf("  [%d] frame_count       : %d\n", (int)l_idx, (int)maskdef_elem->frame_count );
      printf("  [%d] flip_request      : %d\n", (int)l_idx, (int)maskdef_elem->flip_request );

      if(maskdef_elem->mask_vidhand)
      {
         printf("Storyboard list for this maskdef_elem:\n" );
         gap_story_render_debug_print_framerange_list(maskdef_elem->mask_vidhand->frn_list, -1);
      }

      printf("\n  ===== maskdef_elem end  ============ \n" );

  }
}  /* end gap_story_render_debug_print_maskdef_elem */

/* ----------------------------------------------------
 * p_frn_record_type_to_string
 * ----------------------------------------------------
 */
static const char *
p_frn_record_type_to_string(GapStoryRenderFrameType frn_type)
{
  switch(frn_type)
  {
    case GAP_FRN_SILENCE:     return("GAP_FRN_SILENCE"); break;
    case GAP_FRN_COLOR:       return("GAP_FRN_COLOR"); break;
    case GAP_FRN_IMAGE:       return("GAP_FRN_IMAGE"); break;
    case GAP_FRN_ANIMIMAGE:   return("GAP_FRN_ANIMIMAGE"); break;
    case GAP_FRN_FRAMES:      return("GAP_FRN_FRAMES"); break;
    case GAP_FRN_MOVIE:       return("GAP_FRN_MOVIE"); break;
    case GAP_FRN_SECTION:     return("GAP_FRN_SECTION"); break;
  }
  return("** UNDEFINED RECORD TYPE **");
}  /* end p_frn_record_type_to_string */

/* ----------------------------------------------------
 * gap_story_render_debug_print_frame_elem
 * ----------------------------------------------------
 * print all List elements for the given track
 * (negative track number will print all elements)
 */
void
gap_story_render_debug_print_frame_elem(GapStoryRenderFrameRangeElem *frn_elem, gint l_idx)
{
  static const char *normal_track = " (normal)";
  static const char *shadow_track = " (shadow)";

  if(frn_elem)
  {
      const char *track_type;

      track_type = normal_track;
      if((frn_elem->track & 1) == 0)
      {
        track_type = shadow_track;
      }

      printf("\n  [%d] frn_type          : %d   %s\n", (int)l_idx, (int)frn_elem->frn_type
                                                ,p_frn_record_type_to_string(frn_elem->frn_type));
      printf("  [%d] itrack (internal) : %d  %s\n", (int)l_idx, (int)frn_elem->track, track_type );
      printf("  [%d] track (file)      : %d\n", (int)l_idx, (int)(1+(frn_elem->track / 2)) );
      printf("  [%d] basename          : ", (int)l_idx); if(frn_elem->basename) { printf("%s\n", frn_elem->basename );} else { printf ("(null)\n"); }
      printf("  [%d] ext               : ", (int)l_idx); if(frn_elem->ext) { printf("%s\n", frn_elem->ext );} else { printf ("(null)\n"); }
      printf("  [%d] gvahand           : %ld\n", (int)l_idx, (long)frn_elem->gvahand );
      printf("  [%d] seltrack          : %d\n", (int)l_idx, (int)frn_elem->seltrack );
      printf("  [%d] exact_seek        : %d\n", (int)l_idx, (int)frn_elem->exact_seek );
      printf("  [%d] delace            : %.2f\n", (int)l_idx, (float)frn_elem->delace );
      printf("  [%d] filtermacro_file  : ", (int)l_idx); if(frn_elem->filtermacro_file) { printf("%s\n", frn_elem->filtermacro_file );} else { printf ("(null)\n"); }
      printf("  [%d] f.macro_file_to   : ", (int)l_idx); if(frn_elem->filtermacro_file_to) { printf("%s\n", frn_elem->filtermacro_file_to );} else { printf ("(null)\n"); }
      printf("  [%d] fmac_total_steps  : %d\n", (int)l_idx, (int)frn_elem->fmac_total_steps );
      printf("  [%d] fmac_accel        : %d\n", (int)l_idx, (int)frn_elem->fmac_accel );

      printf("  [%d] frame_from        : %.4f\n", (int)l_idx, (float)frn_elem->frame_from );
      printf("  [%d] frame_to          : %.4f\n", (int)l_idx, (float)frn_elem->frame_to );
      printf("  [%d] frames_to_handle  : %d\n", (int)l_idx, (int)frn_elem->frames_to_handle);
      printf("  [%d] delta             : %d\n", (int)l_idx, (int)frn_elem->delta );
      printf("  [%d] step_density      : %.4f\n", (int)l_idx, (float)frn_elem->step_density );

      if(frn_elem->keep_proportions)
      {printf("  [%d] keep_proportions  : TRUE\n", (int)l_idx );}
      else
      {printf("  [%d] keep_proportions  : FALSE\n", (int)l_idx );}

      if(frn_elem->fit_width)
      {printf("  [%d] fit_width         : TRUE\n", (int)l_idx );}
      else
      {printf("  [%d] fit_width         : FALSE\n", (int)l_idx );}

      if(frn_elem->fit_height)
      {printf("  [%d] fit_height        : TRUE\n", (int)l_idx );}
      else
      {printf("  [%d] fit_height        : FALSE\n", (int)l_idx );}


      printf("  [%d] flip_request       : %d\n", (int)l_idx, (int)frn_elem->flip_request );
      if(frn_elem->mask_name)
      {
        printf("  [%d] mask_name         : %s\n", (int)l_idx,  frn_elem->mask_name);
        printf("  [%d] mask_anchor       : %d\n", (int)l_idx, (int)frn_elem->mask_anchor );
        printf("  [%d] mask_stepsize     : %.4f\n", (int)l_idx, (float)frn_elem->mask_stepsize );
        printf("  [%d] mask_framecount   : %.4f\n", (int)l_idx, (float)frn_elem->mask_framecount );
      }
      else
      {
        printf("  [%d] mask_name         : (null)\n", (int)l_idx );
      }



      printf("  [%d] wait_untiltime_sec : %f\n", (int)l_idx, (float)frn_elem->wait_untiltime_sec );
      printf("  [%d] wait_untilframes   : %d\n", (int)l_idx, (int)frn_elem->wait_untilframes );


      printf("  [%d] opacity_from         : %f\n", (int)l_idx, (float)frn_elem->opacity_from );
      printf("  [%d] opacity_to           : %f\n", (int)l_idx, (float)frn_elem->opacity_to );
      printf("  [%d] opacity_dur          : %d\n", (int)l_idx, (int)frn_elem->opacity_dur );
      printf("  [%d] opacity_accel        : %d\n", (int)l_idx, (int)frn_elem->opacity_accel );
      printf("  [%d] opacity_frames_done  : %d\n", (int)l_idx, (int)frn_elem->opacity_frames_done );

      printf("  [%d] rotate_from          : %f\n", (int)l_idx, (float)frn_elem->rotate_from );
      printf("  [%d] rotate_to            : %f\n", (int)l_idx, (float)frn_elem->rotate_to );
      printf("  [%d] rotate_dur           : %d\n", (int)l_idx, (int)frn_elem->rotate_dur );
      printf("  [%d] rotate_accel         : %d\n", (int)l_idx, (int)frn_elem->rotate_accel );
      printf("  [%d] rotate_frames_done   : %d\n", (int)l_idx, (int)frn_elem->rotate_frames_done );

      printf("  [%d] scale_x_from         : %f\n", (int)l_idx, (float)frn_elem->scale_x_from );
      printf("  [%d] scale_x_to           : %f\n", (int)l_idx, (float)frn_elem->scale_x_to );
      printf("  [%d] scale_x_dur          : %d\n", (int)l_idx, frn_elem->scale_x_dur );
      printf("  [%d] scale_x_accel        : %d\n", (int)l_idx, frn_elem->scale_x_accel );
      printf("  [%d] scale_x_frames_done  : %d\n", (int)l_idx, frn_elem->scale_x_frames_done );

      printf("  [%d] scale_y_from         : %f\n", (int)l_idx, (float)frn_elem->scale_y_from );
      printf("  [%d] scale_y_to           : %f\n", (int)l_idx, (float)frn_elem->scale_y_to );
      printf("  [%d] scale_y_dur          : %d\n", (int)l_idx, (int)frn_elem->scale_y_dur );
      printf("  [%d] scale_y_accel        : %d\n", (int)l_idx, frn_elem->scale_y_accel );
      printf("  [%d] scale_y_frames_done  : %d\n", (int)l_idx, frn_elem->scale_y_frames_done );

      printf("  [%d] move_x_from          : %f\n", (int)l_idx, (float)frn_elem->move_x_from );
      printf("  [%d] move_x_to            : %f\n", (int)l_idx, (float)frn_elem->move_x_to );
      printf("  [%d] move_x_dur           : %d\n", (int)l_idx, (int)frn_elem->move_x_dur );
      printf("  [%d] move_x_accel         : %d\n", (int)l_idx, (int)frn_elem->move_x_accel );
      printf("  [%d] move_x_frames_done   : %d\n", (int)l_idx, (int)frn_elem->move_x_frames_done );

      printf("  [%d] move_y_from          : %f\n", (int)l_idx, (float)frn_elem->move_y_from );
      printf("  [%d] move_y_to            : %f\n", (int)l_idx, (float)frn_elem->move_y_to );
      printf("  [%d] move_y_dur           : %d\n", (int)l_idx, (int)frn_elem->move_y_dur );
      printf("  [%d] move_y_accel         : %d\n", (int)l_idx, (int)frn_elem->move_y_accel );
      printf("  [%d] move_y_frames_done   : %d\n", (int)l_idx, (int)frn_elem->move_y_frames_done );

      printf("  [%d] movepath_from        : %f\n", (int)l_idx, (float)frn_elem->movepath_from );
      printf("  [%d] movepath_to          : %f\n", (int)l_idx, (float)frn_elem->movepath_to );
      printf("  [%d] movepath_dur         : %d\n", (int)l_idx, (int)frn_elem->movepath_dur );
      printf("  [%d] movepath_frames_done : %d\n", (int)l_idx, (int)frn_elem->movepath_frames_done );

  }
}  /* end gap_story_render_debug_print_frame_elem */


/* ----------------------------------------------------
 * p_debug_print_render_section_names
 * ----------------------------------------------------
 * print section_name and pointers as debug information to stdout,
 */
static void
p_debug_print_render_section_names(GapStoryRenderVidHandle *vidhand)
{
  GapStoryRenderSection *section;

  printf("\nDEBUG p_debug_print_render_section_names START\n");
  for(section = vidhand->section_list; section != NULL; section = section->next)
  {
      printf("DEBUG render_section: adr vidhand:%ld section:%ld section->frn_list:%ld)"
           , (long)vidhand
           , (long)section
           , (long)section->frn_list
           );
      if (section->section_name == NULL)
      {
        printf("section_name: (null) is the MAIN section\n");
      }
      else
      {
        printf("section->section_name: %s\n", section->section_name);
      }
  }
  printf("DEBUG p_debug_print_render_section_names END\n");
  fflush(stdout);
}  /* end p_debug_print_render_section_names */


/* ----------------------------------------------------
 * gap_story_render_debug_print_framerange_list
 * ----------------------------------------------------
 * print all List elements for the given track
 * (negative track number will print all elements)
 */
void
gap_story_render_debug_print_framerange_list(GapStoryRenderFrameRangeElem *frn_list
                             , gint32 track                    /* -1 show all tracks */
                             )
{
  GapStoryRenderFrameRangeElem *frn_elem;
  gint                 l_idx;

  printf("\ngap_story_render_debug_print_framerange_list: START\n");

  l_idx = 0;
  for(frn_elem = frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
  {
    if((frn_elem->track == track) || (track < 0))
    {
      gap_story_render_debug_print_frame_elem(frn_elem, l_idx);
    }
    l_idx++;
  }

  printf("gap_story_render_debug_print_framerange_list: END\n");
  fflush(stdout);

}  /* end gap_story_render_debug_print_framerange_list */


/* ----------------------------------------------------
 * gap_story_render_debug_print_audiorange_list
 * ----------------------------------------------------
 * print all List elements for the given track
 * (negative track number will print all elements)
 */
void
gap_story_render_debug_print_audiorange_list(GapStoryRenderAudioRangeElem *aud_list
                             , gint32 track                    /* -1 show all tracks */
                             )
{
  GapStoryRenderAudioRangeElem *aud_elem;
  gint                 l_idx;

  printf("\ngap_story_render_debug_print_audiorange_list: START\n");

  l_idx = 0;
  for(aud_elem = aud_list; aud_elem != NULL; aud_elem = (GapStoryRenderAudioRangeElem *)aud_elem->next)
  {
    if((aud_elem->track == track) || (track < 0))
    {
      printf("\n  [%d] aud_type         : %d\n", (int)l_idx, (int)aud_elem->aud_type );
      printf("  [%d] track             : %d\n", (int)l_idx, (int)aud_elem->track );
      printf("  [%d] audiofile         : ", (int)l_idx); if(aud_elem->audiofile) { printf("%s\n", aud_elem->audiofile );} else { printf ("(null)\n"); }
      printf("  [%d] tmp_audiofile     : ", (int)l_idx); if(aud_elem->tmp_audiofile) { printf("%s\n", aud_elem->tmp_audiofile );} else { printf ("(null)\n"); }
      printf("  [%d] gvahand           : %ld\n", (int)l_idx, (long)aud_elem->gvahand );
      printf("  [%d] seltrack          : %d\n", (int)l_idx, (int)aud_elem->seltrack );

      printf("  [%d] samplerate        : %d\n", (int)l_idx, (int)aud_elem->samplerate );
      printf("  [%d] channels          : %d\n", (int)l_idx, (int)aud_elem->channels );
      printf("  [%d] bytes_per_sample  : %d\n", (int)l_idx, (int)aud_elem->bytes_per_sample );
      printf("  [%d] samples           : %d\n", (int)l_idx, (int)aud_elem->samples );


      printf("  [%d] audio_id          : %d\n", (int)l_idx, (int)aud_elem->audio_id);
      printf("  [%d] aud_data          : %ld\n", (int)l_idx, (long)aud_elem->aud_data);
      printf("  [%d] aud_bytelength    : %d\n", (int)l_idx, (int)aud_elem->aud_bytelength);

      printf("  [%d] range_samples     : %d\n", (int)l_idx, (int)aud_elem->range_samples );
      printf("  [%d] fade_in_samples   : %d\n", (int)l_idx, (int)aud_elem->fade_in_samples );
      printf("  [%d] fade_out_samples  : %d\n", (int)l_idx, (int)aud_elem->fade_out_samples );
      printf("  [%d] offset_rangestart : %d\n", (int)l_idx, (int)aud_elem->byteoffset_rangestart );
      printf("  [%d] byteoffset_data   : %d\n", (int)l_idx, (int)aud_elem->byteoffset_data );


      printf("  [%d] wait_untiltime_sec: %f\n", (int)l_idx, (float)aud_elem->wait_untiltime_sec );
      printf("  [%d] wait_until_samples: %d\n", (int)l_idx, (int)aud_elem->wait_until_samples );

      printf("  [%d] max_playtime_sec  : %f\n", (int)l_idx, (float)aud_elem->max_playtime_sec );
      printf("  [%d] range_playtime_sec: %f\n", (int)l_idx, (float)aud_elem->range_playtime_sec );
      printf("  [%d] play_from_sec     : %f\n", (int)l_idx, (float)aud_elem->play_from_sec );
      printf("  [%d] play_to_sec       : %f\n", (int)l_idx, (float)aud_elem->play_to_sec );
      printf("  [%d] volume_start      : %f\n", (int)l_idx, (float)aud_elem->volume_start );
      printf("  [%d] volume            : %f\n", (int)l_idx, (float)aud_elem->volume );
      printf("  [%d] volume_end        : %f\n", (int)l_idx, (float)aud_elem->volume_end );
      printf("  [%d] fade_in_sec       : %f\n", (int)l_idx, (float)aud_elem->fade_in_sec );
      printf("  [%d] fade_out_sec      : %f\n", (int)l_idx, (float)aud_elem->fade_out_sec );
    }
    l_idx++;
  }

  printf("gap_story_render_debug_print_audiorange_list: END\n");
  fflush(stdout);

}  /* end gap_story_render_debug_print_audiorange_list */



/* ----------------------------------------------------
 * p_frame_backup_save
 * ----------------------------------------------------
 */
static void
p_frame_backup_save(  char *key
              , gint32 image_id
              , gint32 layer_id
              , gint32  master_frame_nr
              , gboolean multilayer
             )
{
  gint32  l_len;
  char *l_framename;
  char *l_basename;

  l_len = gimp_get_data_size(key);
  if(l_len <= 0)
  {
    return;
  }

  l_basename  = g_malloc0(l_len);
  gimp_get_data(key, l_basename);
  if(*l_basename != '\0')
  {
    if(multilayer)
    {
      l_framename = gap_lib_alloc_fname(l_basename, master_frame_nr, ".xcf");
    }
    else
    {
      l_framename = gap_lib_alloc_fname(l_basename, master_frame_nr, ".png");
    }

    if(gap_debug) printf("Debug: Saving frame to  file: %s\n", l_framename);

    gimp_file_save(GIMP_RUN_WITH_LAST_VALS, image_id, layer_id, l_framename, l_framename);
    g_free(l_framename);
  }
  g_free(l_basename);
}  /* end p_frame_backup_save */


/* ----------------------------------------------------
 * p_debug_dup_image
 * ----------------------------------------------------
 * Duplicate image, and open a display for the duplicate
 * (Procedure is used for debug only
 */
static void
p_debug_dup_image(gint32 image_id)
{
  gint32 l_dup_image_id;

  l_dup_image_id = gimp_image_duplicate(image_id);
  gimp_display_new(l_dup_image_id);
}  /* end p_debug_dup_image */


/* ----------------------------------------------------
 * p_encoding_monitor
 * ----------------------------------------------------
 * monitor the image before passed to the encoder.
 * - at 1.st call open global_monitor_image_id
 *       and add a display.
 * - on all further calls copy the composite layer
 *      to the global_monitor_image_id
 */
static void
p_encoding_monitor(  char *key
              , gint32 image_id
              , gint32 layer_id
              , gint32  master_frame_nr
             )
{
  gint32  l_len;
  char *l_true_or_false;

  l_len = gimp_get_data_size(key);
  if(l_len <= 0)
  {
    return;
  }

  l_true_or_false  = g_malloc0(l_len);
  gimp_get_data(key, l_true_or_false);
  if(*l_true_or_false == 'T')
  {
     char *l_imagename;

     printf("Monitoring image_id: %d, layer_id:%d  master_frame:%d\n", (int)image_id, (int)layer_id ,(int)master_frame_nr );

     l_imagename = g_strdup_printf(_("encoding_video_frame_%06d"), (int)master_frame_nr);
     if(global_monitor_image_id < 0)
     {
       global_monitor_image_id = gimp_image_duplicate(image_id);
       global_monitor_display_id = gimp_display_new(global_monitor_image_id);
       gimp_image_set_filename(global_monitor_image_id, l_imagename);
     }
     else
     {
       gint          l_nlayers;
       gint32       *l_layers_list;
       gint32        l_fsel_layer_id;

       l_layers_list = gimp_image_get_layers(global_monitor_image_id, &l_nlayers);
       if(l_layers_list != NULL)
       {
         gimp_selection_none(image_id);  /* if there is no selection, copy the complete layer */
         gimp_selection_none(global_monitor_image_id);  /* if there is no selection, copy the complete layer */
         gimp_edit_copy(layer_id);
         l_fsel_layer_id = gimp_edit_paste(l_layers_list[0], FALSE);  /* FALSE paste clear selection */
         gimp_floating_sel_anchor(l_fsel_layer_id);
         g_free (l_layers_list);
         gimp_image_set_filename(global_monitor_image_id, l_imagename);
         gimp_displays_flush();
       }
       else
       {
          printf("no more MONITORING, (user has closed monitor image)\n");
       }
     }
     g_free(l_imagename);
  }
  g_free(l_true_or_false);
}  /* end p_encoding_monitor */



/* --------------------------------
 * p_init_stb_error
 * --------------------------------
 */
static void
p_init_stb_error(GapStoryRenderErrors *sterr)
{
  if(sterr->errtext)   { g_free(sterr->errtext); }
  if(sterr->errline)   { g_free(sterr->errline); }
  if(sterr->warntext)  { g_free(sterr->warntext); }
  if(sterr->warnline)  { g_free(sterr->warnline); }

  sterr->errtext     = NULL;
  sterr->errline     = NULL;
  sterr->warntext    = NULL;
  sterr->warnline    = NULL;
  sterr->currline    = NULL;
  sterr->errline_nr  = 0;
  sterr->warnline_nr = 0;
  sterr->curr_nr     = 0;
}  /* end p_init_stb_error */

/* --------------------------------
 * p_new_stb_error
 * --------------------------------
 */
static GapStoryRenderErrors *
p_new_stb_error(void)
{
  GapStoryRenderErrors *sterr;

  sterr = g_malloc0(sizeof(GapStoryRenderErrors));
  p_init_stb_error(sterr);
  return(sterr);
}  /* end p_new_stb_error */

/* --------------------------------
 * p_free_stb_error
 * --------------------------------
 */
static void
p_free_stb_error(GapStoryRenderErrors *sterr)
{
  p_init_stb_error(sterr);
  g_free(sterr);
}  /* end p_free_stb_error */

/* --------------------------------
 * p_set_stb_error
 * --------------------------------
 */
static void
p_set_stb_error(GapStoryRenderErrors *sterr, char *errtext)
{
  printf("** error: %s\n   [at line:%d] %s\n"
        , errtext
        , (int)sterr->curr_nr
        , sterr->currline
        );
  if(sterr->errtext == NULL)
  {
     sterr->errtext     = g_strdup(errtext);
     sterr->errline_nr  = sterr->curr_nr;
     sterr->errline     = g_strdup(sterr->currline);
  }
}  /* end p_set_stb_error */

/* --------------------------------
 * gap_story_render_set_stb_error
 * --------------------------------
 */
void
gap_story_render_set_stb_error(GapStoryRenderErrors *sterr, char *errtext)
{
  p_set_stb_error(sterr, errtext);
}  /* end gap_story_render_set_stb_error */

/* --------------------------------
 * gap_story_render_set_stb_warning
 * --------------------------------
 */
void
gap_story_render_set_stb_warning(GapStoryRenderErrors *sterr, char *warntext)
{
  printf("** warning: %s\n   [at line:%d] %s\n"
        , warntext
        , (int)sterr->curr_nr
        , sterr->currline
        );
  if(sterr->warntext == NULL)
  {
     sterr->warntext     = g_strdup(warntext);
     sterr->warnline_nr  = sterr->curr_nr;
     sterr->warnline     = g_strdup(sterr->currline);
  }
}  /* end gap_story_render_set_stb_warning */




/* ----------------------------------------------------
 * p_refresh_min_max_vid_tracknumbers
 * ----------------------------------------------------
 * findout the lowest and highest track number used
 * in the current framerange list in the specified storyboard videohandle.
 *
 * (and constraint the values to limits such as array boundaries)
 *
 * This procedure is typicall called each time the frn_list
 * of a storyboard videohandle (GapStoryRenderVidHandle *vidhand)
 * was changed (after creation and after switch to another section)
 */
static void
p_refresh_min_max_vid_tracknumbers(GapStoryRenderVidHandle *vidhand)
{
  GapStoryRenderFrameRangeElem *frn_elem;

  gint32 l_lowest_tracknr;
  gint32 l_highest_tracknr;

  if(vidhand->frn_list == NULL)
  {
    /* if there is no frn_list (may occure in case the STORYBOARD has unknow sections)
     * set both min(max to 0
     */
    l_lowest_tracknr = 0;
    l_highest_tracknr = 0;
  }
  else
  {
    l_lowest_tracknr = GAP_STB_MAX_VID_INTERNAL_TRACKS;
    l_highest_tracknr = -1;

    for(frn_elem = vidhand->frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
    {
      if (frn_elem->track > l_highest_tracknr)
      {
        l_highest_tracknr = frn_elem->track;
      }
      if (frn_elem->track < l_lowest_tracknr)
      {
        l_lowest_tracknr = frn_elem->track;
      }

    }
  }

  vidhand->minVidTrack  = CLAMP(l_lowest_tracknr, 0, GAP_STB_MAX_VID_INTERNAL_TRACKS);
  vidhand->maxVidTrack = CLAMP(l_highest_tracknr, 0, GAP_STB_MAX_VID_INTERNAL_TRACKS);


  if(gap_debug)
  {
    printf("p_refresh_min_max_vid_tracknumbers: min:%d max:%d\n"
       , (int)vidhand->minVidTrack
       , (int)vidhand->maxVidTrack
       );
  }

}  /* end p_refresh_min_max_vid_tracknumbers */


/* --------------------------------
 * p_attribute_query_at_step
 * --------------------------------
 * return
 *  o) from_val (before and at start of transition) or
 *  o) to_val (at end and after transition) or
 *  o) a mixed value according to frame_step progress and acceleratin characteristics
 *     when frame_step is a frame number within the transition.
 */
static gdouble
p_attribute_query_at_step(gint32 frame_step /* current frame (since start of current processed clip */
                ,gdouble from_val
                ,gdouble to_val
                ,gint32  frames_dur        /* duration of the complete transition in frames */
                ,gint32  frames_done       /* already handled steps in previous clips */
                ,gint    accel             /* acceleration characteristic for the transition */
                )
{
  gdouble l_mixed_val;
  gdouble l_mixed_factor;
  gint32  l_steps_since_transition_start;


  l_steps_since_transition_start = frames_done + frame_step;

  if (l_steps_since_transition_start <= 0)
  {
    return (from_val);
  }

  if (l_steps_since_transition_start >= frames_dur)
  {
    return (to_val);
  }

  l_mixed_factor = (gdouble)l_steps_since_transition_start  / (gdouble)frames_dur;
  l_mixed_factor = gap_accelMixFactor(l_mixed_factor, accel);

  l_mixed_val = GAP_BASE_MIX_VALUE(l_mixed_factor, from_val, to_val);

  return(l_mixed_val);

}  /* end p_attribute_query_at_step */



/* ----------------------------------------------------
 * p_select_section_by_name
 * ----------------------------------------------------
 * switch to specified section by
 * setting frn_list and aud_list pointers of the specified video handle vidhand
 * to the sub list of to the specified section_name.
 * section_name NULL refers to the main section.
 * at unknown section_name's set both pointers to NULL.
 */
static void
p_select_section_by_name(GapStoryRenderVidHandle *vidhand, const char *section_name)
{
  GapStoryRenderSection *section;

  for(section = vidhand->section_list; section != NULL; section = section->next)
  {
    if (section_name == NULL)
    {
      if (section->section_name == NULL)
      {
        if(gap_debug)
        {
           printf("p_select_section_by_name: null (switch to MAIN section)\n");
           fflush(stdout);
        }
        break;
      }
    }
    else
    {
      if (section->section_name != NULL)
      {
        if (strcmp(section->section_name, section_name) == 0)
        {
          if(gap_debug)
          {
             printf("p_select_section_by_name: (switch to SUB-SECTION: %s)\n", section_name);
             fflush(stdout);
          }
          break;
        }
      }
    }
  }

  if (section != NULL)
  {
    vidhand->aud_list = section->aud_list;
    if(vidhand->frn_list != section->frn_list)
    {
      vidhand->frn_list = section->frn_list;
      p_refresh_min_max_vid_tracknumbers(vidhand);
    }
  }
  else
  {
    vidhand->frn_list = NULL;
    vidhand->aud_list = NULL;
    p_refresh_min_max_vid_tracknumbers(vidhand);
  }


  if(gap_debug)
  {
    printf("p_select_section_by_name: addr of section: %ld Resulting addr of frn_list: %ld)\n"
       , (long)section
       , (long)vidhand->frn_list
       );
    fflush(stdout);
  }

}  /* end p_select_section_by_name */



/* ----------------------------------------------------
 * p_fetch_framename
 * ----------------------------------------------------
 * fetch frame access (framename or videofilename framenumber)
 * and transition values relevant for a given master_frame_nr in the given video track
 * within a storyboard framerange list.
 * (simple animations without a storyboard file
 *  are represented by a short storyboard framerange list that has
 *  just one element entry at track 1).
 *
 * output is written to the specified GapStbFetchData structure
 * that contains gdouble attribute values (opacity, scale, move) for the frame
 * at track and master_frame_nr position.
 *
 * Note gfd->framename can refere to the relevant frame image that is one of a series of frames
 * (imagefiles) on disc. But it may also refere to a videofilename or to a multilayer image.
 * (in this cases localframe_index referes to the relevant framenumber in the videofile
 * or to the layerstack position in the multilayer image)
 * gfd->framename is set to NULL if there is no frame at desired track and master_frame_nr
 */
static char *
p_fetch_framename(GapStoryRenderFrameRangeElem *frn_list
                 , gint32 master_frame_nr      /* starts at 1 */
                 , gint32 track
                 , GapStbFetchData *gfd        /* out: result structure of the fetch */
                 )
{
  GapStoryRenderFrameRangeElem *frn_elem;
  char   *l_framename;
  gint32  l_frame_group_count;
  gint32  l_fnr;
  gint32  l_step;
  gint32  l_found_at_idx;
  gint32  l_frames_to_handle;

  l_frame_group_count = 0;
  l_framename = NULL;

  /* default attributes (used if storyboard does not define settings) */
  gfd->rotate  = 0.0;
  gfd->opacity = 1.0;
  gfd->scale_x = 1.0;
  gfd->scale_y = 1.0;
  gfd->move_x  = 0.0;
  gfd->move_y  = 0.0;
  gfd->localframe_index = -1;
  gfd->local_stepcount = 0;
  gfd->localframe_tween_rest = 0.0;
  gfd->frn_type = GAP_FRN_SILENCE;
  gfd->keep_proportions = FALSE;
  gfd->fit_width        = TRUE;
  gfd->fit_height       = TRUE;
  gfd->red_f            = 0.0;
  gfd->green_f          = 0.0;
  gfd->blue_f           = 0.0;
  gfd->alpha_f          = 1.0;
  gfd->trak_filtermacro_file = NULL;
  gfd->frn_elem              = NULL;
  gfd->movepath_file_xml     = NULL;
  gfd->movepath_framePhase   = 0;

  l_found_at_idx=0;
  for (frn_elem = frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
  {
    if(frn_elem->track == track)
    {
      l_frames_to_handle = frn_elem->frames_to_handle;
      if (frn_elem->wait_untiltime_sec > 0)
      {
        l_frames_to_handle += MAX(0, frn_elem->wait_untilframes - l_frame_group_count);
      }
      if (master_frame_nr <= l_frame_group_count + l_frames_to_handle)
      {
        gdouble fnr;

        /* calculate positive or negative offset from_frame to desired frame */
        fnr = (gdouble)(frn_elem->delta * (master_frame_nr - (l_frame_group_count +1 )))
              * frn_elem->step_density;

        /* calculate framenumber local to the clip */
        l_fnr = (gint32)(frn_elem->frame_from + fnr);

        {
          gint32 fnrInt;

          fnrInt = fnr;  /* truncate to integer */

          gfd->localframe_tween_rest = fnr - fnrInt;

          if(gap_debug)
          {
            printf("fnr:%.4f, fnrInt:%d localframe_tween_rest:%.4f\n"
                     ,(float)fnr
                     ,(int)fnrInt
                     ,(float)gfd->localframe_tween_rest
                     );
          }
        }

        gfd->local_stepcount = master_frame_nr - l_frame_group_count;
        gfd->local_stepcount -= 1;

        switch(frn_elem->frn_type)
        {
          case GAP_FRN_SILENCE:
          case GAP_FRN_COLOR:
            l_framename = NULL;   /* there is no filename for video silence or unicolor */
            break;
          case GAP_FRN_IMAGE:
            l_framename = g_strdup(frn_elem->basename);   /* use 1:1 basename for single images */
            break;
          case GAP_FRN_ANIMIMAGE:
            l_framename = g_strdup(frn_elem->basename);   /* use 1:1 basename for ainimated single images */
            gfd->localframe_index = l_fnr;                    /* local frame number is index in the layerstack */
            break;
          case GAP_FRN_MOVIE:
            /* video file frame numners start at 1 */
            l_framename = g_strdup(frn_elem->basename);   /* use 1:1 basename for videofiles */
            gfd->localframe_index = l_fnr;                    /* local frame number is the wanted video frame number */
            break;
          case GAP_FRN_FRAMES:
            l_framename = gap_lib_alloc_fname(frn_elem->basename
                                   ,l_fnr
                                   ,frn_elem->ext
                                   );
            break;
          case GAP_FRN_SECTION:
            /* frame numners in storyboard sections start at 1 */
            l_framename = g_strdup(frn_elem->basename);   /* section_name 1:1 for STB sections */
            gfd->localframe_index = l_fnr;                    /* local frame number is the wanted video frame number */
            break;
        }

         /* return values for current fixed attribute settings
          */
         gfd->frn_type              = frn_elem->frn_type;
         gfd->keep_proportions      = frn_elem->keep_proportions;
         gfd->fit_width             = frn_elem->fit_width;
         gfd->fit_height            = frn_elem->fit_height;
         gfd->red_f                 = frn_elem->red_f;
         gfd->green_f               = frn_elem->green_f;
         gfd->blue_f                = frn_elem->blue_f;
         gfd->alpha_f               = frn_elem->alpha_f;
         gfd->trak_filtermacro_file = frn_elem->filtermacro_file;

         frn_elem->last_master_frame_access = master_frame_nr;

         gfd->frn_elem = frn_elem;  /* deliver pointer to the current frn_elem */


         /* calculate effect attributes for the current step
          * where l_step = 0 at the 1.st frame of the local range
          */
         l_step = (master_frame_nr - 1) - l_frame_group_count;


         gfd->rotate = p_attribute_query_at_step(l_step
                                    , frn_elem->rotate_from
                                    , frn_elem->rotate_to
                                    , frn_elem->rotate_dur
                                    , frn_elem->rotate_frames_done
                                    , frn_elem->rotate_accel
                                    );
         gfd->opacity = p_attribute_query_at_step(l_step
                                    , frn_elem->opacity_from
                                    , frn_elem->opacity_to
                                    , frn_elem->opacity_dur
                                    , frn_elem->opacity_frames_done
                                    , frn_elem->opacity_accel
                                    );
         gfd->scale_x = p_attribute_query_at_step(l_step
                                    , frn_elem->scale_x_from
                                    , frn_elem->scale_x_to
                                    , frn_elem->scale_x_dur
                                    , frn_elem->scale_x_frames_done
                                    , frn_elem->scale_x_accel
                                    );
         gfd->scale_y = p_attribute_query_at_step(l_step
                                    , frn_elem->scale_y_from
                                    , frn_elem->scale_y_to
                                    , frn_elem->scale_y_dur
                                    , frn_elem->scale_y_frames_done
                                    , frn_elem->scale_y_accel
                                    );
         gfd->move_x  = p_attribute_query_at_step(l_step
                                    , frn_elem->move_x_from
                                    , frn_elem->move_x_to
                                    , frn_elem->move_x_dur
                                    , frn_elem->move_x_frames_done
                                    , frn_elem->move_x_accel
                                    );
         gfd->move_y  = p_attribute_query_at_step(l_step
                                    , frn_elem->move_y_from
                                    , frn_elem->move_y_to
                                    , frn_elem->move_y_dur
                                    , frn_elem->move_y_frames_done
                                    , frn_elem->move_y_accel
                                    );

         /* movepath transition handling */
         if(frn_elem->movepath_file_xml != NULL)
         {
           gint32  l_steps_since_transition_start;
           gdouble phase;

           l_steps_since_transition_start = frn_elem->movepath_frames_done + l_step;
           phase = 1.0;


           if (l_steps_since_transition_start < frn_elem->movepath_dur)
           {
             gint32 duration;
             gint   accel;

             accel = 0;
             duration = abs(frn_elem->movepath_to - frn_elem->movepath_from);
             gfd->movepath_file_xml = frn_elem->movepath_file_xml;
             phase = p_attribute_query_at_step(l_step
                                                 , frn_elem->movepath_from
                                                 , frn_elem->movepath_to
                                                 , duration
                                                 , frn_elem->movepath_frames_done
                                                 , accel
                                                 );
             gfd->movepath_framePhase = rint(phase);
           }
           if(gap_debug)
           {
             printf("FETCH: l_steps_since_transition_start:%d movepath_dur:%d movepath_framePhase:%d (phase:%.4f)\n"
               , (int)l_steps_since_transition_start
               , (int)frn_elem->movepath_dur
               , (int)gfd->movepath_framePhase
               , (float)phase
               );
           }

         }


        break;
      }
      l_frame_group_count += l_frames_to_handle;
    }
    l_found_at_idx++;
  }

  if(gap_debug)
  {
    printf("p_fetch_framename: track:%d master_frame_nr:%d framename:%s: found_at_idx:%d rotate:%f opa:%f scale:%f %f move:%f %f layerstack_idx:%d"
       , (int)track
       , (int)master_frame_nr
       , l_framename
       , (int)l_found_at_idx
       , (float)gfd->rotate
       , (float)gfd->opacity
       , (float)gfd->scale_x
       , (float)gfd->scale_y
       , (float)gfd->move_x
       , (float)gfd->move_y
       , (int)gfd->localframe_index
       );
     if(gfd->movepath_file_xml != NULL)
     {
       printf(" movepath_framePhase:%f XML:%s"
         ,(float)gfd->movepath_framePhase
         ,gfd->movepath_file_xml
         );
     }
     printf("\n");
  }

  return (l_framename);

}       /* end p_fetch_framename */


/* ---------------------------------
 * p_calculate_frames_to_handle
 * ---------------------------------
 */
static void
p_calculate_frames_to_handle(GapStoryRenderFrameRangeElem *frn_elem)
{
    gdouble fnr;

    if(frn_elem->step_density <= 0)
    {
      /* force legal value */
      frn_elem->step_density = 1.0;
    }

    fnr = (gdouble)(ABS(frn_elem->frame_from - frn_elem->frame_to) +1);
    fnr = fnr / frn_elem->step_density;
    frn_elem->frames_to_handle = MAX((gint32)(fnr + 0.5), 1);
}  /* end p_calculate_frames_to_handle */


/* ----------------------------------------------------
 * p_new_framerange_element
 * ----------------------------------------------------
 * allocate a new GapStoryRenderFrameRangeElem for storyboard processing
 * - read directory to check for first and last available frame Number
 *   for the given filename and extension.
 * - findout processing order (1 ascending, -1 descending)
 * - findout frames_to_handle in the given range.
 *
 * Single images are added with frn_type == GAP_FRN_IMAGE
 *  and frame_from = 1
 *  and frame_to   = N   (10 if image is to display for duration of 10 frames)
 *
 * return GapStoryRenderFrameRangeElem with allocated copies of basename and ext strings.
 */
static GapStoryRenderFrameRangeElem *
p_new_framerange_element(GapStoryRenderFrameType  frn_type
                      ,gint32 track
                      ,const char *basename           /* basename or full imagename  for frn_type GAP_FRN_IMAGE */
                      ,const char *ext                /* NULL for frn_type GAP_FRN_IMAGE and GAP_FRN_MOVIE */
                      ,gint32  frame_from             /* IN: range start */
                      ,gint32  frame_to               /* IN: range end */
                      ,const char *storyboard_file    /* IN: NULL if no storyboard file is used */
                      ,const char *preferred_decoder  /* IN: NULL if no preferred_decoder is specified */
                      ,const char *filtermacro_file   /* IN: NULL, or name of the macro file */
                      ,GapStoryRenderFrameRangeElem *frn_list /* NULL or list of already known ranges */
                      ,GapStoryRenderErrors *sterr           /* element to store Error/Warning report */
                      ,gint32 seltrack      /* IN: select videotrack number 1 upto 99 for GAP_FRN_MOVIE */
                      ,gint32 exact_seek    /* IN: 0 fast seek, 1 exact seek (only for GVA Videoreads) */
                      ,gdouble delace    /* IN: 0.0 no deinterlace, 1.0-1.99 odd 2.0-2.99 even rows (only for GVA Videoreads) */
                      ,gdouble step_density    /* IN:  1==normal stepsize 1:1   0.5 == each frame twice, 2.0 only every 2nd frame */
                      ,gint32 flip_request            /* 0 NONE, 1 flip horizontal, 2 flip vertical, 3 rotate 180degree */
                      ,const char   *mask_name        /* reference to layer mask definition */
                      ,gdouble mask_stepsize          /* stepsize for the layer mask */
                      ,GapStoryMaskAnchormode mask_anchor  /* how to apply the layer mask */
                      ,gboolean mask_disable
                      ,gint32 fmac_total_steps
                      ,gint32 fmac_accel
                      ,const char *colormask_file      /* IN: NULL, or name of the colormask parameter file */
                      )
{
  GapStoryRenderFrameRangeElem *frn_elem;

  if(gap_debug)
  {
     printf("\np_new_framerange_element: START frn_type:%d\n", (int)frn_type);
     printf("  track:%d:\n", (int)track);
     printf("  frame_from:%d:\n", (int)frame_from);
     printf("  frame_to:%d:\n", (int)frame_to);
     printf("  step_density:%f:\n", (float)step_density);
     if(basename)          printf("  basename:%s:\n", basename);
     if(ext)               printf("  ext:%s:\n", ext);
     if(storyboard_file)   printf("  storyboard_file:%s:\n", storyboard_file);
     if(preferred_decoder) printf("  preferred_decoder:%s:\n", preferred_decoder);
     if(colormask_file)    printf("  colormask_file:%s:\n", colormask_file);
  }


  frn_elem = g_malloc0(sizeof(GapStoryRenderFrameRangeElem));
  frn_elem->frn_type         = frn_type;
  frn_elem->track            = track;
  frn_elem->frame_from       = frame_from;
  frn_elem->frame_to         = frame_to;
  frn_elem->frames_to_handle = 0;
  frn_elem->delta            = 1;
  frn_elem->step_density     = step_density;
  frn_elem->last_master_frame_access = -1;   /* -1 indicate that there was no access */

  frn_elem->flip_request  = flip_request;
  frn_elem->mask_framecount = 0;
  frn_elem->mask_stepsize = mask_stepsize;
  frn_elem->mask_anchor   = mask_anchor;

  /* disabled masks are ignored for storyboard processing
   * by using a mask_name == NULL
   */
  frn_elem->mask_name     = NULL;
  frn_elem->colormask_file     = NULL;
  if(!mask_disable)
  {
    if(mask_name)
    {
      frn_elem->mask_name = g_strdup(mask_name);
    }
    if(colormask_file)
    {
      frn_elem->colormask_file = gap_file_make_abspath_filename(colormask_file, storyboard_file);

      if(!g_file_test(frn_elem->colormask_file, G_FILE_TEST_EXISTS))
      {
         char *l_errtxt;

         l_errtxt = g_strdup_printf("colormask_file not found:  %s", frn_elem->colormask_file);
         p_set_stb_error(sterr, l_errtxt);
         g_free(l_errtxt);
         g_free(frn_elem->colormask_file);
         frn_elem->colormask_file = NULL;
      }
    }
  }


  /* default attributes (used if storyboard does not define settings) */
  frn_elem->red_f              = 0.0;
  frn_elem->green_f            = 0.0;
  frn_elem->blue_f             = 0.0;
  frn_elem->alpha_f            = 1.0;
  frn_elem->keep_proportions   = FALSE;
  frn_elem->fit_width          = TRUE;
  frn_elem->fit_height         = TRUE;
  frn_elem->wait_untiltime_sec = 0.0;
  frn_elem->wait_untilframes   = 0;
  frn_elem->rotate_from        = 0.0;
  frn_elem->rotate_to          = 0.0;
  frn_elem->rotate_dur         = 0;
  frn_elem->opacity_from       = 1.0;
  frn_elem->opacity_to         = 1.0;
  frn_elem->opacity_dur        = 0;
  frn_elem->scale_x_from       = 1.0;
  frn_elem->scale_x_to         = 1.0;
  frn_elem->scale_x_dur        = 0;
  frn_elem->scale_y_from       = 1.0;
  frn_elem->scale_y_to         = 1.0;
  frn_elem->scale_y_dur        = 0;
  frn_elem->move_x_from        = 0.0;
  frn_elem->move_x_to          = 0.0;
  frn_elem->move_x_dur         = 0;
  frn_elem->move_y_from        = 0.0;
  frn_elem->move_y_to          = 0.0;
  frn_elem->move_y_dur         = 0;
  frn_elem->filtermacro_file   = NULL;
  frn_elem->filtermacro_file_to = NULL;
  frn_elem->fmac_total_steps   = 1;
  frn_elem->fmac_accel         = 0;
  frn_elem->gvahand            = NULL;
  frn_elem->seltrack           = seltrack;
  frn_elem->exact_seek         = exact_seek;
  frn_elem->delace             = delace;

  frn_elem->movepath_from      = 0.0;
  frn_elem->movepath_to        = 0.0;
  frn_elem->movepath_frames_done = 0;
  frn_elem->movepath_dur         = 0;
  frn_elem->movepath_file_xml    = NULL;


  frn_elem->next               = NULL;




  if(ext)
  {
    if(*ext == '.')
    {
      frn_elem->ext              = g_strdup(ext);
    }
    else
    {
      frn_elem->ext              = g_strdup_printf(".%s", ext);
    }
  }

  /* check basename for absolute pathname.
   * Except for frn_type GAP_FRN_SECTION, where basename
   * refers to a section_name and not to a filename(part).
   */
  if(basename)
  {
    if (frn_type == GAP_FRN_SECTION)
    {
      frn_elem->basename = g_strdup(basename);
    }
    else
    {
      frn_elem->basename = gap_file_make_abspath_filename(basename, storyboard_file);
    }
  } /* end check for absolute pathname */

  /* check filtermacro_file for absolute pathname */
  frn_elem->fmac_total_steps = fmac_total_steps;
  frn_elem->fmac_accel = fmac_accel;
  frn_elem->filtermacro_file      = NULL;
  if(filtermacro_file)
  {
    if(*filtermacro_file != '\0')
    {
      frn_elem->filtermacro_file = gap_file_make_abspath_filename(filtermacro_file, storyboard_file);

      if(!g_file_test(frn_elem->filtermacro_file, G_FILE_TEST_EXISTS))
      {
         char *l_errtxt;

         l_errtxt = g_strdup_printf("filtermacro_file not found:  %s", frn_elem->filtermacro_file);
         p_set_stb_error(sterr, l_errtxt);
         g_free(l_errtxt);
         g_free(frn_elem->filtermacro_file);
         frn_elem->filtermacro_file = NULL;
      }
      else
      {
        frn_elem->filtermacro_file_to = gap_fmac_get_alternate_name(filtermacro_file);
        if(frn_elem->filtermacro_file_to)
        {
         if(!gap_fmac_chk_filtermacro_file(frn_elem->filtermacro_file_to))
         {
           g_free(frn_elem->filtermacro_file_to);
           frn_elem->filtermacro_file_to = NULL;
         }
        }

      }
    }
  }


  /* check processing order delta */
  if (frn_elem->frame_from > frn_elem->frame_to)
  {
    frn_elem->delta            = -1;   /* -1 is inverse (descending) order */
  }
  else
  {
    frn_elem->delta            = 1;    /* +1 is normal (ascending) order */
  }


  /* calculate frames_to_handle respecting step_density */
  p_calculate_frames_to_handle(frn_elem);

  if(gap_debug)
  {
    if(frn_elem->basename) printf("\np_new_framerange_element: frn_elem->basename:%s:\n", frn_elem->basename);
    if(frn_elem->ext)      printf("p_new_framerange_element: frn_elem->ext:%s:\n",frn_elem->ext);
    printf("p_new_framerange_element: frn_elem->frame_from:%d:\n", (int)frn_elem->frame_from);
    printf("p_new_framerange_element: frn_elem->frame_to:%d:\n", (int)frn_elem->frame_to);
    printf("p_new_framerange_element: frn_elem->frames_to_handle:%d:\n", (int)frn_elem->frames_to_handle);
    printf("\np_new_framerange_element: END\n");
  }


  return(frn_elem);
}       /* end p_new_framerange_element */



/* --------------------------------
 * p_add_frn_list
 * --------------------------------
 */
static void
p_add_frn_list(GapStoryRenderVidHandle *vidhand, GapStoryRenderFrameRangeElem *frn_elem)
{
  GapStoryRenderFrameRangeElem *frn_listend;


  if((vidhand)  && (frn_elem))
  {
     if(vidhand->parsing_section == NULL)
     {
       printf("** INTERNAL ERROR parsing_section is NULL\n");
       return;
     }

     frn_listend = vidhand->parsing_section->frn_list;
     if (vidhand->parsing_section->frn_list == NULL)
     {
       /* 1. element (or returned list) starts frn_list */
       vidhand->parsing_section->frn_list = frn_elem;
     }
     else
     {
       /* link frn_elem (that can be a single ement or list) to the end of frn_list */
       frn_listend = vidhand->parsing_section->frn_list;
       while(frn_listend->next != NULL)
       {
          frn_listend = (GapStoryRenderFrameRangeElem *)frn_listend->next;
       }
       frn_listend->next = (GapStoryRenderFrameRangeElem *)frn_elem;
     }
  }
}  /* end p_add_frn_list */


/* ----------------------------------------------------
 * p_step_all_vtrack_attributes
 * ----------------------------------------------------
 */
static void
p_step_all_vtrack_attributes(gint32 track
                       , gint32 frames_to_handle
                       , GapStoryRenderVTrackArray *vtarr
                      )
{
  vtarr->attr[track].rotate_frames_done += frames_to_handle;
  vtarr->attr[track].opacity_frames_done += frames_to_handle;
  vtarr->attr[track].scale_x_frames_done += frames_to_handle;
  vtarr->attr[track].scale_y_frames_done += frames_to_handle;
  vtarr->attr[track].move_x_frames_done += frames_to_handle;
  vtarr->attr[track].move_y_frames_done += frames_to_handle;
  vtarr->attr[track].movepath_frames_done += frames_to_handle;

}  /* end p_step_all_vtrack_attributes */


/* ----------------------------------------------------
 * p_set_vtrack_attributes
 * ----------------------------------------------------
 * set current video track attributes for the
 * Framerange_Element. from the current attribute array that represents
 * the video track context.
 *
 * attribute array is changed to the state after
 * the playback of the Framerange_element.
 * since GAP-2.7.x this just affects the new attributes *_frames_done
 * the other settings *_from, *_to, *_dur and *_accel are copied
 * unchanged, the current mixed value(s) are calculated later
 * (see p_attribute_query_at_step) based on the complete transition.
 *
 * Example for fade_in Attribute settings:
 *    IN:    opacity_from: 0.0   opacity_to: 1.0  opacity_dur: 50 frames
 *    if the range has 50 or more frames
 *    the fade_in can be handled fully within the range,
 *    if the range is for example 20 frames (smaller than the duration 50)
 *       the opacity_frames_done is increased by 20 frames
 *       and the rest of 30 frames to complete the fade-in action
 *       will be handled in the next frame range element (if there is any)
 *
 */
static void
p_set_vtrack_attributes(GapStoryRenderFrameRangeElem *frn_elem
                       ,GapStoryRenderVTrackArray *vtarr
                      )
{
  gint32 track;

  track = frn_elem->track;

  frn_elem->keep_proportions      = vtarr->attr[track].keep_proportions;
  frn_elem->fit_width             = vtarr->attr[track].fit_width;
  frn_elem->fit_height            = vtarr->attr[track].fit_height;
  frn_elem->mask_framecount       = vtarr->attr[track].mask_framecount;


  frn_elem->rotate_from          = vtarr->attr[track].rotate_from;
  frn_elem->rotate_to            = vtarr->attr[track].rotate_to;
  frn_elem->rotate_dur           = vtarr->attr[track].rotate_dur;
  frn_elem->rotate_accel         = vtarr->attr[track].rotate_accel;
  frn_elem->rotate_frames_done   = vtarr->attr[track].rotate_frames_done;

  frn_elem->opacity_from         = vtarr->attr[track].opacity_from;
  frn_elem->opacity_to           = vtarr->attr[track].opacity_to;
  frn_elem->opacity_dur          = vtarr->attr[track].opacity_dur;
  frn_elem->opacity_accel        = vtarr->attr[track].opacity_accel;
  frn_elem->opacity_frames_done  = vtarr->attr[track].opacity_frames_done;

  frn_elem->scale_x_from         = vtarr->attr[track].scale_x_from;
  frn_elem->scale_x_to           = vtarr->attr[track].scale_x_to;
  frn_elem->scale_x_dur          = vtarr->attr[track].scale_x_dur;
  frn_elem->scale_x_accel        = vtarr->attr[track].scale_x_accel;
  frn_elem->scale_x_frames_done  = vtarr->attr[track].scale_x_frames_done;

  frn_elem->scale_y_from         = vtarr->attr[track].scale_y_from;
  frn_elem->scale_y_to           = vtarr->attr[track].scale_y_to;
  frn_elem->scale_y_dur          = vtarr->attr[track].scale_y_dur;
  frn_elem->scale_y_accel        = vtarr->attr[track].scale_y_accel;
  frn_elem->scale_y_frames_done  = vtarr->attr[track].scale_y_frames_done;

  frn_elem->move_x_from          = vtarr->attr[track].move_x_from;
  frn_elem->move_x_to            = vtarr->attr[track].move_x_to;
  frn_elem->move_x_dur           = vtarr->attr[track].move_x_dur;
  frn_elem->move_x_accel         = vtarr->attr[track].move_x_accel;
  frn_elem->move_x_frames_done   = vtarr->attr[track].move_x_frames_done;

  frn_elem->move_y_from          = vtarr->attr[track].move_y_from;
  frn_elem->move_y_to            = vtarr->attr[track].move_y_to;
  frn_elem->move_y_dur           = vtarr->attr[track].move_y_dur;
  frn_elem->move_y_accel         = vtarr->attr[track].move_y_accel;
  frn_elem->move_y_frames_done   = vtarr->attr[track].move_y_frames_done;


  frn_elem->movepath_from        = vtarr->attr[track].movepath_from;
  frn_elem->movepath_to          = vtarr->attr[track].movepath_to;
  frn_elem->movepath_dur         = vtarr->attr[track].movepath_dur;
  /// frn_elem->movepath_accel       = vtarr->attr[track].movepath_accel; // (?)
  frn_elem->movepath_frames_done = vtarr->attr[track].movepath_frames_done;
  frn_elem->movepath_file_xml    = NULL;
  if ((vtarr->attr[track].movepath_file_xml != NULL)
  &&  (vtarr->attr[track].movepath_frames_done <= vtarr->attr[track].movepath_dur))
  {
    frn_elem->movepath_file_xml    = g_strdup(vtarr->attr[track].movepath_file_xml);
  }

  /* advance mask_framecount */
  vtarr->attr[track].mask_framecount += frn_elem->frames_to_handle;


  p_step_all_vtrack_attributes(track, frn_elem->frames_to_handle, vtarr);

}  /* end p_set_vtrack_attributes  */


/* ---------------------------------
 * p_vidclip_add_as_is
 * ---------------------------------
 * set vtrack attribtes and add the frn_elem to the
 * list of videoclips (frn_list).
 * NOTE: the mask_framecount is set from current vattr context.
 */
static void
p_vidclip_add_as_is(GapStoryRenderFrameRangeElem *frn_elem
   ,GapStoryRenderVidHandle *vidhand
   ,GapStoryRenderVTrackArray *vtarr
   )
{
  if(frn_elem)
  {
    vtarr->attr[frn_elem->track].frame_count += frn_elem->frames_to_handle;
    p_set_vtrack_attributes(frn_elem, vtarr);
    p_add_frn_list(vidhand, frn_elem);

    if(gap_debug)
    {
      printf("#------------------ \n");
      gap_story_render_debug_print_frame_elem(frn_elem, -4);
      printf("\n#------------------ \n");
    }
  }
}  /* end p_vidclip_add_as_is */


/* ---------------------------------
 * p_vidclip_shadow_add_silence
 * ---------------------------------
 */
static void
p_vidclip_shadow_add_silence(gint32 shadow_track
   ,gint32 fill_shadow_frames
   ,GapStoryRenderVidHandle *vidhand
   ,GapStoryRenderVTrackArray *vtarr
   ,const char *storyboard_file)
{
  GapStoryRenderFrameRangeElem *frn_elem;

  frn_elem = p_new_framerange_element(GAP_FRN_SILENCE
                                     , shadow_track
                                     , NULL            /* basename   */
                                     , NULL            /* extension  */
                                     , 1               /* frame_from */
                                     , fill_shadow_frames /* frame_to   */
                                     , storyboard_file
                                     , NULL            /* preferred_decoder */
                                     , NULL            /* filtermacro_file */
                                     , vidhand->frn_list
                                     , vidhand->sterr
                                     , 1              /* seltrack */
                                     , 0              /* exact_seek*/
                                     , 0.0            /* delace */
                                     , 1.0            /* step_density */
                                     , GAP_STB_FLIP_NONE    /* flip_request */
                                     , NULL                 /* mask_name */
                                     , 1.0                  /* mask_stepsize */
                                     , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                     , TRUE                 /* mask_disable */
                                     , 1                    /* fmac_total_steps */
                                     , 0                    /* fmac_accel */
                                     , NULL                 /* colormask_file */
                                     );
  if(frn_elem)
  {
    vtarr->attr[frn_elem->track].frame_count += frn_elem->frames_to_handle;
            p_set_vtrack_attributes(frn_elem, vtarr);
    /* do NOT step vtrack attributes
     * because ths fill operation with empty frames in the shadow track
     * creates frames before the current position and needs no current vtrack attributes
     * and MUST NOT change the current vattr settings.
     */
    p_add_frn_list(vidhand, frn_elem);
  }
}  /* end p_vidclip_shadow_add_silence */


/* ---------------------------------
 * p_recalculate_range_part
 * ---------------------------------
 * calculate a new from_frames and to_frames values
 * to reduce the clip length to the specified rest_frames.
 * lost_frames is an offest from the start.
 *
 *
 *  #########################################################
 *  |                   |                       |           |
 *  |<-- lost_frames -->|<----- rest_frames --->|           |
 *  |                                           |           |
 *  |                                                       |
 *  |<--- frames_to_handle (before recalculate) ----------->|
 */
static void
p_recalculate_range_part(GapStoryRenderFrameRangeElem *frn_elem
   , gint32 lost_frames
   , gint32 rest_frames)
{
    gdouble l_offs_lost;
    gdouble l_offs_rest;
    gdouble l_orig_from;
    gdouble l_orig_to;
    gint32 l_orig_frames_to_handle;

    l_orig_from = frn_elem->frame_from;
    l_orig_to = frn_elem->frame_to;

    p_calculate_frames_to_handle(frn_elem);
    l_orig_frames_to_handle = frn_elem->frames_to_handle;

    if ((lost_frames + rest_frames) > l_orig_frames_to_handle)
    {
      printf("p_recalculate_range_part: ** ERROR cant split %d frames into parts of %d + %d\n"
          , (int)frn_elem->frames_to_handle
          , (int)lost_frames
          , (int)rest_frames
          );
      if(lost_frames > frn_elem->frames_to_handle)
      {
        return;
      }
      rest_frames = frn_elem->frames_to_handle - lost_frames;
    }

    l_offs_lost = (gdouble)lost_frames * frn_elem->step_density;
    l_offs_rest = (gdouble)(MAX(0,rest_frames -1)) * frn_elem->step_density;

    if(frn_elem->frame_to >= frn_elem->frame_from)
    {
      frn_elem->frame_from += l_offs_lost;
      frn_elem->frame_to = frn_elem->frame_from + l_offs_rest;
    }
    else
    {
      frn_elem->frame_from -= l_offs_lost;
      frn_elem->frame_to = frn_elem->frame_from - l_offs_rest;

      frn_elem->frame_from += 0.5;
      frn_elem->frame_to += 0.5;
    }

    p_calculate_frames_to_handle(frn_elem);


    if(gap_debug)
    {
      printf("\nRANGE PART (in): orig from:%.3f to:%.3f to_handle:%d   lost_frames:%d rest_frames:%d\n"
         , (float)l_orig_from
         , (float)l_orig_to
         , (int)l_orig_frames_to_handle
         , (int)lost_frames
         , (int)rest_frames
         );

      printf("RANGE PART (out): from:%f to:%f  offs_lost:%f offs_rest:%f  frames_to_handle: %d\n\n"
         , (float)frn_elem->frame_from
         , (float)frn_elem->frame_to
         , (float)l_offs_lost
         , (float)l_offs_rest
         , (int)frn_elem->frames_to_handle
         );
    }

    if(frn_elem->frames_to_handle != rest_frames)
    {
       if(gap_debug)
       {
         /* result may differ due to rounding
          * in case step_density != 1.0 is used
          */
          printf("\n\nRANGE PART (dif) frames_to_handle != rest_frames due to rounding differences\n");
          printf("  ==> FORCE frames_to_handle: from caluclated value: %d forced value: %d\n\n"
            ,(int)frn_elem->frames_to_handle
            ,(int)rest_frames
            );
       }
       frn_elem->frames_to_handle = rest_frames;

    }

}  /* end p_recalculate_range_part */

/* ---------------------------------
 * p_copy_vattr_values
 * ---------------------------------
 * copy video attribute values from specified src track
 * to specified dest track.
 * NOTE:
 *   the current frame_count and overlap_count are NOT copied!
 *   (typical use of this procedure is to transfer the settings
 *    from a normal track to a shadow track, where the counters
 *    must not be affected by the copy)
 */
static void
p_copy_vattr_values(gint32 src_track
                      , gint32 dst_track
                      , GapStoryRenderVTrackArray *vtarr
                      )
{
  vtarr->attr[dst_track].keep_proportions  = vtarr->attr[src_track].keep_proportions;
  vtarr->attr[dst_track].fit_width         = vtarr->attr[src_track].fit_width;
  vtarr->attr[dst_track].fit_height        = vtarr->attr[src_track].fit_height;


  vtarr->attr[dst_track].rotate_from         = vtarr->attr[src_track].rotate_from;
  vtarr->attr[dst_track].rotate_to           = vtarr->attr[src_track].rotate_to;
  vtarr->attr[dst_track].rotate_dur          = vtarr->attr[src_track].rotate_dur;
  vtarr->attr[dst_track].rotate_accel        = vtarr->attr[src_track].rotate_accel;
  vtarr->attr[dst_track].rotate_frames_done  = vtarr->attr[src_track].rotate_frames_done;

  vtarr->attr[dst_track].opacity_from        = vtarr->attr[src_track].opacity_from;
  vtarr->attr[dst_track].opacity_to          = vtarr->attr[src_track].opacity_to;
  vtarr->attr[dst_track].opacity_dur         = vtarr->attr[src_track].opacity_dur;
  vtarr->attr[dst_track].opacity_accel       = vtarr->attr[src_track].opacity_accel;
  vtarr->attr[dst_track].opacity_frames_done = vtarr->attr[src_track].opacity_frames_done;

  vtarr->attr[dst_track].scale_x_from        = vtarr->attr[src_track].scale_x_from;
  vtarr->attr[dst_track].scale_x_to          = vtarr->attr[src_track].scale_x_to;
  vtarr->attr[dst_track].scale_x_dur         = vtarr->attr[src_track].scale_x_dur;
  vtarr->attr[dst_track].scale_x_accel       = vtarr->attr[src_track].scale_x_accel;
  vtarr->attr[dst_track].scale_x_frames_done = vtarr->attr[src_track].scale_x_frames_done;

  vtarr->attr[dst_track].scale_y_from        = vtarr->attr[src_track].scale_y_from;
  vtarr->attr[dst_track].scale_y_to          = vtarr->attr[src_track].scale_y_to;
  vtarr->attr[dst_track].scale_y_dur         = vtarr->attr[src_track].scale_y_dur;
  vtarr->attr[dst_track].scale_y_accel       = vtarr->attr[src_track].scale_y_accel;
  vtarr->attr[dst_track].scale_y_frames_done = vtarr->attr[src_track].scale_y_frames_done;

  vtarr->attr[dst_track].move_x_from         = vtarr->attr[src_track].move_x_from;
  vtarr->attr[dst_track].move_x_to           = vtarr->attr[src_track].move_x_to;
  vtarr->attr[dst_track].move_x_dur          = vtarr->attr[src_track].move_x_dur;
  vtarr->attr[dst_track].move_x_accel        = vtarr->attr[src_track].move_x_accel;
  vtarr->attr[dst_track].move_x_frames_done  = vtarr->attr[src_track].move_x_frames_done;

  vtarr->attr[dst_track].move_y_from         = vtarr->attr[src_track].move_y_from;
  vtarr->attr[dst_track].move_y_to           = vtarr->attr[src_track].move_y_to;
  vtarr->attr[dst_track].move_y_dur          = vtarr->attr[src_track].move_y_dur;
  vtarr->attr[dst_track].move_y_accel        = vtarr->attr[src_track].move_y_accel;
  vtarr->attr[dst_track].move_y_frames_done  = vtarr->attr[src_track].move_y_frames_done;

  vtarr->attr[dst_track].movepath_from        = vtarr->attr[src_track].movepath_from;
  vtarr->attr[dst_track].movepath_to          = vtarr->attr[src_track].movepath_to;
  vtarr->attr[dst_track].movepath_dur         = vtarr->attr[src_track].movepath_dur;
  vtarr->attr[dst_track].movepath_frames_done = vtarr->attr[src_track].movepath_frames_done;
  vtarr->attr[dst_track].movepath_file_xml    = vtarr->attr[src_track].movepath_file_xml;

}  /* end p_copy_vattr_values */


/* ---------------------------------
 * p_vidclip_split_and_add_frn_list
 * ---------------------------------
 * split the current clip into parts used for overlap in the shadow track
 * and remaining part in the normal track.
 * if the required amount of overlaping frames is not available in this
 * currently handled clip, the splitting will continue in the following
 * clips later on. In this case there will be no remaning part.
 * In some rare cases the current clip is completely thrown away
 * because the needed amount for overlapping will be fetched from
 * one of the following handled clips.
 */
static void
p_vidclip_split_and_add_frn_list(GapStoryRenderFrameRangeElem *frn_elem
   ,GapStoryRenderVidHandle *vidhand
   ,GapStoryRenderVTrackArray *vtarr
   ,const char *storyboard_file)
{
  gint32 l_track;
  gint32 l_shadow_track;
  gint32 l_shadow_frames_free;
  gint32 l_cut_frames;           /* frames cut off from start of this video clip */
  gint32 l_overlap_frames;       /* overlapping frames handled by this video clip */
  gint32 l_fill_shadow_frames;   /* requred empty frames to fill up shadow track */
  gint32 l_lost_frames;          /* frames in this clip thrown away because no space in shadow track */
  gint32 l_remain_frames;        /* frames reamaining to be placed in normal track */
  gint32 l_dead_frames;          /* frames in this clip and further clips thrown away because no space in shadow track */
  gint32 l_need_frames;          /* required number of frames to overlap */

  l_track = frn_elem->track;
  l_shadow_track = l_track - 1;

  l_shadow_frames_free = MAX(0, (vtarr->attr[l_track].frame_count
                       - vtarr->attr[l_shadow_track].frame_count));


  l_cut_frames = MIN(vtarr->attr[l_track].overlap_count, frn_elem->frames_to_handle);
  l_fill_shadow_frames =
      MAX(0,
           ((vtarr->attr[l_track].frame_count
             - vtarr->attr[l_track].overlap_count)
             - vtarr->attr[l_shadow_track].frame_count)
          );
  l_need_frames = MAX(0, (l_shadow_frames_free - l_fill_shadow_frames));
  l_dead_frames = vtarr->attr[l_track].overlap_count - l_need_frames;
  l_overlap_frames = MAX(0, (l_cut_frames - l_dead_frames));

  l_lost_frames = l_cut_frames - l_overlap_frames;
  l_remain_frames = frn_elem->frames_to_handle - l_cut_frames;

  if(gap_debug)
  {
    printf("SPLIT: free_shadow:%d fill_shadow:%d  need:%d dead:%d cut: %d, overlap:%d, lost:%d remain:%d\n"
       , (int)l_shadow_frames_free
       , (int)l_fill_shadow_frames
       , (int)l_need_frames
       , (int)l_dead_frames
       , (int)l_cut_frames
       , (int)l_overlap_frames
       , (int)l_lost_frames
       , (int)l_remain_frames
       );

  }


  if(l_fill_shadow_frames > 0)
  {
    p_vidclip_shadow_add_silence(l_shadow_track
             , l_fill_shadow_frames
             , vidhand
             , vtarr
             , storyboard_file
             );
    /* reset mask_framecount in shadow track to 0  */
    vtarr->attr[l_shadow_track].mask_framecount = 0;
  }

  if(l_overlap_frames > 0)
  {
    GapStoryRenderFrameRangeElem *frn_elem_dup;

    /* shadow track shall continue with current vatt settings
     * of the corresponding normal track
     */
    p_copy_vattr_values(l_track          /* source */
                       ,l_shadow_track   /* destination */
                       ,vtarr
                       );

    /* create a copy for the shadow track */
    frn_elem_dup = p_new_framerange_element(frn_elem->frn_type
                      ,l_shadow_track
                      ,frn_elem->basename
                      ,frn_elem->ext
                      ,frn_elem->frame_from
                      ,frn_elem->frame_to
                      ,storyboard_file
                      ,vidhand->preferred_decoder
                      ,frn_elem->filtermacro_file
                      ,vidhand->frn_list
                      ,vidhand->sterr
                      ,frn_elem->seltrack
                      ,frn_elem->exact_seek
                      ,frn_elem->delace
                      ,frn_elem->step_density
                      ,frn_elem->flip_request
                      ,frn_elem->mask_name
                      ,frn_elem->mask_stepsize
                      ,frn_elem->mask_anchor
                      ,FALSE     /* keep mask enabled if the element has one */
                      ,frn_elem->fmac_total_steps
                      ,frn_elem->fmac_accel
                      ,frn_elem->colormask_file
                      );


    p_recalculate_range_part(frn_elem_dup
                            ,l_lost_frames
                            ,l_overlap_frames
                            );
    p_vidclip_add_as_is(frn_elem_dup, vidhand, vtarr);

    /* copy vattr settings back from shadow track to normal track
     * (after the overlapping frames were processed in the
     * shadow track), the normal track will continue with settings
     * changed by shadow track processing
     */
    p_copy_vattr_values(l_shadow_track   /* source */
                       ,l_track          /* destination */
                       ,vtarr
                       );

    /* continue mask_framecount in normal track (that has started in the shadow track) */
    vtarr->attr[l_track].mask_framecount = vtarr->attr[l_shadow_track].mask_framecount;

    /* decrement overlap_count by */
    vtarr->attr[l_track].overlap_count -= l_cut_frames;
  }


  if(l_remain_frames > 0)
  {
    p_recalculate_range_part(frn_elem
                            ,l_cut_frames
                            ,l_remain_frames
                            );
    p_vidclip_add_as_is(frn_elem, vidhand, vtarr);
  }

}  /* end p_vidclip_split_and_add_frn_list */



/* ---------------------------------
 * p_vidclip_add
 * ---------------------------------
 * set vtrack attribtes and add the frn_elem to the
 * list of videoclips (frn_list)
 * Overlapping is handled by splitting th video clip
 * into appropriate parts, where the overlapping frames
 * are placed into the shadow track.
 */
static void
p_vidclip_add(GapStoryRenderFrameRangeElem *frn_elem
   ,GapStoryRenderVidHandle *vidhand
   ,GapStoryRenderVTrackArray *vtarr
   ,const char *storyboard_file
   ,gboolean first_of_group
   )
{
  if(frn_elem == NULL)
  {
    return;
  }



  if(vtarr->attr[frn_elem->track].overlap_count > 0)
  {
    if(first_of_group)
    {
      /* reset mask frame progress to 0 in the shadow track */
      vtarr->attr[frn_elem->track -1].mask_framecount = 0;   /* shadow track */
    }
    /* an overlap transition is pending
     * have to split off the overlapping frame part
     * to the shadow track, the rest will be added to
     * the normal track
     */
    p_vidclip_split_and_add_frn_list(frn_elem, vidhand, vtarr, storyboard_file);
  }
  else
  {
    if(first_of_group)
    {
      /* reset mask frame progress to 0 before processing each parsed clip.
       * the mask_framecount handles clip internal start value for fetching layer mask frames
       * in case were the scanned clip is splitted internally
       * (due to repetitions, pingpong mode, or overlapping)
       * the splitted parts 2 up to N will have mask_framecount > 0
       */
      vtarr->attr[frn_elem->track].mask_framecount = 0;
    }
    /* normal scenario without shadow track overlapping
     * simply add the video clip 1:1 to the list
     */
    p_vidclip_add_as_is(frn_elem, vidhand, vtarr);
  }

}  /* end p_vidclip_add */


/* ----------------------------------------------------
 * p_clear_vattr_array
 * ----------------------------------------------------
 */
static void
p_clear_vattr_array(GapStoryRenderVTrackArray *vtarr)
{
  gint   l_idx;

  vtarr->max_tracknum = GAP_STB_MAX_VID_INTERNAL_TRACKS -1;

  /* clear video track attribute settings for all tracks */
  for(l_idx=0; l_idx <= vtarr->max_tracknum; l_idx++)
  {
    vtarr->attr[l_idx].frame_count   = 0;
    vtarr->attr[l_idx].overlap_count = 0;
    vtarr->attr[l_idx].mask_framecount = 0;

    vtarr->attr[l_idx].keep_proportions     = FALSE;
    vtarr->attr[l_idx].fit_width            = TRUE;
    vtarr->attr[l_idx].fit_height           = TRUE;

    vtarr->attr[l_idx].rotate_from          = 0.0;
    vtarr->attr[l_idx].rotate_to            = 0.0;
    vtarr->attr[l_idx].rotate_dur           = 0;
    vtarr->attr[l_idx].rotate_accel         = 0;
    vtarr->attr[l_idx].rotate_frames_done   = 0;

    vtarr->attr[l_idx].opacity_from         = 1.0;
    vtarr->attr[l_idx].opacity_to           = 1.0;
    vtarr->attr[l_idx].opacity_dur          = 0;
    vtarr->attr[l_idx].opacity_accel        = 0;
    vtarr->attr[l_idx].opacity_frames_done  = 0;

    vtarr->attr[l_idx].scale_x_from         = 1.0;
    vtarr->attr[l_idx].scale_x_to           = 1.0;
    vtarr->attr[l_idx].scale_x_dur          = 0;
    vtarr->attr[l_idx].scale_x_accel        = 0;
    vtarr->attr[l_idx].scale_x_frames_done  = 0;

    vtarr->attr[l_idx].scale_y_from         = 1.0;
    vtarr->attr[l_idx].scale_y_to           = 1.0;
    vtarr->attr[l_idx].scale_y_dur          = 0;
    vtarr->attr[l_idx].scale_y_accel        = 0;
    vtarr->attr[l_idx].scale_y_frames_done  = 0;

    vtarr->attr[l_idx].move_x_from          = 0.0;
    vtarr->attr[l_idx].move_x_to            = 0.0;
    vtarr->attr[l_idx].move_x_dur           = 0;
    vtarr->attr[l_idx].move_x_accel         = 0;
    vtarr->attr[l_idx].move_x_frames_done   = 0;

    vtarr->attr[l_idx].move_y_from          = 0.0;
    vtarr->attr[l_idx].move_y_to            = 0.0;
    vtarr->attr[l_idx].move_y_dur           = 0;
    vtarr->attr[l_idx].move_y_accel         = 0;
    vtarr->attr[l_idx].move_y_frames_done   = 0;

    vtarr->attr[l_idx].movepath_from        = 0.0;
    vtarr->attr[l_idx].movepath_to          = 0.0;
    vtarr->attr[l_idx].movepath_dur         = 0;
    vtarr->attr[l_idx].movepath_frames_done = 0;
    vtarr->attr[l_idx].movepath_file_xml    = NULL;

  }
}  /* end p_clear_vattr_array */

/* -------------------------------------
 * p_fmt_string_has_framenumber_format
 * -------------------------------------
 * return true if the specified format string contains "%06d" (or "%02d" up to "%09d")
 *        false if the format has no such numeric part for the framenumber.
 *
 */
static gboolean
p_fmt_string_has_framenumber_format(const char *fmt_string)
{
  const char *ptr;
  gboolean l_found;

  l_found = FALSE;
  ptr = fmt_string;
  while(ptr)
  {
    if(ptr[0] == '\0')
    {
      break;
    }

    if (ptr[0] == '%')
    {
      if(ptr[1] != '\0')
      {
        if(ptr[2] != '\0')
        {
          if(ptr[3] != '\0')
          {
            if((ptr[1] == '0') && (ptr[3] == 'd'))
            {
              if((ptr[2] >= '2') && (ptr[2] <= '9'))
              {
                l_found = TRUE;
                break;
              }
            }
          }
        }
      }
    }
    ptr++;
  }
  return (l_found);

}  /* end p_fmt_string_has_framenumber_format */

/* -------------------------------------
 * p_fmt_string_has_videobasename_format
 * -------------------------------------
 * return true if the specified format string contains "%s"
 *        false if the format has no such videobasename placeholder part.
 *
 */
static gboolean
p_fmt_string_has_videobasename_format(const char *fmt_string)
{
  const char *ptr;
  gboolean l_found;

  l_found = FALSE;
  ptr = fmt_string;
  while(ptr)
  {
    if(ptr[0] == '\0')
    {
      break;
    }

    if ((ptr[0] == '%') && (ptr[1] == 's'))
    {
      l_found = TRUE;
      break;
    }
    ptr++;
  }
  return (l_found);

}  /* end p_fmt_string_has_videobasename_format */

/* ----------------------------------------------------
 * p_storyboard_analyze
 * ----------------------------------------------------
 * this procedure checks the specified storyboard (GapStoryBoard *stb) in memory
 * and converts all elements to the
 * corresponding rangelist structures (audio or frames or attr tables)
 * OUT: mainsection_frame_count is set to the number of frames in the main section
 *      (if the main section has more than one track, the longest track
 *       is used to set the mainsection_frame_count).
 */
static void
p_storyboard_analyze(GapStoryBoard *stb
                      , gint32 *mainsection_frame_count
                      , GapStoryRenderVidHandle *vidhand
                      )
{
  GapStoryRenderVTrackArray        vtarray;
  GapStoryRenderVTrackArray       *vtarr;
  GapStorySection *stb_section;
  GapStoryElem *stb_elem;
  char *storyboard_file;

  GapStoryRenderAudioRangeElem *aud_elem;
  GapStoryRenderFrameRangeElem *frn_elem;
  GapStoryRenderFrameRangeElem *frn_list;
  GapStoryRenderFrameRangeElem *frn_pinglist;
  GapStoryRenderErrors         *sterr;
  GapStoryRenderFrameRangeElem *frn_known_list;

  gint32 l_track;
  gint   l_idx;
  gint32 l_max_elems;
  gint32 l_cnt_elems;
  gint32 highest_vtrack;

  sterr=vidhand->sterr;
  frn_list = NULL;
  vtarr = &vtarray;

  p_clear_vattr_array(vtarr);

  storyboard_file = stb->storyboardfile;

  /* copy Master informations: */
  if(stb->master_width > 0)
  {
    vidhand->master_width     = stb->master_width;
    vidhand->master_height    = stb->master_height;
  }
  if(stb->master_framerate > 0)
  {
    vidhand->master_framerate = stb->master_framerate;
  }
  vidhand->preferred_decoder = NULL;
  if(stb->preferred_decoder)
  {
    vidhand->preferred_decoder = g_strdup(stb->preferred_decoder);
  }

  /* stuff for automatically insert of alpha channel for clip type MOVIE */
  vidhand->master_insert_alpha_format = NULL;
  if(stb->master_insert_alpha_format)
  {
    vidhand->master_insert_alpha_format = g_strdup(stb->master_insert_alpha_format);
    vidhand->master_insert_alpha_format_has_framenumber =
      p_fmt_string_has_framenumber_format(vidhand->master_insert_alpha_format);
    vidhand->master_insert_alpha_format_has_videobasename =
      p_fmt_string_has_videobasename_format(vidhand->master_insert_alpha_format);
  }

  /* stuff for automatically logo inserts */
  vidhand->master_insert_area_format = NULL;
  if(stb->master_insert_area_format)
  {
    vidhand->master_insert_area_format = g_strdup(stb->master_insert_area_format);
    vidhand->master_insert_area_format_has_framenumber =
      p_fmt_string_has_framenumber_format(vidhand->master_insert_area_format);
    vidhand->master_insert_area_format_has_videobasename =
      p_fmt_string_has_videobasename_format(vidhand->master_insert_area_format);
  }

  if(stb->master_volume >= 0)
  {
    vidhand->master_volume = stb->master_volume;
  }
  if(stb->master_samplerate > 0)
  {
    vidhand->master_samplerate = stb->master_samplerate;
  }


  vidhand->section_list = NULL;

  for(stb_section = stb->stb_section; stb_section != NULL; stb_section = stb_section->next)
  {
    if (stb_section == stb->mask_section)
    {
        /* ignore mask definitions (this are all elements in mask_section) here,
         * because mask definitions are handled separately.
         * (for the rendering mask_definitions are not handled like sub sections,
         * mask_definitions have global scope in the whole storyboard
         * see procedure p_copy_mask_definitions_to_vidhand)
         */
        continue;
    }
    vidhand->parsing_section = p_new_render_section(stb_section->section_name);
    p_append_render_section_to_vidhand(vidhand, vidhand->parsing_section);

    /* clear video track attribute settings for all tracks.
     * video attribute array holds current transition value context (opacity, move and scale)
     * of all tracks and is updated at parsing of the the current clip.
     * each processed clip gets its start and end values (_from _to) from the array,
     * and gets the information how many frames of the transition are already handled in previous
     * clips (_frames_done) as start value.
     *
     * The video attribute array must be cleared at start of each section.
     */
    p_clear_vattr_array(vtarr);

    /* count Elements in the current STB section and check highest video track number */
    highest_vtrack = 0;
    l_max_elems = 0;
    for(stb_elem = stb_section->stb_elem; stb_elem != NULL;  stb_elem = stb_elem->next)
    {
      l_max_elems++;
      if(stb_elem->track > highest_vtrack)
      {
        highest_vtrack = stb_elem->track;
      }
    }

    /* Loop foreach Element in the current STB section */
    l_cnt_elems = 0;
    for(stb_elem = stb_section->stb_elem; stb_elem != NULL;  stb_elem = stb_elem->next)
    {
      if(stb_elem->track == GAP_STB_MASK_TRACK_NUMBER)
      {
        /* ignore mask definitions, in the reserved GAP_STB_MASK_TRACK_NUMBER track
         * in this loop,
         * mask definitions are handled separately.
         * (GAP_STB_MASK_TRACK_NUMBER shall not occure in main and sub sections
         * in new versions of storyboard processing)
         */
        continue;
      }

      /* convert video tracknumbers from file to internal representation
       * note that internal tracknumbers 0, 2, 4 ... are reserved for shadow tracks
       * that are generated only for internal usage if a videotrack uses overlapping
       * frames.
       * the lowest internal tracknumber always appears in foreground
       * (it is placed on top of layerstack at rendering of the composite frame)
       */
      if (stb->master_vtrack1_is_toplayer)
      {
        /* default order track 1 is the top layer
         *   1 ==> 1
         *   2 ==> 3
         *   3 ==> 5
         */
        l_track    = 1 + (2 * MAX((stb_elem->track -1), 0));
      }
      else
      {
        /* inverse order track 1 is the background layer
         *   1 ==> 5
         *   2 ==> 3
         *   3 ==> 1
         */
        l_track    = 1 + (2 * MAX((highest_vtrack - stb_elem->track), 0));
      }

      frn_known_list  = vidhand->frn_list;

      if (stb_section->section_name != NULL)
      {
        if (stb_elem->record_type == GAP_STBREC_VID_SECTION)
        {
          /* section playback is only supported in the main section
           * in other section use black frames when they contain
           * GAP_STBREC_VID_SECTION (this case should not happen regulary)
           */
          stb_elem->record_type = GAP_STBREC_VID_BLACKSECTION;
        }
      }

      /* reset mask frame progress to 1 before processing each parsed clip.
       * the mask_framecount handles clip internal start value for fetching layer mask frames
       * in case were the scanned clip is splitted internally
       * (due to repetitions, pingpong mode, or overlapping)
       * the splitted parts 2 up to N will have mask_framecount > 0
       */
      vtarr->attr[l_track].mask_framecount = 0;

      switch(stb_elem->record_type)
      {
        case GAP_STBREC_ATT_TRANSITION:
          {
            gint ii;

            vtarr->attr[l_track].overlap_count    += MAX(stb_elem->att_overlap, 0);
            vtarr->attr[l_track].fit_width        = stb_elem->att_fit_width;
            vtarr->attr[l_track].fit_height       = stb_elem->att_fit_height;
            vtarr->attr[l_track].keep_proportions = stb_elem->att_keep_proportions;
            for(ii=0; ii < GAP_STB_ATT_TYPES_ARRAY_MAX; ii++)
            {
              if(stb_elem->att_arr_enable[ii])
              {
                switch(ii)
                {
                  case GAP_STB_ATT_TYPE_ROTATE:
                    vtarr->attr[l_track].rotate_from  = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].rotate_to    = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].rotate_dur   = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].rotate_accel = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].rotate_frames_done  = 0;
                    break;
                  case GAP_STB_ATT_TYPE_OPACITY:
                    vtarr->attr[l_track].opacity_from  = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].opacity_to    = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].opacity_dur   = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].opacity_accel = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].opacity_frames_done  = 0;
                    break;
                  case GAP_STB_ATT_TYPE_MOVE_X:
                    vtarr->attr[l_track].move_x_from   = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].move_x_to     = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].move_x_dur    = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].move_x_accel  = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].move_x_frames_done = 0;
                    break;
                  case GAP_STB_ATT_TYPE_MOVE_Y:
                    vtarr->attr[l_track].move_y_from   = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].move_y_to     = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].move_y_dur    = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].move_y_accel  = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].move_y_frames_done = 0;
                    break;
                  case GAP_STB_ATT_TYPE_ZOOM_X:
                    vtarr->attr[l_track].scale_x_from  = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].scale_x_to    = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].scale_x_dur   = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].scale_x_accel = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].scale_x_frames_done = 0;
                    break;
                  case GAP_STB_ATT_TYPE_ZOOM_Y:
                    vtarr->attr[l_track].scale_y_from  = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].scale_y_to    = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].scale_y_dur   = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].scale_y_accel = stb_elem->att_arr_value_accel[ii];
                    vtarr->attr[l_track].scale_y_frames_done = 0;
                    break;
                  case GAP_STB_ATT_TYPE_MOVEPATH:
                    vtarr->attr[l_track].movepath_from   = stb_elem->att_arr_value_from[ii];
                    vtarr->attr[l_track].movepath_to     = stb_elem->att_arr_value_to[ii];
                    vtarr->attr[l_track].movepath_dur    = stb_elem->att_arr_value_dur[ii];
                    vtarr->attr[l_track].movepath_frames_done = 0;
                    /* no need to strdup for the vattr table here.
                     * becaue we use the reference to the original data in this table
                     * (therefore dont g_free
                     * the g_strdup is done later when the movepath_file_xml is
                     * set in the individual GapStoryRenderFrameRangeElem frn_elem
                     * (see p_set_vtrack_attributes)
                     */
                    vtarr->attr[l_track].movepath_file_xml  = NULL;
                    if(stb_elem->att_movepath_file_xml != NULL)
                    {
                      if(stb_elem->att_movepath_file_xml[0] != '\0')
                      {
                        gboolean is_valid;

                        is_valid = gap_mov_exec_check_valid_xml_paramfile(stb_elem->att_movepath_file_xml);
                        if (is_valid)
                        {
                          vtarr->attr[l_track].movepath_file_xml = stb_elem->att_movepath_file_xml;
                        }
                      }
                    }
                    break;
                }
              }
            }

          }
          break;
        case GAP_STBREC_VID_SILENCE:
          if(!vidhand->ignore_video)
          {
            /* add framerange element for the current storyboard_file line */
            frn_elem = p_new_framerange_element(GAP_FRN_SILENCE
                                               , l_track
                                               , NULL            /* basename   */
                                               , NULL            /* extension  */
                                               , 1               /* frame_from */
                                               , stb_elem->nloop /* frame_to   */
                                               , storyboard_file
                                               , NULL            /* preferred_decoder */
                                               , NULL            /* filtermacro_file */
                                               , frn_known_list
                                               , sterr
                                               , 1              /* seltrack */
                                               , 0              /* exact_seek*/
                                               , 0.0            /* delace */
                                               , 1.0            /* step_density */
                                               , GAP_STB_FLIP_NONE    /* flip_request */
                                               , NULL                 /* mask_name */
                                               , 1.0                  /* mask_stepsize */
                                               , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                               , TRUE                 /* mask_disable */
                                               , 1                    /* fmac_total_steps */
                                               , 0                    /* fmac_accel */
                                               , NULL                 /* colormask_file */
                                               );
            if(frn_elem)
            {
              frn_elem->wait_untiltime_sec = stb_elem->vid_wait_untiltime_sec;
              frn_elem->wait_untilframes = stb_elem->vid_wait_untiltime_sec * vidhand->master_framerate;

              p_vidclip_add(frn_elem, vidhand, vtarr, storyboard_file, TRUE);
            }
          }
          break;
        case GAP_STBREC_VID_BLACKSECTION:
          if(!vidhand->ignore_video)
          {
            /* add framerange element for the current storyboard_file line */
            frn_elem = p_new_framerange_element(GAP_FRN_SILENCE
                                               , l_track
                                               , NULL            /* basename   */
                                               , NULL            /* extension  */
                                               , 1               /* frame_from */
                                               , 1 + abs(stb_elem->to_frame - stb_elem->from_frame)
                                               , storyboard_file
                                               , NULL            /* preferred_decoder */
                                               , NULL            /* filtermacro_file */
                                               , frn_known_list
                                               , sterr
                                               , 1              /* seltrack */
                                               , 0              /* exact_seek*/
                                               , 0.0            /* delace */
                                               , 1.0            /* step_density */
                                               , GAP_STB_FLIP_NONE    /* flip_request */
                                               , NULL                 /* mask_name */
                                               , 1.0                  /* mask_stepsize */
                                               , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                               , TRUE                 /* mask_disable */
                                               , 1                    /* fmac_total_steps */
                                               , 0                    /* fmac_accel */
                                               , NULL                 /* colormask_file */
                                               );
            if(frn_elem)
            {
              p_vidclip_add(frn_elem, vidhand, vtarr, storyboard_file, TRUE);
            }
          }
          break;
        case GAP_STBREC_VID_COLOR:
          if(!vidhand->ignore_video)
          {
            /* add framerange element for the current storyboard_file line */
            frn_elem = p_new_framerange_element(GAP_FRN_COLOR
                                               , l_track
                                               , NULL
                                               , NULL            /* extension  */
                                               , 1               /* frame_from */
                                               , stb_elem->nloop /* frame_to   */
                                               , storyboard_file
                                               , NULL            /* referred_decoder */
                                               , NULL            /* filtermacro_file */
                                               , frn_known_list
                                               , sterr
                                               , 1              /* seltrack */
                                               , 0              /* exact_seek*/
                                               , 0.0            /* delace */
                                               , 1.0            /* step_density */
                                               , stb_elem->flip_request
                                               , stb_elem->mask_name
                                               , stb_elem->mask_stepsize
                                               , stb_elem->mask_anchor
                                               , stb_elem->mask_disable
                                               , stb_elem->fmac_total_steps
                                               , stb_elem->fmac_accel
                                               , stb_elem->colormask_file
                                               );
            if(frn_elem)
            {
              frn_elem->red_f           = CLAMP(stb_elem->color_red,   0.0, 1.0);
              frn_elem->green_f         = CLAMP(stb_elem->color_green, 0.0, 1.0);
              frn_elem->blue_f          = CLAMP(stb_elem->color_blue,  0.0, 1.0);
              frn_elem->alpha_f         = CLAMP(stb_elem->color_alpha, 0.0, 1.0);

              p_vidclip_add(frn_elem, vidhand, vtarr, storyboard_file, TRUE);
            }
          }
          break;
        case GAP_STBREC_VID_IMAGE:
          if(!vidhand->ignore_video)
          {
            /* add framerange element for the current storyboard_file line */
            frn_elem = p_new_framerange_element(GAP_FRN_IMAGE
                                               , l_track
                                               , stb_elem->orig_filename
                                               , NULL            /* extension  */
                                               , 1               /* frame_from */
                                               , stb_elem->nloop /* frame_to   */
                                               , storyboard_file
                                               , vidhand->preferred_decoder
                                               , stb_elem->filtermacro_file
                                               , frn_known_list
                                               , sterr
                                               , 1              /* seltrack */
                                               , 0              /* exact_seek*/
                                               , 0.0            /* delace */
                                               , 1.0            /* step_density */
                                               , stb_elem->flip_request
                                               , stb_elem->mask_name
                                               , stb_elem->mask_stepsize
                                               , stb_elem->mask_anchor
                                               , stb_elem->mask_disable
                                               , stb_elem->fmac_total_steps
                                               , stb_elem->fmac_accel
                                               , stb_elem->colormask_file
                                               );
            if(frn_elem)
            {
              p_vidclip_add(frn_elem, vidhand, vtarr, storyboard_file, TRUE);
            }
          }
          break;
        case GAP_STBREC_VID_ANIMIMAGE:
        case GAP_STBREC_VID_FRAMES:
        case GAP_STBREC_VID_MOVIE:
        case GAP_STBREC_VID_SECTION:
          if(!vidhand->ignore_video)
          {
             GapStoryRenderFrameType        l_frn_type;
             gint32   l_repcnt;
             gint32   l_sub_from;
             gint32   l_sub_to;
             gint     l_pingpong;
             char    *l_file_or_basename;
             char    *l_ext_ptr;
             gboolean first_of_group;

             l_ext_ptr = NULL;
             l_file_or_basename = stb_elem->orig_filename;
             l_pingpong = 1;
             if(stb_elem->playmode == GAP_STB_PM_PINGPONG)
             {
               l_pingpong = 2;
             }

             l_frn_type = GAP_FRN_ANIMIMAGE;
             switch(stb_elem->record_type)
             {
               case GAP_STBREC_VID_ANIMIMAGE:
                 l_frn_type = GAP_FRN_ANIMIMAGE;
                 l_file_or_basename = stb_elem->orig_filename;
                 break;
               case GAP_STBREC_VID_FRAMES:
                 l_frn_type = GAP_FRN_FRAMES;
                 l_ext_ptr = stb_elem->ext;
                 l_file_or_basename = stb_elem->basename;
                 break;
               case GAP_STBREC_VID_MOVIE:
                 l_frn_type = GAP_FRN_MOVIE;
                 l_file_or_basename = stb_elem->orig_filename;
                 break;
               case GAP_STBREC_VID_SECTION:
                 l_frn_type = GAP_FRN_SECTION;
                 l_file_or_basename = stb_elem->orig_filename;
                 break;
               default:
                 break;  /* should never be reached */
             }

             frn_pinglist = frn_known_list;
             first_of_group = TRUE;
             /* expand element according to pingpong and nloop settings  */
             for(l_repcnt=0; l_repcnt < stb_elem->nloop; l_repcnt++)
             {
               l_sub_from = stb_elem->from_frame;
               l_sub_to   = stb_elem->to_frame;
               for(l_idx=0; l_idx < l_pingpong; l_idx++)
               {
                 /* add framerange element for the current storyboard_file line */
                 frn_elem = p_new_framerange_element( l_frn_type
                                                    , l_track
                                                    , l_file_or_basename
                                                    , l_ext_ptr
                                                    , l_sub_from
                                                    , l_sub_to
                                                    , storyboard_file
                                                    , gap_story_get_preferred_decoder(stb, stb_elem)
                                                    , stb_elem->filtermacro_file
                                                    , frn_pinglist
                                                    , sterr
                                                    , stb_elem->seltrack
                                                    , stb_elem->exact_seek
                                                    , stb_elem->delace
                                                    , stb_elem->step_density
                                                    , stb_elem->flip_request
                                                    , stb_elem->mask_name
                                                    , stb_elem->mask_stepsize
                                                    , stb_elem->mask_anchor
                                                    , stb_elem->mask_disable
                                                    , stb_elem->fmac_total_steps
                                                    , stb_elem->fmac_accel
                                                    , stb_elem->colormask_file
                                                    );
                 if(frn_elem)
                 {
                   /* prepare for pingpong mode with inverted range,
                    * omitting the start/end frames
                    */
                   l_sub_from = frn_elem->frame_to   - frn_elem->delta;
                   l_sub_to   = frn_elem->frame_from + frn_elem->delta;

                   p_vidclip_add(frn_elem, vidhand, vtarr, storyboard_file, first_of_group);

                   first_of_group = FALSE;
                   frn_pinglist = frn_list;

                 }
               }
             }


          }
          break;
        case GAP_STBREC_AUD_SILENCE:
          if(!vidhand->ignore_audio)
          {
            /* add audiorange element for the current storyboard_file line */
            aud_elem = gap_story_render_audio_new_audiorange_element(GAP_AUT_SILENCE
                                               , l_track
                                               , NULL
                                               , vidhand->master_samplerate
                                               , 0              /* from_sec     */
                                               , stb_elem->aud_play_to_sec      /* to_sec       */
                                               , 0.0            /* vol_start    */
                                               , 0.0            /* volume       */
                                               , 0.0            /* vol_end      */
                                               , 0.0            /* fade_in_sec  */
                                               , 0.0            /* fade_out_sec */
                                               , vidhand->util_sox
                                               , vidhand->util_sox_options
                                               , storyboard_file
                                               , vidhand->preferred_decoder
                                               , vidhand->aud_list  /* known audio range elements */
                                               , sterr
                                               , 1              /* seltrack */
                                               , vidhand->create_audio_tmp_files
                                               , 0.0 /* min_play_sec */
                                               , 0.0 /* max_play_sec */
                                               , vidhand
                                               );
            if(aud_elem)
            {
               aud_elem->wait_untiltime_sec = stb_elem->aud_wait_untiltime_sec;
               aud_elem->wait_until_samples = stb_elem->aud_wait_untiltime_sec * aud_elem->samplerate;
               gap_story_render_audio_add_aud_list(vidhand, aud_elem);
            }
          }
          break;
        case GAP_STBREC_AUD_SOUND:
        case GAP_STBREC_AUD_MOVIE:
          if(!vidhand->ignore_audio)
          {
            GapStoryRenderAudioType  l_aud_type;
            gint     l_rix;

            if(stb_elem->record_type == GAP_STBREC_AUD_SOUND)
            {
              l_aud_type = GAP_AUT_AUDIOFILE;
            }
            else
            {
              l_aud_type = GAP_AUT_MOVIE;
            }

            for(l_rix=0; l_rix < stb_elem->nloop; l_rix++)
            {
              /* add audiorange element for the current storyboard_file line */
              aud_elem = gap_story_render_audio_new_audiorange_element(l_aud_type  /* GAP_AUT_MOVIE or GAP_AUT_AUDIOFILE */
                                             , l_track
                                             , stb_elem->aud_filename
                                             , vidhand->master_samplerate
                                             , stb_elem->aud_play_from_sec
                                             , stb_elem->aud_play_to_sec
                                             , stb_elem->aud_volume_start
                                             , stb_elem->aud_volume
                                             , stb_elem->aud_volume_end
                                             , stb_elem->aud_fade_in_sec
                                             , stb_elem->aud_fade_out_sec
                                             , vidhand->util_sox
                                             , vidhand->util_sox_options
                                             , storyboard_file
                                             , gap_story_get_preferred_decoder(stb, stb_elem)
                                             , vidhand->aud_list  /* known audio range elements */
                                             , sterr
                                             , stb_elem->aud_seltrack
                                             , vidhand->create_audio_tmp_files
                                             , stb_elem->aud_min_play_sec
                                             , stb_elem->aud_max_play_sec
                                             , vidhand
                                             );
              if(aud_elem)
              {
                gap_story_render_audio_add_aud_list(vidhand, aud_elem);
              }
            }
          }
          break;
        default:
          break;
      }

      /* progress handling */
      l_cnt_elems++;

      *vidhand->progress = (gdouble)l_cnt_elems / (gdouble)l_max_elems;
      if(vidhand->status_msg)
      {
        g_snprintf(vidhand->status_msg, vidhand->status_msg_len
                  , _("analyze line %d (out of %d)")
                  , (int)l_cnt_elems
                  , (int)l_max_elems
                  );
      }


    }  /* END Loop foreach Element in the current STB section */

    if (stb_section->section_name == NULL)
    {
      gint vii;
      /* section_name NULL is the MAIN section
       */
      vidhand->frn_list = vidhand->parsing_section->frn_list;
      vidhand->aud_list = vidhand->parsing_section->aud_list;

      /* findout total frame_count of the main section
       * (is the max frame_count of all tracks
       * within the MAIN section)
       */
      for(vii=0; vii <= vtarr->max_tracknum; vii++)
      {
        if (*mainsection_frame_count < vtarr->attr[vii].frame_count)
        {
          *mainsection_frame_count = vtarr->attr[vii].frame_count;
        }
      }


    }

  }  /* END loop for all sections of the storyboard */


}       /* end p_storyboard_analyze */


/* ----------------------------------------------------
 * p_framerange_list_from_storyboard
 * ----------------------------------------------------
 * this procedure builds up a framerange list
 * from the  given storyboard structure in MEMORY
 * or by parsing the specified storyboard_file.
 *
 * return the framerange_list (scanned from storyboard or storyboard_file)
 */
static GapStoryRenderFrameRangeElem *
p_framerange_list_from_storyboard(const char *storyboard_file
                      ,gint32 *frame_count
                      ,GapStoryRenderVidHandle *vidhand
                      ,GapStoryBoard *stb_mem_ptr
                      )
{
  GapStoryRenderErrors            *sterr;

  sterr=vidhand->sterr;
  *frame_count = 0;

  if(gap_debug)
  {
    printf("p_framerange_list_from_storyboard: START vidhand:%ld stb_mem_ptr;%ld"
      , (long)vidhand
      , (long)stb_mem_ptr
      );
    if (storyboard_file == NULL)
    {
      printf(" storyboard_file: (null)");
    }
    else
    {
      printf(" storyboard_file:%s"
        , storyboard_file
        );
    }
    printf("\n");
  }

  /* convert from GapStoryBoard to render representation
   * of the storyboard.
   */
  {
    GapStoryBoard *stb;

    stb = NULL;

    if(stb_mem_ptr)
    {
      /* we have a storyboard already available in MEMORY
       * make a full copy for further processing.
       */
      stb = gap_story_duplicate_full(stb_mem_ptr);
    }

    if(stb == NULL)
    {
      /* load and parse the storyboard structure from file */
      stb = gap_story_parse(storyboard_file);
    }

    if(stb)
    {
      if(stb->errtext != NULL)
      {
        /* report the 1.st error */
        sterr->curr_nr  = stb->errline_nr;
        sterr->currline = stb->errline;
        p_set_stb_error(sterr, stb->errtext);
      }
      else
      {
        /* findout min and max playtime for audio clip references
         * that are to extract from videofiles
         */
        gap_story_set_aud_movie_min_max(stb);

        /* analyze the stb list and transform this list
         * to vidhand->section_list->frn_list and vidhand->section_list->aud_list
         */
        p_storyboard_analyze(stb
                            ,frame_count
                            ,vidhand
                            );

        /* mask definitions */
        p_copy_mask_definitions_to_vidhand(stb, vidhand);
      }
      gap_story_free_storyboard(&stb);

    }
  }

  /* select MAIN section (has section_name NULL) per default
   * (Note: this sets vidhand->frn_list to the  frn_list of the MAIN section)
   */
  p_select_section_by_name(vidhand, NULL);

  if((vidhand->frn_list == 0) || (*frame_count == 0))
  {
    *frame_count = 0;
    sterr->currline = "(eof)";

    p_set_stb_error(sterr, _("No Frames or Images found ..."));
    /* note: this can occure if there are non empty sub sections
     *       but the MAIN section is empty.
     *       (in such a case nothing will be rendered).
     */
  }

  if(gap_debug)
  {
    printf("p_framerange_list_from_storyboard: END vidhand:%ld\n"
      , (long)vidhand
      );
  }

  return (vidhand->frn_list);
}       /* end p_framerange_list_from_storyboard */


/* ----------------------------------------------------
 * p_free_framerange_list
 * ----------------------------------------------------
 */
static void
p_free_framerange_list(GapStoryRenderFrameRangeElem * frn_list)
{
  GapStoryRenderFrameRangeElem *frn_elem;
  GapStoryRenderFrameRangeElem *frn_next;

  for(frn_elem = frn_list; frn_elem != NULL; frn_elem = frn_next)
  {
    if(frn_elem->basename)          { g_free(frn_elem->basename);}
    if(frn_elem->ext)               { g_free(frn_elem->ext);}
    if(frn_elem->filtermacro_file)  { g_free(frn_elem->filtermacro_file);}
    if(frn_elem->movepath_file_xml) { g_free(frn_elem->movepath_file_xml);}

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
    if(frn_elem->gvahand)           { p_call_GVA_close(frn_elem->gvahand);}
#endif

    frn_next = (GapStoryRenderFrameRangeElem *)frn_elem->next;
    g_free(frn_elem);
  }
}       /* end p_free_framerange_list */



/* ----------------------------------------------------
 * p_new_render_section
 * ----------------------------------------------------
 */
static GapStoryRenderSection *
p_new_render_section(const char *section_name)
{
   GapStoryRenderSection *new_render_section;

   new_render_section = g_new(GapStoryRenderSection, 1);
   new_render_section->frn_list = NULL;
   new_render_section->aud_list = NULL;
   new_render_section->section_name = NULL;
   if (section_name != NULL)
   {
     new_render_section->section_name = g_strdup(section_name);
   }
   new_render_section->next = NULL;
   return (new_render_section);
}  /* end p_new_render_section */


/* ----------------------------------------------------
 * p_append_render_section_to_vidhand
 * ----------------------------------------------------
 * append the specified new_render_section at end of the section_list
 * of the specified video handle.
 */
static void
p_append_render_section_to_vidhand(GapStoryRenderVidHandle *vidhand
  , GapStoryRenderSection *new_render_section)
{
  GapStoryRenderSection *render_section;

  for(render_section = vidhand->section_list; render_section != NULL; render_section = render_section->next)
  {
    if(render_section->next == NULL)
    {
        break;
    }
  }
  if (render_section != NULL)
  {
    render_section->next = new_render_section;
  }
  else
  {
    vidhand->section_list = new_render_section;
  }
}  /* end p_append_render_section_to_vidhand */



/* ----------------------------------------------------
 * p_open_mask_vidhand
 * ----------------------------------------------------
 */
static void
p_open_mask_vidhand(GapStoryElem *stb_elem, GapStoryRenderMaskDefElem *maskdef_elem)
{
  GapLibTypeInputRange input_mode;

  input_mode = GAP_RNGTYPE_IMAGE;
  switch(stb_elem->record_type)
  {
    case GAP_STBREC_VID_IMAGE:
      input_mode = GAP_RNGTYPE_IMAGE;
      break;
    case GAP_STBREC_VID_ANIMIMAGE:
      input_mode = GAP_RNGTYPE_LAYER;
      break;
    case GAP_STBREC_VID_FRAMES:
      input_mode = GAP_RNGTYPE_FRAMES;
      break;
    case GAP_STBREC_VID_MOVIE:
      input_mode = GAP_RNGTYPE_MOVIE;
      break;
    default:
      break;
  }


  if(gap_debug)
  {
    printf("p_open_mask_vidhand: MASK_ELEM\n");
    gap_story_debug_print_elem(stb_elem);
  }

  maskdef_elem->mask_vidhand = p_open_video_handle_private( TRUE         /* ignore_audio */
                            , FALSE        /* dont ignore_video */
                            , FALSE        /* create_audio_tmp_files */
                            , NULL         /* progress_ptr */
                            , NULL         /* status_msg */
                            , 0            /* status_msg_len */
                            , NULL         /* storyboard_file */
                            ,stb_elem->basename
                            ,stb_elem->ext
                            ,stb_elem->from_frame
                            ,stb_elem->to_frame
                            ,&maskdef_elem->frame_count
                            ,FALSE                      /* do_gimp_progress */
                            ,input_mode
                            ,stb_elem->orig_filename    /* imagename */
                            ,stb_elem->preferred_decoder
                            ,stb_elem->seltrack
                            ,stb_elem->exact_seek
                            ,stb_elem->delace
                            ,FALSE                      /* compensate_framerange */
                            ,NULL                       /* stb_mem_ptr */
                            ,NULL                       /* util_sox */
                            ,NULL                       /* util_sox_options */
                            );
  if(maskdef_elem->mask_vidhand)
  {
    maskdef_elem->mask_vidhand->is_mask_handle = TRUE;
  }
}  /* end p_open_mask_vidhand */



/* ----------------------------------------------------
 * p_copy_mask_definitions
 * ----------------------------------------------------
 * copy mask definitions and open a sub GapStoryRenderVidHandle
 * for each mask definition.
 * those handles are used to fetch masks from any type of clips.
 */
static void
p_copy_mask_definitions_to_vidhand(GapStoryBoard *stb_ptr, GapStoryRenderVidHandle *vidhand)
{
  GapStoryElem *stb_elem;
  vidhand->maskdef_elem = NULL; /* start with empty mask definition list */

  if (stb_ptr->mask_section == NULL)
  {
    return;
  }

  for(stb_elem = stb_ptr->mask_section->stb_elem; stb_elem != NULL;  stb_elem = stb_elem->next)
  {
    if(stb_elem->track != GAP_STB_MASK_TRACK_NUMBER)
    {
      /* mask definitions are restricted to internal track number 0,
       * ignore other tracks for maskdefinition (shall not occure)
       */
      continue;
    }
    if ((stb_elem->record_type == GAP_STBREC_VID_SECTION)
    || (stb_elem->record_type == GAP_STBREC_VID_BLACKSECTION))
    {
      /* section playback is NOT supported for mask definitions.
       * ignore such elements (that shall not occure in regular case)
       */
      continue;
    }
    if(stb_elem->mask_name)
    {
      GapStoryRenderMaskDefElem *maskdef_elem;
      GapStoryElem      *stb_elem_ref;

      /* check if there are references (in any section) to this mask definition */
      stb_elem_ref = gap_story_find_mask_reference_by_name(stb_ptr, stb_elem->mask_name);
      if(stb_elem_ref == NULL)
      {
        /* this mask definition is not refered,
         * no processing is necessary
         */
        continue;
      }

      if(gap_debug)
      {
        printf("p_copy_mask_definitions_to_vidhand: \n");
        gap_story_debug_print_elem(stb_elem);
      }


      maskdef_elem = g_new(GapStoryRenderMaskDefElem, 1);
      if(maskdef_elem)
      {
          maskdef_elem->mask_name = g_strdup(stb_elem->mask_name);
          maskdef_elem->record_type = (gint32)stb_elem->record_type;
          maskdef_elem->frame_count = 0;
          maskdef_elem->flip_request = stb_elem->flip_request;
          maskdef_elem->mask_vidhand = NULL;
          maskdef_elem->next = vidhand->maskdef_elem;

          p_open_mask_vidhand(stb_elem, maskdef_elem);

          /* link mask definition element as 1st element to the list */
          vidhand->maskdef_elem = maskdef_elem;
      }
    }
  }

}  /* end p_copy_mask_definitions */


/* ----------------------------------------------------
 * p_free_mask_definitions
 * ----------------------------------------------------
 * free all mask definitions and close sub GapStoryRenderVidHandle
 * for all mask definitions.
 */
static void
p_free_mask_definitions(GapStoryRenderVidHandle *vidhand)
{
  GapStoryRenderMaskDefElem *maskdef_elem;
  GapStoryRenderMaskDefElem *maskdef_next;

  for(maskdef_elem = vidhand->maskdef_elem; maskdef_elem != NULL;  maskdef_elem = maskdef_next)
  {
    if(maskdef_elem->mask_vidhand)
    {
      gap_story_render_close_vid_handle(maskdef_elem->mask_vidhand);
    }
    if(maskdef_elem->mask_name)
    {
      g_free(maskdef_elem->mask_name);
    }

    maskdef_next = maskdef_elem->next;
    g_free(maskdef_elem);
  }

}  /* end p_free_mask_definitions */



/* ----------------------------------------------------
 * gap_story_render_close_vid_handle
 * ----------------------------------------------------
 * close video handle (free framelist and errors)
 */
void
gap_story_render_close_vid_handle(GapStoryRenderVidHandle *vidhand)
{
   GapStoryRenderSection *render_section;

   render_section = vidhand->section_list;

   while(render_section != NULL)
   {
     GapStoryRenderSection *next_render_section;

     next_render_section = render_section->next;

     p_free_framerange_list(render_section->frn_list);

     if (render_section->section_name != NULL)
     {
       g_free(render_section->section_name);
     }
     g_free(render_section);

     render_section = next_render_section;
   }

   p_free_stb_error(vidhand->sterr);
   p_free_mask_definitions(vidhand);

   /* unregister frame fetcher resource usage (i.e. the image cache) */
   gap_frame_fetch_unregister_user(vidhand->ffetch_user_id);
   vidhand->section_list = NULL;
   vidhand->frn_list = NULL;
   vidhand->sterr = NULL;
}  /* end gap_story_render_close_vid_handle */


/* ----------------------------------------------------
 * gap_story_render_set_audio_resampling_program
 * ----------------------------------------------------
 */
void
gap_story_render_set_audio_resampling_program(GapStoryRenderVidHandle *vidhand
                           , char *util_sox
                           , char *util_sox_options
                           )
{
  if(vidhand)
  {
     if(vidhand->util_sox)
     {
       g_free(vidhand->util_sox);
       vidhand->util_sox = NULL;
     }
     if(vidhand->util_sox_options)
     {
       g_free(vidhand->util_sox_options);
       vidhand->util_sox_options = NULL;
     }

     if(util_sox)
     {
       vidhand->util_sox = g_strdup(util_sox);
     }
     if(util_sox_options)
     {
       vidhand->util_sox_options = g_strdup(util_sox_options);
     }

  }
}  /* end gap_story_render_set_audio_resampling_program */


/* ----------------------------------------------------
 * p_find_maskdef_by_name
 * ----------------------------------------------------
 */
static GapStoryRenderMaskDefElem *
p_find_maskdef_by_name(GapStoryRenderVidHandle *vidhand, const char *mask_name)
{
  GapStoryRenderMaskDefElem *maskdef_elem;

  for(maskdef_elem = vidhand->maskdef_elem; maskdef_elem != NULL;  maskdef_elem = maskdef_elem->next)
  {
    if(strcmp(maskdef_elem->mask_name, mask_name) == 0)
    {
      return(maskdef_elem);
    }
  }
  return (NULL);

}  /* end p_find_maskdef_by_name */


/* ----------------------------------------------------
 * p_mask_image_fetcher
 * ----------------------------------------------------
 * fetch specified mask frame with only one composite layer.
 * if the mask definition is not found,
 * then deliver -1.
 * if the mask definition has less frames than master_frame_nr, then deliver the
 * last available frame as mask.
 * return the image id.
 */
static gint32
p_mask_fetcher(GapStoryRenderVidHandle *vidhand
   , const char *mask_name
   , gint32 master_frame_nr
   , gint32 mask_width
   , gint32 mask_height
   , gint32 *layer_id_ptr           /* OUT: Id of the only layer in the composite image */
   , gboolean *was_last_maskframe   /* OUT: true if this was the last maskframe */
   , gboolean makeGrayFlattened     /* IN   true flatten and convert to GRAY, false keep color and alpha channel */
   )
{
  GapStoryRenderMaskDefElem *maskdef_elem;
  gint32 image_id;

  *was_last_maskframe = FALSE;
  image_id = -1;


  maskdef_elem = p_find_maskdef_by_name(vidhand, mask_name);

  if(maskdef_elem == NULL)
  {
    printf("\n** Error MASK fetch could not find maskdef_elem (== NULL) MASK: %s IGNORED\n"
           , mask_name);
  }
  else
  {
    gint32 l_framenr;

    /* limit access to last available frame */
    l_framenr = MIN(master_frame_nr, maskdef_elem->frame_count);


    if(gap_debug)
    {
       printf("\n############# MASK start FETCH ##########\n");
       printf("MASK relevant framenr:%d\n"
            , (int)l_framenr
            );
       gap_story_render_debug_print_maskdef_elem(maskdef_elem, -7);
    }

    image_id = p_story_render_fetch_composite_image_private(maskdef_elem->mask_vidhand
                  , l_framenr        /* starts at 1 */
                  , mask_width       /* desired  Width in pixels */
                  , mask_height      /* desired  Height in pixels */
                  , NULL             /* NULL if no filtermacro is used */
                  , layer_id_ptr     /* OUT: Id of the only layer in the composite image */
                  , NULL             /* each mask has its own mask_vidhand where
                                      * the elements are in main section (section_name = NULL)
                                      */
                  , FALSE            /* enable_rgb888_flag */
                  , NULL             /* GapStoryFetchResult */
                 );

    if(gap_debug)
    {
      printf("\n.........#### MASK end FETCH ####......\n");
    }

    if(makeGrayFlattened == TRUE)
    {
      if(gimp_image_base_type(image_id) != GIMP_GRAY)
      {
        gimp_image_convert_grayscale(image_id);
      }
    }

    *layer_id_ptr = gap_layer_flip(*layer_id_ptr, maskdef_elem->flip_request);

    if(makeGrayFlattened == TRUE)
    {
      if(gimp_drawable_has_alpha(*layer_id_ptr))
      {
        *layer_id_ptr = gimp_image_flatten(image_id);
      }
    }

    if(gap_debug)
    {
      printf("p_mask_fetcher: flip_request:%d\n"
        ,(int)maskdef_elem->flip_request
        );
    }

    if (l_framenr == maskdef_elem->frame_count)
    {
      *was_last_maskframe = TRUE;
    }

  }

  return(image_id);
}  /* end p_mask_fetcher */



/* ----------------------------------------------------
 * p_fetch_and_add_layermask
 * ----------------------------------------------------
 */
static void
p_fetch_and_add_layermask(GapStoryRenderVidHandle *vidhand
                  , GapStoryRenderFrameRangeElem *frn_elem
                  , gint32 local_stepcount
                  , gint32 image_id
                  , gint32 layer_id
                  , GapStoryMaskAnchormode mask_anchor
                  )
{
  gint32  l_tmp_mask_image_id;
  gint32  l_tmp_mask_layer_id;
  gint32  l_master_framenr;
  gdouble l_framenr;
  gboolean l_found_in_cache;
  gboolean l_was_last_maskframe;
  gboolean l_makeGrayFlattened;

  /* both local_stepcount and mask_framecount start with 0 for the 1st element */
  l_framenr = frn_elem->mask_stepsize * (gdouble)(frn_elem->mask_framecount + local_stepcount);
  l_master_framenr = 1 + (gint32)(l_framenr);
  l_makeGrayFlattened = TRUE;

  if (mask_anchor == GAP_MSK_ANCHOR_XCOLOR)
  {
    l_makeGrayFlattened = FALSE;
  }

  if(gap_debug)
  {
    printf("\n============--------------====\n");
    printf("p_fetch_and_add_layermask: local_stepcount: %d master_framenr:%d\n"
          ,(int)local_stepcount
          ,(int)l_master_framenr
          );
    gap_story_render_debug_print_frame_elem(frn_elem, -5);
  }


  l_found_in_cache = FALSE;

  // l_tmp_mask_image_id = TODO lookup in the cache ################

  if(l_found_in_cache)
  {
     if(gap_debug)
     {
       printf("FOUND MASK in cache: mask_name:%s master_framenr:%d\n"
              ,frn_elem->mask_name
              ,(int)l_master_framenr
              );
     }
  }
  else
  {
    l_tmp_mask_image_id = p_mask_fetcher(vidhand
                              , frn_elem->mask_name
                              , l_master_framenr
                              , gimp_drawable_width(layer_id)
                              , gimp_drawable_height(layer_id)
                              ,&l_tmp_mask_layer_id
                              ,&l_was_last_maskframe
                              , l_makeGrayFlattened
                              );
  }

  if(gap_debug)
  {
    printf("MASK image_id: %d l_tmp_mask_layer_id:%d\n"
           ,(int)l_tmp_mask_image_id
           ,(int)l_tmp_mask_layer_id
           );
  }

  if(l_tmp_mask_image_id >= 0)
  {
     gint32 l_new_layer_mask_id;


     if(1==0)
     {
       p_debug_dup_image(l_tmp_mask_image_id);
     }


     /* add aplha channel if necessary
      * (this is required to allow adding the layermask
      */
     if(!gimp_drawable_has_alpha(layer_id))
     {
       gimp_layer_add_alpha(layer_id);
     }

     if (mask_anchor == GAP_MSK_ANCHOR_XCOLOR)
     {
       /* render the layermask by applying the mask as colormask */
       gap_colormask_apply_to_layer_of_same_size_from_file (layer_id
                                  , l_tmp_mask_layer_id       /* the colormask to be applied */
                                  , frn_elem->colormask_file  /* colormask parameter file */
                                  , TRUE                      /* keepLayerMask  */
                                  , FALSE                     /* doProgress */
                            );
     }
     else
     {
       l_new_layer_mask_id = gimp_layer_create_mask(layer_id, GIMP_ADD_WHITE_MASK);
       gimp_layer_add_mask(layer_id, l_new_layer_mask_id);


       /* overwrite the white layer mask with the fetched mask */
       gap_layer_copy_content(l_new_layer_mask_id   /* dst_drawable_id */
                             ,l_tmp_mask_layer_id     /* src_drawable_id */
                             );
     }



     if(!l_found_in_cache)
     {
       // TODO: decide if l_tmp_mask_image_id should be stored in cache or to delete immediate.
       // store if:
       //  - there are more than one references
       //  - if mask stepsize < 1.0
       //  - the last frame of the mask sequence  (l_framenr == maskdef_elem->frame_count)
       //     (that has to be repeated for all remaining frame in the clip)

       // if((frn_elem->mask_stepsize < 1.0)
       // || (l_was_last_maskframe)
       // )



       gap_image_delete_immediate(l_tmp_mask_image_id);
     }
  }

}  /* end p_fetch_and_add_layermask */



/* ----------------------------------------------------
 * p_open_video_handle_private
 * ----------------------------------------------------
 * this procedure builds a framerange list from
 * the given storyboard file.
 * if NULL is passed as storyboard_file
 * the list is built with just one entry.
 * from basename and ext parameters.
 *
 * return framerange list
 */
static GapStoryRenderVidHandle *
p_open_video_handle_private(    gboolean ignore_audio
                      , gboolean ignore_video
                      , gboolean create_audio_tmp_files
                      , gdouble  *progress_ptr
                      , char *status_msg
                      , gint32 status_msg_len
                      , const char *storyboard_file
                      , const char *basename
                      , const char *ext
                      , gint32  frame_from
                      , gint32  frame_to
                      , gint32 *frame_count   /* output total frame_count , or 0 on failure */
                      , gboolean do_gimp_progress
                      , GapLibTypeInputRange input_mode
                      , const char *imagename
                      , const char *preferred_decoder
                      , gint32 seltrack
                      , gint32 exact_seek
                      , gdouble delace
                      , gboolean compensate_framerange
                      , GapStoryBoard *stb_mem_ptr
                      , char *util_sox
                      , char *util_sox_options
                      )
{
  GapStoryRenderVidHandle *vidhand;
  GapStoryRenderSection *render_section;
  GapStoryRenderFrameRangeElem *frn_elem;

  vidhand = g_malloc0(sizeof(GapStoryRenderVidHandle));
  render_section = NULL;

  if(gap_debug)
  {
    printf("p_open_video_handle_private: new vidhand:%ld\n", (long)vidhand);
  }

  if(progress_ptr) { vidhand->progress = progress_ptr; }
  else             { vidhand->progress = &vidhand->dummy_progress; }

  if(status_msg)
  {
    vidhand->status_msg = status_msg;
    vidhand->status_msg_len = status_msg_len;
  }
  else
  {
    vidhand->status_msg = NULL;
    vidhand->status_msg_len = 0;
  }

  /* registrate as user of the frame fetcher resources (i.e. the image cache) */
  vidhand->ffetch_user_id = gap_frame_fetch_register_user("gap_story_render_processor.p_open_video_handle_private");

  vidhand->isLogResourceUsage = FALSE;
  vidhand->resourceLogInterval = gap_base_get_gimprc_int_value(GAP_GIMPRC_VIDEO_STORYBOARD_RESOURCE_LOG_INTERVAL
                                                     , 0  /* default value 0 turns off resource logging */
                                                     , 0
                                                     , 100000
                                                     );
  if(vidhand->resourceLogInterval > 0)
  {
    vidhand->isLogResourceUsage = TRUE;
  }
  p_initOptionalMulitprocessorSupport(vidhand);

  vidhand->frn_list = NULL;
  vidhand->preferred_decoder = NULL;
  vidhand->master_insert_alpha_format = NULL;
  vidhand->master_insert_alpha_format_has_videobasename = FALSE;
  vidhand->master_insert_alpha_format_has_framenumber = FALSE;

  vidhand->master_insert_area_format = NULL;
  vidhand->master_insert_area_format_has_videobasename = FALSE;
  vidhand->master_insert_area_format_has_framenumber = FALSE;

  vidhand->do_gimp_progress = do_gimp_progress;
  *vidhand->progress = 0.0;
  vidhand->sterr = p_new_stb_error();
  vidhand->master_framerate = 25.0;
  vidhand->master_width = 0;
  vidhand->master_height = 0;
  vidhand->master_samplerate = 44100;    /* 44.1 kHZ CD standard Quality */
  vidhand->master_volume     = 1.0;
  vidhand->util_sox          = NULL;
  vidhand->util_sox_options  = NULL;
  gap_story_render_set_audio_resampling_program(vidhand, util_sox, util_sox_options);

  vidhand->ignore_audio      = ignore_audio;
  vidhand->ignore_video      = ignore_video;
  vidhand->create_audio_tmp_files = create_audio_tmp_files;

  vidhand->maskdef_elem = NULL;
  vidhand->is_mask_handle = FALSE;
  vidhand->parsing_section = NULL;
  vidhand->section_list = NULL;

  global_monitor_image_id = -1;
  *frame_count = 0;

  if((storyboard_file) && (input_mode == GAP_RNGTYPE_STORYBOARD))
  {
    if(*storyboard_file != '\0')
    {
      vidhand->frn_list = p_framerange_list_from_storyboard(storyboard_file
                                                  , frame_count
                                                  , vidhand
                                                  , stb_mem_ptr);
    }
    render_section = vidhand->section_list;
  }

  if (vidhand->section_list == NULL)
  {
    /* make sure that the video handle always has a main section
     * with section Name NULL.
     * (this can occure if not directly created from a storyboard
     * or when creating sub video handle for mask processing)
     */
    vidhand->section_list = p_new_render_section(NULL);
    vidhand->parsing_section = vidhand->section_list;
    render_section = vidhand->section_list;

    if(gap_debug)
    {
      printf("p_open_video_handle_private: added default MAIN render_section\n");
    }
  }

  if(gap_debug)
  {
    printf("p_open_video_handle_private: OPENING vidhand:%ld\n"
      , (long)vidhand
      );
    p_debug_print_render_section_names(vidhand);
  }

  if((render_section->frn_list == NULL)
  && (input_mode == GAP_RNGTYPE_LAYER)
  && (imagename))
  {
      gint32 l_from;
      gint32 l_to;

      l_from = frame_from;
      l_to = frame_to;
      if(compensate_framerange)
      {
        /* layerindex starts with 0, but master_index should start with 1
         * increment by 1 to compensate. (only needed for single multilayer image encoding)
         */
        l_from = 1;
        l_to = 1+ MAX(frame_to,   frame_from);

      }

      /* add element for animimage (one multilayer image) */
      frn_elem = p_new_framerange_element(GAP_FRN_ANIMIMAGE
                                         , 1           /* track */
                                         , imagename
                                         , NULL
                                         , l_from
                                         , l_to
                                         , NULL       /* storyboard_file */
                                         , NULL       /* preferred_decoder */
                                         , NULL       /* filtermacro_file */
                                         , NULL       /* frn_list */
                                         , vidhand->sterr
                                         , 1          /* seltrack */
                                         , 0              /* exact_seek*/
                                         , 0.0            /* delace */
                                         , 1.0            /* step_density */
                                         , GAP_STB_FLIP_NONE    /* flip_request */
                                         , NULL                 /* mask_name */
                                         , 1.0                  /* mask_stepsize */
                                         , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                         , TRUE                 /* mask_disable */
                                         , 1                    /* fmac_total_steps */
                                         , 0                    /* fmac_accel */
                                         , NULL                 /* colormask_file */
                                         );
      if(frn_elem)
      {
        *frame_count = frn_elem->frames_to_handle;
      }

      if(compensate_framerange)
      {
        /* add a layer 0 (as separate 1.st listelement)
         * to compensate framenumbers if the range does not start with 1
         */
        render_section->frn_list = p_new_framerange_element(GAP_FRN_ANIMIMAGE
                                         , 1           /* track */
                                         , imagename
                                         , NULL
                                         , 1          /* from */
                                         , 1          /* to */
                                         , NULL       /* storyboard_file */
                                         , NULL       /* preferred_decoder */
                                         , NULL       /* filtermacro_file */
                                         , NULL       /* frn_list */
                                         , vidhand->sterr
                                         , 1              /* seltrack */
                                         , 0              /* exact_seek*/
                                         , 0.0            /* delace */
                                         , 1.0            /* step_density */
                                         , GAP_STB_FLIP_NONE    /* flip_request */
                                         , NULL                 /* mask_name */
                                         , 1.0                  /* mask_stepsize */
                                         , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                         , TRUE                 /* mask_disable */
                                         , 1                    /* fmac_total_steps */
                                         , 0                    /* fmac_accel */
                                         , NULL                 /* colormask_file */
                                         );
        render_section->frn_list->frames_to_handle = l_from -1;
        render_section->frn_list->next = frn_elem;
        if(gap_debug)
        {
          printf("***************************\n");
          printf("COMPENSATE LAYERS (ANIMIMAGE):\n");
          gap_story_render_debug_print_framerange_list(render_section->frn_list , -1);
          printf("***************************\n");
        }
      }
      else
      {
        render_section->frn_list = frn_elem;
      }
  }

  if((render_section->frn_list == NULL)
  && (basename)
  && (ext))
  {
    gint32 l_from;
    gint32 l_to;

    l_from = frame_from;
    l_to = frame_to;
    if(compensate_framerange)
    {
      /* layerindex starts with 0, but master_index should start with 1
       * increment by 1 to compensate. (only needed for single multilayer image encoding)
       */
      l_from = MIN(frame_from, frame_to);
      l_to = MAX(frame_to,   frame_from);
    }

    if(input_mode == GAP_RNGTYPE_FRAMES)
    {
      /* element for framerange */
      frn_elem = p_new_framerange_element(GAP_FRN_FRAMES
                                         , 1           /* track */
                                         , basename
                                         , ext
                                         , l_from
                                         , l_to
                                         , NULL       /* storyboard_file */
                                         , NULL       /* preferred_decoder */
                                         , NULL       /* filtermacro_file */
                                         , NULL       /* frn_list */
                                         , vidhand->sterr
                                         , 1          /* seltrack */
                                         , 0              /* exact_seek*/
                                         , 0.0            /* delace */
                                         , 1.0            /* step_density */
                                         , GAP_STB_FLIP_NONE    /* flip_request */
                                         , NULL                 /* mask_name */
                                         , 1.0                  /* mask_stepsize */
                                         , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                         , TRUE                 /* mask_disable */
                                         , 1                    /* fmac_total_steps */
                                         , 0                    /* fmac_accel */
                                         , NULL                 /* colormask_file */
                                         );
      if(frn_elem) *frame_count = frn_elem->frames_to_handle;


      if(compensate_framerange)
      {
        /* add a SILENCE dummy (as 1.st listelement)
         * to compensate framenumbers if the range does not start with 1
         */
        render_section->frn_list = p_new_framerange_element(GAP_FRN_SILENCE
                                           , 1               /* track */
                                           , NULL
                                           , NULL
                                           , 0               /* frame_from */
                                           , l_from -1       /* frame_to */
                                           , NULL            /* storyboard_file */
                                           , NULL            /* preferred_decoder */
                                           , NULL            /* filtermacro_file */
                                           , NULL            /* frn_list */
                                           , vidhand->sterr
                                           , 1               /* seltrack */
                                           , 0               /* exact_seek*/
                                           , 0.0             /* delace */
                                           , 1.0             /* step_density */
                                           , GAP_STB_FLIP_NONE    /* flip_request */
                                           , NULL                 /* mask_name */
                                           , 1.0                  /* mask_stepsize */
                                           , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                           , TRUE                 /* mask_disable */
                                           , 1                    /* fmac_total_steps */
                                           , 0                    /* fmac_accel */
                                           , NULL                 /* colormask_file */
                                           );
        render_section->frn_list->frames_to_handle = l_from -1;
        render_section->frn_list->next = frn_elem;
        if(gap_debug)
        {
          printf("***************************\n");
          printf("COMPENSATE FRAMES:\n");
          gap_story_render_debug_print_framerange_list(render_section->frn_list , -1);
          printf("***************************\n");
        }
      }
      else
      {
        render_section->frn_list = frn_elem;
      }
    }
  }


  if((render_section->frn_list == NULL)
  && (input_mode == GAP_RNGTYPE_IMAGE)
  && (imagename))
  {
      /* add element for single image
       * (typical used only for mask definitions)
       */
      frn_elem = p_new_framerange_element(GAP_FRN_IMAGE
                                         , 1           /* track */
                                         , imagename
                                         , NULL
                                         , 1          /* frame_from */
                                         , 1          /* frame_to */
                                         , NULL       /* storyboard_file */
                                         , NULL       /* preferred_decoder */
                                         , NULL       /* filtermacro_file */
                                         , NULL       /* frn_list */
                                         , vidhand->sterr
                                         , 1          /* seltrack */
                                         , 0              /* exact_seek*/
                                         , 0.0            /* delace */
                                         , 1.0            /* step_density */
                                         , GAP_STB_FLIP_NONE    /* flip_request */
                                         , NULL                 /* mask_name */
                                         , 1.0                  /* mask_stepsize */
                                         , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                         , TRUE                 /* mask_disable */
                                         , 1                    /* fmac_total_steps */
                                         , 0                    /* fmac_accel */
                                         , NULL                 /* colormask_file */
                                         );
      if(frn_elem)
      {
        *frame_count = frn_elem->frames_to_handle;
      }
      render_section->frn_list = frn_elem;
  }


  if((render_section->frn_list == NULL)
  && (input_mode == GAP_RNGTYPE_MOVIE)
  && (imagename))
  {
      /* add element for single movie input
       * (typical used only for mask definitions)
       */
      frn_elem = p_new_framerange_element(GAP_FRN_MOVIE
                                         , 1           /* track */
                                         , imagename
                                         , NULL
                                         , frame_from
                                         , frame_to
                                         , NULL       /* storyboard_file */
                                         , preferred_decoder
                                         , NULL       /* filtermacro_file */
                                         , NULL       /* frn_list */
                                         , vidhand->sterr
                                         , seltrack
                                         , exact_seek
                                         , delace
                                         , 1.0            /* step_density */
                                         , GAP_STB_FLIP_NONE    /* flip_request */
                                         , NULL                 /* mask_name */
                                         , 1.0                  /* mask_stepsize */
                                         , GAP_MSK_ANCHOR_CLIP  /* mask_anchor */
                                         , TRUE                 /* mask_disable */
                                         , 1                    /* fmac_total_steps */
                                         , 0                    /* fmac_accel */
                                         , NULL                 /* colormask_file */
                                         );
      if(frn_elem)
      {
        *frame_count = frn_elem->frames_to_handle;
      }
      render_section->frn_list = frn_elem;
  }


  /* select MAIN section (has section_name NULL) per default */
  p_select_section_by_name(vidhand, NULL);

  /* p_free_stb_error(vidhand->sterr); */

  p_refresh_min_max_vid_tracknumbers(vidhand);


  if(gap_debug)
  {
    printf("\n\np_open_video_handle_private: END vidhand:%ld\n\n", (long)vidhand);
    gap_story_render_debug_print_framerange_list(vidhand->frn_list, -1);
  }
  return(vidhand);

} /* end p_open_video_handle_private */


/* ----------------------------------------------------
 * gap_story_render_open_extended_video_handle
 * ----------------------------------------------------
 * this procedure builds a framerange list from
 * the given storyboard file.
 * if NULL is passed as storyboard_file
 * the list is built with just one entry.
 * from basename and ext parameters.
 *
 * return framerange list
 */
GapStoryRenderVidHandle *
gap_story_render_open_extended_video_handle(    gboolean ignore_audio
                      , gboolean ignore_video
                      , gboolean create_audio_tmp_files
                      , gdouble  *progress_ptr
                      , char *status_msg
                      , gint32 status_msg_len
                      , GapLibTypeInputRange input_mode
                      , const char *imagename
                      , const char *storyboard_file
                      , const char *basename
                      , const char *ext
                      , gint32  frame_from
                      , gint32  frame_to
                      , gint32 *frame_count   /* output total frame_count , or 0 on failure */
                      , char *util_sox
                      , char *util_sox_options
                      )
{
  return(p_open_video_handle_private( ignore_audio  /* ignore_audio */
                            , ignore_video          /* dont ignore_video */
                            , create_audio_tmp_files
                            , progress_ptr          /* progress_ptr */
                            , status_msg            /* status_msg */
                            , status_msg_len        /* status_msg_len */
                            ,storyboard_file
                            ,basename
                            ,ext
                            ,frame_from
                            ,frame_to
                            ,frame_count
                            ,FALSE          /* DONT do_gimp_progress */
                            ,input_mode
                            ,imagename
                            ,NULL           /* preferred_decoder */
                            ,1              /* seltrack */
                            ,0              /* exact_seek */
                            ,0.0            /* delace */
                            ,TRUE           /* compensate_framerange */
                            ,NULL           /* stb_mem_ptr */
                            ,util_sox
                            ,util_sox_options
                            )
         );

} /* end gap_story_render_open_extended_video_handle */

/* ----------------------------------------------------
 * gap_story_render_open_vid_handle_from_stb
 * ----------------------------------------------------
 * this procedure builds a framerange list from
 * the given storyboard in MEMORY.
 *
 * this wrapper to p_open_video_handle
 * ignores AUDIO informations in the storyboard file
 * (Typically the Player uses this variant
 *  for playback of composite storyboard video)
 *
 * return the video handle
 */
GapStoryRenderVidHandle *
gap_story_render_open_vid_handle_from_stb(GapStoryBoard *stb_mem_ptr
                          ,gint32 *frame_count   /* output total frame_count , or 0 on failure */
                          )
{
  GapStoryRenderVidHandle *l_vidhand;

  l_vidhand = NULL;
  if(stb_mem_ptr)
  {
    l_vidhand = p_open_video_handle_private( TRUE         /* ignore_audio */
                            , FALSE        /* dont ignore_video */
                            , FALSE        /* create_audio_tmp_files */
                            , NULL         /* progress_ptr */
                            , NULL         /* status_msg */
                            , 0            /* status_msg_len */
                            ,stb_mem_ptr->storyboardfile
                            , NULL         /* basename */
                            , NULL         /* ext */
                            ,1             /* frame_from (not relevant) */
                            ,1             /* frame_to (not relevant)  */
                            ,frame_count
                            ,FALSE         /* do_gimp_progress */
                            ,GAP_RNGTYPE_STORYBOARD         /* input_mode */
                            ,NULL           /* imagename */
                            ,NULL           /* preferred_decoder */
                            ,1              /* seltrack */
                            ,0              /* exact_seek */
                            ,0.0            /* delace */
                            ,TRUE           /* compensate_framerange */
                            ,stb_mem_ptr    /* stb_mem_ptr */
                            ,NULL           /* util_sox */
                            ,NULL           /* util_sox_options */
                            );
  }

  return(l_vidhand);
}  /* end gap_story_render_open_vid_handle_from_stb */

/* ----------------------------------------------------
 * gap_story_render_open_vid_handle
 * ----------------------------------------------------
 * this procedure builds a framerange list from
 * the given storyboard file.
 * if NULL is passed as storyboard_file
 * the list is built with just one entry
 * from basename and ext parameters.
 *
 * this wrapper to p_open_video_handle
 * ignores AUDIO informations in the storyboard file
 * (The encoders use this variant
 *  because the common GUI usually creates
 *  the composite audio track, and passes
 *  the resulting WAV file ready to use)
 *
 * return the video handle
 */
GapStoryRenderVidHandle *
gap_story_render_open_vid_handle(GapLibTypeInputRange input_mode
                      , gint32 image_id
                      , const char *storyboard_file
                      , const char *basename
                      , const char *ext
                      , gint32  frame_from
                      , gint32  frame_to
                      , gint32 *frame_count   /* output total frame_count , or 0 on failure */
                      )
{
  GapStoryRenderVidHandle *l_vidhand;
  char *imagename;

  imagename = NULL;
  if(image_id >= 0)
  {
    imagename = gimp_image_get_filename(image_id);
  }
  l_vidhand = p_open_video_handle_private( TRUE         /* ignore_audio */
                            , FALSE        /* dont ignore_video */
                            , FALSE        /* create_audio_tmp_files */
                            , NULL         /* progress_ptr */
                            , NULL         /* status_msg */
                            , 0            /* status_msg_len */
                            ,storyboard_file
                            ,basename
                            ,ext
                            ,frame_from
                            ,frame_to
                            ,frame_count
                            ,TRUE          /* do_gimp_progress */
                            ,input_mode
                            ,imagename
                            ,NULL           /* preferred_decoder */
                            ,1              /* seltrack */
                            ,0              /* exact_seek */
                            ,0.0            /* delace */
                            ,TRUE           /* compensate_framerange */
                            ,NULL           /* stb_mem_ptr */
                            ,NULL           /* util_sox */
                            ,NULL           /* util_sox_options */
                            );

  if(imagename)
  {
    g_free(imagename);
  }
  return(l_vidhand);
}


/* ----------------------------------------------------
 * p_exec_filtermacro
 * ----------------------------------------------------
 * - execute the (optional) filtermacro_file if not NULL
 *   (filtermacro_file is a set of one or more gimp_filter procedures
 *    with predefined parameter values)
 * returns the resulting layer_id (this may be the same as the specified layer_id at calling time
 *           but can change in case the called filter did add additional layers that were
 *           merged to one resulting layer (either in the called filter or after the filtercall
 *           by this procedure)
 */
static gint32
p_exec_filtermacro(gint32 image_id, gint32 layer_id, const char *filtermacro_file
    , const char *filtermacro_file_to
    , gdouble current_step
    , gint32 total_steps
    , gint accelerationCharacteristic
)
{
  GimpParam* l_params;
  gint   l_retvals;
  gint32 l_rc_layer_id;
  gint          l_nlayers;
  gint32       *l_layers_list;
  static gint32 funcId = -1;

  GAP_TIMM_GET_FUNCTION_ID(funcId, "p_exec_filtermacro");
  GAP_TIMM_START_FUNCTION(funcId);

  l_rc_layer_id = layer_id;
  if (filtermacro_file)
  {
    if(*filtermacro_file != '\0')
    {
       if(gap_debug)
       {
         printf("DEBUG: before execute filtermacro_file:%s image_id:%d layer_id:%d current:%f total:%d\n"
                              , filtermacro_file
                              , (int)image_id
                              , (int)layer_id
                              , (float)current_step
                              , (int)total_steps
                              );
       }

       if(! gimp_drawable_has_alpha (layer_id))
       {
         /* some filtermacros do not work with layer that do not have an alpha channel
          * and cause gimp to fail on attempt to call gimp_pixel_rgn_init
          * with both dirty and shadow flag set to TRUE
          * in this situation GIMP displays the error message
          *    "expected tile ack and received: 5"
          *    and causes the called plug-in to exit immediate without success
          * Therefore always add an alpha channel before calling a filtermacro.
          */
          gimp_layer_add_alpha(layer_id);
       }

       if((filtermacro_file_to == NULL)
       || (total_steps <= 1))
       {
          /* execute simple GAP Filtermacro_file */
          l_params = gimp_run_procedure (GAP_FMACNAME_PLUG_IN_NAME_FMAC,
                     &l_retvals,
                     GIMP_PDB_INT32,    GIMP_RUN_NONINTERACTIVE,
                     GIMP_PDB_IMAGE,    image_id,
                     GIMP_PDB_DRAWABLE, layer_id,
                     GIMP_PDB_STRING,   filtermacro_file,
                     GIMP_PDB_END);
       }
       else
       {
           gdouble   current_accel_step;

           current_accel_step = gap_calculate_current_step_with_acceleration(current_step
                                   , total_steps
                                   , accelerationCharacteristic
                                   );

           /* execute varying value mix of 2 GAP Filtermacro_files */
           l_params = gimp_run_procedure (GAP_FMACNAME_PLUG_IN_NAME_FMAC_VARYING,
                     &l_retvals,
                     GIMP_PDB_INT32,    GIMP_RUN_NONINTERACTIVE,
                     GIMP_PDB_IMAGE,    image_id,
                     GIMP_PDB_DRAWABLE, layer_id,
                     GIMP_PDB_STRING,   filtermacro_file,
                     GIMP_PDB_STRING,   filtermacro_file_to,
                     GIMP_PDB_FLOAT,    current_accel_step,
                     GIMP_PDB_INT32,    total_steps,
                     GIMP_PDB_END);
       }

       if(l_params[0].data.d_status != GIMP_PDB_SUCCESS)
       {
         printf("ERROR: filtermacro_file:%s failed\n", filtermacro_file);
         l_rc_layer_id = -1;
       }
       g_free(l_params);

       l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
       if(l_layers_list != NULL)
       {
         l_rc_layer_id = l_layers_list[0];
         g_free (l_layers_list);
       }
       if(l_nlayers > 1 )
       {
         /* merge visible layers (reduce to single layer) */
         l_rc_layer_id = gap_image_merge_visible_layers(image_id, GIMP_CLIP_TO_IMAGE);
       }

    }
  }

  if(gap_debug)
  {
    if(l_rc_layer_id != layer_id)
    {
      printf("p_exec_filtermacro: layer_id:%d HAS CHANGED to %d\n"
        ,(int)layer_id
        ,(int)l_rc_layer_id
        );

    }
  }

  GAP_TIMM_STOP_FUNCTION(funcId);
  return(l_rc_layer_id);
} /* end p_exec_filtermacro */


/* ----------------------------------------------------
 * p_transform_operate_on_full_layer
 * ----------------------------------------------------
 * check if it is required to operate on the full calculated resulting layer (return TRUE)
 * or if we can operate on a smaller clipped rectangle (return FALSE)
 */
static gboolean
p_transform_operate_on_full_layer(GapStoryCalcAttr *calculated, gint32 comp_image_id, gint32 tmp_image_id
  , GapStoryRenderFrameRangeElem *frn_elem)
{
  gint32 calculated_area;
  gint32 visible_on_composite_area;
  gint32 composite_img_area;
  gint32 tmp_image_area;
  gboolean l_ret;

  l_ret = TRUE;

  if(calculated->rotate != 0.0)
  {
    return (l_ret);
  }
  calculated_area = calculated->width * calculated->height;
  visible_on_composite_area = calculated->visible_width * calculated->visible_height;
  composite_img_area = gimp_image_width(comp_image_id) * gimp_image_height(comp_image_id);

  if(gap_debug)
  {
      tmp_image_area = gimp_image_width(tmp_image_id) * gimp_image_height(tmp_image_id);
      printf("p_transform_operate_on_full_layer areasize tmp:%d, calc:%d, visble:%d, composite:%d\n"
         , (int)tmp_image_area
         , (int)calculated_area
         , (int)visible_on_composite_area
         , (int)composite_img_area
         );
  }

  if ((calculated_area > composite_img_area)
  ||  (visible_on_composite_area < composite_img_area))
  {
    if((frn_elem->mask_name != NULL)
    && (frn_elem->mask_anchor != GAP_MSK_ANCHOR_MASTER))
    {
      tmp_image_area = gimp_image_width(tmp_image_id) * gimp_image_height(tmp_image_id);

      /* operation on clipped rectangle requires creation of the mask already at
       * tmp_image_area size. in case the calculated area
       * is smaller than the original tmp image, it will be
       * more efficient to operate with the complete layer at calculated_area size
       */
      if (tmp_image_area < calculated_area)
      {
        l_ret = FALSE; /* operate on visble rectangle only */
      }
    }
    else
    {
      l_ret = FALSE; /* operate on visble rectangle only */
    }
  }

  return (l_ret);

}  /* end p_transform_operate_on_full_layer */


/* ----------------------------------------------------
 * p_transform_rotate_layer_at
 * ----------------------------------------------------
 * rotate layer by the specified angle in degree
 * the center of the rotation is specified in image coordinates
 * via image_offset_x and image_offset_y.
 */
void
p_transform_rotate_layer_at(gint32 image_id, gint32 layer_id, gdouble rotate
   , gint image_offset_x, gint image_offset_y)
{
  gint32       l_mask_id;
  gint32       l_center_x;
  gint32       l_center_y;
  gdouble      l_angle_rad;
  gdouble      l_angle_deg;
  gint         l_layer_offset_x;
  gint         l_layer_offset_y;

  l_angle_deg = rotate;
  while(l_angle_deg >= 360.0)
  {
    l_angle_deg -=360;
  }
  while(l_angle_deg <= -360.0)
  {
    l_angle_deg +=360;
  }

  if ((l_angle_deg < 0.05) && (l_angle_deg > -0.05))
  {
    /* ignore very small rotations */
    return;
  }
  l_angle_rad = (l_angle_deg * G_PI) / 180.0;

  /* check for layermask */
  l_mask_id = gimp_layer_get_mask(layer_id);
  if(l_mask_id >= 0)
  {
     /* apply the layermask
      *   some transitions (especially rotate) can't operate proper on
      *   layers with masks !
      *   (tests with gimp_rotate resulted in trashed images,
      *    even if the mask was rotated too)
      */
      gimp_layer_remove_mask (layer_id, GIMP_MASK_APPLY);
  }

  /* remove selection (if there is one)
   *   if there is a selection transitions (gimp_rotate)
   *   will create new layer and do not operate on l_cp_layer_id
   */
  gimp_selection_none(image_id);

  gimp_drawable_offsets(layer_id, &l_layer_offset_x, &l_layer_offset_y );

  /* calculate rotation center in layer coordinates */
  l_center_x  = image_offset_x + l_layer_offset_x;
  l_center_y  = image_offset_y + l_layer_offset_y;

  if(gap_debug)
  {
    printf("p_transform_rotate_layer_at: called at layer_id:%d l_angle_deg:%.4f\n"
           "  image_offset_x:%d image_offset_y:%d\n"
           "  l_layer_offset_x:%d l_layer_offset_y:%d  l_center_x:%d l_center_y:%d"
      , (int)layer_id
      , (float)l_angle_deg
      , (int)image_offset_x
      , (int)image_offset_y
      , (int)l_layer_offset_x
      , (int)l_layer_offset_y
      , (int)l_center_x
      , (int)l_center_y
      );
  }

  gimp_context_set_defaults();
  gimp_context_set_transform_resize(GIMP_TRANSFORM_RESIZE_ADJUST);   /* do NOT clip transformation results */                                 


  /* perform rotation of the layer (rotation also changes size as needed) */
  gimp_item_transform_rotate(layer_id
                                        , l_angle_rad
                                        , FALSE            /* auto_center */
                                        , l_center_x
                                        , l_center_y
                                        );


}  /* end p_transform_rotate_layer_at */






/* ----------------------------------------------------
 * p_transform_with_movepath_processing
 * ----------------------------------------------------
 * - copy the layer (layer_id) from tmp_image to the composite image
 *   and apply movepath processing for complex transformations on the copied layer.
 * - the source Layer (layer_id) must be part of tmp_image_id
 * - additionally to the movepath transitions
 *   also set opacity, move, scale and rotate the result layer of movepath processing
 *   according to video attributes
 * - optional apply transparency in case mask is attached (anchored to clip or master)
 *   In case mask is anchored to clip, it is applied BEFORE movepath transformations
 *
 * return the new created layer id in the comp_image_id
 *   (that is already added to the composte image on top of layerstack
 *    and is already transformed
 *    -- except postprocessing for mask anchor master and opacity )
 */
static gint32
p_transform_with_movepath_processing( gint32 comp_image_id
                         , gint32 tmp_image_id
                         , gint32 layer_id
                         , gboolean keep_proportions
                         , gboolean fit_width
                         , gboolean fit_height
                         , gdouble rotate     /* rotation in degree */
                         , gdouble opacity    /* 0.0 upto 1.0 */
                         , gdouble scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                         , gdouble move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         , const char *movepath_file_xml
                         , gdouble movepath_framePhase
                         )
{
  gint32 vid_width;
  gint32 vid_height;
  gint32 l_new_layer_id;
  gint32 l_movepath_layer_id;
  gint32 l_tmp_movpath_image_id;
  gint32 l_empty_layer_id;

  gint   l_base_offset_x;
  gint   l_base_offset_y;
  gint   l_mov_dx;
  gint   l_mov_dy;
  gint   result_width;
  gint   result_height;

  if(gap_debug)
  {
    printf("\n--\np_transform_with_movepath_processing: called at layer_id: %d, tmp_image_id:%d comp_image_id:%d\n"
      , (int)layer_id
      , (int)tmp_image_id
      , (int)comp_image_id
      );
    if(frn_elem != NULL)
    {
      gap_story_render_debug_print_frame_elem(frn_elem, -77);
    }
  }

  l_tmp_movpath_image_id = -1;


  vid_width = gimp_image_width(comp_image_id);
  vid_height = gimp_image_height(comp_image_id);

  if(frn_elem != NULL)
  {
    /* process mask before the movepath transitions
     * (but only in case when not achored to master, but is anchored to clip)
     */
    if((frn_elem->mask_name != NULL)
    && (frn_elem->mask_anchor != GAP_MSK_ANCHOR_MASTER))
    {
      /* fetch and add mask at calculated scaled size of layer_id */
      p_fetch_and_add_layermask(vidhand
                , frn_elem
                , local_stepcount
                , tmp_image_id
                , layer_id
                , frn_elem->mask_anchor
                );
      /* apply the mask (necessary because some of the following transformations on this layer
       * would ignore the layer mask)
       */
      gimp_layer_remove_mask(layer_id, GIMP_MASK_APPLY);
    }
  }


  /* create a temporary image in composite video size with a transparent layer
   * (this image acts as frame for the movepath processing,
   * and the layer_id acts as the moving object -- to be transformed --)
   */
  l_tmp_movpath_image_id = gimp_image_new(vid_width, vid_height, GIMP_RGB);
  gimp_image_undo_disable(l_tmp_movpath_image_id);


  l_empty_layer_id = gimp_layer_new(l_tmp_movpath_image_id, "empty",
                        vid_width, vid_height,
                        GIMP_RGBA_IMAGE,
                        100.0,     /* Opacity */
                        GIMP_NORMAL_MODE);
  gimp_image_insert_layer(l_tmp_movpath_image_id, l_empty_layer_id, 0, 0);
  gap_layer_clear_to_color(l_empty_layer_id
                          , 0.0, 0.0, 0.0, 0.0  /* r,g,b,a (black full transparent) */
                          );

  gap_mov_exec_move_path_singleframe_directcall(l_tmp_movpath_image_id
     , layer_id
     , keep_proportions
     , fit_width
     , fit_height
     , movepath_framePhase
     , movepath_file_xml
     );

  l_movepath_layer_id = gap_image_merge_visible_layers(l_tmp_movpath_image_id
                         , GIMP_EXPAND_AS_NECESSARY
                         );

  /* at this point the complex movepath transformation is done
   * and the result is available as layer l_movepath_layer_id
   * note that fit size flags were already handled in the movepath transformation.
   *
   * further processing continues with the result of movepath processing
   * (the image l_tmp_movpath_image_id)
   * note that the resulting layer (l_movepath_layer_id) may be oversized,
   * i.e. may overlap image boudaries due to the movepath transformations
   * (when the clip to image flag was not active in movepath processing)
   */
  if(gap_debug)
  {
    printf("p_transform_and_add_layer: MOVEPATH DONE at layer_id: %d (l_movepath_layer_id:%d), tmp_image_id:%d\n"
      , (int)layer_id
      , (int)l_movepath_layer_id
      , (int)tmp_image_id
      );
  }

  /* findout the offsets of the  layer within the Image */
  gimp_drawable_offsets(l_movepath_layer_id, &l_base_offset_x, &l_base_offset_y );

  /* handle movement */
  l_mov_dx = rint((gdouble)vid_width * move_x);
  l_mov_dy = rint((gdouble)vid_height * move_y);
  if((l_mov_dx != 0) || (l_mov_dy != 0))
  {
    gimp_layer_set_offsets(l_movepath_layer_id
                         , l_base_offset_x + l_mov_dx
                         , l_base_offset_y + l_mov_dy
                         );
  }

  /* handle rotation where the center of the rotation
   * is center of the frame image + movment offset (l_mov_dx / l_mov_dy)
   */
  p_transform_rotate_layer_at(l_tmp_movpath_image_id
                            , l_movepath_layer_id
                            , rotate
                            , (vid_width / 2.0) + l_mov_dx
                            , (vid_height / 2.0) + l_mov_dy
                            );

  /* handle scaling */
  result_width  = MAX(1, rint(((gdouble)vid_width  * scale_x)));
  result_height = MAX(1, rint(((gdouble)vid_height * scale_y)));

  if((result_width != vid_width)
  || (result_height != vid_height))
  {
    gint offs_x;
    gint offs_y;

    if(gap_debug)
    {
      printf("DEBUG: p_transform_with_movepath_processing scaling tmp image from %dx%d to %dx%d\n"
                            , (int)gimp_image_width(l_tmp_movpath_image_id)
                            , (int)gimp_image_height(l_tmp_movpath_image_id)
                            , (int)result_width
                            , (int)result_width
                            );

    }


    if((result_width >= vid_width)
    && (result_height >= vid_height))
    {
      /* handle enlarge image in both dimensions scenario */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, result_height);
      offs_x = rint((result_width - vid_width) / 2.0);
      offs_y = rint((result_height - vid_height) / 2.0);
      gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);
    }
    else if ((result_width <= vid_width)
    && (result_height <= vid_height))
    {
      /* handle shrink image in both dimensions scenario */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, result_height);
      offs_x = rint((vid_width - result_width) / 2.0);
      offs_y = rint((vid_height - result_height) / 2.0);

      /* resize image canvas back to coposite videoframe size */
      gimp_image_resize(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);
    }
    else if ((result_width > vid_width)
    && (result_height <= vid_height))
    {
      /* handle enlarge width but shrink height scenario */
      /* 1. enlarge width, keep same image height */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
      offs_x = rint((result_width - vid_width) / 2.0);
      offs_y = 0;
      gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);

      /* 2. shrink height, keep same image width */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
      offs_x = 0;
      offs_y = rint((vid_height - result_height) / 2.0);

      /* resize image canvas back to coposite videoframe size */
      gimp_image_resize(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);

    }
    else if ((result_width < vid_width)
    && (result_height >= vid_height))
    {
      /* handle enlarge height but shrink width scenario */
      /* 1. enlarge height, keep same image width */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
      offs_x = 0;
      offs_y = rint((result_height - vid_height) / 2.0);
      gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);

      /* 2. shrink width, keep same image height */
      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
      offs_x = rint((vid_width - result_width) / 2.0);
      offs_y = 0;

      /* resize image canvas back to coposite videoframe size */
      gimp_image_resize(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);

    }



  }

  /* trim resulting layer to imagesize after all transformations
   * (move, rotate, and scale) are processed
   * This includes both extesion to image size and crop parts outside boundaries.
   */
  gimp_layer_resize_to_image_size(l_movepath_layer_id);

  /* make 1:1 copy of the l_movepath_layer_id (that has already same size as
   * the composite image and is already transformed)
   * and add to the composite image (at top)
   */
  l_new_layer_id = gimp_layer_new_from_drawable(l_movepath_layer_id, comp_image_id);
  if(l_new_layer_id >= 0)
  {
    if(! gimp_drawable_has_alpha(l_new_layer_id))
    {
       /* have to add alpha channel */
       gimp_layer_add_alpha(l_new_layer_id);
    }
    gimp_image_insert_layer(comp_image_id, l_new_layer_id, 0, 0);
    gimp_layer_set_offsets(l_new_layer_id, 0, 0);
  }

//p_debug_dup_image(l_tmp_movpath_image_id);


  if(l_tmp_movpath_image_id >= 0)
  {
    gap_image_delete_immediate(l_tmp_movpath_image_id);
  }

  return(l_new_layer_id);

}   /* end p_transform_with_movepath_processing */


/* ----------------------------------
 * p_transform_postprocessing
 * ----------------------------------
 * perform the final processing steps on the transformed
 * layer. This includes applying mask (but only in case mask is anchored to master)
 * and setting the opacity and layermode.
 */
static void
p_transform_postprocessing(gint32 new_layer_id
  , GapStoryRenderFrameRangeElem *frn_elem
  , GapStoryRenderVidHandle *vidhand
  , gdouble opacity
  , gint32 local_stepcount
  , gint32 fmacLayermode    /* layermode after (optional) filtermacro execution */
  )
{
  if((frn_elem->mask_name != NULL)
  && (frn_elem->mask_anchor == GAP_MSK_ANCHOR_MASTER))
  {
     p_fetch_and_add_layermask(vidhand
                  , frn_elem
                  , local_stepcount
                  , gimp_item_get_image(new_layer_id)
                  , new_layer_id
                  , frn_elem->mask_anchor
                  );

  }

  gimp_layer_set_opacity(new_layer_id, opacity);
  if (fmacLayermode != GIMP_NORMAL_MODE)
  {
    /* apply the layermode as established via filtermacro call
     * ... but only in case when there was no other transformation
     *     (such as move path) that has already set
     *     another layermode than GIMP_NORMAL_MODE
     */
    if (gimp_layer_get_mode(new_layer_id) == GIMP_NORMAL_MODE)
    {
      gimp_layer_set_mode(new_layer_id, fmacLayermode);
    }
  }

}  /* end p_transform_postprocessing */



/* ----------------------------------------------------
 * p_transform_and_add_layer
 * ----------------------------------------------------
 * - copy the layer (layer_id) from tmp_image to the composite image
 *   using copy/paste
 * - the source Layer (layer_id) must be part of tmp_image_id
 * - set opacity, scale and move layer_id according to video attributes
 * - optional apply transparency in case mask is attached (anchored to clip or master)
 *
 * return the new created layer id in the comp_image_id
 *   (that is already added to the composte image on top of layerstack)
 */
static gint32
p_transform_and_add_layer( gint32 comp_image_id
                         , gint32 tmp_image_id
                         , gint32 layer_id
                         , gboolean keep_proportions
                         , gboolean fit_width
                         , gboolean fit_height
                         , gdouble rotate     /* rotation in degree */
                         , gdouble opacity    /* 0.0 upto 1.0 */
                         , gdouble scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                         , gdouble move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                         , const char *filtermacro_file
                         , gint32 flip_request
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         , const char *movepath_file_xml
                         , gdouble movepath_framePhase
                         )
{
  gint32 vid_width;
  gint32 vid_height;
  gint32 l_new_layer_id;
  gint32 l_fsel_layer_id;
  gint32 l_fmac_layer_id;
  gint32 l_tmp_image_id;
  gint32 l_layermode;

  GapStoryCalcAttr  calculate_attributes;
  GapStoryCalcAttr  *calculated;

  static gint32 funcId = -1;
  static gint32 funcIdPath = -1;
  static gint32 funcIdFull = -1;
  static gint32 funcIdScale = -1;
  static gint32 funcIdRotate = -1;
  static gint32 funcIdClipped = -1;
  static gint32 funcIdClipScale = -1;

  GAP_TIMM_GET_FUNCTION_ID(funcId,          "p_transform_and_add_layer");
  GAP_TIMM_GET_FUNCTION_ID(funcIdPath,      "p_transform_and_add_layer.PathFullsize");
  GAP_TIMM_GET_FUNCTION_ID(funcIdFull,      "p_transform_and_add_layer.Fullsize");
  GAP_TIMM_GET_FUNCTION_ID(funcIdScale,     "p_transform_and_add_layer.ScaleFullsize");
  GAP_TIMM_GET_FUNCTION_ID(funcIdRotate,    "p_transform_and_add_layer.RotateFullsize");
  GAP_TIMM_GET_FUNCTION_ID(funcIdClipped,   "p_transform_and_add_layer.Clippedsize");
  GAP_TIMM_GET_FUNCTION_ID(funcIdClipScale, "p_transform_and_add_layer.ScaleClippedsize");

  GAP_TIMM_START_FUNCTION(funcId);


  if(gap_debug)
  {
    printf("p_transform_and_add_layer: called at layer_id: %d, tmp_image_id:%d comp_image_id:%d\n"
      , (int)layer_id
      , (int)tmp_image_id
      , (int)comp_image_id);
    gap_story_render_debug_print_frame_elem(frn_elem, -77);
  }

  l_tmp_image_id = tmp_image_id;

  /* execute input track specific  filtermacro (optional if supplied)
   * local_stepcount  (usually delivered by procedure p_fetch_framename)
   *  is used to define fmac_current_step
   * (starts at 0)
   */
  l_fmac_layer_id = p_exec_filtermacro(l_tmp_image_id
                      , layer_id
                      , filtermacro_file
                      , frn_elem->filtermacro_file_to
                      , (gdouble)(local_stepcount) /* fmac_current_step */
                      , frn_elem->fmac_total_steps
                      , frn_elem->fmac_accel
                    );
  /* the filtermacro may have set another layermode than GIMP_NORMAL_MODE ..
   * .. therfore save to l_layermode and apply to final resulting layer postprocessing
   */
  l_layermode = gimp_layer_get_mode(l_fmac_layer_id);

  if(gap_debug)
  {
    printf("p_transform_and_add_layer: FILTERMACRO DONE at layer_id: %d (l_fmac_layer_id:%d), l_tmp_image_id:%d\n"
      , (int)layer_id
      , (int)l_fmac_layer_id
      , (int)l_tmp_image_id
      );
  }



  calculated = &calculate_attributes;

  layer_id = gap_layer_flip(l_fmac_layer_id, flip_request);

  /* execute complex move path transformation (if movepath xml settings present) */
  if(movepath_file_xml != NULL)
  {
    GAP_TIMM_START_FUNCTION(funcIdPath);

    l_new_layer_id = p_transform_with_movepath_processing(comp_image_id
                            , tmp_image_id
                            , layer_id
                            , keep_proportions
                            , fit_width
                            , fit_height
                            , rotate     /* rotation in degree */
                            , opacity    /* 0.0 upto 1.0 */
                            , scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                            , scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                            , move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                            , move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                            , frn_elem
                            , vidhand
                            , local_stepcount
                            , movepath_file_xml
                            , movepath_framePhase
                         );

      calculated->opacity = CLAMP(opacity * 100.0, 0.0, 100.0);
      GAP_TIMM_STOP_FUNCTION(funcIdPath);
  }
  else
  {

    vid_width = gimp_image_width(comp_image_id);
    vid_height = gimp_image_height(comp_image_id);

    /* expand layer to tmp image size (before applying any scaling) */
    gimp_layer_resize_to_image_size(layer_id);

    /* calculate scaling, offsets and opacity  according to current attributes */
    gap_story_file_calculate_render_attributes(&calculate_attributes
      , vid_width
      , vid_height
      , vid_width
      , vid_height
      , gimp_image_width(l_tmp_image_id)
      , gimp_image_height(l_tmp_image_id)
      , keep_proportions
      , fit_width
      , fit_height
      , rotate
      , opacity
      , scale_x
      , scale_y
      , move_x
      , move_y
      );

    calculated = &calculate_attributes;


    /* add a new transparent layer to the composite image */
    l_new_layer_id = gimp_layer_new(comp_image_id
                              , "pasted_track"
                              , vid_width
                              , vid_height
                              , GIMP_RGBA_IMAGE
                              , 0.0         /* Opacity full transparent */
                              ,GIMP_NORMAL_MODE);
    gimp_image_insert_layer(comp_image_id, l_new_layer_id, 0, 0);
    gap_layer_clear_to_color(l_new_layer_id, 0.0, 0.0, 0.0, 0.0);

    if (TRUE == p_transform_operate_on_full_layer(calculated, comp_image_id, l_tmp_image_id, frn_elem))
    {
      GAP_TIMM_START_FUNCTION(funcIdFull);

      /* operate on layer in full calculated size */
      /* ---------------------------------------- */
      if(gap_debug)
      {
        printf("p_transform operate on FULL size layer\n");
      }
      /* check size and scale source layer_id to calculated size
       */
      if ((gimp_image_width(l_tmp_image_id) != calculated->width)
      ||  (gimp_image_height(l_tmp_image_id) != calculated->height) )
      {
        if(gap_debug)
        {
          printf("DEBUG: p_transform_and_add_layer scaling tmp image from %dx%d to %dx%d\n"
                            , (int)gimp_image_width(l_tmp_image_id)
                            , (int)gimp_image_height(l_tmp_image_id)
                            , (int)calculated->width
                            , (int)calculated->height
                            );

        }
        GAP_TIMM_START_FUNCTION(funcIdScale);
        gimp_layer_scale(layer_id, calculated->width, calculated->height
                        , FALSE  /* FALSE: centered at image TRUE: centered local on layer */
                        );
        GAP_TIMM_STOP_FUNCTION(funcIdScale);
      }



      if((frn_elem->mask_name != NULL)
      && (frn_elem->mask_anchor != GAP_MSK_ANCHOR_MASTER))
      {
         /* fetch and add mask at calculated scaled size of layer_id */
         p_fetch_and_add_layermask(vidhand
                  , frn_elem
                  , local_stepcount
                  , l_tmp_image_id
                  , layer_id
                  , frn_elem->mask_anchor
                  );
        /* apply the mask (necessary because the following copy with this layer
         * as source would ignore the layer mask)
         */
        gimp_layer_remove_mask(layer_id, GIMP_MASK_APPLY);
      }

      if((rotate  > 0.05) || (rotate < -0.05))
      {
        gint32       l_orig_width;
        gint32       l_orig_height;

        l_orig_width  = gimp_drawable_width(layer_id);
        l_orig_height  = gimp_drawable_height(layer_id);

        GAP_TIMM_START_FUNCTION(funcIdRotate);

        gap_story_transform_rotate_layer(l_tmp_image_id, layer_id, rotate);


        GAP_TIMM_STOP_FUNCTION(funcIdRotate);

        /* recalculate offests to compensate size changes caused by rotation */
        calculated->x_offs = calculated->x_offs + (l_orig_width / 2.0) - (gimp_drawable_width(layer_id) / 2.0);
        calculated->y_offs = calculated->y_offs + (l_orig_height / 2.0) - (gimp_drawable_height(layer_id) / 2.0);
      }

      /* copy from tmp_image and paste to composite_image
       * force copying of the complete layer by clearing the selection
       */
      gimp_selection_none(l_tmp_image_id);
      gimp_edit_copy(layer_id);
      l_fsel_layer_id = gimp_edit_paste(l_new_layer_id, FALSE);  /* FALSE paste clear selection */

      gimp_layer_set_offsets(l_fsel_layer_id
                        , calculated->x_offs
                        , calculated->y_offs
                        );

      gimp_floating_sel_anchor(l_fsel_layer_id);

      GAP_TIMM_STOP_FUNCTION(funcIdFull);
    }
    else
    {
      GAP_TIMM_START_FUNCTION(funcIdClipped);

      /* operate on clipped rectangle size (rotation not handled in this case) */
      /* --------------------------------------------------------------------- */
      if(gap_debug)
      {
        printf("p_transform operate on CLIPPED RECTANGLE\n");
      }

      if ((calculated->visible_width <= 0) || (calculated->visible_height <= 0))
      {
        /* nothing will be visible (width or height is 0), so we can skip copying and scaling */
        return (l_new_layer_id);  /* that is still fully transparent */
      }

      if((frn_elem->mask_name != NULL)
      && (frn_elem->mask_anchor != GAP_MSK_ANCHOR_MASTER))
      {
        /* add and apply layermask at original unscaled tmp_image size */
        p_fetch_and_add_layermask(vidhand
                    , frn_elem
                    , local_stepcount
                    , l_tmp_image_id
                    , layer_id
                    , frn_elem->mask_anchor
                    );
        /* apply the mask (necessary because the following copy with this layer
         * as source would ignore the layer mask)
         */
        gimp_layer_remove_mask(layer_id, GIMP_MASK_APPLY);
      }

      /* copy selected clipped source rectangle from tmp_image to composite image
       * as floating selection attached to l_new_layer_id (visble part at source size)
       */
      {
        gdouble sx;
        gdouble sy;
        gdouble swidth;
        gdouble sheight;

        sx = 0;
        if (calculated->x_offs < 0)
        {
          sx = (0 - calculated->x_offs) *
              ((gdouble)gimp_image_width(l_tmp_image_id) / MAX((gdouble)calculated->width, 1.0));
        }

        sy = 0;
        if (calculated->y_offs < 0)
        {
          sy = (0 - calculated->y_offs) *
              ((gdouble)gimp_image_height(l_tmp_image_id) / MAX((gdouble)calculated->height, 1.0));
        }

        swidth = calculated->visible_width * gimp_image_width(l_tmp_image_id) / MAX(calculated->width, 1);
        sheight = calculated->visible_height * gimp_image_height(l_tmp_image_id) / MAX(calculated->height, 1);

        if(gap_debug)
        {
          printf("p_transform source rectangle offset sx:%.4f, sy:%.4f, swidth:%.4f, sheight:%.4f\n"
             , (float)sx
             , (float)sy
             , (float)swidth
             , (float)sheight
             );
        }

        gimp_context_push();
        gimp_context_set_feather(FALSE);
        gimp_context_set_feather_radius(0.0, 0.0);

        gimp_image_select_rectangle(l_tmp_image_id
                      , GIMP_CHANNEL_OP_REPLACE
                      , sx, sy
                      , swidth, sheight
                      );
        gimp_context_pop();
      }

      gimp_edit_copy(layer_id);
      l_fsel_layer_id = gimp_edit_paste(l_new_layer_id, FALSE);  /* FALSE paste clear selection */


      /* scale floating selection to visible target size */
      if ((gimp_drawable_width(l_fsel_layer_id) != calculated->visible_width)
      ||  (gimp_drawable_height(l_fsel_layer_id) != calculated->visible_height) )
      {
        GAP_TIMM_START_FUNCTION(funcIdClipScale);
        if(gap_debug)
        {
          printf("DEBUG: p_transform_and_add_layer scaling floating sel layer from (%dx%d) to ==> (%dx%d)\n"
                            , (int)gimp_drawable_width(l_fsel_layer_id)
                            , (int)gimp_drawable_height(l_fsel_layer_id)
                            , (int)calculated->visible_width
                            , (int)calculated->visible_height
                            );

        }

        gimp_layer_scale(l_fsel_layer_id, calculated->visible_width, calculated->visible_height
                      , FALSE  /* FALSE: centered at image TRUE: centered local on layer */
                      );

        GAP_TIMM_STOP_FUNCTION(funcIdClipScale);
      }

      /* move floating selection according to target offsets
       * (negative offsets are truncated to 0
       * because this case is already handled by selecting only the visible clipped rectangle)
       */
      gimp_layer_set_offsets(l_fsel_layer_id
                          , MAX(0, calculated->x_offs)
                          , MAX(0, calculated->y_offs)
                          );

      gimp_floating_sel_anchor(l_fsel_layer_id);


      GAP_TIMM_STOP_FUNCTION(funcIdClipped);

    }    /* end processing on CLIPPED part */

  }


  /* final common processing to be applied on the newly added layer */
  p_transform_postprocessing(l_new_layer_id
     , frn_elem
     , vidhand
     , calculated->opacity
     , local_stepcount
     , l_layermode
     );


  GAP_TIMM_STOP_FUNCTION(funcId);

  return(l_new_layer_id);
}   /* end p_transform_and_add_layer */



/* ---------------------------------------------------
 * gap_story_render_transform_with_movepath_processing
 * ---------------------------------------------------
 * move path processing variant for render preview purpose
 * (without mask handling)
 */
gint32
gap_story_render_transform_with_movepath_processing(gint32 comp_image_id
                         , gint32 tmp_image_id  /* must contain layer_id */
                         , gint32 layer_id
                         , gboolean keep_proportions
                         , gboolean fit_width
                         , gboolean fit_height
                         , gdouble rotate     /* rotation in degree */
                         , gdouble opacity    /* 0.0 upto 1.0 */
                         , gdouble scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                         , gdouble move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                         , gdouble move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                         , const char *movepath_file_xml
                         , gdouble movepath_framePhase
                         )
{
  gint32 l_new_layer_id;

  l_new_layer_id = p_transform_with_movepath_processing(comp_image_id
                            , tmp_image_id
                            , layer_id
                            , keep_proportions
                            , fit_width
                            , fit_height
                            , rotate     /* rotation in degree */
                            , opacity    /* 0.0 upto 1.0 */
                            , scale_x    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                            , scale_y    /* 0.0 upto 10.0 where 1.0 = 1:1 */
                            , move_x     /* -1.0 upto +1.0 where 0 = no move, -1 is left outside */
                            , move_y     /* -1.0 upto +1.0 where 0 = no move, -1 is top outside */
                            , NULL       /* frn_elem */
                            , NULL       /* vidhand */
                            , 0          /* local_stepcount */
                            , movepath_file_xml
                            , movepath_framePhase
                         );
  return (l_new_layer_id);

}  /* end gap_story_render_transform_with_movepath_processing */



/* ----------------------------------------------------
 * p_prepare_RGB_image
 * ----------------------------------------------------
 * prepare image for encoding
 * - clear undo stack
 * - convert to RGB
 * - merge all visible layer to one layer that
 *   fits the image size.
 *
 * return the resulting layer_id.
 */
static gint32
p_prepare_RGB_image(gint32 image_id)
{
  gint          l_nlayers;
  gint32       *l_layers_list;
  gint32 l_layer_id;

  l_layer_id = -1;
 /* dont waste time and memory for undo in noninteracive processing
  * of the frames
  */
  /*  gimp_image_undo_enable(image_id); */ /* clear undo stack */
  /* no more gimp_image_undo_enable, because this results in Warnings since gimp-2.1.6
   * Gimp-Core-CRITICAL **: file gimpimage.c: line 1708 (gimp_image_undo_thaw): assertion `gimage->undo_freeze_count > 0' failed
   */
  gimp_image_undo_disable(image_id); /*  NO Undo */

  l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
  if(l_layers_list != NULL)
  {
    l_layer_id = l_layers_list[0];
    g_free (l_layers_list);
  }

  if((l_nlayers > 1 ) || (gimp_layer_get_mask (l_layer_id) >= 0))
  {
     if(gap_debug) printf("DEBUG: p_prepare_image merge layers tmp image\n");

     /* merge visible layers (reduce to single layer) */
     l_layer_id = gap_image_merge_visible_layers(image_id, GIMP_CLIP_TO_IMAGE);
  }

  /* convert TO RGB if needed */
  if(gimp_image_base_type(image_id) != GIMP_RGB)
  {
     gimp_image_convert_rgb(image_id);
  }

  if(l_layer_id >= 0)
  {
    gimp_layer_resize_to_image_size(l_layer_id);
  }

  return(l_layer_id);
} /* end p_prepare_RGB_image */

/* ----------------------------------------------------
 * p_limit_open_videohandles
 * ----------------------------------------------------
 * if the number of currently_open_videohandles exceeds a limit,
 * then try to close the handle(s) until the number is below the limit.
 * (but do not close handles that are involved in fetching the current master_frame_nr)
 *
 * This kind of garbage collection is useful for storyboards that are using many
 * different videofile references and would run into memory and other resource problems
 * when all handles are kept open until the end of rendering process.
 * (note that each video handle has its own frame cache)
 *
 * TODO check for videohandles in the mask section
 */
static void
p_limit_open_videohandles(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 currently_open_videohandles
                      , gint32 max_open_videohandles
                      )
{
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
#define GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES 12
  GapStoryRenderSection *section;
  GapStoryRenderFrameRangeElem *frn_elem;
  gint32 l_count_open_videohandles;

  l_count_open_videohandles = currently_open_videohandles;

  if (l_count_open_videohandles < max_open_videohandles)
  {
    /* we are below the limit, nothing left to do in that case */
    return;
  }

  for(section = vidhand->section_list; section != NULL; section = section->next)
  {
    for (frn_elem = section->frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
    {
      if((frn_elem->last_master_frame_access < master_frame_nr)
      && (frn_elem->gvahand != NULL))
      {
         if(gap_debug)
         {
           printf("too many open videofiles %d detected (limit:%d) at master_frame_nr:%d\n"
                  " CLOSING GVA handle for video read access %s\n"
              , (int)l_count_open_videohandles
              , (int)max_open_videohandles
              , (int)master_frame_nr
              , frn_elem->basename
              );
         }
         p_call_GVA_close(frn_elem->gvahand);
         frn_elem->gvahand = NULL;
         l_count_open_videohandles--;

         if (l_count_open_videohandles < max_open_videohandles)
         {
           return;
         }

      }
    }
  }

  /* at this point there are still too many GVA video handles open
   * (this may occure if videos are used as masks, therefore try to close
   * mask video handles too.)
   */
  if(vidhand->is_mask_handle != TRUE)
  {
    GapStoryRenderMaskDefElem *maskdef_elem;

    maskdef_elem = vidhand->maskdef_elem;
    if(maskdef_elem != NULL)
    {
      if(maskdef_elem->mask_vidhand != NULL)
      {
        p_limit_open_videohandles(maskdef_elem->mask_vidhand
                                 , master_frame_nr
                                 , l_count_open_videohandles
                                 , max_open_videohandles
                                 );
      }
    }
  }

#endif
  return;

} /* end p_limit_open_videohandles */


/* -----------------------------------------
 * p_calculateGvahandHolderRank
 * -----------------------------------------
 * calculate rank for potential  reusage of an already open gva video handle
 *
 * return rank
 *    5 .. videoFrameNrToBeReadNext is available in fcache and curent position >= videoFrameNrToBeReadNext same track
 *    4 .. videoFrameNrToBeReadNext is available in fcache and curent position >= videoFrameNrToBeReadNext)
 *    3 .. videoFrameNrToBeReadNext is reachable with a few sequential read operaton same track
 *    2 .. videoFrameNrToBeReadNext is reachable with a few sequential read operaton)
 *    1 .. videoFrameNrToBeReadNext requires forward seek operaton
 *    0 .. videoFrameNrToBeReadNext requires backward seek operaton
 */
static gint32
p_calculateGvahandHolderRank(GapStoryRenderFrameRangeElem *frn_elem
                            , gint32 videoFrameNrToBeReadNext
                            , gint32 track)
{
  t_GVA_Handle      *gvahand;
  t_GVA_RetCode      l_fcr;

  gvahand = frn_elem->gvahand;
  l_fcr = GVA_search_fcache(gvahand, videoFrameNrToBeReadNext);

  if ((l_fcr == GVA_RET_OK)
  && (gvahand->current_seek_nr >= videoFrameNrToBeReadNext))
  {
    if(frn_elem->track == track)
    {
      return(GVAHAND_HOLDER_RANK_MAX_LEVEL);
    }
    return(GVAHAND_HOLDER_RANK_4);
  }

  if(((gvahand->current_seek_nr + NEAR_FRAME_DISTANCE) > videoFrameNrToBeReadNext)
  &&  (gvahand->current_seek_nr <= videoFrameNrToBeReadNext ) )
  {
    if(frn_elem->track == track)
    {
      return(GVAHAND_HOLDER_RANK_3);
    }
    return(GVAHAND_HOLDER_RANK_2);

  }

  if (gvahand->current_seek_nr <= videoFrameNrToBeReadNext)
  {
    return(GVAHAND_HOLDER_RANK_NO_BENEFIT_LEVEL);
  }

  return(GVAHAND_HOLDER_RANK_MIN_LEVEL);


}  /* end p_calculateGvahandHolderRank */


/* ----------------------------------------------------
 * p_try_to_steal_gvahand
 * ----------------------------------------------------
 * try to steal an alread open GVA handle for video read from another
 * element.
 * conditions: must use same videofile, seltrack and exact_seek mode
 * but steal only handles that are not in current access
 * (where the last accessed master_frame_nr is lower
 * than the current one)
 */
static t_GVA_Handle *
p_try_to_steal_gvahand(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , GapStoryRenderFrameRangeElem *requesting_frn_elem
                      )
{
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
  GapStoryRenderSection *section;
  GapStoryRenderFrameRangeElem *frn_elem;
  GapStoryRenderFrameRangeElem *frn_elem_gvahandHolder;  /* the element that holds the best matching handle */
  char  *basename;             /* the videofile name */
  gint32 exact_seek;
  gint32 seltrack;
  gint32 track;
  gint32 gvahandHolderRank;
  gint32 videoFrameNrToBeReadNext;
  gint32 l_count_open_videohandles;
  gint32 l_max_open_videohandles;

  l_count_open_videohandles = 0;
  basename   = requesting_frn_elem->basename;
  exact_seek = requesting_frn_elem->exact_seek;
  seltrack   = requesting_frn_elem->seltrack;
  track      = requesting_frn_elem->track;
  frn_elem_gvahandHolder = NULL;
  videoFrameNrToBeReadNext = requesting_frn_elem->frame_from;
  gvahandHolderRank = -1;  /* intal value lower than lowest regular rank */
  l_max_open_videohandles = gap_base_get_gimprc_int_value(GAP_GIMPRC_VIDEO_STORYBOARD_MAX_OPEN_VIDEOFILES
                                                        , GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES
                                                        , 2
                                                        , 100
                                                        );

  for(section = vidhand->section_list; section != NULL; section = section->next)
  {
    for (frn_elem = section->frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
    {
      if (frn_elem->gvahand != NULL)
      {
        l_count_open_videohandles++;
      }
      if((frn_elem->exact_seek == exact_seek)
      && (frn_elem->last_master_frame_access < master_frame_nr)
      && (frn_elem->seltrack == seltrack)
      && (frn_elem->gvahand != NULL))
      {
        if(strcmp(frn_elem->basename, basename) == 0)
        {
          gint32 rank;
          rank = p_calculateGvahandHolderRank(frn_elem, videoFrameNrToBeReadNext, track);

          if(rank > gvahandHolderRank)
          {
            frn_elem_gvahandHolder = frn_elem;
            gvahandHolderRank = rank;
            if(rank >= GVAHAND_HOLDER_RANK_MAX_LEVEL)
            {
              /* we can skip further checks because optimal matching gva handle was found */
              break;
            }
          }

         }
      }
    }
  }

  /* check for open videos in case there are mask definitions
   * this is not done in case we are already rendering a mask (where vidhand is the mask handle)
   * In case the rank is above the GVAHAND_HOLDER_RANK_NO_BENEFIT_LEVEL
   * we can skip this check, because an already opened handled will be reused.
   */
  if((vidhand->is_mask_handle != TRUE)
  && (gvahandHolderRank <=  GVAHAND_HOLDER_RANK_NO_BENEFIT_LEVEL))
  {
    GapStoryRenderMaskDefElem *maskdef_elem;

    for(maskdef_elem = vidhand->maskdef_elem; maskdef_elem != NULL;  maskdef_elem = maskdef_elem->next)
    {
      if(maskdef_elem->mask_vidhand)
      {
        for (frn_elem = maskdef_elem->mask_vidhand->frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
        {
          if (frn_elem->gvahand != NULL)
          {
            l_count_open_videohandles++;
          }

        }
      }
    }
  }


  if (frn_elem_gvahandHolder != NULL)
  {
    if ((gvahandHolderRank >  GVAHAND_HOLDER_RANK_NO_BENEFIT_LEVEL)
    || (l_count_open_videohandles >= l_max_open_videohandles))
    {
      t_GVA_Handle *gvahand;
      if(gap_debug)
      {
        printf("(RE)using GVA handle for %s gvahand:%ld Rank:%d holder(track:%d from:%d to:%d) requestor(track:%d from:%d to:%d) open videofile: %d (max:%d)\n"
              , frn_elem_gvahandHolder->basename
              , (long)gvahand
              , (int)gvahandHolderRank
              , (int)frn_elem_gvahandHolder->track
              , (int)frn_elem_gvahandHolder->frame_from
              , (int)frn_elem_gvahandHolder->frame_to
              , (int)requesting_frn_elem->track
              , (int)requesting_frn_elem->frame_from
              , (int)requesting_frn_elem->frame_to
              , (int)l_count_open_videohandles
              , (int)l_max_open_videohandles
              );
      }
      gvahand = frn_elem_gvahandHolder->gvahand;
      frn_elem_gvahandHolder->gvahand = NULL;   /* steal from this element */
      return(gvahand);
    }
  }


  p_limit_open_videohandles(vidhand, master_frame_nr, l_count_open_videohandles, l_max_open_videohandles);
#endif
  return(NULL);  /* nothing found to steal from, return NULL */

} /* end p_try_to_steal_gvahand */




/* ------------------------------------
 * gap_story_render_calc_audio_playtime
 * ------------------------------------
 * - check all audio tracks for audio playtime
 *   set *aud_total_sec to the playtime of the
 *   logest audio track playtime.
 */
void
gap_story_render_calc_audio_playtime(GapStoryRenderVidHandle *vidhand, gdouble *aud_total_sec)
{
  gap_story_render_audio_calculate_playtime(vidhand, aud_total_sec);
}  /* gap_story_render_calc_audio_playtime */


/* -------------------------------------------
 * gap_story_render_create_composite_audiofile
 * -------------------------------------------
 */
gboolean
gap_story_render_create_composite_audiofile(GapStoryRenderVidHandle *vidhand
                            , char *comp_audiofile
                            )
{
  return (gap_story_render_audio_create_composite_audiofile(vidhand, comp_audiofile));

}   /* end gap_story_render_create_composite_audiofile */


/* --------------------------------------------
 * gap_story_convert_layer_to_RGB_thdata
 * --------------------------------------------
 * convert the specified gimp drawable to thdata Buffer
 * (Bytesequence RGB 8)
 * if the drawable is a gray image it is converted
 * to RGB (red, green and blue are set to the same value).
 */
guchar *
gap_story_convert_layer_to_RGB_thdata(gint32 layer_id, gint32 *RAW_size
  , gint32 *th_bpp
  , gint32 *th_width
  , gint32 *th_height
  )
{
  GimpDrawable *drawable;
  GimpPixelRgn pixel_rgn;
  GimpImageType drawable_type;
  guchar *RAW_data;
  guchar *RAW_ptr;
  guchar *pixelrow_data;
  guint   l_row;
  gint32  l_idx;
  gint32  l_blue;
  gint32  l_green;
  gint32  l_red;
  gint32  l_rowstride;

  drawable = gimp_drawable_get (layer_id);
  drawable_type = gimp_drawable_type (drawable->drawable_id);
  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE);

  l_rowstride = drawable->width * drawable->bpp;
  pixelrow_data = (guchar *)g_malloc0(l_rowstride);
  *RAW_size = drawable->width * drawable->height * 3;
  *th_bpp = 3;
  *th_width = drawable->width;
  *th_height = drawable->height;

  RAW_data = (guchar *)g_malloc0((drawable->width * drawable->height * 3));

  RAW_ptr = RAW_data;
  l_red   = 0;
  l_green = 1;
  l_blue  = 2;
  if((drawable_type == GIMP_GRAY_IMAGE)
  || (drawable_type == GIMP_GRAYA_IMAGE))
  {
    l_green = 0;
    l_blue  = 0;
  }

  for(l_row = 0; l_row < drawable->height; l_row++)
  {
     gint32 l_src_row;

     l_src_row = l_row;

     gimp_pixel_rgn_get_rect (&pixel_rgn, pixelrow_data
                              , 0
                              , l_src_row
                              , drawable->width
                              , 1);
     for(l_idx=0;l_idx < l_rowstride; l_idx += drawable->bpp)
     {
       *(RAW_ptr++) = pixelrow_data[l_idx + l_red];
       *(RAW_ptr++) = pixelrow_data[l_idx + l_green];
       *(RAW_ptr++) = pixelrow_data[l_idx + l_blue];
     }
  }
  g_free(pixelrow_data);
  return(RAW_data);
}    /* end gap_story_convert_layer_to_RGB_thdata */


/* ---------------------------------------
 * gap_story_render_fetch_composite_vthumb
 * ---------------------------------------
 * render composite image and convert
 * to (RGB 8) thumbdata.
 */
guchar *
gap_story_render_fetch_composite_vthumb(GapStoryRenderVidHandle *stb_comp_vidhand
  , gint32 framenumber
  , gint32 width, gint32 height
  )
{
  gint32 composite_image_id;
  gint32 l_layer_id;
  guchar *th_data;


  if(gap_debug)
  {
      printf("\n###\n###\nSTART VTHUMB rendering at master_frame_nr %d"
             " with this list of elements:\n"
             ,(int)framenumber
             );
      gap_story_render_debug_print_framerange_list(stb_comp_vidhand->frn_list, -1);
  }


  th_data = NULL;
  composite_image_id = gap_story_render_fetch_composite_image(
                               stb_comp_vidhand
                               , framenumber
                               , width
                               , height
                               , NULL   /*  filtermacro_file */
                               , &l_layer_id
                               );

  if(composite_image_id >= 0)
  {
    gint32 RAW_size;
    gint32 th_bpp;
    gint32 th_width;
    gint32 th_height;

    th_data = gap_story_convert_layer_to_RGB_thdata(l_layer_id
               , &RAW_size
               , &th_bpp
               , &th_width
               , &th_height
               );
    if ((th_width != width) || (th_height != height))
    {
      printf("ERROR: gap_story_render_fetch_composite_vthumb width/height"
          " expected:(%d/%d) actual:(%d/%d)\n"
          ,(int)width
          ,(int)height
          ,(int)th_width
          ,(int)th_height
          );
    }

    gimp_image_delete(composite_image_id);
  }

  return (th_data);

}  /* end gap_story_render_fetch_composite_vthumb */



/* ----------------------------------------------------
 * p_dump_stb_resources_gvahand
 * ----------------------------------------------------
 * dump GVA video handle information for all currently open
 * videohandles (eg. all elements in MAIN and all SubSections)
 */
void
p_dump_stb_resources_gvahand(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr)
{
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
  GapStoryRenderSection *section;
  GapStoryRenderFrameRangeElem *frn_elem;
  gint32                        l_max_open_videohandles;
  gint32                        l_count_open_videohandles;

  l_max_open_videohandles = gap_base_get_gimprc_int_value(GAP_GIMPRC_VIDEO_STORYBOARD_MAX_OPEN_VIDEOFILES
                                                        , GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES
                                                        , 2
                                                        , 100
                                                        );

  l_count_open_videohandles = 0;



  for(section = vidhand->section_list; section != NULL; section = section->next)
  {
    char                  *section_name;

    section_name = section->section_name;
    if (section_name == NULL)
    {
      section_name = "MAIN";
    }

    for (frn_elem = section->frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
    {
      if (frn_elem->gvahand != NULL)
      {
        t_GVA_Handle *gvahand;

        l_count_open_videohandles++;
        gvahand = frn_elem->gvahand;

        printf("STB Section:%s GVA_handle: %s  master_frame_nr:%d currFrameNr:%d fcache elemSize:%d byteSize:%d\n"
          , section_name
          , gvahand->filename
          , (int) master_frame_nr
          , (int) gvahand->current_frame_nr
          , (int)GVA_get_fcache_size_in_elements(gvahand)
          , (int)GVA_get_fcache_size_in_bytes(gvahand)
          );
      }
    }

  }


  printf("STB at master_frame_nr:%d currently_open GVA_handles:%d (limit video-storyboard-max-open-videofiles:%d)\n"
    ,(int)master_frame_nr
    ,(int)l_count_open_videohandles
    ,(int)l_max_open_videohandles
    );

#endif
}  /* end p_dump_stb_resources_gvahand */



/* ----------------------------------------------------
 * p_story_render_fetch_composite_image_or_buffer
 * ----------------------------------------------------
 * fetch composite VIDEO Image at a given master_frame_nr
 * within a storyboard framerange list.
 *
 * the returned image is flattend RGB and scaled to
 * desired video framesize.
 *
 *  it is a merged result of all video tracks,
 *
 *  frames at master_frame_nr were loaded
 *  for all video tracks and added to the composite image
 *   (track 0 on top, track N on bottom
 *    of the layerstack)
 *  opacity, scaling and move (decenter) attributes
 *  were set to according to current Video Attributes.
 *
 * an (optional) filtermacro_file is performed on the
 * composite image.
 *
 * (simple animations without a storyboard file
 *  are represented by a short storyboard framerange list that has
 *  just one element entry at track 1).
 *
 * return image_id of resulting image and the flattened resulting layer_id
 */
static gint32
p_story_render_fetch_composite_image_or_buffer(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr  /* starts at 1 */
                    , gint32  vid_width       /* desired Video Width in pixels */
                    , gint32  vid_height      /* desired Video Height in pixels */
                    , char *filtermacro_file  /* NULL if no filtermacro is used */
                    , gint32 *layer_id        /* output: Id of the only layer in the composite image */
                    , gboolean enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                    , GapStoryFetchResult      *gapStoryFetchResult
                 )
{
  gint32 image_id;

  image_id = p_story_render_fetch_composite_image_private(vidhand
                                                  ,master_frame_nr
                                                  ,vid_width
                                                  ,vid_height
                                                  ,filtermacro_file
                                                  ,layer_id
                                                  ,NULL      /* NULL as section name referes to MAIN section */
                                                  ,enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                                                  ,gapStoryFetchResult
                                                 );

  if (image_id >= 0)
  {
    if(gapStoryFetchResult != NULL)
    {
      gapStoryFetchResult->resultEnum = GAP_STORY_FETCH_RESULT_IS_IMAGE;
      gapStoryFetchResult->layer_id = *layer_id;
      gapStoryFetchResult->image_id = image_id;
    }
  }
  if(vidhand->isLogResourceUsage)
  {
    if((master_frame_nr % vidhand->resourceLogInterval) == 0)
    {
      gap_frame_fetch_dump_resources();
      p_dump_stb_resources_gvahand(vidhand, master_frame_nr);
    }
  }


  return (image_id);

}  /* end p_story_render_fetch_composite_image_or_buffer */


/* ----------------------------------------------------
 * gap_story_render_fetch_composite_image (simple API)
 * ----------------------------------------------------
 * fetch composite VIDEO Image at a given master_frame_nr
 * within a storyboard framerange list.
 *
 * the returned image is flattend RGB and scaled to
 * desired video framesize.
 *
 *  it is a merged result of all video tracks,
 *
 *  frames at master_frame_nr were loaded
 *  for all video tracks and added to the composite image
 *   (track 0 on top, track N on bottom
 *    of the layerstack)
 *  opacity, scaling and move (decenter) attributes
 *  were set to according to current Video Attributes.
 *
 * an (optional) filtermacro_file is performed on the
 * composite image.
 *
 * (simple animations without a storyboard file
 *  are represented by a short storyboard framerange list that has
 *  just one element entry at track 1).
 *
 * return image_id of resulting image and the flattened resulting layer_id
 */
gint32
gap_story_render_fetch_composite_image(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr  /* starts at 1 */
                    , gint32  vid_width       /* desired Video Width in pixels */
                    , gint32  vid_height      /* desired Video Height in pixels */
                    , char *filtermacro_file  /* NULL if no filtermacro is used */
                    , gint32 *layer_id        /* output: Id of the only layer in the composite image */
                 )
{
  gint32 image_id;

  image_id = p_story_render_fetch_composite_image_or_buffer(vidhand
                                                  ,master_frame_nr
                                                  ,vid_width
                                                  ,vid_height
                                                  ,filtermacro_file
                                                  ,layer_id
                                                  ,FALSE
                                                  ,NULL
                                                  );
  return (image_id);

}  /* end gap_story_render_fetch_composite_image */



/* ------------------------------------------------
 * p_split_delace_value
 * ------------------------------------------------
 * split the specified delace value:
 *    integer part is deinterlace mode,
 *      (0 NO,
 *       1 Odd,
 *       2 Even,
 *       3 Odd First,
 *       4 Even first)
 *    rest is the threshold value.
 * The localframe_tween_rest is a positive value < 1.0.
 * This is only relevant in case delace mode is 3 Odd First or 4 Even first.
 * For clips with standard stepsize 1 localframe_tween_rest is always 0.
 * In Clips with non-integer stepsize localframe_tween_rest referes
 * between 2 framenumbers, where the value 0.5 is the middle.
 * An Interlaced frame contains 2 half-frames where one half-frame is represented by
 * the even, the other half-frame by the odd lines.
 *
 * therfore localframe_tween_rest values >= 0.5 selects the other half-frame,
 * in case the enable_interlace_tween_pick option is enabled.
 *
 * OUT: *deinterlace_ptr
 *       0 NO,
 *       1 Odd,
 *       2 Even,
 * OUT: *threshold_ptr  The threshold < 1.0 for smooth mix of 2 pixel rows.
 */
static void
p_split_delace_value(gdouble delace, gdouble localframe_tween_rest, gint32 *deinterlace_ptr, gdouble *threshold_ptr)
{
  gint32 delace_int;

  delace_int = delace;
  *threshold_ptr = delace - (gdouble)delace_int;

  switch (delace_int)
  {
    case 4:
      *deinterlace_ptr = 2;
      if (localframe_tween_rest >= 0.5)
      {
        *deinterlace_ptr = 1;
      }
      break;
    case 3:
      *deinterlace_ptr = 1;
      if (localframe_tween_rest >= 0.5)
      {
        *deinterlace_ptr = 2;
      }
      break;
    case 2:
      *deinterlace_ptr = 2;
      break;
    case 1:
      *deinterlace_ptr = 1;
      break;
    default:
      *deinterlace_ptr = 0;
      break;
  }

}  /* end p_split_delace_value */


/* -------------------------------------------------------------------
 * p_conditional_delace_drawable
 * -------------------------------------------------------------------
 * general deinterlace handling for frames, images an animimages
 * (except cliptype movie)
 */
static void
p_conditional_delace_drawable(GapStbFetchData *gfd, gint32 drawable_id)
{
  gint32  l_deinterlace;
  gdouble l_threshold;

  if(gfd->frn_type == GAP_FRN_MOVIE)
  {
    /* deinterlace for cliptype movie is already handled by the API at fetching.
     */
    return;
  }

  /* split delace value: integer part is deinterlace mode, rest is threshold */
  p_split_delace_value(gfd->frn_elem->delace
             , gfd->localframe_tween_rest
             , &l_deinterlace
             , &l_threshold
             );
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
  if (l_deinterlace != 0)
  {
    GVA_delace_drawable(drawable_id, l_deinterlace, l_threshold);
  }
#endif
}  /* end p_conditional_delace_drawable */

/* ------------
 * p_init_gfd
 * ------------
 */
static void
p_init_gfd(GapStbFetchData *gfd)
{
  gfd->localframe_tween_rest = 0.0;
  gfd->comp_image_id   = -1;
  gfd->tmp_image_id    = -1;
  gfd->layer_id        = -1;
  gfd->gapStoryFetchResult = NULL;
  gfd->isRgb888Result      = FALSE;
}  /* end p_init_gfd */


/* -------------------------------------------------------------------
 * p_is_larger_image_variant_expected
 * -------------------------------------------------------------------
 */
static gboolean
p_is_larger_image_variant_expected(GapStbFetchData *gfdCurrent
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32 vid_width
  , gint32 vid_height
  , gint32 originalWidth
  , gint32 originalHeight
  , gint32 currentPrescaleWidth
  , gint32 currentPrescaleHeight
  )
{
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;
  GapStoryCalcAttr  calculate_attributes;
  GapStoryCalcAttr  *calculated;
  gint32 lookForwardMasterFframeNr;
  gboolean foundLargerVariant;


  gfd = &gapStbFetchData;
  p_init_gfd(gfd);
  calculated = &calculate_attributes;

  foundLargerVariant = FALSE;

  /* check upto 500 further frames for usage of the same image
   * to findout maximum required prescale size in the near rendering future..
   * (note that in practice it is typical that one of the break conditions
   * occurs much earlier before the 500 checks are done)
   */
  for(lookForwardMasterFframeNr = master_frame_nr + 1;
      lookForwardMasterFframeNr < master_frame_nr + 500;
      lookForwardMasterFframeNr++)
  {
    gint32 l_track;
    gboolean nextCompositeFrameIncludesSameImage;

    nextCompositeFrameIncludesSameImage = FALSE;

    if(gap_debug)
    {
      printf("  o lookForwardMasterFframeNr:%d\n", lookForwardMasterFframeNr);
    }

    for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
    {
      gfd->framename = p_fetch_framename(vidhand->frn_list
                 , lookForwardMasterFframeNr /* starts at 1 */
                 , l_track
                 , gfd
                 );
      if (gfd->framename != NULL)
      {
        if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
        || (gfd->frn_type == GAP_FRN_IMAGE)
        || (gfd->frn_type == GAP_FRN_FRAMES))
        {
          if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
          {
            nextCompositeFrameIncludesSameImage = TRUE;
            gap_story_file_calculate_render_attributes(&calculate_attributes
                  , vid_width
                  , vid_height
                  , vid_width
                  , vid_height
                  , originalWidth
                  , originalHeight
                  , gfd->keep_proportions
                  , gfd->fit_width
                  , gfd->fit_height
                  , gfd->rotate
                  , gfd->opacity
                  , gfd->scale_x
                  , gfd->scale_y
                  , gfd->move_x
                  , gfd->move_y
                  );
             if ((calculated->width > currentPrescaleWidth)
             ||  (calculated->height > currentPrescaleHeight))
             {
               foundLargerVariant = TRUE;
             }


          }
        }

        g_free(gfd->framename);
      }
    }

    if ((nextCompositeFrameIncludesSameImage != TRUE)
    ||  (foundLargerVariant == TRUE))
    {
      break;
    }
  }


  return (foundLargerVariant);

}  /* end p_is_larger_image_variant_expected */



/* -------------------------------------------------------------------
 * p_prescale_image_size_handling
 * -------------------------------------------------------------------
 * fetch a single image or animimage at prescaled size.
 * this procedure checks some of the frames to be rendered next
 * for usage of the same image and calculates the prescale size as the maximum
 * refered size, except the size grows more than 150 percent.
 * this calculated prescale size is used to (down) scale the cached image.
 * this shall speedup rendering of large images
 * when refered multiple times at video size (that is typically much smaller)
 */
static gint32
p_prescale_image_size_handling(GapStbFetchData *gfdCurrent
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32 vid_width
  , gint32 vid_height
  )
{
  gint32 l_fetched_image_id;
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;
  GapStoryCalcAttr  calculate_attributes;
  GapStoryCalcAttr  *calculated;
  gint32 currentPrescaleWidth;
  gint32 currentPrescaleHeight;
  gint32 maxPrescaleWidth;
  gint32 maxPrescaleHeight;
  gint32 originalWidth;
  gint32 originalHeight;
  gint32 lookForwardMasterFframeNr;
  gboolean foundSmallerVariant;
  gboolean foundTooLargeVariant;  /* larger than 150% */

  gfd = gfdCurrent;
  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
                        , gfdCurrent->framename            /* full filename of the image */
                        , TRUE /*  enable caching */
                        ,0     /* prescaleWidth is not yet known */
                        ,0     /* prescaleHeight is not yet known */
                        ,&originalWidth
                        ,&originalHeight
                       );
  if (l_fetched_image_id < 0)
  {
    /* failed to fetch image */
    return (l_fetched_image_id);
  }

  calculated = &calculate_attributes;

  /* calculate scaling, offsets and opacity  according to current attributes
   */
  gap_story_file_calculate_render_attributes(&calculate_attributes
      , vid_width
      , vid_height
      , vid_width
      , vid_height
      , originalWidth
      , originalHeight
      , gfd->keep_proportions
      , gfd->fit_width
      , gfd->fit_height
      , gfd->rotate
      , gfd->opacity
      , gfd->scale_x
      , gfd->scale_y
      , gfd->move_x
      , gfd->move_y
      );

  currentPrescaleWidth = calculated->width;
  currentPrescaleHeight = calculated->height;
  maxPrescaleWidth = currentPrescaleWidth;
  maxPrescaleHeight = currentPrescaleHeight;

  if(gap_debug)
  {
    printf("p_prescale_image_size_handling master_frame_nr:%d\n"
           "original:(%dx%d) currentPrescale:(%dx%d) imgId:%d actualSIZE: (%dx%d) filename:%s\n"
      , (int)master_frame_nr
      , (int)originalWidth
      , (int)originalHeight
      , (int)currentPrescaleWidth
      , (int)currentPrescaleHeight
      , (int)l_fetched_image_id
      , (int)gimp_image_width(l_fetched_image_id)
      , (int)gimp_image_height(l_fetched_image_id)
      , gfdCurrent->framename
      );
  }

  if ((gimp_image_width(l_fetched_image_id) == currentPrescaleWidth)
  &&  (gimp_image_height(l_fetched_image_id) == currentPrescaleHeight))
  {
    /* wanted prescale size for rendering current frame
     * is same as actual size of the cached image
     * (no need for further checks so far..)
     */
    if(gap_debug)
    {
      printf("p_prescale_image_size_handling master_frame_nr:%d EXACT SAME SIZE filename:%s\n"
      , (int)master_frame_nr
      , gfdCurrent->framename
      );
    }
    return (l_fetched_image_id);
  }

  if ((gimp_image_width(l_fetched_image_id) < originalWidth)
  ||  (gimp_image_height(l_fetched_image_id) < originalHeight))
  {
     if ((gimp_image_width(l_fetched_image_id) >= currentPrescaleWidth)
     &&  (gimp_image_height(l_fetched_image_id) >= currentPrescaleHeight))
     {
       /* fetched image is already down scaled.
        * optional check for further (chained) downscale
        */
       gboolean  foundLargerVariant = FALSE;
       gboolean  prescaleEnabledDownscaleChainDefault = TRUE;

       if(gap_base_get_gimprc_gboolean_value(GAP_VIDEO_STORYBOARD_PRESCALE_ENABLE_DOWNSCALE_CHAIN
         , prescaleEnabledDownscaleChainDefault))
       {
         foundLargerVariant = p_is_larger_image_variant_expected(gfdCurrent
           , vidhand
           , master_frame_nr
           , vid_width
           , vid_height
           , originalWidth
           , originalHeight
           , currentPrescaleWidth
           , currentPrescaleHeight
          );
       }
       if(foundLargerVariant != TRUE)
       {
         /* there is no more reference to the same image
          * that requires larger variant than currentPrescale size in near rendering future)
          * ----
          * this additional scale down step by step results in speed up downscale rendering sequences
          * because downscale rendering sequences example would look like this:
          *     frame 000001 800x600 --> 750x550
          *     frame 000002 750x550 --> 700x500
          *     frame 000003 700x500 --> 650x450
          * this behaviour (chained muliple downscales) could also result in some quality loss
          * (and can be disabled via prescaleEnableChainedDownscale)
          */
         if(gap_debug)
         {
           printf("p_prescale_image_size_handling master_frame_nr:%d\n"
                " CHAINED DOWNSCALE from:(%dx%d) --> to:(%dx%d) filename:%s\n"
           , (int)master_frame_nr
           , (int)gimp_image_width(l_fetched_image_id)
           , (int)gimp_image_height(l_fetched_image_id)
           , (int)currentPrescaleWidth
           , (int)currentPrescaleHeight
           , gfdCurrent->framename
           );
         }
         gimp_image_undo_disable(l_fetched_image_id);
         gap_frame_fetch_image_scale(l_fetched_image_id, currentPrescaleWidth, currentPrescaleHeight);
       }
       else
       {
         /* without the optional chek the same downscale rendering sequences example looks like this:
          *     frame 000001 800x600-->750x550
          *     frame 000002 800x600-->700x500
          *     frame 000003 800x600-->650x450
          */
         if(gap_debug)
         {
           printf("p_prescale_image_size_handling master_frame_nr:%d\n"
                " ALREADY DOWNSCALED filename:%s\n"
           , (int)master_frame_nr
           , gfdCurrent->framename
           );
         }
       }

       return (l_fetched_image_id);
     }
  }


  gfd = &gapStbFetchData;
  p_init_gfd(gfd);

  foundSmallerVariant = FALSE;
  foundTooLargeVariant = FALSE;

  /* check upto 500 further frames for usage of the same image
   * to findout maximum required prescale size in the near rendering future..
   * (note that in practice it is typical that one of the break conditions
   * occurs much earlier before the 500 checks are done)
   */
  for(lookForwardMasterFframeNr = master_frame_nr + 1;
      lookForwardMasterFframeNr < master_frame_nr + 500;
      lookForwardMasterFframeNr++)
  {
    gint32 l_track;
    gboolean nextCompositeFrameIncludesSameImage;
    gboolean nextCompositeFrameIncludesSameImageAtSamePrescaleSize;

    if(gap_debug)
    {
      printf("  lookForwardMasterFframeNr:%d\n", lookForwardMasterFframeNr);
    }

    nextCompositeFrameIncludesSameImage = FALSE;
    nextCompositeFrameIncludesSameImageAtSamePrescaleSize = FALSE;
    for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
    {
      gfd->framename = p_fetch_framename(vidhand->frn_list
                 , lookForwardMasterFframeNr /* starts at 1 */
                 , l_track
                 , gfd
                 );
      if (gfd->framename != NULL)
      {
        if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
        || (gfd->frn_type == GAP_FRN_IMAGE)
        || (gfd->frn_type == GAP_FRN_FRAMES))
        {
          if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
          {
            nextCompositeFrameIncludesSameImage = TRUE;

            gap_story_file_calculate_render_attributes(&calculate_attributes
                  , vid_width
                  , vid_height
                  , vid_width
                  , vid_height
                  , originalWidth
                  , originalHeight
                  , gfd->keep_proportions
                  , gfd->fit_width
                  , gfd->fit_height
                  , gfd->rotate
                  , gfd->opacity
                  , gfd->scale_x
                  , gfd->scale_y
                  , gfd->move_x
                  , gfd->move_y
                  );
             if ((calculated->width == maxPrescaleWidth)
             &&  (calculated->height == maxPrescaleHeight))
             {
               nextCompositeFrameIncludesSameImageAtSamePrescaleSize = TRUE;
             }
             else
             {
               nextCompositeFrameIncludesSameImageAtSamePrescaleSize = FALSE;
             }


             if ((calculated->width < currentPrescaleWidth)
             &&  (calculated->height < currentPrescaleHeight))
             {
               foundSmallerVariant = TRUE;
             }

             if (calculated->width > originalWidth)
             {
               foundTooLargeVariant = TRUE;
             }
             else
             {
               if (calculated->width > maxPrescaleWidth)
               {
                 maxPrescaleWidth = calculated->width;
                 maxPrescaleHeight = calculated->height;
               }
             }


          }
        }

        g_free(gfd->framename);
      }
    }
    if ((nextCompositeFrameIncludesSameImage != TRUE)
    ||  (nextCompositeFrameIncludesSameImageAtSamePrescaleSize == TRUE)
    ||  (foundSmallerVariant == TRUE)
    ||  (foundTooLargeVariant == TRUE))
    {
      /* stop looking forward when:
       * o) next frame does not refere to same image
       * o) or refers to same image at exact same prescale size
       * o) or refers to smaller representation
       *    (assume zoom out where usage of larger variant is NOT expected in near future)
       * o) or refers to much larger variant > 150 %
       */
      break;
    }
  }

  /* the 2nd fetch call (with prescale size != 0) triggers prescaling */
  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
                        , gfdCurrent->framename            /* full filename of the image */
                        , TRUE /*  enable caching */
                        , maxPrescaleWidth
                        , maxPrescaleHeight
                        , &originalWidth
                        , &originalHeight
                       );
  if(gap_debug)
  {
    printf("p_prescale_image_size_handling master_frame_nr:%d lookForwardMasterFframeNr:%d\n"
           "maxPrescaleWidth:%d maxPrescaleHeight:%d imgId:%d (%dx%d) filename:%s\n"
      , (int)master_frame_nr
      , (int)lookForwardMasterFframeNr
      , (int)maxPrescaleWidth
      , (int)maxPrescaleHeight
      , (int)l_fetched_image_id
      , (int)gimp_image_width(l_fetched_image_id)
      , (int)gimp_image_height(l_fetched_image_id)
      , gfdCurrent->framename
      );
  }

  return (l_fetched_image_id);

}  /* end p_prescale_image_size_handling */


/* -------------------------------------------------------------------
 * p_stb_render_image_or_animimage (GAP_FRN_ANIMIMAGE or GAP_FRN_IMAGE
 * -------------------------------------------------------------------
 * fetch a single image or animimage
 */
static void
p_stb_render_image_or_animimage(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr, gint32 vid_width, gint32 vid_height)
{
  gint32        l_orig_image_id;
  gint          l_nlayers;
  gint32       *l_layers_list;


  if(gap_debug)
  {
    printf("p_stb_render_image_or_animimage START master_frame_nr:%d\n"
      ,(int)master_frame_nr
       );
  }


  /* filtermacro shall be applied at original image size
   * therefore disable prescale handling when filtermacro or
   * complex movepath processing is present
   */
  if ((gfd->trak_filtermacro_file == NULL)
  &&  (gfd->movepath_file_xml == NULL))
  {
    if(gap_debug)
    {
      printf("CALLING p_prescale_image_size_handling master_frame_nr:%d\n"
         ,(int)master_frame_nr
         );
    }
    /* prescale handling */
    l_orig_image_id =
       p_prescale_image_size_handling(gfd, vidhand, master_frame_nr, vid_width, vid_height);
  }
  else
  {
    if(gap_debug)
    {
      printf("CALLING gap_frame_fetch_orig_image master_frame_nr:%d\n"
         ,(int)master_frame_nr
         );
    }
    l_orig_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
                        , gfd->framename            /* full filename of the image */
                        , TRUE /*  enable caching */
                       );
  }

  if (l_orig_image_id < 0)
  {
    printf("Error fetching image: %s", gfd->framename);
    return;
  }

  gimp_selection_none(l_orig_image_id);
  if(gfd->frn_type == GAP_FRN_IMAGE)
  {
    gfd->tmp_image_id = gap_frame_fetch_image_duplicate(l_orig_image_id);
    gfd->layer_id = p_prepare_RGB_image(gfd->tmp_image_id);
    gap_frame_fetch_remove_parasite(gfd->tmp_image_id);
    if(gap_debug)
    {
      printf("IMAGE fetch  master_frame_nr:%d  (dup)tmp_image_id:%d layer_id:%d l_orig_image_id:%d\n"
         ,(int)master_frame_nr
         ,(int)gfd->tmp_image_id
         ,(int)gfd->layer_id
         ,(int)l_orig_image_id
         );
    }
  }
  else
  {
    /* GAP_FRN_ANIMIMAGE */
    if(gap_debug)
    {
      printf("ANIM fetch  gfd->localframe_index: %d master:%d  from: %d to: %d\n"
        ,(int)gfd->localframe_index
        ,(int)master_frame_nr
        ,(int)gfd->frn_elem->frame_from
        ,(int)gfd->frn_elem->frame_to
        );
    }

    gfd->tmp_image_id = gap_image_create_unicolor_image(&gfd->layer_id
                                            , gimp_image_width(l_orig_image_id)
                                            , gimp_image_height(l_orig_image_id)
                                            , 0.0, 0.0, 0.0, 0.0);
    gimp_layer_add_alpha(gfd->layer_id);
    l_layers_list = gimp_image_get_layers(l_orig_image_id, &l_nlayers);
    if(l_layers_list != NULL)
    {
       if((gfd->localframe_index < l_nlayers)
       && (gfd->localframe_index >= 0))
       {
          gint32 l_fsel_layer_id;

          if(gap_debug)
          {
            printf("ANIM-IMG: layer_id: %d gimp_layer_get_apply_mask:%d\n"
               ,(int)l_layers_list[gfd->localframe_index]
               ,(int)gimp_layer_get_apply_mask(l_layers_list[gfd->localframe_index])
               );
          }


          gimp_item_set_visible(l_layers_list[gfd->localframe_index], TRUE);
          if (0 != gimp_layer_get_apply_mask(l_layers_list[gfd->localframe_index]))
          {
            /* the layer has an active mask, apply the mask now
             * because copying from the layer ignores the mask
             */
            gimp_layer_remove_mask(l_layers_list[gfd->localframe_index], GIMP_MASK_APPLY);
          }
          gimp_layer_resize_to_image_size(l_layers_list[gfd->localframe_index]);
          gimp_edit_copy(l_layers_list[gfd->localframe_index]);
          l_fsel_layer_id = gimp_edit_paste(gfd->layer_id, FALSE);  /* FALSE paste clear selection */
          gimp_floating_sel_anchor(l_fsel_layer_id);
       }
       g_free (l_layers_list);
    }
  }

}  /* end p_stb_render_image_or_animimage */

/* --------------------------------------------------
 * p_is_another_clip_playing_the_same_video_backwards
 * --------------------------------------------------
 * check if there are other video clips (in any track)
 * that referes to the same videofile and plays the frames in reverse order
 */
static gboolean
p_is_another_clip_playing_the_same_video_backwards(GapStoryRenderFrameRangeElem *frn_elem_ref)
{
  GapStoryRenderFrameRangeElem *frn_elem;

  for (frn_elem = frn_elem_ref->next; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
  {
      if(frn_elem->frn_type == GAP_FRN_MOVIE)
      {
        if((frn_elem->exact_seek == frn_elem_ref->exact_seek)
        && (frn_elem->seltrack == frn_elem_ref->seltrack)
        && (strcmp(frn_elem->basename, frn_elem_ref->basename) == 0)
        && (frn_elem->frame_from > frn_elem->frame_to))
        {
          /* we found a reference to the same video in future clip where it splays backwards */
          return (TRUE);
        }
      }
  }
  return (FALSE);
}  /* end p_is_another_clip_playing_the_same_video_backwards */


/* ----------------------------------------------------
 * p_check_and_open_video_handle
 * ----------------------------------------------------
 * check and make sure that the clip specified with frn_elem has a
 * usable GVA video handle attached.
 * further set the frame cache size according to direction and configuration.
 * Note that typical processing for video encoding with ascending sequential
 * frame access does not speed up with big fcache but wastes memory
 * (especially on HD videos)
 * On the other hand beackwards playing clips can have remarkable
 * better performance using the GVA api internal frame cache.
 * (because it saves a lot of slow seek operations)
 * therfore the configured fcache size is only set in case the videoclip
 * is played backwards (in this or future clips refering the same videofile)
 *
 * returns the actual picked size of the frame cache
 */
static void
p_check_and_open_video_handle(GapStoryRenderFrameRangeElem *frn_elem
   , GapStoryRenderVidHandle *vidhand
   , gint32 master_frame_nr
   , const gchar *videofile
   )
{
  gboolean isPlayingBackwards;

  if(frn_elem->frame_from > frn_elem->frame_to)
  {
    isPlayingBackwards = TRUE;
  }
  else
  {
    isPlayingBackwards = FALSE;
  }

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT

  if(frn_elem->gvahand == NULL)
  {
     /* before we open a new GVA videohandle, lets check
      * if another element has already opened this videofile,
      * and reuse the already open gvahand handle if possible
      */
     frn_elem->gvahand = p_try_to_steal_gvahand(vidhand
                                                 , master_frame_nr
                                                 , frn_elem
                                                 );
     if(frn_elem->gvahand == NULL)
     {
       if(vidhand->preferred_decoder)
       {
         frn_elem->gvahand = GVA_open_read_pref(videofile
                                , frn_elem->seltrack
                                , 1 /* aud_track */
                                , vidhand->preferred_decoder
                                , FALSE  /* use MMX if available (disable_mmx == FALSE) */
                                );
       }
       else
       {
         frn_elem->gvahand = GVA_open_read(videofile
                                           ,frn_elem->seltrack
                                           ,1 /* aud_track */
                                           );
       }
       if(frn_elem->gvahand)
       {
         gint32   fcacheSize;

         fcacheSize = gap_base_get_gimprc_int_value(GAP_GIMPRC_VIDEO_STORYBOARD_FCACHE_SIZE_PER_VIDEOFILE
                                                     , GAP_STB_RENDER_GVA_FRAMES_TO_KEEP_CACHED  /* default */
                                                     , 2   /* min */
                                                     , 250 /* max */
                                                   );

         if(!isPlayingBackwards)
         {
           if(FALSE == p_is_another_clip_playing_the_same_video_backwards(frn_elem))
           {
             /* use small fcache for standard ascending frame access */
             fcacheSize = SMALL_FCACHE_SIZE_AT_FORWARD_READ;
           }
         }
         GVA_set_fcache_size(frn_elem->gvahand, fcacheSize);

         frn_elem->gvahand->do_gimp_progress = vidhand->do_gimp_progress;
         if(frn_elem->exact_seek == 1)
         {
           /* configure the GVA Procedures for exact (but very slow) seek emulaion */
           frn_elem->gvahand->emulate_seek = TRUE;
         }
       }
     }
  }
#endif
}  /* end p_check_and_open_video_handle */



static GThreadPool *
p_get_PrefetchThreadPool()
{
  if (prefetchThreadPool == NULL)
  {
    gint    maxThreads;
    GError *error = NULL;


    maxThreads = gap_base_get_gimprc_int_value(GAP_GIMPRC_VIDEO_STORYBOARD_MAX_OPEN_VIDEOFILES
                                                , GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES
                                                , 2
                                                , 100
                                                );

    prefetchThreadPool = g_thread_pool_new((GFunc)p_videoPrefetchWorkerThreadFunction
                                         ,NULL        /* user data */
                                         ,maxThreads  /* max_threads */
                                         ,TRUE        /* exclusive */
                                         ,&error      /* GError **error */
                                         );
    if (prefetchThreadPool == NULL)
    {
      printf("** ERROR could not create prefetchThreadPool\n");
    }
  }

  return (prefetchThreadPool);
}


/* -------------------------------------------
 * p_initOptionalMulitprocessorSupport
 * -------------------------------------------
 * this procedure creates a thread pool in case
 * the gimprc parameters are configured for multiprocessor support.
 * further the vidhand->isMultithreadEnabled is set accordingly.
 * Note that gimprc configuration is ignored in case
 * GIMP_GAP was compiled without GAP_ENABLE_VIDEOAPI_SUPPORT.
 */
static void
p_initOptionalMulitprocessorSupport(GapStoryRenderVidHandle *vidhand)
{
  vidhand->isMultithreadEnabled = FALSE;

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
  vidhand->isMultithreadEnabled = gap_story_isMultiprocessorSupportEnabled();
  if (vidhand->isMultithreadEnabled)
  {
    /* check and init thread system */
    vidhand->isMultithreadEnabled = gap_base_thread_init();
  }

  if (vidhand->isMultithreadEnabled)
  {
    p_get_PrefetchThreadPool();
  }
#endif
}  /* end p_initOptionalMulitprocessorSupport */


/* ---------------------------------------------------
 * p_call_GVA_close (GAP_FRN_MOVIE)
 * ---------------------------------------------------
 * check if the GVA video handle has VideoPrefetchData attached
 * this can occure in case of multiprocessor environment where
 * prefetch may be running as parallel thread.
 * in case the attached VideoPrefetchData indicates such an active thread
 * wait until this thread has finished and free resources (mutex)
 * finally clode the GVA video handle.
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static void
p_call_GVA_close(t_GVA_Handle *gvahand)
{
  if(gap_debug)
  {
    printf("p_call_GVA_close gvahand:%ld\n"
          ,(long)gvahand
          );
  }
  if(gvahand)
  {
    if(gap_debug)
    {
      printf("p_call_GVA_close  gvahand:%ld (close is pending for %s)\n"
           ,(long)gvahand
            ,gvahand->filename
            );
    }
    if(gvahand->user_data)
    {
      VideoPrefetchData *vpre;

      vpre = (VideoPrefetchData *)gvahand->user_data;
      if(vpre)
      {
        GMutex            *mutex;
        gint retryCount = 0;

        mutex = vpre->mutex;

RETRY:
        GVA_fcache_mutex_lock (vpre->gvahand);

        if((vpre->isPrefetchThreadRunning == TRUE)
        && (retryCount < 100))
        {
          if(gap_debug)
          {
            printf("call_GVA_closeWAIT until prefetch worker thread finished (close is pending for %s) retry:%d mutex:%ld fcache_mutex:%ld\n"
                  ,gvahand->filename
                  ,(int)retryCount
                  ,(long)mutex
                  ,(long)gvahand->fcache_mutex
                  );
          }
          g_cond_wait (vpre->prefetchDoneCond, vpre->mutex);

          if(gap_debug)
          {
            printf("call_GVA_close WAKE-UP prefetch worker thread finished (closing video %s) mutex:%ld\n"
                  ,gvahand->filename
                  ,(long)mutex
                  );
          }
          GVA_fcache_mutex_unlock (vpre->gvahand);

          /* thread may need a short time to unlock the mutex after sending the prefetchDoneCond  */
          g_usleep(150);

          retryCount++;
          goto RETRY;
        }
        else
        {
          if(gap_debug)
          {
            printf("call_GVA_close PrefetchThread NOT RUNNING, (closing video %s) retry:%d mutex:%ld fcache_mutex:%ld\n"
                  ,gvahand->filename
                  ,retryCount
                  ,(long)mutex
                  ,(long)gvahand->fcache_mutex
                  );
          }
        }
        /* detach the fcache mutex (to prevent API from using the lock again) */
        gvahand->fcache_mutex = NULL;
        vpre->mutex = NULL;
        gvahand->user_data = NULL;
        g_mutex_unlock(mutex);


        /* dispose the fcache mutex */
        //g_mutex_free (mutex);               // TODO: g_mutex_free sometimes leads to CRASH
        p_pooled_g_mutex_free(mutex);         // As workaround keep mutex alive in a pool for reuse...
        g_cond_free (vpre->prefetchDoneCond);

        vpre->prefetchDoneCond = NULL;

      }
    }
    GVA_close(gvahand);
  }
}  /* end  p_call_GVA_close */
#endif


/* --------------------------------------------------------------
 * p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888
 * --------------------------------------------------------------
 * wrapper for debug logging purpose.
 */
static void
p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888(t_GVA_Handle *gvahand
                 , gint32   framenumber
                 , gint32   deinterlace
                 , gdouble  threshold
                 , gint32   numProcessors
                 , GVA_fcache_fetch_result *fcacheFetchResult
                 , const char *caller
                 )
{
  if(gap_debug)
  {
    printf("before call GVA_search_fcache: gvahand:%ld framenumber:%d deinterlace:%d numProcessors:%d caller:%s\n"
      ,(long)gvahand
      ,(int)framenumber
      ,(int)deinterlace
      ,(int)numProcessors
      ,caller
      );
  }

  GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888(gvahand
                 , framenumber
                 , deinterlace
                 , threshold
                 , numProcessors                       /* numProcessors */
                 , fcacheFetchResult
                 );

  if(gap_debug)
  {
    printf("after call GVA_search_fcache: "
      "gvahand:%ld framenumber:%d isRgb888Result:%d data:%ld image_id:%d layer_id:%d isFrameAvailable:%d\n"
      ,(long)gvahand
      ,(int)framenumber
      ,(int)fcacheFetchResult->isRgb888Result
      ,(long)fcacheFetchResult->rgbBuffer.data
      ,(int)fcacheFetchResult->image_id
      ,(int)fcacheFetchResult->layer_id
      ,(int)fcacheFetchResult->isFrameAvailable
      );
  }

}  /* end p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888 */



/* ---------------------------------------------------
 * p_get_gapStoryFetchResult_from_fcacheFetchResult
 * ---------------------------------------------------
 */
static void
p_get_gapStoryFetchResult_from_fcacheFetchResult(GapStbFetchData *gfd, GVA_fcache_fetch_result *fcacheFetchResult)
{
  /* get id of the newly created image from its layerId */
  gfd->tmp_image_id = fcacheFetchResult->image_id;

  if(gfd->gapStoryFetchResult != NULL)
  {
    gfd->gapStoryFetchResult->layer_id = fcacheFetchResult->layer_id;
    gfd->gapStoryFetchResult->image_id = fcacheFetchResult->image_id;
    if (fcacheFetchResult->isRgb888Result == TRUE)
    {
      /* result is available as rgb888 in gfd->gapStoryFetchResult->raw_rgb_data */
      gfd->gapStoryFetchResult->resultEnum = GAP_STORY_FETCH_RESULT_IS_RAW_RGB888;

      if(gfd->gapStoryFetchResult->raw_rgb_data != fcacheFetchResult->rgbBuffer.data)
      {
        gfd->gapStoryFetchResult->raw_rgb_data = fcacheFetchResult->rgbBuffer.data;
      }
    }
    else
    {
      gfd->gapStoryFetchResult->resultEnum = GAP_STORY_FETCH_RESULT_IS_IMAGE;
    }
  }
}  /* end p_get_gapStoryFetchResult_from_fcacheFetchResult */


/* ---------------------------------------------------
 * p_stb_render_movie_single_processor (GAP_FRN_MOVIE)
 * ---------------------------------------------------
 * fetch frame from a videofile (gfd->framename contains the videofile name)
 * in a single proceeor environment.
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static void
p_stb_render_movie_single_processor(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32  vid_width, gint32  vid_height)
{
  static gint32 funcId = -1;
  GVA_fcache_fetch_result fcacheFetchResult;

  GAP_TIMM_GET_FUNCTION_ID(funcId, "p_stb_render_movie_single_processor");
  GAP_TIMM_START_FUNCTION(funcId);

  /* if(gap_debug) { printf("p_stb_render_movie_single_processor START\n"); } */

  fcacheFetchResult.isRgb888Result = FALSE;  /* configure fcache for standard fetch as gimp layer */
  fcacheFetchResult.rgbBuffer.data = NULL;

  if(gfd->gapStoryFetchResult != NULL)
  {
    fcacheFetchResult.isRgb888Result = gfd->isRgb888Result;
    fcacheFetchResult.rgbBuffer.data = gfd->gapStoryFetchResult->raw_rgb_data;
  }

  if(gfd->frn_elem->gvahand)
  {
     gint32 l_deinterlace;
     gdouble l_threshold;

     /* split delace value: integer part is deinterlace mode, rest is threshold */
     p_split_delace_value(gfd->frn_elem->delace
             , gfd->localframe_tween_rest
             , &l_deinterlace
             , &l_threshold
             );


     /* attempt to read frame from the GVA API internal framecache */
     p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888(gfd->frn_elem->gvahand
                 , gfd->localframe_index   /* framenumber */
                 , l_deinterlace
                 , l_threshold
                 , 1                       /* numProcessors */
                 , &fcacheFetchResult
                 , "(single A)"
                 );

     if (fcacheFetchResult.isFrameAvailable != TRUE)
     {
       /* if no success, we try explicite read that frame  */
       if(gfd->frn_elem->gvahand->current_seek_nr != gfd->localframe_index)
       {
         if(((gfd->frn_elem->gvahand->current_seek_nr + NEAR_FRAME_DISTANCE) > gfd->localframe_index)
         &&  (gfd->frn_elem->gvahand->current_seek_nr < gfd->localframe_index ) )
         {
           /* near forward seek is performed by sequential reads
            * note that a few sequential reads are typically faster than seek operations
            * (even if native seek support is available for a videofile)
            */
           while(gfd->frn_elem->gvahand->current_seek_nr < gfd->localframe_index)
           {
             if (GVA_get_next_frame(gfd->frn_elem->gvahand) != GVA_RET_OK)
             {
               break;
             }
           }
         }
         else
         {
           gboolean isPlayingBackwards;

           if(gfd->frn_elem->frame_from > gfd->frn_elem->frame_to)
           {
             isPlayingBackwards = TRUE;
           }
           else
           {
             isPlayingBackwards = FALSE;
           }

           if(vidhand->do_gimp_progress)
           {
              gimp_progress_init(_("Seek Inputvideoframe..."));
           }

           /* for backwards playing clip seek before the wanted position
            * and read some frames until wanted position is reaced to fill the fcache
            * (this shall speed up the next few backwards reads that can be fetched from fcache)
            */
           if(isPlayingBackwards)
           {
             gdouble seekFrameNumber;
             gdouble delta;


             delta = GVA_get_fcache_size_in_elements(gfd->frn_elem->gvahand) -1;
             seekFrameNumber = MAX((gdouble)gfd->localframe_index - delta, 2);
             GVA_seek_frame(gfd->frn_elem->gvahand, seekFrameNumber, GVA_UPOS_FRAMES);
             while(gfd->frn_elem->gvahand->current_seek_nr < gfd->localframe_index)
             {
               if(gap_debug)
               {
                 printf("BACKWARD fcache filling read current_seek_nr:%d target_frame_nr:%d\n"
                   ,gfd->frn_elem->gvahand->current_seek_nr
                   ,gfd->localframe_index
                   );
               }
               GVA_get_next_frame(gfd->frn_elem->gvahand);
             }
           }
           else
           {
             GVA_seek_frame(gfd->frn_elem->gvahand, (gdouble)gfd->localframe_index, GVA_UPOS_FRAMES);
           }
           if(vidhand->do_gimp_progress)
           {
              gimp_progress_init(_("Continue Encoding..."));
           }
        }
       }

       if(GVA_get_next_frame(gfd->frn_elem->gvahand) == GVA_RET_OK)
       {
         p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888(gfd->frn_elem->gvahand
                 , gfd->localframe_index   /* framenumber */
                 , l_deinterlace
                 , l_threshold
                 , 1                       /* numProcessors */
                 , &fcacheFetchResult
                 , "(single B)"
                 );
       }
     }

     if (fcacheFetchResult.isFrameAvailable == TRUE)
     {
       p_get_gapStoryFetchResult_from_fcacheFetchResult(gfd, &fcacheFetchResult);
     }
     else
     {
       gfd->tmp_image_id = -1;
     }
  }


  GAP_TIMM_STOP_FUNCTION(funcId);

}  /* end p_stb_render_movie_single_processor */
#endif



/* -------------------------------------------
 * p_call_GVA_get_next_frame_andSendReadySignal
 * -------------------------------------------
 * sequential read the next frame from videofile
 * and send signal targetFrameReadyCond after the specified targetFrameNumber
 * was read (and is now available in the fcache)
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static gboolean
p_call_GVA_get_next_frame_andSendReadySignal(VideoPrefetchData *vpre, gint32 targetFrameNumber)
{
  if(gap_debug)
  {
    printf("p_call_GVA_get_next_frame TID:%lld gvahand:%ld targetFrameNumber:%d seek_nr:%d\n"
      , (long long int)gap_base_get_thread_id()
      , (long)vpre->gvahand
      , (int)targetFrameNumber
      , (int)vpre->gvahand->current_seek_nr
      );
  }

  if(GVA_get_next_frame(vpre->gvahand) != GVA_RET_OK)
  {
    if(gap_debug)
    {
      printf("p_call_GVA_get_next_frame FAILED on TID:%lld gvahand:%ld targetFrameNumber:%d seek_nr:%d\n"
      , (long long int)gap_base_get_thread_id()
      , (long)vpre->gvahand
      , (int)targetFrameNumber
      , (int)vpre->gvahand->current_seek_nr
      );
    }
    return (FALSE);
  }
  GVA_fcache_mutex_lock (vpre->gvahand);
  if (vpre->gvahand->current_frame_nr == targetFrameNumber)
  {
    if(gap_debug)
    {
      printf("p_call_GVA_get_next_frame TID:%lld gvahand:%ld targetFrameNumber:%d SEND targetFrameReadyCond\n"
        , (long long int)gap_base_get_thread_id()
        , (long)vpre->gvahand
        , (int)targetFrameNumber
        );
    }
    g_cond_signal  (vpre->targetFrameReadyCond);
  }

  GVA_fcache_mutex_unlock (vpre->gvahand);
  
  return (TRUE);

}  /* end p_call_GVA_get_next_frame_andSendReadySignal */
#endif

/* -------------------------------------------
 * p_videoPrefetchWorkerThreadFunction
 * -------------------------------------------
 * this procedure runs as thread pool function to prefetch the next few
 * videoframes (targetFrameNumber upto prefetchFrameNumber)  into the GVA api framecache.
 * o) after the target frame was fetched it sends out the signal condition targetFrameReadyCond
 * o) after all frames upto prefetchFrameNumber are fetched the signal condition prefetchDoneCond is sent.
 *
 * In case there is more than one videotrack to be processed,
 * there may be more than one prefetch worker thread running parallel at the same time
 * where each of those threads has its own VideoPrefetchData (and therfore has its own gva video handle)
 *
 * Note: prefetch of backwards read fill up the fcache too, but in this case
 * the target framenumber is the last one to be read from the videofile
 * and the main thread has to wait until the whole prefetch cycle is finished.
 * Therefore there is no performance advantage compared with a single processor on backward reads.
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static void
p_videoPrefetchWorkerThreadFunction (VideoPrefetchData *vpre)
{
  gint32               prefetchFrameNumber;
  gint32               targetFrameNumber;
  gboolean             isPlayingBackwards;

  if(gap_debug)
  {
    printf("p_videoPrefetchWorkerThreadFunction START (before mutex lock) TID:%lld gvahand:%ld  targetFrameNumber:%d\n"
         ,(long long int)gap_base_get_thread_id()
         ,(long)vpre->gvahand
         ,(int)vpre->targetFrameNumber
         );
  }

  GVA_fcache_mutex_lock (vpre->gvahand);

  prefetchFrameNumber = vpre->prefetchFrameNumber;
  targetFrameNumber = vpre->targetFrameNumber;
  if(vpre->isPlayingBackwards)
  {
    isPlayingBackwards = TRUE;
    prefetchFrameNumber = targetFrameNumber;
  }
  else
  {
    isPlayingBackwards = FALSE;
  }

  GVA_fcache_mutex_unlock (vpre->gvahand);

  if(targetFrameNumber >= 0)
  {
    if(((vpre->gvahand->current_seek_nr + NEAR_FRAME_DISTANCE) > prefetchFrameNumber)
    &&  (vpre->gvahand->current_seek_nr <= targetFrameNumber ) )
    {
      if(gap_debug)
      {
        printf("p_videoPrefetchWorkerThreadFunction TID:%lld NEAR FORWARD READ gvahand:%ld targetFrameNumber:%d\n"
        , (long long int)gap_base_get_thread_id()
         ,(long)vpre->gvahand
         ,(int)targetFrameNumber
         );
      }
      /* near forward seek is performed by sequential reads
       * note that a few sequential reads are typically faster than seek operations
       * (even if native seek support is available for a videofile)
       */
      while(vpre->gvahand->current_seek_nr <= prefetchFrameNumber)
      {
        if (TRUE != p_call_GVA_get_next_frame_andSendReadySignal(vpre, targetFrameNumber))
        {
          break;  /* stop on errors or EOF */
        }
      }
    }
    else
    {
      /* for backwards playing clip seek before the wanted position
       * and read some frames until wanted position is reaced to fill the fcache
       * (this shall speed up the next few backwards reads that can be fetched from fcache,
       * but the main thread must wait until all frames are done because the
       * wanted frame is the last one to be fetched)
       */
      if(isPlayingBackwards)
      {
        gdouble seekFrameNumber;
        gdouble delta;

        if(gap_debug)
        {
          printf("p_videoPrefetchWorkerThreadFunction TID:%lld gvahand:%ld BACKWARD READ targetFrameNumber:%d\n"
             , (long long int)gap_base_get_thread_id()
             ,(long)vpre->gvahand
             ,(int)targetFrameNumber
             );
        }

        delta = GVA_get_fcache_size_in_elements(vpre->gvahand) -1;
        seekFrameNumber = MAX((gdouble)targetFrameNumber - delta, 2);
        GVA_seek_frame(vpre->gvahand, seekFrameNumber, GVA_UPOS_FRAMES);
        while(vpre->gvahand->current_seek_nr <= targetFrameNumber)
        {
            if(gap_debug)
            {
              printf("BACKWARD TID:%lld prefetch fcache filling read gvahand:%ld current_seek_nr:%d target_frame_nr:%d\n"
                , (long long int)gap_base_get_thread_id()
                ,(long)vpre->gvahand
                ,(int)vpre->gvahand->current_seek_nr
                ,(int)prefetchFrameNumber
                );
            }
            if (TRUE != p_call_GVA_get_next_frame_andSendReadySignal(vpre, targetFrameNumber))
            {
              break;  /* stop on errors or EOF */
            }
        }
      }
      else
      {
        if(gap_debug)
        {
          printf("p_videoPrefetchWorkerThreadFunction TID:%lld SEEK / READ gvahand:%ld targetFrameNumber:%d\n"
                , (long long int)gap_base_get_thread_id()
                ,(long)vpre->gvahand
                ,(int)targetFrameNumber
                );
        }
        GVA_seek_frame(vpre->gvahand, (gdouble)targetFrameNumber, GVA_UPOS_FRAMES);
        while(vpre->gvahand->current_seek_nr <= prefetchFrameNumber)
        {
          if (TRUE != p_call_GVA_get_next_frame_andSendReadySignal(vpre, targetFrameNumber))
          {
            break;  /* stop on errors or EOF */
          }


        }
      }
    }
  }


  GVA_fcache_mutex_lock (vpre->gvahand);
  vpre->isPrefetchThreadRunning = FALSE;
  g_cond_signal  (vpre->targetFrameReadyCond);
  g_cond_signal  (vpre->prefetchDoneCond);
  GVA_fcache_mutex_unlock (vpre->gvahand);

  if(gap_debug)
  {
    printf("p_videoPrefetchWorkerThreadFunction DONE TID:%lld gvahand:%ld\n"
       ,(long long int)gap_base_get_thread_id()
       ,(long)vpre->gvahand
       );
  }

}  /* end p_videoPrefetchWorkerThreadFunction */
#endif



/* -------------------------------------------
 * p_getPredictedNextFramenr
 * -------------------------------------------
 * calculate predicted frmanumber for prefetch on forward read
 * Frames from targetFrameNr upto the calculated predicted frmanumber
 * shall be read parallel in advance.
 * prefetch is limited by MULTITHREAD_PREFETCH_AMOUNT, the GVA fcache size and
 * the highestReferedFrameNr (current check is limited to current clip)
 *
 * TODO if the same video is also refered in the next clip in continous sequence.
 * the highestReferedFrameNr could be increased accordingly.
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static gint32
p_getPredictedNextFramenr(gint32 targetFrameNr, GapStoryRenderFrameRangeElem *frn_elem)
{
  gint32 predictedFrameNr;


  if (frn_elem->frame_from > frn_elem->frame_to)
  {
    /* Backward Read */
    predictedFrameNr = targetFrameNr;
    if(gap_debug)
    {
      printf("p_getPredictedNextFramenr BACKWARD READ targetFrameNr:%d predictedFrameNr:%d total_frames:%d\n"
        ,(int)targetFrameNr
        ,(int)predictedFrameNr
        ,(int)frn_elem->gvahand->total_frames
        );
    }
  }
  else
  {
    gint32 fcacheSize;
    gint32 prefetchAmount;
    gint32 highestReferedFrameNr;

    fcacheSize = GVA_get_fcache_size_in_elements(frn_elem->gvahand);
    prefetchAmount = MIN(MULTITHREAD_PREFETCH_AMOUNT, fcacheSize -1);

    highestReferedFrameNr = frn_elem->frame_to;
//     if(frn_elem->gvahand->all_frames_counted == TRUE)
//     {
//       highestReferedFrameNr = MIN(frn_elem->frame_to, frn_elem->gvahand->total_frames);
//     }

    predictedFrameNr = MIN((targetFrameNr + prefetchAmount), highestReferedFrameNr);

    if(gap_debug)
    {
      printf("p_getPredictedNextFramenr targetFrameNr:%d predictedFrameNr:%d total_frames:%d (all_counted:%d) amount:%d hi:%d fcacheSize:%d\n"
        ,(int)targetFrameNr
        ,(int)predictedFrameNr
        ,(int)frn_elem->gvahand->total_frames
        ,(int)frn_elem->gvahand->all_frames_counted
        ,(int)prefetchAmount
        ,(int)highestReferedFrameNr
        ,(int)fcacheSize
        );
    }

  }

  return (predictedFrameNr);

}  /* end  p_getPredictedNextFramenr */
#endif


/* ------------------------------------------------
 * p_stb_render_movie_multiprocessor (GAP_FRN_MOVIE)
 * ------------------------------------------------
 * this procedure runs as main thread when fetching video frames in
 * multithread environment.
 * It reads the required target frame from the GVA api fcache,
 * and trigers a parallel prefetch thread.
 * The parallel running prefetch thread fills up the GVA api fcache
 * by reading (more than one) frame from the videofile in advance.
 * to increase fcache hit chance at the next call.
 *
 * in case the fcache does not (yet) contain the target frame
 * the main thread has to wait until the target frame is ready.
 * (This is typical at the first call and whenever the target framenumber
 * differs significant from the previous call)
 */
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
static void
p_stb_render_movie_multiprocessor(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32  vid_width, gint32  vid_height)
{
  VideoPrefetchData *vpre;
  gint32             l_deinterlace;
  gdouble            l_threshold;
  t_GVA_RetCode      l_fcr;
  gint32             retryCount;
  gint32             predictedNextFrameNr;
  gint32             targetFrameNumber;
  gint32             numProcessors;

  GVA_fcache_fetch_result fcacheFetchResult;
  GError *error;

  static gint32 funcId = -1;
  static gint32 funcIdWait = -1;

  GAP_TIMM_GET_FUNCTION_ID(funcId, "p_stb_render_movie_multiprocessor");
  GAP_TIMM_GET_FUNCTION_ID(funcIdWait, "p_stb_render_movie_multiprocessor.Wait");

  GAP_TIMM_START_FUNCTION(funcId);

  /* if(gap_debug) { printf("p_stb_render_movie_multiprocessor START\n"); } */

  error = NULL;
  targetFrameNumber = gfd->localframe_index; /* this framenumber is required now for processing */
  numProcessors = gap_base_get_numProcessors();

  fcacheFetchResult.isRgb888Result = FALSE;  /* configure fcache for standard fetch as gimp layer */
  fcacheFetchResult.rgbBuffer.data = NULL;

  if(gfd->gapStoryFetchResult != NULL)
  {
    fcacheFetchResult.isRgb888Result = gfd->isRgb888Result;
    fcacheFetchResult.rgbBuffer.data = gfd->gapStoryFetchResult->raw_rgb_data;
  }


 /* split delace value: integer part is deinterlace mode, rest is threshold */
  p_split_delace_value(gfd->frn_elem->delace
             , gfd->localframe_tween_rest
             , &l_deinterlace
             , &l_threshold
             );

  predictedNextFrameNr = p_getPredictedNextFramenr(targetFrameNumber, gfd->frn_elem);

  vpre = (VideoPrefetchData *)gfd->frn_elem->gvahand->user_data;
  if(vpre == NULL)
  {
    /* attach VideoPrefetchData for multithread usage as user_data to the GVA handle */
    vpre = g_new(VideoPrefetchData, 1);
    vpre->gvahand = gfd->frn_elem->gvahand;
    vpre->prefetchFrameNumber = predictedNextFrameNr;
    vpre->targetFrameNumber = targetFrameNumber;
    vpre->isPrefetchThreadRunning = FALSE;
    vpre->mutex = p_pooled_g_mutex_new ();
    vpre->targetFrameReadyCond = g_cond_new ();
    vpre->prefetchDoneCond = g_cond_new ();
    vpre->isPlayingBackwards = FALSE;
    if (gfd->frn_elem->frame_from > gfd->frn_elem->frame_to)
    {
      vpre->isPlayingBackwards = TRUE;
    }
    gfd->frn_elem->gvahand->user_data = vpre;

    /* Let the GVA api know about the mutex.
     * This triggers g_mutex_lock / g_mutex_unlock calls in API internal functions
     * dealing with the fcache access.
     */
    gfd->frn_elem->gvahand->fcache_mutex = vpre->mutex;
  }

  for(retryCount=0; retryCount < 200; retryCount++)
  {
    /* check if targetFrameNumber is available in the GVA API internal framecache
     * and get a (optional deinterlaced) copy as gimp layer with positive layerId when TRUE.
     */
    p_call_GVA_search_fcache_and_get_frame_as_gimp_layer_or_rgb888(gfd->frn_elem->gvahand
                 , targetFrameNumber
                 , l_deinterlace
                 , l_threshold
                 , numProcessors
                 , &fcacheFetchResult
                 , "(multi)"
                 );
    if (fcacheFetchResult.isFrameAvailable == TRUE)
    {
      /* fcache hit */
      gboolean triggerMorePrefetch;
      gint32   fnr;

      if(gap_debug)
      {
        printf("FCACHE-HIT gvahand:%ld framenr:%d predictedFrameNr:%d retryCount:%d\n"
          , (long)vpre->gvahand
          , (int)targetFrameNumber
          , (int)predictedNextFrameNr
          , (int)retryCount
          );
      }
      triggerMorePrefetch = FALSE;
      if ((vpre->isPrefetchThreadRunning != TRUE)
      && (predictedNextFrameNr > targetFrameNumber))
      {
        for(fnr = targetFrameNumber +1 ; fnr <= predictedNextFrameNr; fnr++)
        {
          l_fcr = GVA_search_fcache(gfd->frn_elem->gvahand, fnr);
          if (l_fcr != GVA_RET_OK)
          {
            triggerMorePrefetch = TRUE;
            break;
          }
        }
      }
      if (triggerMorePrefetch == TRUE)
      {
        if(TRUE == GVA_fcache_mutex_trylock (vpre->gvahand))
        {
          predictedNextFrameNr = p_getPredictedNextFramenr(targetFrameNumber +1, gfd->frn_elem);
          vpre->prefetchFrameNumber = predictedNextFrameNr;
          vpre->targetFrameNumber = fnr;
          if(gap_debug)
          {
            printf("MORE-PREFETCH PUSH-1 gvahand:%ld framenr:%d predictedFrameNr:%d retryCount:%d\n"
              , (long)vpre->gvahand
              , (int)fnr
              , (int)predictedNextFrameNr
              , (int)retryCount
              );
          }

          vpre->isPrefetchThreadRunning = TRUE;
          /* (re)activate a worker thread for next prefetch that fills fcache upto prefetchFrameNumber */
          g_thread_pool_push (p_get_PrefetchThreadPool()
                           , vpre    /* VideoPrefetchData */
                           , &error
                           );
          GVA_fcache_mutex_unlock (vpre->gvahand);
        }
      }
      break;
    }
    else
    {
      /* frame is NOT (yet) in fcache */
      GVA_fcache_mutex_lock (vpre->gvahand);

      if(vpre->isPrefetchThreadRunning != TRUE)
      {
        vpre->prefetchFrameNumber = predictedNextFrameNr;
        vpre->targetFrameNumber = targetFrameNumber;

        if(gap_debug)
        {
            printf("TRIGGER PREFETCH PUSH-2 gvahand:%ld targetFrameNumber:%d predictedFrameNr:%d retryCount:%d\n"
              , (long)vpre->gvahand
              , (int)targetFrameNumber
              , (int)predictedNextFrameNr
              , (int)retryCount
              );
        }

        vpre->isPrefetchThreadRunning = TRUE;
        /* (re)activate a worker thread for next prefetch that fills fcache upto prefetchFrameNumber */
        g_thread_pool_push (p_get_PrefetchThreadPool()
                         , vpre    /* VideoPrefetchData */
                         , &error
                         );

      }

      if(gap_debug)
      {
        printf("WAIT  gvahand:%ld until prefetch worker thread has fetched target framenr:%d predictedFrameNr:%d retryCount:%d\n"
          , (long)vpre->gvahand
          , (int)targetFrameNumber
          , (int)predictedNextFrameNr
          , (int)retryCount
          );
      }

      GAP_TIMM_START_FUNCTION(funcIdWait);

      /* wait until next frame s fetched
       * g_cond_wait Waits until this thread is woken up on targetFrameReadyCond.
       * The mutex is unlocked before falling asleep and locked again before resuming.
       */
      g_cond_wait (vpre->targetFrameReadyCond, vpre->mutex);


      GAP_TIMM_STOP_FUNCTION(funcIdWait);

      if(gap_debug)
      {
        printf("WAKE-UP gvahand:%ld target framenr:%d predictedFrameNr:%d retryCount:%d\n"
          , (long)vpre->gvahand
          , (int)targetFrameNumber
          , (int)predictedNextFrameNr
          , (int)retryCount
          );
      }
      GVA_fcache_mutex_unlock (vpre->gvahand);

      /* retry another attempt to get the frame from the fcache (that shall be filled by the worker thread */
    }
  }

  if(gap_debug)
  {
    printf("RETRY LOOP gvahand:%ld done target framenr:%d predictedFrameNr:%d layerId:%d isRgb888Result:%d retryCount:%d\n"
      , (long)vpre->gvahand
      , (int)targetFrameNumber
      , (int)predictedNextFrameNr
      , (int)fcacheFetchResult.layer_id
      , (int)fcacheFetchResult.isRgb888Result
      , (int)retryCount
      );
  }

  if(fcacheFetchResult.isFrameAvailable == TRUE)
  {
    p_get_gapStoryFetchResult_from_fcacheFetchResult(gfd, &fcacheFetchResult);
  }
  else
  {
    if(gfd->gapStoryFetchResult != NULL)
    {
      gfd->gapStoryFetchResult->resultEnum = GAP_STORY_FETCH_RESULT_IS_ERROR;
    }
    printf("** ERROR Failed to fetch from video:%s gvahand:%ld frame:%d predictedFrameNr:%d retryCount:%d\n"
          , gfd->frn_elem->gvahand->filename
          , (long)gfd->frn_elem->gvahand
          , (int)gfd->localframe_index
          , (int)predictedNextFrameNr
          , (int)retryCount
          );
  }

  GAP_TIMM_STOP_FUNCTION(funcId);

}  /* end p_stb_render_movie_multiprocessor */
#endif


/* -------------------------------------------
 * p_stb_render_movie (GAP_FRN_MOVIE)
 * -------------------------------------------
 * fetch frame from a videofile (gfd->framename contains the videofile name)
 * on success the fetched imageId is set in the  gfd->tmp_image_id
 * on failure gfd->tmp_image_id is set to -1.
 */
static void
p_stb_render_movie(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32  vid_width, gint32  vid_height)
{
  gfd->tmp_image_id = -1;

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT

  p_check_and_open_video_handle(gfd->frn_elem
                               , vidhand
                               , master_frame_nr
                               , gfd->frn_elem->basename /* videofile name */
                               );
  if(gfd->frn_elem->gvahand)
  {
    if (vidhand->isMultithreadEnabled == TRUE)
    {
      p_stb_render_movie_multiprocessor(gfd, vidhand, master_frame_nr, vid_width, vid_height);
    }
    else
    {
      p_stb_render_movie_single_processor(gfd, vidhand, master_frame_nr, vid_width, vid_height);
    }
  }

#endif

}  /* end p_stb_render_movie */



/* -------------------------------------------
 * p_stb_render_section (GAP_FRN_SECTION)
 * -------------------------------------------
 * handle section rendering (via recursive call)
 */
static void
p_stb_render_section(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32  vid_width, gint32  vid_height
  , const char *section_name)
{
  gint32 sub_layer_id;
  gint32 sub_master_frame_nr;
  const char *sub_section_name;
  gboolean orig_do_progress;
  gdouble  orig_progress;

  if(gap_debug)
  {
    printf("SUB-SECTION: before RECURSIVE call\n");
  }
  orig_do_progress = vidhand->do_gimp_progress;
  orig_progress = *vidhand->progress;
  vidhand->do_gimp_progress = FALSE;
  sub_section_name = gfd->framename;
  sub_master_frame_nr = gfd->localframe_index;
  gfd->tmp_image_id =
     p_story_render_fetch_composite_image_private(vidhand
                                           , sub_master_frame_nr
                                           , vid_width
                                           , vid_height
                                           , NULL /* filtrmacro_file */
                                           , &sub_layer_id
                                           , sub_section_name
                                           , FALSE            /* enable_rgb888_flag */
                                           , NULL             /* GapStoryFetchResult */
                                           );

  if(gap_debug)
  {
    printf("SUB-SECTION: after RECURSIVE call\n");
  }
  /* The recursive call of p_story_render_fetch_composite_image_private
   * has set frn_list and aud_list to a sub_section.
   * therefore switch back to current section after the call.
   * furthermore restore progress settings.
   */
  p_select_section_by_name(vidhand, section_name);
  vidhand->do_gimp_progress = orig_do_progress;
  *vidhand->progress = orig_progress;

}  /* end p_stb_render_section */


/* -------------------------------------------------------------------
 * p_check_next_composite_frame_includes_same_image
 * -------------------------------------------------------------------
 * return true in case the next composite frame includes the same image.
 */
static gboolean
p_check_next_composite_frame_includes_same_image(GapStbFetchData *gfdCurrent
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr)
{
  gint32 l_track;
  gboolean nextCompositeFrameIncludesSameImage;
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;

  nextCompositeFrameIncludesSameImage = FALSE;

  gfd = &gapStbFetchData;
  p_init_gfd(gfd);

  for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
  {
    gfd->framename = p_fetch_framename(vidhand->frn_list
               , master_frame_nr + 1
               , l_track
               , gfd
               );
    if (gfd->framename != NULL)
    {
      if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
      || (gfd->frn_type == GAP_FRN_IMAGE)
      || (gfd->frn_type == GAP_FRN_FRAMES))
      {
        if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
        {
          nextCompositeFrameIncludesSameImage = TRUE;
        }
      }

      g_free(gfd->framename);
    }
  }

  return (nextCompositeFrameIncludesSameImage);
}




/* -------------------------------------------
 * p_stb_render_frame_images (GAP_FRN_FRAMES)
 * -------------------------------------------
 * gfd->framename  is one single imagefile out of a series of numbered imagefiles.
 * (note that the gfd->framename is already full qualified
 *  and includes path name, numberpart and extension)
 */
static void
p_stb_render_frame_images(GapStbFetchData *gfd, GapStoryRenderVidHandle *vidhand, gint32 master_frame_nr
  , gint32 vid_width, gint32 vid_height)
{
  gboolean nextCompositeFrameIncludesSameImage;
  gboolean enableCaching;
  gint32   l_fetched_image_id;
  gint32   originalWidth;
  gint32   originalHeight;
  gint32   prescaleWidth;
  gint32   prescaleHeight;
  GapStoryCalcAttr  calculate_attributes;
  GapStoryCalcAttr  *calculated;

  if(gap_debug)
  {
    printf("FRAME fetch gfd->framename: %s\n    ===> master:%d  from: %d to: %d\n"
      ,gfd->framename
      ,(int)master_frame_nr
      ,(int)gfd->frn_elem->frame_from
      ,(int)gfd->frn_elem->frame_to
      );
  }

  if (!g_file_test(gfd->framename, G_FILE_TEST_EXISTS))
  {
    gfd->tmp_image_id = -1;
    return;
  }


  nextCompositeFrameIncludesSameImage =
     p_check_next_composite_frame_includes_same_image(gfd, vidhand, master_frame_nr);

  enableCaching = nextCompositeFrameIncludesSameImage;

  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
                        , gfd->framename            /* full filename of the image */
                        , enableCaching
                        ,0     /* prescaleWidth is not yet known */
                        ,0     /* prescaleHeight is not yet known */
                        ,&originalWidth
                        ,&originalHeight
                       );
  calculated = &calculate_attributes;

  /* calculate scaling, offsets and opacity  according to current attributes
   */
  gap_story_file_calculate_render_attributes(&calculate_attributes
      , vid_width
      , vid_height
      , vid_width
      , vid_height
      , originalWidth
      , originalHeight
      , gfd->keep_proportions
      , gfd->fit_width
      , gfd->fit_height
      , gfd->rotate
      , gfd->opacity
      , gfd->scale_x
      , gfd->scale_y
      , gfd->move_x
      , gfd->move_y
      );

  prescaleWidth = calculated->width;
  prescaleHeight = calculated->height;

  if (gap_frame_fetch_is_image_in_cache(l_fetched_image_id))
  {
    gboolean fetchedImageUsable;

    fetchedImageUsable = FALSE;
    if ((gimp_image_width(l_fetched_image_id) >= prescaleWidth)
    &&  (gimp_image_height(l_fetched_image_id) >= prescaleHeight))
    {
      fetchedImageUsable = TRUE;
    }
    else
    {
      if ((gimp_image_width(l_fetched_image_id) == originalWidth)
      &&  (gimp_image_height(l_fetched_image_id) == originalHeight))
      {
        fetchedImageUsable = TRUE;
      }
    }


    if(fetchedImageUsable == TRUE)
    {
      if(gap_debug)
      {
        printf("p_stb_render_frame_images DUP cached image (%dx%d) at master_frame_nr:%d %s\n"
           ,(int)gimp_image_width(l_fetched_image_id)
           ,(int)gimp_image_height(l_fetched_image_id)
           ,(int)master_frame_nr
           , gfd->framename
           );
      }
      gfd->tmp_image_id = gap_frame_fetch_image_duplicate(l_fetched_image_id);
      gap_frame_fetch_remove_parasite(gfd->tmp_image_id);
    }
    else
    {
      if(gap_debug)
      {
        printf("p_stb_render_frame_images RE-LOAD cached image (%dx%d) too small at master_frame_nr:%d %s\n"
           ,(int)gimp_image_width(l_fetched_image_id)
           ,(int)gimp_image_height(l_fetched_image_id)
           ,(int)master_frame_nr
           , gfd->framename
           );
      }
      /* the cached image is downscaled and smaller than required size.
       * in this case load again from file (at original size to ensure
       * best possible render quality)
       */
      gfd->tmp_image_id = gap_lib_load_image(gfd->framename);
    }
  }
  else
  {
    if(gap_debug)
    {
      printf("p_stb_render_frame_images UNCACHED fetched image (%dx%d) at master_frame_nr:%d %s\n"
         ,(int)gimp_image_width(l_fetched_image_id)
         ,(int)gimp_image_height(l_fetched_image_id)
         ,(int)master_frame_nr
         , gfd->framename
         );
    }
    /* use fetched image (that is not member of the cache
     * directly without making a copy
     */
    gfd->tmp_image_id = l_fetched_image_id;
  }


}  /* end p_stb_render_frame_images */


/* -------------------------------------------
 * p_stb_render_composite_image_postprocessing
 * -------------------------------------------
 * perform postprocessing on the composite frame image.
 * this includes
 *  - convert to gray (only when fetching masks)
 *  - optional applying the global filtermacro
 *  - check size and scale (if filtermacro has changed size of the composite image)
 *  - check layers and flatten in case there is more than one layer
 * The result is a single layer ( gfd->layer_id ) in the composite image.
 */
static void
p_stb_render_composite_image_postprocessing(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand
  , gint32 master_frame_nr
  , gint32  vid_width, gint32  vid_height
  , char *filtermacro_file
  , const char *section_name
  )
{
  gint          l_nlayers;
  gint32       *l_layers_list;

  if(gfd->comp_image_id  < 0)
  {
    /* none of the tracks had a frame image on this master_frame_nr position
     * create a blank image (VID_SILENNCE)
     */
    gfd->comp_image_id = gap_image_create_unicolor_image(&gfd->layer_id
                         , vid_width
                         , vid_height
                         , 0.0
                         , 0.0
                         , 0.0
                         , 1.0
                         );
  }

  /* debug: disabled code to display a copy of the image */
  if(1==0)
  {
    p_debug_dup_image(gfd->comp_image_id);
  }


  /* check the layerstack
   */
  l_layers_list = gimp_image_get_layers(gfd->comp_image_id, &l_nlayers);
  if(l_layers_list != NULL)
  {
    gfd->layer_id = l_layers_list[0];
    g_free (l_layers_list);
  }


  if((vidhand->is_mask_handle != TRUE)
  && (section_name == NULL))
  {
    /* debug feature: save the multilayer composite frame
     * before it is passed to the filtermacro
     * (always off for mask fetching)
     */
    p_frame_backup_save(  GAP_VID_ENC_SAVE_MULTILAYER
                      , gfd->comp_image_id
                      , gfd->layer_id
                      , master_frame_nr
                      , TRUE               /* can be multilayer */
                      );
  }

  if((l_nlayers > 1 )
  || (gimp_drawable_has_alpha(gfd->layer_id)))
  {
     if(gap_debug)
     {
      printf("DEBUG: p_stb_render_composite_image_postprocessing flatten Composite image\n");
     }

     /* flatten current frame image (reduce to single layer) */
     gfd->layer_id = gimp_image_flatten (gfd->comp_image_id);
  }


  /* execute filtermacro (optional if supplied) */
  p_exec_filtermacro(gfd->comp_image_id
                    , gfd->layer_id
                    , filtermacro_file
                    , NULL /* have no 2nd filtermacro_file_to for varying parameter sets */
                    , 0.0  /* current_step */
                    , 1    /* total_steps */
                    , 0    /* accelerationCharacteristic 0 = off */
                    );

  /* check again size and scale image to desired Videosize if needed */
  if ((gimp_image_width(gfd->comp_image_id) != vid_width)
  ||  (gimp_image_height(gfd->comp_image_id) != vid_height) )
  {
     if(gap_debug) printf("DEBUG: p_stb_render_composite_image_postprocessing: scaling tmp image\n");

     gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);
  }

  /* check again for layerstack (macro could have add more layers)
   * or there might be an alpha channel
   */
  l_layers_list = gimp_image_get_layers(gfd->comp_image_id, &l_nlayers);
  if(l_layers_list != NULL)
  {
    gfd->layer_id = l_layers_list[0];
    g_free (l_layers_list);
  }
  if((l_nlayers > 1 )
  || (gimp_drawable_has_alpha(gfd->layer_id)))
  {
     if(gap_debug) printf("DEBUG: p_stb_render_composite_image_postprocessing  FINAL flatten Composite image\n");

      /* flatten current frame image (reduce to single layer) */
      gfd->layer_id = gimp_image_flatten (gfd->comp_image_id);
  }

}  /* end p_stb_render_composite_image_postprocessing */




/* -------------------------------------------
 * p_stb_render_result_monitoring
 * -------------------------------------------
 *
 */
static void
p_stb_render_result_monitoring(GapStbFetchData *gfd, gint32 master_frame_nr)
{
  /* debug feature: save the flattened composite as jpg frame  before it is passed to the encoder */
  p_frame_backup_save(  GAP_VID_ENC_SAVE_FLAT
                     , gfd->comp_image_id
                     , gfd->layer_id
                     , master_frame_nr
                     , FALSE               /* is no multilayer, use jpeg */
                     );

  /* debug feature: monitor composite image while encoding */
  p_encoding_monitor(GAP_VID_ENC_MONITOR
                     , gfd->comp_image_id
                     , gfd->layer_id
                     , master_frame_nr
                     );

}  /* end p_stb_render_result_monitoring */


/* ------------------------
 * p_paste_logo_pattern
 * ------------------------
 * replace logo area with the specified logo pattern
 */
static void
p_paste_logo_pattern(gint32 drawable_id
  , gint32 logo_pattern_id
  , gint32 offsetX
  , gint32 offsetY
  )
{
  gint32        l_fsel_layer_id;
  gint          l_src_offset_x;
  gint          l_src_offset_y;

  gimp_selection_all(gimp_item_get_image(logo_pattern_id));

  /* findout the offsets of the replacement_pattern layer within the source Image */
  gimp_drawable_offsets(logo_pattern_id, &l_src_offset_x, &l_src_offset_y );

  gimp_edit_copy(logo_pattern_id);
  l_fsel_layer_id = gimp_edit_paste(drawable_id, TRUE);  /* FALSE paste clear selection */
  gimp_selection_none(gimp_item_get_image(logo_pattern_id));

  if(gap_debug)
  {
    gint          l_fsel_offset_x;
    gint          l_fsel_offset_y;

    gimp_drawable_offsets(l_fsel_layer_id, &l_fsel_offset_x, &l_fsel_offset_y );

    printf("p_paste_logo_pattern: l_src_offsets: (%d %d) fsel:(%d %d) final offsets: (%d %d) rep_id:%d\n"
      ,(int)l_src_offset_x
      ,(int)l_src_offset_y
      ,(int)l_fsel_offset_x
      ,(int)l_fsel_offset_y
      ,(int)(offsetX + l_src_offset_x)
      ,(int)(offsetY + l_src_offset_y)
      ,(int)logo_pattern_id
      );
  }

  gimp_layer_set_offsets(l_fsel_layer_id
                        , offsetX + l_src_offset_x
                        , offsetY + l_src_offset_y);

  gimp_floating_sel_anchor(l_fsel_layer_id);

} /* end p_copy_and_paste_replacement_pattern */


/* -------------------------------------
 * p_get_insert_area_filename
 * -------------------------------------
 * get filename for an image that shall
 * be used as logo for automatically area insertation
 * on MOVIE clips.
 */
static char*
p_get_insert_area_filename(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand)
{
  char *logo_imagename;
  char *videofilename_without_path;

  videofilename_without_path = gap_lib_build_basename_without_ext(gfd->framename);

  if (vidhand->master_insert_area_format_has_framenumber)
  {
    if (vidhand->master_insert_area_format_has_videobasename)
    {
      logo_imagename =
         g_strdup_printf(vidhand->master_insert_area_format
                       , videofilename_without_path
                       , gfd->localframe_index   /* videoFrameNr */
                       );
    }
    else
    {
      logo_imagename =
         g_strdup_printf(vidhand->master_insert_area_format
                       , gfd->localframe_index   /* videoFrameNr */
                       );
    }
  }
  else
  {
    if (vidhand->master_insert_area_format_has_videobasename)
    {
      logo_imagename =
         g_strdup_printf(vidhand->master_insert_area_format
                       , videofilename_without_path
                       );
    }
    else
    {
      logo_imagename = g_strdup(vidhand->master_insert_area_format);
    }
  }

  if(gap_debug)
  {
    printf("p_get_insert_area_filename: format:%s\n video:%s\n logo_imagename:%s\n"
        , vidhand->master_insert_area_format
        , videofilename_without_path
        , logo_imagename
        );
  }

  g_free(videofilename_without_path);

  return(logo_imagename);

}  /* end p_get_insert_area_filename */


/* -------------------------------------
 * p_do_insert_area_processing
 * -------------------------------------
 * add logo area to video clip
 */
static void
p_do_insert_area_processing(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand)
{
  char *logo_imagename;

  logo_imagename = p_get_insert_area_filename(gfd, vidhand);

  if(g_file_test(logo_imagename, G_FILE_TEST_EXISTS))
  {
    gint32 logo_image_id;
    gint32 logo_layer_id;

    if (vidhand->master_insert_area_format_has_framenumber)
    {
      logo_image_id = gap_lib_load_image(logo_imagename);
    }
    else
    {
      /* use framefetcher cache in case all frames shall get the same logo */
      logo_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
                            , logo_imagename
                            , TRUE /*  enable caching */
                           );
    }

    if(logo_image_id < 0)
    {
      printf("p_do_insert_area_processing: ERROR could not load logo_imagename:%s\n", logo_imagename);

      g_free(logo_imagename);
      return;
    }


    gimp_selection_none(logo_image_id);
    logo_layer_id = p_prepare_RGB_image(logo_image_id);

    p_paste_logo_pattern(gfd->layer_id
                         , logo_layer_id
                         , 0, 0  /* offest_X, offset_Y */
                         );
    if (vidhand->master_insert_area_format_has_framenumber)
    {
      /* do not keep individual per frame logo images cached
       */
      gap_image_delete_immediate(logo_image_id);
    }
  }

  g_free(logo_imagename);

}  /* end p_do_insert_area_processing */




/* ----------------------------------------------------
 * p_prepare_GRAY_image
 * ----------------------------------------------------
 * prepare image to be applied as transparency channel to a frame.
 * - clear undo stack
 * - convert to GREY
 * - merge all visible layer to one layer that
 *   fits the image size.
 *
 * return the resulting layer_id.
 */
static gint32
p_prepare_GRAY_image(gint32 image_id)
{
  gint          l_nlayers;
  gint32       *l_layers_list;
  gint32 l_layer_id;

  l_layer_id = -1;
 /* dont waste time and memory for undo in noninteracive processing
  * of the frames
  */
  /*  gimp_image_undo_enable(image_id); */ /* clear undo stack */
  /* no more gimp_image_undo_enable, because this results in Warnings since gimp-2.1.6
   * Gimp-Core-CRITICAL **: file gimpimage.c: line 1708 (gimp_image_undo_thaw): assertion `gimage->undo_freeze_count > 0' failed
   */
  gimp_image_undo_disable(image_id); /*  NO Undo */

  l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
  if(l_layers_list != NULL)
  {
    l_layer_id = l_layers_list[0];
    g_free (l_layers_list);
  }

  if((l_nlayers > 1 ) || (gimp_layer_get_mask (l_layer_id) >= 0))
  {
     if(gap_debug) printf("DEBUG: p_prepare_image merge layers tmp image\n");

     /* merge visible layers (reduce to single layer) */
     l_layer_id = gap_image_merge_visible_layers(image_id, GIMP_CLIP_TO_IMAGE);
  }

  /* convert TO GREY if needed */
  if(gimp_image_base_type(image_id) != GIMP_GRAY)
  {
     gimp_image_convert_grayscale(image_id);
  }

  if(l_layer_id >= 0)
  {
    gimp_layer_resize_to_image_size(l_layer_id);
  }

  return(l_layer_id);

} /* end p_prepare_GRAY_image */



/* -------------------------------------
 * p_get_insert_alpha_filename
 * -------------------------------------
 * get filename for an image that shall
 * be used as aplha channel for automatically alpha channel
 * generation on MOVIE clips.
 */
static char*
p_get_insert_alpha_filename(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand)
{
  char *alpha_imagename;
  char *videofilename_without_path;

  videofilename_without_path =   gap_lib_build_basename_without_ext(gfd->framename);

  if (vidhand->master_insert_alpha_format_has_framenumber)
  {
    if (vidhand->master_insert_alpha_format_has_videobasename)
    {
      alpha_imagename =
         g_strdup_printf(vidhand->master_insert_alpha_format
                       , videofilename_without_path
                       , gfd->localframe_index   /* videoFrameNr */
                       );
    }
    else
    {
      alpha_imagename =
         g_strdup_printf(vidhand->master_insert_alpha_format
                       , gfd->localframe_index   /* videoFrameNr */
                       );
    }
  }
  else
  {
    if (vidhand->master_insert_alpha_format_has_videobasename)
    {
      alpha_imagename =
         g_strdup_printf(vidhand->master_insert_alpha_format
                       , videofilename_without_path
                       );
    }
    else
    {
      alpha_imagename = g_strdup(vidhand->master_insert_alpha_format);
    }
  }

  if(gap_debug)
  {
    printf("p_get_insert_alpha_filename: format:%s\n video:%s\n alpha_imagename:%s\n"
        , vidhand->master_insert_alpha_format
        , videofilename_without_path
        , alpha_imagename
        );
  }

  g_free(videofilename_without_path);

  return(alpha_imagename);

}  /* end p_get_insert_alpha_filename */

/* -------------------------------------
 * p_do_insert_alpha_processing
 * -------------------------------------
 * adds alpha channel for videoframes
 * based on an image or series of frames
 * matching the configured format string.
 * (VID_MASTER_INSERT_ALPHA)
 *
 */
static void
p_do_insert_alpha_processing(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand)
{
  char *alpha_imagename;

  alpha_imagename = p_get_insert_alpha_filename(gfd, vidhand);


  if(g_file_test(alpha_imagename, G_FILE_TEST_EXISTS))
  {
    gint32 alpha_image_id;
    gint32 alpha_layer_id;
    gint   vid_width;
    gint   vid_height;

    if (vidhand->master_insert_alpha_format_has_framenumber)
    {
      alpha_image_id = gap_lib_load_image(alpha_imagename);
    }
    else
    {
      /* use framefetcher cache in case all frames shall get alpha from the same image */
      alpha_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
                            , alpha_imagename
                            , TRUE /*  enable caching */
                           );
    }

    if(alpha_image_id < 0)
    {
      printf("p_do_insert_alpha_processing: ERROR could not load alpha_imagename:%s\n"
            , alpha_imagename
            );
      g_free(alpha_imagename);
      return;
    }


    gimp_selection_none(alpha_image_id);
    alpha_layer_id = p_prepare_GRAY_image(alpha_image_id);

    vid_width = gimp_image_width(gfd->tmp_image_id);
    vid_height = gimp_image_height(gfd->tmp_image_id);

    /* scale alpha image to Videosize (if not already equal) */
    if ((gimp_image_width(alpha_image_id) != vid_width)
    ||  (gimp_image_height(alpha_image_id) != vid_height) )
    {
       if(gap_debug)
       {
         printf("DEBUG: p_do_insert_alpha_processing scaling alpha image\n");
       }
       gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);
    }

    if(! gimp_drawable_has_alpha(gfd->layer_id))
    {
      /* have to add alpha channel */
      gimp_layer_add_alpha(gfd->layer_id);
    }


    /* copy alpha_layer_id into the alpha channel of the current frame */
    gap_layer_copy_picked_channel(gfd->layer_id, 3  /* dst_pick is the alpha channel */
                               ,alpha_layer_id, 0   /* gray value */
                               ,FALSE  /* shadow */
                               );

    if (vidhand->master_insert_alpha_format_has_framenumber)
    {
      /* do not keep individual per frame alpha images cached
       */
      gap_image_delete_immediate(alpha_image_id);
    }
  }

  g_free(alpha_imagename);

}  /* end p_do_insert_alpha_processing */

/* --------------------------------------------
 * p_isFiltermacroActive
 * --------------------------------------------
 */
static gboolean
p_isFiltermacroActive(const char *filtermacro_file)
{
  if(filtermacro_file)
  {
     if(*filtermacro_file != '\0')
     {
       return(TRUE);
     }
  }
  return(FALSE);

}  /* end p_isFiltermacroActive */


/* --------------------------------------------
 * p_story_render_bypass_where_possible          rgb888 handling
 * --------------------------------------------
 * this procedure checks if the current frame can be directly fetched as rgb888 buffer
 * form a videoclip without the need to convert to gimp drawable and rendering transitions.
 * (this can speed up viedeoencoders that can handle rgb888 data)
 *
 * if an rgb888 fetch is possible for the current master_frame_nr
 * the fetch is performed and TRUE will be returned.
 * otherwise FALSE is returned.
 */
static gboolean
p_story_render_bypass_where_possible(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr  /* starts at 1 */
                    , gint32  vid_width       /* desired Video Width in pixels */
                    , gint32  vid_height      /* desired Video Height in pixels */
                    , gboolean enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                    , GapStoryFetchResult      *gapStoryFetchResult
                 )
{
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;
  GapStbFetchData *gfdMovie;
  gchar           *videofileName;
  gboolean         isByPassRenderEngine;
  gint32           l_track;

  isByPassRenderEngine = FALSE;
  gfd = &gapStbFetchData;
  gfdMovie = NULL;
  videofileName = NULL;

  if(gap_debug)
  {
    printf("p_story_render_bypass_where_possible START master_frame_nr:%d minVidTrack:%d maxVidTrack:%d enable_rgb888_flag:%d\n"
      ,(int)master_frame_nr
      ,(int)vidhand->minVidTrack
      ,(int)vidhand->maxVidTrack
      ,(int)enable_rgb888_flag
      );
  }

  /* check clip rerferences in all tracks.
   * (bypass is possible in case the track on top the layerstack refers to a fully opaque movie clip
   * at full image same size without any transitions.
   * the loop starts at minVidTrack (typically 0) that refers to top of the layerstack position 
   * (i.e. foreground)
   */
  for(l_track = vidhand->minVidTrack; l_track <= vidhand->maxVidTrack; l_track++)
  {
    gfd->framename = p_fetch_framename(vidhand->frn_list
                 , master_frame_nr /* starts at 1 */
                 , l_track
                 , gfd
                 );

    if(gap_debug)
    {
      printf("BYPASSCHECK: track:%d master_frame_nr:%d framename:%s "
             " trak_filtermacroAdr:%ld maskNameAdr:%ld insert_alpha:%ld insert_area:%ld\n"
        ,(int)l_track
        ,(int)master_frame_nr
        ,gfd->framename
        ,(long)gfd->trak_filtermacro_file
        ,(long)gfd->frn_elem->mask_name
        ,(long)vidhand->master_insert_alpha_format
        ,(long)vidhand->master_insert_area_format
        );
    }


    if(gfd->framename)
    {
      if(gfd->frn_type == GAP_FRN_MOVIE)
      {
          if((gfd->opacity == 1.0)
          && (gfd->rotate  == 0.0)
          && (gfd->scale_x == 1.0)
          && (gfd->scale_y == 1.0)
          && (gfd->move_x  == 0.0)
          && (gfd->move_y  == 0.0)
          && (gfd->frn_elem->flip_request == GAP_STB_FLIP_NONE)
          && (gfd->frn_elem->mask_name == NULL)
          && (p_isFiltermacroActive(gfd->trak_filtermacro_file) != TRUE)
          && (gfd->movepath_file_xml == NULL)
          && (gfd->movepath_framePhase = 0.0)
          // Fit options are not relevant when clip and master size are exact the same
          // and no scaling is done.
          //     && (gfd->fit_width)
          //     && (gfd->fit_height)
          //     && (!gfd->keep_proportions)
          )
          {
            gboolean isAutoInsertActive;

            isAutoInsertActive = FALSE;

            if(vidhand->master_insert_alpha_format != NULL)
            {
              char *alpha_imagename;
              alpha_imagename = p_get_insert_alpha_filename(gfd, vidhand);

              if(g_file_test(alpha_imagename, G_FILE_TEST_EXISTS))
              {
                isAutoInsertActive = TRUE;;
              }
              g_free(alpha_imagename);
            }

            if((vidhand->master_insert_area_format != NULL)
            && (isAutoInsertActive != TRUE))
            {
              char *logo_imagename;
              logo_imagename = p_get_insert_area_filename(gfd, vidhand);

              if(g_file_test(logo_imagename, G_FILE_TEST_EXISTS))
              {
                isAutoInsertActive = TRUE;;
              }
              g_free(logo_imagename);
            }

            if (isAutoInsertActive != TRUE)
            {
              gfdMovie = gfd;
              videofileName = g_strdup(gfd->framename);
            }
          }

      }
      g_free(gfd->framename);
    }

    if(gfdMovie != NULL)
    {
      break;
    }

    if(gfd->frn_type != GAP_FRN_SILENCE)
    {
      if(gfd->opacity != 0.0)
      {
        break;
      }
    }

    /* at this point we detected that current layer is fully transparent
     * therefore we can continue checking the next track (i.e. lower layerstack position)
     */

  }       /* end for loop over all video tracks */




  if((gfdMovie != NULL)
  && (videofileName != NULL))
  {
     gfdMovie->framename = videofileName;
     p_check_and_open_video_handle(gfdMovie->frn_elem
                                  , vidhand
                                  , master_frame_nr
                                  , videofileName
                                  );
     if(gap_debug)
     {
       printf("BYPASSCHECK(2): master_frame_nr:%d framename:%s "
              " gvahand size:(%d x %d) vid size:(%d x %d)\n"
         ,(int)master_frame_nr
         ,videofileName
         ,(int)gfdMovie->frn_elem->gvahand->width
         ,(int)gfdMovie->frn_elem->gvahand->height
         ,(int)vid_width
         ,(int)vid_height
         );
     }

     if(gfdMovie->frn_elem->gvahand)
     {
       if((gfdMovie->frn_elem->gvahand->width == vid_width)
       && (gfdMovie->frn_elem->gvahand->height == vid_height))
       {
         /* because there are no transformations and the movie clip is the only active track (i.e. layer)
          * we can bypass the render processing and deliver the frame data as rgb888 buffer
          */
         gfdMovie->gapStoryFetchResult = gapStoryFetchResult;
         gfdMovie->isRgb888Result      = enable_rgb888_flag;
         p_stb_render_movie(gfdMovie, vidhand, master_frame_nr, vid_width, vid_height);

         if(gap_debug)
         {
           printf("BYPASSCHECK(3): master_frame_nr:%d framename:%s resultEnum:%d\n"
             ,(int)master_frame_nr
             ,videofileName
             ,(int)gapStoryFetchResult->resultEnum
             );
         }

         if(gapStoryFetchResult->resultEnum == GAP_STORY_FETCH_RESULT_IS_RAW_RGB888)
         {
           isByPassRenderEngine = TRUE;
         }
       }
     }

  }

  if(videofileName != NULL)
  {
    g_free(videofileName);
  }

  return(isByPassRenderEngine);

}  /* end p_story_render_bypass_where_possible  */


/* --------------------------------------------
 * p_story_render_fetch_composite_image_private
 * --------------------------------------------
 * this procedure builds the composite image for the frame number specified by master_frame_nr.
 * Therefore all videotracks of the storyboard (specified via an already opened handle vidhand)
 * are fetched as layers. The track number is the layerstack index.
 * optional filtermacro processing is done for the separate layers (clip specific filtermacre)
 * and for the composite image (global filtermacro)
 *
 * This procedure can deliver the image id of the resulting composite image
 * as returncode. Optionally the result can be delivered as GapStoryFetchResult struct
 * (in case gapStoryFetchResult was provided not NULL by the caller)
 *
 * The GapStoryFetchResult enables the direct fetch of rgb888 frame data feature.
 * Therefore the caller must also set enable_rgb888_flag = TRUE to activate that feature.
 * direct fetch can bypass the render engine for frames in refered video clips
 * that can be copied 1:1 when no transformations are required to render that frame.
 */
static gint32
p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
                    , gint32 master_frame_nr       /* starts at 1 */
                    , gint32  vid_width            /* desired Video Width in pixels */
                    , gint32  vid_height           /* desired Video Height in pixels */
                    , char *filtermacro_file       /* NULL if no filtermacro is used */
                    , gint32 *layer_id             /* output: Id of the only layer in the composite image */
                    , const char *section_name     /* NULL for main section */
                    , gboolean enable_rgb888_flag  /* enable fetch as rgb888 data buffer */
                    , GapStoryFetchResult      *gapStoryFetchResult
                 )
{
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;

  gint32  l_track;

  static gint32 funcId = -1;
  static gint32 funcIdDirect = -1;
  static gint32 funcIdDirectScale = -1;

  GAP_TIMM_GET_FUNCTION_ID(funcId, "p_story_render_fetch_composite_image_private");
  GAP_TIMM_GET_FUNCTION_ID(funcIdDirect, "p_story_render_fetch_composite_image_private.Direct");
  GAP_TIMM_GET_FUNCTION_ID(funcIdDirectScale, "p_story_render_fetch_composite_image_private.Direct.Scale");

  GAP_TIMM_START_FUNCTION(funcId);


  gfd = &gapStbFetchData;
  p_init_gfd(gfd);

  *layer_id         = -1;



  if(gap_debug)
  {
    printf("p_story_render_fetch_composite_image_private START  master_frame_nr:%d  %dx%d vidhand:%ld"
           " enable_rgb888_flag:%d gapStoryFetchResultAdr:%ld filtermacroAdr:%ld\n"
        , (int)master_frame_nr
        , (int)vid_width
        , (int)vid_height
        , (long)vidhand
        , (int)enable_rgb888_flag
        , (long)gapStoryFetchResult
        , (long)filtermacro_file
        );
    if (section_name == NULL)
    {
      printf(" section_name: (null)\n");
    }
    else
    {
      printf(" section_name: %s\n", section_name);
    }
  }

  p_select_section_by_name(vidhand, section_name);

  if(gap_debug)
  {
    if((vidhand->is_mask_handle == FALSE)
    && (master_frame_nr == 1)
    )
    {
      printf("\n###\n###\nSTART rendering at master_frame_nr 1 with this list of elements:\n");
      gap_story_render_debug_print_framerange_list(vidhand->frn_list, -1);
    }
  }


  if((enable_rgb888_flag == TRUE)
  && (section_name == NULL)
  && (gapStoryFetchResult != NULL)
  && (p_isFiltermacroActive(filtermacro_file) != TRUE))
  {
    gboolean isByPassRenderEngine;

    isByPassRenderEngine = p_story_render_bypass_where_possible(vidhand
                    , master_frame_nr
                    , vid_width
                    , vid_height
                    , enable_rgb888_flag
                    , gapStoryFetchResult
                    );

    if (isByPassRenderEngine == TRUE)
    {
      if(gap_debug)
      {
        printf("p_story_render_fetch_composite_image_private: "
               "isByPassRenderEngine is TRUE (deliver rgb888) enable_rgb888_flag:%d TRUE:%d\n"
               ,(int)enable_rgb888_flag
               ,(int)TRUE
               );
      }
      /* the result is availables as rgb888 buffer in the gapStoryFetchResult struct
       * (no layer/ image was created for for performance reasons)
       */
      return (-1);
    }
  }

  /* reverse order, has the effect, that track 0 is processed as last track
   * and will be put on top of the layerstack (i.e. in the foreground)
   */
  for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
  {
    gfd->framename = p_fetch_framename(vidhand->frn_list
                 , master_frame_nr /* starts at 1 */
                 , l_track
                 , gfd
                 );


     if((gfd->framename) || (gfd->frn_type == GAP_FRN_COLOR))
     {
       if(gfd->frn_type == GAP_FRN_COLOR)
       {
           gfd->tmp_image_id = gap_image_create_unicolor_image(&gfd->layer_id
                                                , vid_width
                                                , vid_height
                                                , gfd->red_f
                                                , gfd->green_f
                                                , gfd->blue_f
                                                , gfd->alpha_f
                                                );

       }
       else
       {
         if(gfd->framename)
         {
           if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
           || (gfd->frn_type == GAP_FRN_IMAGE))
           {
             p_stb_render_image_or_animimage(gfd, vidhand, master_frame_nr, vid_width, vid_height);
           }
           else
           {
             if(gfd->frn_type == GAP_FRN_MOVIE)
             {
               p_stb_render_movie(gfd, vidhand, master_frame_nr, vid_width, vid_height);
             }
             else
             {
               if(gfd->frn_type == GAP_FRN_SECTION)
               {
                 p_stb_render_section(gfd, vidhand, master_frame_nr, vid_width, vid_height, section_name);
               }
               else
               {
                 /* GAP_FRN_FRAMES */
                 p_stb_render_frame_images(gfd, vidhand, master_frame_nr, vid_width, vid_height);
               }
             }
           }
           if(gfd->tmp_image_id < 0)
           {
              printf("\n** ERROR fetching master_frame_nr:%d, from framename:%s targetFrameNumber:%d Current CLIP was:\n"
                 , (int)master_frame_nr
                 , gfd->framename
                 , gfd->localframe_index
                 );
              gap_story_render_debug_print_frame_elem(gfd->frn_elem, -1);
              printf("\n** storyboard render processing failed\n");
              g_free(gfd->framename);

              GAP_TIMM_STOP_FUNCTION(funcId);
              return -1;
           }
           gfd->layer_id = p_prepare_RGB_image(gfd->tmp_image_id);

           if(gfd->frn_type == GAP_FRN_MOVIE)
           {
             if(vidhand->master_insert_alpha_format)
             {
               p_do_insert_alpha_processing(gfd, vidhand);
             }
             if(vidhand->master_insert_area_format)
             {
               p_do_insert_area_processing(gfd, vidhand);
             }
           }

           p_conditional_delace_drawable(gfd, gfd->layer_id);
           g_free(gfd->framename);
         }
       }


       if(gap_debug)
       {
         printf("p_prepare_RGB_image returned layer_id: %d, tmp_image_id:%d (master_frame_nr:%d, comp_image_id:%d)\n"
            , (int)gfd->layer_id
            , (int)gfd->tmp_image_id
            , (int)master_frame_nr
            , (int)gfd->comp_image_id
            );
       }

       if(gfd->comp_image_id  < 0)
       {
         if((gfd->opacity == 1.0)
         && (gfd->rotate == 0.0)
         && (gfd->scale_x == 1.0)
         && (gfd->scale_y == 1.0)
         && (gfd->move_x == 0.0)
         && (gfd->move_y == 0.0)
         && (gfd->fit_width)
         && (gfd->fit_height)
         && (!gfd->keep_proportions)
         && (gfd->frn_elem->flip_request == GAP_STB_FLIP_NONE)
         && (gfd->frn_elem->mask_name == NULL)
         && (gfd->trak_filtermacro_file == NULL)
         && (gfd->frn_type != GAP_FRN_ANIMIMAGE)
         && (gfd->movepath_file_xml == NULL)
         )
         {
           GAP_TIMM_START_FUNCTION(funcIdDirect);
           /* because there are no transformations in the first handled track,
            * we can save time and directly use the loaded tmp image as base for the composite image
            */
           gfd->comp_image_id = gfd->tmp_image_id;


           /* scale image to desired Videosize */
           if ((gimp_image_width(gfd->comp_image_id) != vid_width)
           ||  (gimp_image_height(gfd->comp_image_id) != vid_height) )
           {
              GAP_TIMM_START_FUNCTION(funcIdDirectScale);

              if(gap_debug)
              {
                printf("DEBUG: p_story_render_fetch_composite_image_private scaling composite image\n");
              }
              gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);

              GAP_TIMM_STOP_FUNCTION(funcIdDirectScale);
           }
           GAP_TIMM_STOP_FUNCTION(funcIdDirect);
         }
         else
         {
           /* create empty backgound */
           gint32 l_empty_layer_id;
           gfd->comp_image_id = gap_image_create_unicolor_image(&l_empty_layer_id
                                , vid_width
                                , vid_height
                                , 0.0
                                , 0.0
                                , 0.0
                                , 1.0
                                );
         }
       }

       if(gfd->tmp_image_id != gfd->comp_image_id)
       {
         p_transform_and_add_layer(gfd->comp_image_id, gfd->tmp_image_id, gfd->layer_id
                                  ,gfd->keep_proportions
                                  ,gfd->fit_width
                                  ,gfd->fit_height
                                  ,gfd->rotate
                                  ,gfd->opacity
                                  ,gfd->scale_x
                                  ,gfd->scale_y
                                  ,gfd->move_x
                                  ,gfd->move_y
                                  ,gfd->trak_filtermacro_file
                                  ,gfd->frn_elem->flip_request
                                  ,gfd->frn_elem
                                  ,vidhand
                                  ,gfd->local_stepcount
                                  ,gfd->movepath_file_xml
                                  ,gfd->movepath_framePhase
                                   );
         gap_image_delete_immediate(gfd->tmp_image_id);
       }

     }
  }       /* end for loop over all video tracks */


  p_stb_render_composite_image_postprocessing(gfd
          , vidhand, master_frame_nr
          , vid_width, vid_height
          , filtermacro_file
          , section_name
          );



  *layer_id = gfd->layer_id;

  if((vidhand->is_mask_handle != TRUE)
  && (section_name == NULL))
  {
    p_stb_render_result_monitoring(gfd, master_frame_nr);
  }

  if(gap_debug)
  {
     printf("p_story_render_fetch_composite_image_private END  master_frame_nr:%d  image_id:%d layer_id:%d\n"
           , (int)master_frame_nr
           , (int)gfd->comp_image_id
           , (int)*layer_id );
  }

  GAP_TIMM_STOP_FUNCTION(funcId);

  return(gfd->comp_image_id);

} /* end p_story_render_fetch_composite_image_private */


/* -------------------------------------------------------------------
 * gap_story_render_fetch_composite_image_or_chunk (see included file)
 * -------------------------------------------------------------------
 *
 */

#include "gap_story_render_lossless.c"
