/* 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * 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_render_audio.h"
#include "gap_story_render_processor.h"
#include "gap_fmac_name.h"
#include "gap_frame_fetcher.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 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;
  char            *trak_filtermacro_file;

}  GapStbFetchData;

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

#define MAX_IMG_CACHE_ELEMENTS 6

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



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

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_find_min_max_vid_tracknumbers(GapStoryRenderFrameRangeElem *frn_list
                             , gint32 *lowest_tracknr
                             , gint32 *highest_tracknr
                             );
static void     p_step_attribute(gint32 frames_handled
                 ,gdouble *from_val
                 ,gdouble *to_val
                 ,gint32  *dur
                 );
static gdouble  p_step_attribute_read(gint32 frame_step
                 ,gdouble from_val
                 ,gdouble to_val
                 ,gint32  dur
                 );
static gdouble  p_step_attribute_read(gint32 frame_step
                 ,gdouble from_val
                 ,gdouble to_val
                 ,gint32  dur
                 );

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
                            , GapStoryRenderFrameType *frn_type
                            , char **filtermacro_file
                            , gint32   *localframe_index  /* used only for ANIMIMAGE and videoclip, -1 for all other types */
                            , gint32   *local_stepcount   /* nth frame within this clip, starts with 0 */
                            , gdouble  *localframe_tween_rest /* non-integer part of the position. value < 1.0 for tween fetching */
                            , gboolean *keep_proportions
                            , gboolean *fit_width
                            , gboolean *fit_height
                            , gdouble  *red_f
                            , gdouble  *green_f
                            , gdouble  *blue_f
                            , gdouble  *alpha_f
                            , gdouble *opacity       /* output opacity 0.0 upto 1.0 */
                            , gdouble *scale_x       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                            , gdouble *scale_y       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                            , gdouble *move_x        /* output -1.0 upto 1.0 where 0.0 is centered */
                            , gdouble *move_y        /* output -1.0 upto 1.0 where 0.0 is centered */
                            , GapStoryRenderFrameRangeElem **frn_elem_ptr  /* OUT pointer to the relevant framerange element */
                           );
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
                          );
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 */
                    );
static void       p_fetch_and_add_layermask(GapStoryRenderVidHandle *vidhand
                    , GapStoryRenderFrameRangeElem *frn_elem
                    , gint32 local_stepcount
                    , gint32 image_id
                    , gint32 layer_id
                    );

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
                      );
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
                         );
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 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 */
                         , char *filtermacro_file
                         , gint32 flip_request
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         );
static gint32     p_create_unicolor_image(gint32 *layer_id, gint32 width , gint32 height
                       , gdouble r_f, gdouble g_f, gdouble b_f, gdouble a_f);
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
                      );


static t_GVA_Handle * p_try_to_steal_gvahand(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , char *basename             /* the videofile name */
                      , gint32 exact_seek
                      );
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_stb_render_image_or_animimage(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr);
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 void       p_stb_render_frame_images(GapStbFetchData *gfd, gint32 master_frame_nr);
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 void       p_do_insert_area_processing(GapStbFetchData *gfd
                      , GapStoryRenderVidHandle *vidhand);

  
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 */
                   );


/* ----------------------------------------------------
 * 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      : %d\n", (int)l_idx, (int)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           : %d\n", (int)l_idx, (int)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] 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] 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_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] 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_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 );
  }
}  /* 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:%d section:%d section->frn_list:%d)"
           , (int)vidhand
           , (int)section
           , (int)section->frn_list
           );
      if (section->section_name == NULL)
      {
        printf("section_name: (null) e.g. MAIN\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           : %d\n", (int)l_idx, (int)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          : %d\n", (int)l_idx, (int)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_find_min_max_vid_tracknumbers
 * ----------------------------------------------------
 * findout the lowest and highest track number used
 * in the framerange list
 */
static void
p_find_min_max_vid_tracknumbers(GapStoryRenderFrameRangeElem *frn_list
                             , gint32 *lowest_tracknr
                             , gint32 *highest_tracknr
                             )
{
  GapStoryRenderFrameRangeElem *frn_elem;

  *lowest_tracknr = GAP_STB_MAX_VID_INTERNAL_TRACKS;
  *highest_tracknr = -1;

  for(frn_elem = frn_list; frn_elem != NULL; frn_elem = (GapStoryRenderFrameRangeElem *)frn_elem->next)
  {
    if (frn_elem->track > *highest_tracknr)
    {
      *highest_tracknr = frn_elem->track;
    }
    if (frn_elem->track < *lowest_tracknr)
    {
      *lowest_tracknr = frn_elem->track;
    }

  }

  if(gap_debug) printf("p_find_min_max_vid_tracknumbers: min:%d max:%d\n", (int)*lowest_tracknr, (int)*highest_tracknr);

}  /* end p_find_min_max_vid_tracknumbers */


/* ---------------------------------------
 * p_step_attribute
 * ---------------------------------------
 * calculate remaining attribute value after N frames_handled
 * and change *from_val and *dur accordingly
 */
static void
p_step_attribute(gint32 frames_handled
                ,gdouble *from_val
                ,gdouble *to_val
                ,gint32  *dur
                )
{
  gint32 l_rest_steps;
  gdouble step_delta_val;

  l_rest_steps =  *dur  - frames_handled;
  if((l_rest_steps <= 0) || (*dur <= 0))
  {
    *from_val = *to_val;
    *dur = 0;
  }
  else
  {
    step_delta_val =  (*to_val - *from_val) / (gdouble)(*dur);

    *from_val = *to_val - (step_delta_val * l_rest_steps) ;
    *dur = l_rest_steps;
  }
}  /* end p_step_attribute */



/* --------------------------------
 * p_step_attribute_read
 * --------------------------------
 * return attribute value after N frame steps
 */
static gdouble
p_step_attribute_read(gint32 frame_step
                ,gdouble from_val
                ,gdouble to_val
                ,gint32  dur
                )
{
  gdouble l_from_val;
  gdouble l_to_val;
  gint32  l_dur;

  l_from_val    = from_val;
  l_to_val      = to_val;
  l_dur         = dur;

  p_step_attribute( frame_step
                  , &l_from_val
                  , &l_to_val
                  , &l_dur
                  );

  return(l_from_val);

}  /* end p_step_attribute */


/* ----------------------------------------------------
 * 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->frn_list = section->frn_list;
    vidhand->aud_list = section->aud_list;
  }
  else
  {
    vidhand->frn_list = NULL;
    vidhand->aud_list = NULL;
  }
  if(gap_debug)
  {
    printf("p_select_section_by_name: addr of section: %d Resulting addr of frn_list: %d)\n"
       , (int)section
       , (int)vidhand->frn_list
       );
    fflush(stdout);
  }

}  /* end p_select_section_by_name */


/* ----------------------------------------------------
 * p_fetch_framename
 * ----------------------------------------------------
 * fetch framename 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 gduoble attribute values (opacity, scale, move) for the frame
 * at track and master_frame_nr position.
 *
 * return the name of the frame (that is to be displayed at position master_frame_nr).
 * return 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
                 , GapStoryRenderFrameType *frn_type
                 , char **filtermacro_file
                 , gint32   *localframe_index  /* starts at 1, used for ANIMIMAGE and VIDEOFILES, -1 for all other types */
                 , gint32   *local_stepcount   /* nth frame within this clip, starts with 0 */
                 , gdouble  *localframe_tween_rest /* non-integer part of the position. value < 1.0 for tween fetching */
                 , gboolean *keep_proportions
                 , gboolean *fit_width
                 , gboolean *fit_height
                 , gdouble  *red_f
                 , gdouble  *green_f
                 , gdouble  *blue_f
                 , gdouble  *alpha_f
                 , gdouble *opacity       /* output opacity 0.0 upto 1.0 */
                 , gdouble *scale_x       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                 , gdouble *scale_y       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                 , gdouble *move_x        /* output -1.0 upto 1.0 where 0.0 is centered */
                 , gdouble *move_y        /* output -1.0 upto 1.0 where 0.0 is centered */
                 , GapStoryRenderFrameRangeElem **frn_elem_ptr  /* OUT pointer to the relevant framerange element */
                 )
{
  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) */
  *opacity = 1.0;
  *scale_x = 1.0;
  *scale_y = 1.0;
  *move_x  = 0.0;
  *move_y  = 0.0;
  *localframe_index = -1;
  *local_stepcount = 0;
  *localframe_tween_rest = 0.0;
  *frn_type = GAP_FRN_SILENCE;
  *keep_proportions = FALSE;
  *fit_width        = TRUE;
  *fit_height       = TRUE;
  *red_f            = 0.0;
  *green_f          = 0.0;
  *blue_f           = 0.0;
  *alpha_f          = 1.0;
  *filtermacro_file = NULL;
  *frn_elem_ptr      = NULL;

  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 */
          
          *localframe_tween_rest = fnr - fnrInt;
          
          if(gap_debug)
          {
            printf("fnr:%.4f, fnrInt:%d localframe_tween_rest:%.4f\n"
                     ,(float)fnr
                     ,(int)fnrInt
                     ,(float)*localframe_tween_rest
                     );
          }
        }

        *local_stepcount = master_frame_nr - l_frame_group_count;
        *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 */
            *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 */
            *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 */
            *localframe_index = l_fnr;                    /* local frame number is the wanted video frame number */
            break;
        }

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

         frn_elem->last_master_frame_access = master_frame_nr;

         *frn_elem_ptr     = 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;



         *opacity = p_step_attribute_read(l_step
                                    , frn_elem->opacity_from
                                    , frn_elem->opacity_to
                                    , frn_elem->opacity_dur
                                    );
         *scale_x = p_step_attribute_read(l_step
                                    , frn_elem->scale_x_from
                                    , frn_elem->scale_x_to
                                    , frn_elem->scale_x_dur
                                    );
         *scale_y = p_step_attribute_read(l_step
                                    , frn_elem->scale_y_from
                                    , frn_elem->scale_y_to
                                    , frn_elem->scale_y_dur
                                    );
         *move_x  = p_step_attribute_read(l_step
                                    , frn_elem->move_x_from
                                    , frn_elem->move_x_to
                                    , frn_elem->move_x_dur
                                    );
         *move_y  = p_step_attribute_read(l_step
                                    , frn_elem->move_y_from
                                    , frn_elem->move_y_to
                                    , frn_elem->move_y_dur
                                    );
        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 opa:%f scale:%f %f move:%f %f layerstack_idx:%d\n"
       ,(int)track
       ,(int)master_frame_nr
       , l_framename
       ,(int)l_found_at_idx
       , (float)*opacity
       , (float)*scale_x
       , (float)*scale_y
       , (float)*move_x
       , (float)*move_y
       , (int)*localframe_index
       );
  }

  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
                      )
{
  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);
  }


  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;
  if(!mask_disable)
  {
    if(mask_name)
    {
      frn_elem->mask_name = g_strdup(mask_name);
    }
  }

  /* 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->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->gvahand            = NULL;
  frn_elem->seltrack           = seltrack;
  frn_elem->exact_seek         = exact_seek;
  frn_elem->delace             = delace;
  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->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
                      )
{
  p_step_attribute( frames_to_handle
                  , &vtarr->attr[track].opacity_from
                  , &vtarr->attr[track].opacity_to
                  , &vtarr->attr[track].opacity_dur
                  );

  p_step_attribute( frames_to_handle
                  , &vtarr->attr[track].scale_x_from
                  , &vtarr->attr[track].scale_x_to
                  , &vtarr->attr[track].scale_x_dur
                  );

  p_step_attribute( frames_to_handle
                  , &vtarr->attr[track].scale_y_from
                  , &vtarr->attr[track].scale_y_to
                  , &vtarr->attr[track].scale_y_dur
                  );

  p_step_attribute( frames_to_handle
                  , &vtarr->attr[track].move_x_from
                  , &vtarr->attr[track].move_x_to
                  , &vtarr->attr[track].move_x_dur
                  );

  p_step_attribute( frames_to_handle
                  , &vtarr->attr[track].move_y_from
                  , &vtarr->attr[track].move_y_to
                  , &vtarr->attr[track].move_y_dur
                  );
}  /* end p_step_all_vtrack_attributes */


/* ----------------------------------------------------
 * p_set_vtrack_attributes
 * ----------------------------------------------------
 * set current video track attributes for the
 * Framerange_Element. from the current attribute array.
 *
 * attribute array is changed to the state after
 * the playback of the Framerange_element.
 *
 * 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,
 *    the result will be:
 *       OUT:  opacity_from: 1.0   opacity_to: 1.0  opacity_dur: 0 frames
 *
 *    if the range is for example 25 frames (smaller than the duration 50)
 *    the result will be:
 *       OUT:  opacity_from: 0.5   opacity_to: 1.0  opacity_dur: 25 frames
 *       because there are 25 rest frames to complete the fade in action
 *       in the next range (if there will be 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->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->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_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->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_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;


  /* 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 */
                                     );
  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].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].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_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].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_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;

}  /* 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
                      );


    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].opacity_from  = 1.0;
    vtarr->attr[l_idx].opacity_to    = 1.0;
    vtarr->attr[l_idx].opacity_dur   = 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_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].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_y_from   = 0.0;
    vtarr->attr[l_idx].move_y_to     = 0.0;
    vtarr->attr[l_idx].move_y_dur    = 0;
  }
}  /* 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;

  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);
  }

  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 (e.g. 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 values (opacity, move and scale)
     * of all tracks and is updated at parsing of the the current clip.
     * each processed clip gets its start values from the array, and modifies the values
     * according to the amount of framesteps of a transition that can be handled
     * by processing the clip.
     * processing of a transition attribute element explicite sets the specified
     * number of framesteps, and from_ and to values for the next transition.
     *
     * 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
       * (e.g. 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_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];
                    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];
                    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];
                    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];
                    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];
                    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 */
                                               );
            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 */
                                               );
            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
                                               );
            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
                                               );
            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
                                                    );
                 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:%d stb_mem_ptr;%d"
      , (int)vidhand
      , (int)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:%d\n"
      , (int)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);}

#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
    if(frn_elem->gvahand)           { 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 */
                            );
  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 (e.g. 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 as gray image 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 */
   )
{
  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);
     }

    /* the composite image fecther already converts to gray when called
     * with a mask videohandle (marked with is_mask_handle flag)
     */
    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)
                                      */
                 );

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


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

    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
                  )
{
  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;

  /* 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);

  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
                              );
  }

  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);
     }
     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
                      )
{
  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:%d\n", (int)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 (e.g. the image cache) */
  vidhand->ffetch_user_id = gap_frame_fetch_register_user("gap_story_render_processor.p_open_video_handle_private");

  vidhand->frn_list = NULL;
  vidhand->preferred_decoder = NULL;
  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;     /* use DEFAULT resample program (sox), where needed */
  vidhand->util_sox_options  = NULL;     /* use DEFAULT 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:%d\n"
      , (int)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 */
                                         );
      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 */
                                         );
        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 */
                                         );
      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 */
                                           );
        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 */
                                         );
      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 */
                                         );
      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); */

  if(gap_debug)
  {
    printf("\n\np_open_video_handle_private: END vidhand:%d\n\n", (int)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 */
                      )
{
  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 */
                            )
         );

} /* 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 */
                            );
  }

  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 */
                            );

  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)
 */
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
)
{
  GimpParam* l_params;
  gint   l_retvals;
  gint32 l_rc_layer_id;
  gint          l_nlayers;
  gint32       *l_layers_list;


  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
          * Therfore 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
       {
           if(current_step > total_steps)
           {
             current_step = total_steps;
           }
           if(current_step < 0.0)
           {
             current_step = 0.0;
           }
           /* 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_step,  /* 0.0 upto 1.0 */
                     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)
       {
         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);
       }

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


/* ----------------------------------------------------
 * p_transform_and_add_layer
 * ----------------------------------------------------
 * - copy the layer (layer_id) 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
 *
 * 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 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 */
                         , char *filtermacro_file
                         , gint32 flip_request
                         , GapStoryRenderFrameRangeElem *frn_elem
                         , GapStoryRenderVidHandle *vidhand
                         , gint32 local_stepcount
                         )
{
  gint32 vid_width;
  gint32 vid_height;
  gint32 l_new_layer_id;
  gint32 l_fsel_layer_id;
  GapStoryCalcAttr  calculate_attributes;
  GapStoryCalcAttr  *calculated;

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

  /* 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)
   */
  p_exec_filtermacro(tmp_image_id
                      , layer_id
                      , filtermacro_file
                      , frn_elem->filtermacro_file_to
                      , (gdouble)(local_stepcount) /* fmac_current_step */
                      , frn_elem->fmac_total_steps
                    );

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

  layer_id = gap_layer_flip(layer_id, flip_request);


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

  vid_width = gimp_image_width(comp_image_id);
  vid_height = gimp_image_height(comp_image_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(tmp_image_id)
    , gimp_image_height(tmp_image_id)
    , keep_proportions
    , fit_width
    , fit_height
    , opacity
    , scale_x
    , scale_y
    , move_x
    , move_y
    );

  calculated = &calculate_attributes;


  /* check size and scale source layer_id to calculated size
   */
  if ((gimp_image_width(tmp_image_id) != calculated->width)
  ||  (gimp_image_height(tmp_image_id) != calculated->height) )
  {
    if(gap_debug)
    {
      printf("DEBUG: p_transform_and_add_layer scaling layer from %dx%d to %dx%d\n"
                            , (int)gimp_image_width(tmp_image_id)
                            , (int)gimp_image_height(tmp_image_id)
                            , (int)calculated->width
                            , (int)calculated->height
                            );

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

  }


  /* add a new layer to 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_add_layer(comp_image_id, l_new_layer_id, 0);
  gap_layer_clear_to_color(l_new_layer_id, 0.0, 0.0, 0.0, 0.0);

  if((frn_elem->mask_name != NULL)
  && (frn_elem->mask_anchor == GAP_MSK_ANCHOR_CLIP))
  {
     p_fetch_and_add_layermask(vidhand
                  , frn_elem
                  , local_stepcount
                  , tmp_image_id
                  , layer_id
                  );
     /* 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 from tmp_image and paste to composite_image
   * force copying of the complete layer by clearing the selection
   */
  gimp_selection_none(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);

  if((frn_elem->mask_name != NULL)
  && (frn_elem->mask_anchor == GAP_MSK_ANCHOR_MASTER))
  {
     p_fetch_and_add_layermask(vidhand
                  , frn_elem
                  , local_stepcount
                  , comp_image_id
                  , l_new_layer_id
                  );

  }

  gimp_layer_set_opacity(l_new_layer_id, calculated->opacity);

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





/* ----------------------------------------------------
 * p_create_unicolor_image
 * ----------------------------------------------------
 * - create a new image with one black filled layer
 *   (both have the requested size)
 *
 * return the new created image_id
 *   and the layer_id of the black_layer
 */
static gint32
p_create_unicolor_image(gint32 *layer_id, gint32 width , gint32 height
                       , gdouble r_f, gdouble g_f, gdouble b_f, gdouble a_f)
{
  gint32 l_empty_layer_id;
  gint32 l_image_id;

  *layer_id = -1;
  l_image_id = gimp_image_new(width, height, GIMP_RGB);
  if(l_image_id >= 0)
  {
    l_empty_layer_id = gimp_layer_new(l_image_id, "black_background",
                          width, height,
                          GIMP_RGBA_IMAGE,
                          100.0,     /* Opacity full opaque */
                          GIMP_NORMAL_MODE);
    gimp_image_add_layer(l_image_id, l_empty_layer_id, 0);

    /* clear layer to unique color */
    gap_layer_clear_to_color(l_empty_layer_id, r_f, g_f, b_f, a_f);

    *layer_id = l_empty_layer_id;
  }
  return(l_image_id);
}       /* end p_create_unicolor_image */

/* ----------------------------------------------------
 * 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)
 */
static void
p_limit_open_videohandles(GapStoryRenderVidHandle *vidhand
                      , gint32 master_frame_nr
                      , gint32 currently_open_videohandles
                      )
{
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
#define GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES 12
  GapStoryRenderFrameRangeElem *frn_elem;
  gint32 l_count_open_videohandles;
  gint32 l_max_open_videohandles;


  l_max_open_videohandles = gap_base_get_gimprc_int_value("video-storyboard-max-open-videofiles"
                                                        , GAP_STB_DEFAULT_MAX_OPEN_VIDEOFILES
                                                        , 2
                                                        , 100
                                                        );
  l_count_open_videohandles = currently_open_videohandles;
  
  if (l_count_open_videohandles <= l_max_open_videohandles)
  {
    /* we are below the limit, nothing left to do in that case */
    return;
  }
  
  for (frn_elem = vidhand->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)l_max_open_videohandles
            , (int)master_frame_nr
            , frn_elem->basename
            );
       }
       GVA_close(frn_elem->gvahand);
       frn_elem->gvahand = NULL;
       l_count_open_videohandles--;

       if (l_count_open_videohandles <= l_max_open_videohandles)
       {
         return;
       }
       
    }
  }
#endif
  return;

} /* end p_limit_open_videohandles */


/* ----------------------------------------------------
 * p_try_to_steal_gvahand
 * ----------------------------------------------------
 * try to steal an alread open GVA handle for video read from another
 * element.
 * conditions: must use same videofile, 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
                      , char *basename             /* the videofile name */
                      , gint32 exact_seek
                      )
{
#ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
  GapStoryRenderFrameRangeElem *frn_elem;
  gint32 l_count_open_videohandles;

  l_count_open_videohandles = 0;
  for (frn_elem = vidhand->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->gvahand != NULL))
    {
      if(strcmp(frn_elem->basename, basename) == 0)
      {
         t_GVA_Handle *gvahand;

         if(gap_debug)
         {
           printf("(RE)using an already open GVA handle for video read access %s\n"
                  , frn_elem->basename
                  );
         }
         gvahand = frn_elem->gvahand;
         frn_elem->gvahand = NULL;   /* steal from this element */
         return(gvahand);
      }
    }
  }
  p_limit_open_videohandles(vidhand, master_frame_nr, l_count_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 */





/* ----------------------------------------------------
 * gap_story_render_fetch_composite_image
 * ----------------------------------------------------
 * 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 */
                 )
{
  return (
    p_story_render_fetch_composite_image_private(vidhand
                                                  ,master_frame_nr
                                                  ,vid_width
                                                  ,vid_height
                                                  ,filtermacro_file
                                                  ,layer_id
                                                  ,NULL));
}  /* 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_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        l_orig_image_id;
  gint          l_nlayers;
  gint32       *l_layers_list;

  l_orig_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
                        , gfd->framename            /* full filename of the image */
                        , TRUE /*  enable caching */
                       );
  
  gimp_selection_none(l_orig_image_id);
  if(gfd->frn_type == GAP_FRN_IMAGE)
  {
    gfd->layer_id = p_prepare_RGB_image(l_orig_image_id);
    gfd->tmp_image_id = gimp_image_duplicate(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 = p_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_drawable_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_stb_render_movie (GAP_FRN_MOVIE)
 * -------------------------------------------
 * fetch frame from a videofile (gfd->framename contains the videofile name) 
 */
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

  if(gfd->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
      */
     gfd->frn_elem->gvahand = p_try_to_steal_gvahand(vidhand
                                                 , master_frame_nr
                                                 , gfd->frn_elem->basename
                                                 , gfd->frn_elem->exact_seek
                                                 );
     if(gfd->frn_elem->gvahand == NULL)
     {
       if(vidhand->preferred_decoder)
       {
         gfd->frn_elem->gvahand = GVA_open_read_pref(gfd->framename
                                , gfd->frn_elem->seltrack
                                , 1 /* aud_track */
                                , vidhand->preferred_decoder
                                , FALSE  /* use MMX if available (disable_mmx == FALSE) */
                                );
       }
       else
       {
         gfd->frn_elem->gvahand = GVA_open_read(gfd->framename
                                           ,gfd->frn_elem->seltrack
                                           ,1 /* aud_track */
                                           );
       }

       if(gfd->frn_elem->gvahand)
       {
         GVA_set_fcache_size(gfd->frn_elem->gvahand, GAP_STB_RENDER_GVA_FRAMES_TO_KEEP_CACHED);

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

  }

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

     /* 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
             );


     /* set image and layer in the gvahand structure invalid,
      * to force creation of a new image in the following call of  GVA_frame_to_gimp_layer
      */
     gfd->frn_elem->gvahand->image_id = -1;
     gfd->frn_elem->gvahand->layer_id = -1;


     /* attempt to read frame from the GVA API internal framecache */

     /* printf("\nST: before  GVA_debug_print_fcache (2) #:%d\n", (int)gfd->localframe_index );
      * GVA_debug_print_fcache(gfd->frn_elem->gvahand);
      * printf("ST: before  GVA_frame_to_gimp_layer (2) attempt cache read  #:%d\n", (int)gfd->localframe_index );
      */

     l_fcr = GVA_frame_to_gimp_layer(gfd->frn_elem->gvahand
                       , TRUE                 /* delete_mode */
                       , gfd->localframe_index   /* framenumber */
                       , l_deinterlace
                       , l_threshold
                       );

     if (l_fcr != GVA_RET_OK)
     {
       /* 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 + GAP_STB_RENDER_GVA_FRAMES_TO_KEEP_CACHED) > gfd->localframe_index)
         &&  (gfd->frn_elem->gvahand->current_seek_nr < gfd->localframe_index ) )
         {
           /* near forward seek is performed by dummyreads to fill up the framecache
            */
           while(gfd->frn_elem->gvahand->current_seek_nr < gfd->localframe_index)
           {
             GVA_get_next_frame(gfd->frn_elem->gvahand);
           }
         }
         else
         {
           if(vidhand->do_gimp_progress)
           {
              gimp_progress_init(_("Seek Inputvideoframe..."));
           }
           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)
       {
         GVA_frame_to_gimp_layer(gfd->frn_elem->gvahand
                         , TRUE   /* delete_mode */
                         , gfd->localframe_index   /* framenumber */
                         , l_deinterlace
                         , l_threshold
                         );
       }
     }
     /* take the newly created image from gvahand stucture */
     gfd->tmp_image_id = gfd->frn_elem->gvahand->image_id;
     gfd->frn_elem->gvahand->image_id = -1;
     gfd->frn_elem->gvahand->layer_id = -1;
  }
#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
                                           );

  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_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, gint32 master_frame_nr)
{
  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
      );
  }
  gfd->tmp_image_id = gap_lib_load_image(gfd->framename);

}  /* 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 = p_create_unicolor_image(&gfd->layer_id
                         , vid_width
                         , vid_height
                         , 0.0
                         , 0.0
                         , 0.0
                         , 1.0
                         );
  }

  if(vidhand->is_mask_handle == TRUE)
  {
    /* we are running as mask fetcher,
     * therefore convert to GRAY image
     */
    if(gimp_image_base_type(gfd->comp_image_id) != GIMP_GRAY)
    {
      gimp_image_convert_grayscale(gfd->comp_image_id);
    }
  }

  /* 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 parametersets */
                    , 0.0  /* current_step */
                    , 1    /* total_steps */
                    );

  /* 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_story_render_fetch_composite_image_private: scaling tmp image\n");

     gimp_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_story_render_fetch_composite_image_private  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;
  gint32        image_id;

  image_id = gimp_drawable_get_image(drawable_id);
  gimp_selection_all(gimp_drawable_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_drawable_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_do_insert_area_processing
 * -------------------------------------
 */
static void
p_do_insert_area_processing(GapStbFetchData *gfd
  , GapStoryRenderVidHandle *vidhand)
{
  char *logo_imagename;
  char *videofilename_without_path;
  
  
  videofilename_without_path = gap_story_build_basename(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_do_insert_area_processing: format:%s\n video:%s\n logo_imagename:%s\n"
        , vidhand->master_insert_area_format
        , videofilename_without_path
        , logo_imagename
        );
  }


  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);
      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);
    }
  }

}  /* end p_do_insert_area_processing */

/* --------------------------------------------
 * p_story_render_fetch_composite_image_private
 * --------------------------------------------
 * this procedure builds the composite image for the fram 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)
 */
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 */
                 )
{
  GapStbFetchData gapStbFetchData;
  GapStbFetchData *gfd;

  gint    l_track;
  gint32    l_track_min;
  gint32    l_track_max;

  gdouble l_red_f;
  gdouble l_green_f;
  gdouble l_blue_f;
  gdouble l_alpha_f;

  gfd = &gapStbFetchData;
  gfd->localframe_tween_rest = 0.0;
  gfd->comp_image_id   = -1;
  gfd->tmp_image_id    = -1;
  gfd->layer_id        = -1;
  *layer_id         = -1;

  if(gap_debug)
  {
    printf("p_story_render_fetch_composite_image_private START  master_frame_nr:%d  %dx%d vidhand:%d\n"
        , (int)master_frame_nr
        , (int)vid_width
        , (int)vid_height
        , (int)vidhand
        );
    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)
    // && (section_name == NULL)
    )
    {
      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);
    }
  }

  p_find_min_max_vid_tracknumbers(vidhand->frn_list, &l_track_min, &l_track_max);


  /* reverse order, has the effect, that track 0 is processed as last track
   * and will be put on top of the layerstack
   */
  for(l_track = MIN(GAP_STB_MAX_VID_INTERNAL_TRACKS, l_track_max); l_track >= MAX(0, l_track_min); l_track--)
  {
    gfd->framename = p_fetch_framename(vidhand->frn_list
                 , master_frame_nr /* starts at 1 */
                 , l_track
                 , &gfd->frn_type
                 , &gfd->trak_filtermacro_file
                 , &gfd->localframe_index   /* used only for ANIMIMAGE, SECTION and Videoframe Number, -1 for all other types */
                 , &gfd->local_stepcount    /* nth frame within this clip */
                 , &gfd->localframe_tween_rest  /* non integer part of local position (in case stepsize != 1) */
                 , &gfd->keep_proportions
                 , &gfd->fit_width
                 , &gfd->fit_height
                 , &l_red_f
                 , &l_green_f
                 , &l_blue_f
                 , &l_alpha_f
                 , &gfd->opacity       /* output opacity 0.0 upto 1.0 */
                 , &gfd->scale_x       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                 , &gfd->scale_y       /* output 0.0 upto 10.0 where 1.0 is 1:1 */
                 , &gfd->move_x        /* output -1.0 upto 1.0 where 0.0 is centered */
                 , &gfd->move_y        /* output -1.0 upto 1.0 where 0.0 is centered */
                 , &gfd->frn_elem      /* output selected to the relevant framerange element */
                 );


     if((gfd->framename) || (gfd->frn_type == GAP_FRN_COLOR))
     {
       if(gfd->frn_type == GAP_FRN_COLOR)
       {
           gfd->tmp_image_id = p_create_unicolor_image(&gfd->layer_id
                                                , vid_width
                                                , vid_height
                                                , l_red_f
                                                , l_green_f
                                                , l_blue_f
                                                , l_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);
           }
           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, master_frame_nr);
               }
             }
           }
           if(gfd->tmp_image_id < 0)
           {
              printf("\n** ERROR fetching master_frame_nr:%d, from framename:%s Current CLIP was:\n"
                 , (int)master_frame_nr
                 , (int)gfd->framename
                 );
              gap_story_render_debug_print_frame_elem(gfd->frn_elem, -1);
              printf("\n** storyboard render processing failed\n");
              g_free(gfd->framename);
              return -1;
           }
           gfd->layer_id = p_prepare_RGB_image(gfd->tmp_image_id);
           if((gfd->frn_type == GAP_FRN_MOVIE) && (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\n"
            , (int)gfd->layer_id
            , (int)gfd->tmp_image_id
            );
       }         

       if(gfd->comp_image_id  < 0)
       {
         if((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->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)
         )
         {
           /* 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) )
           {
              if(gap_debug) printf("DEBUG: p_story_render_fetch_composite_image_private scaling composite image\n");
              gimp_image_scale(gfd->comp_image_id, vid_width, vid_height);
           }
         }
         else
         {
           /* create empty backgound */
           gint32 l_empty_layer_id;
           gfd->comp_image_id = p_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->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
                                   );
         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 );
  }

  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"
