#!/usr/bin/perl

use strict;
use Encode qw( encode );
#use vars (@ARGV);
use HTML::Template;
use Data::Dump qw(dump);
use Frontier::Client;
use Getopt::Long;
use Time::HiRes qw(time);
use POSIX qw(strftime);

sub timestamp_now
{
    my $t = time();
    my $date = strftime "%Y-%m-%d %H:%M:%S", localtime $t;
    $date .= sprintf ".%03d", ($t-int($t))*1000; # without rounding
};

# NIVEA_SOARES_EM_TUA_PRESENCA_Nívea.mp4
# Barak_Te_Quiero_Adorar_Live_DVD_Generación_Sedienta.mp4

my @SHM = ();

my $tmpl_main_txt = <<EOT;
<?xml version="1.0" ?>
<mlt>

<TMPL_LOOP NAME="STEREOS">
    <!-- cnt0=<TMPL_VAR NAME="cnt0" ESCAPE="HTML"> -->
    <!-- cnt1=<TMPL_VAR NAME="cnt1" ESCAPE="HTML"> -->
    <producer id="clip_stereo_<TMPL_VAR NAME="IDX" ESCAPE="HTML">" in="<TMPL_VAR NAME="CLIP_IN" ESCAPE="HTML">" out="<TMPL_VAR NAME="CLIP_OUT" ESCAPE="HTML">">
        <property name="resource"><TMPL_VAR NAME="res" ESCAPE="HTML"></property>
        <property name="audio_index">all</property>
        <filter mlt_service="audiochannels"></filter>
        <filter mlt_service="swresample"></filter>
    </producer>
</TMPL_LOOP>

    <producer id="clip_src" in="<TMPL_VAR NAME="CLIP_IN" ESCAPE="HTML">" out="<TMPL_VAR NAME="CLIP_OUT" ESCAPE="HTML">">
        <property name="resource"><TMPL_VAR NAME="resource" ESCAPE="HTML"></property>
        <property name="audio_index">all</property>
<TMPL_IF force_progressive>
        <property name="progressive">0</property>
        <property name="force_progressive">0</property></TMPL_IF>
<TMPL_IF force_aspect_ratio>
        <property name="force_aspect_ratio"><TMPL_VAR NAME="force_aspect_ratio" ESCAPE="HTML"></property></TMPL_IF>

<TMPL_IF audiomap_enable>
        <filter mlt_service="meta_embed">
            <property name="meta">meta.attr.amap.markup</property>
            <property name="value"><TMPL_VAR NAME="audiomap_0"><TMPL_VAR NAME="audiomap_1"><TMPL_VAR NAME="audiomap_2"><TMPL_VAR NAME="audiomap_3"><TMPL_VAR NAME="audiomap_4"><TMPL_VAR NAME="audiomap_5"><TMPL_VAR NAME="audiomap_6"><TMPL_VAR NAME="audiomap_7"></property>
        </filter>

        <filter mlt_service="audiomap">
            <property name="0"><TMPL_VAR NAME="audiomap_0"></property>
            <property name="1"><TMPL_VAR NAME="audiomap_1"></property>
            <property name="2"><TMPL_VAR NAME="audiomap_2"></property>
            <property name="3"><TMPL_VAR NAME="audiomap_3"></property>
            <property name="4"><TMPL_VAR NAME="audiomap_4"></property>
            <property name="5"><TMPL_VAR NAME="audiomap_5"></property>
            <property name="6"><TMPL_VAR NAME="audiomap_6"></property>
            <property name="7"><TMPL_VAR NAME="audiomap_7"></property>
        </filter></TMPL_IF>

        <filter mlt_service="audiochannels"></filter>

        <filter mlt_service="swresample"></filter>

<TMPL_IF commercials_handler_enable>
        <filter mlt_service="commercials_handler">
            <property name="curr_ply_duration"><TMPL_VAR NAME="commercials_handler_curr_ply_duration"></property>
            <property name="next_ply_duration"><TMPL_VAR NAME="commercials_handler_next_ply_duration"></property>
            <property name="curr_ply_type"><TMPL_VAR NAME="commercials_handler_curr_ply_type"></property>
            <property name="next_ply_type"><TMPL_VAR NAME="commercials_handler_next_ply_type"></property>
            <property name="curr_ply_start_time"><TMPL_VAR NAME="commercials_handler_curr_ply_start_time"></property>
            <property name="next_ply_start_time"><TMPL_VAR NAME="commercials_handler_next_ply_start_time"></property>
            <property name="nextC0_ply_start_time"><TMPL_VAR NAME="commercials_handler_nextC0_ply_start_time"></property>
        </filter></TMPL_IF>

<TMPL_IF audioamp_volume>
        <filter mlt_service="volume">
            <property name="gain"><TMPL_VAR NAME="audioamp_volume"></property>
            <property name="end">0</property>
        </filter></TMPL_IF>

<TMPL_IF dtmf_filter_enable>
        <filter mlt_service="mute_mask">
            <property name="value">12</property>
        </filter>
        <filter mlt_service="dtmf_seq">
            <property name="offset"><TMPL_VAR NAME="dtmf_filter_offset"></property>
            <property name="seq"><TMPL_VAR NAME="dtmf_filter_seq"></property>
        </filter></TMPL_IF>

<TMPL_IF oxtel_cmd_list><TMPL_LOOP NAME="oxtel_cmd_list">
        <filter mlt_service="oxtel_cmd_embed">
            <property name="offset"><TMPL_VAR NAME="POS"></property>
            <property name="seq"><TMPL_VAR NAME="SEQ" ESCAPE="HTML"></property>
        </filter></TMPL_LOOP>
</TMPL_IF>

        <filter mlt_service="imx_crop">
<TMPL_IF undo_letterbox>
            <property name="undo_letterbox">1</property></TMPL_IF>
        </filter>

    </producer>

    <tractor id="clip">
        <multitrack>
            <track producer="clip_src"/> <!-- 0 -->
<TMPL_LOOP NAME="STEREOS">
            <track producer="clip_stereo_<TMPL_VAR NAME="IDX" ESCAPE="HTML">"/> <!-- <TMPL_VAR NAME="CNT1" ESCAPE="HTML"> --></TMPL_LOOP>
        </multitrack>

<TMPL_LOOP NAME="STEREOS">
        <transition in="0" out="<TMPL_VAR NAME="CLIP_DUR" ESCAPE="HTML">">
            <property name="mlt_service">stereo_mux</property>
            <property name="a_track">0</property>
            <property name="b_track"><TMPL_VAR NAME="CNT1" ESCAPE="HTML"></property>
            <property name="dst"><TMPL_VAR NAME="IDX" ESCAPE="HTML"></property>
        </transition></TMPL_LOOP>

    </tractor>


<TMPL_LOOP NAME="LIST">

<TMPL_IF MATTE>
    <producer id="producer<TMPL_VAR NAME="IDX">f">
        <property name="force_progressive">1</property>
<TMPL_LOOP PRODUCER_PROPERTIES>
        <property name="<TMPL_VAR NAME="NAME">"><TMPL_VAR NAME="VALUE" ESCAPE="HTML"></property></TMPL_LOOP>
    </producer>

<TMPL_IF LOOPED>
    <playlist id="producer<TMPL_VAR NAME="IDX">f_looped" force_loop="1">
        <entry producer="producer<TMPL_VAR NAME="IDX">f" <TMPL_IF LOOPED_IN>in="<TMPL_VAR NAME="LOOPED_IN">"</TMPL_IF> <TMPL_IF LOOPED_OUT>out="<TMPL_VAR NAME="LOOPED_OUT">"</TMPL_IF> />
    </playlist></TMPL_IF>

    <playlist id="playlist<TMPL_VAR NAME="IDX">f">
<TMPL_IF BLANK_IN>        <blank length="<TMPL_VAR NAME="BLANK_IN">"/></TMPL_IF>
<TMPL_LOOP ENTRIES>
        <entry producer="producer<TMPL_VAR NAME="IDX">f<TMPL_IF LOOPED>_looped</TMPL_IF>" in="<TMPL_VAR NAME="IN">" out="<TMPL_VAR NAME="OUT">"/></TMPL_LOOP>
<TMPL_IF BLANK_OUT2>        <blank length="<TMPL_VAR NAME="BLANK_OUT2">"/></TMPL_IF>
<TMPL_IF BLANK_OUT>        <blank length="<TMPL_VAR NAME="BLANK_OUT">"/></TMPL_IF>
    </playlist>


    <producer id="producer<TMPL_VAR NAME="IDX">k">
        <property name="force_progressive">1</property>
<TMPL_LOOP PRODUCER_MATTE_PROPERTIES>
        <property name="<TMPL_VAR NAME="NAME">"><TMPL_VAR NAME="VALUE" ESCAPE="HTML"></property></TMPL_LOOP>
    </producer>

<TMPL_IF LOOPED>
    <playlist id="producer<TMPL_VAR NAME="IDX">k_looped" force_loop="1">
        <entry producer="producer<TMPL_VAR NAME="IDX">k" <TMPL_IF LOOPED_IN>in="<TMPL_VAR NAME="LOOPED_IN">"</TMPL_IF> <TMPL_IF LOOPED_OUT>out="<TMPL_VAR NAME="LOOPED_OUT">"</TMPL_IF> />
    </playlist></TMPL_IF>

    <playlist id="playlist<TMPL_VAR NAME="IDX">k">
<TMPL_IF BLANK_IN>        <blank length="<TMPL_VAR NAME="BLANK_IN">"/></TMPL_IF>
<TMPL_LOOP ENTRIES>
        <entry producer="producer<TMPL_VAR NAME="IDX">k<TMPL_IF LOOPED>_looped</TMPL_IF>" in="<TMPL_VAR NAME="IN">" out="<TMPL_VAR NAME="OUT">"/></TMPL_LOOP>
<TMPL_IF BLANK_OUT2>        <blank length="<TMPL_VAR NAME="BLANK_OUT2">"/></TMPL_IF>
<TMPL_IF BLANK_OUT>        <blank length="<TMPL_VAR NAME="BLANK_OUT">"/></TMPL_IF>
    </playlist>


    <tractor id="playlist<TMPL_VAR NAME="IDX">">
        <multitrack>
            <track producer="playlist<TMPL_VAR NAME="IDX">f"/>
            <track producer="playlist<TMPL_VAR NAME="IDX">k"/>
        </multitrack>
        <transition in="<TMPL_VAR NAME="TRANSITION_IN">" out="<TMPL_VAR NAME="TRANSITION_OUT">">
            <property name="mlt_service">matte</property>
            <property name="a_track">0</property>
            <property name="b_track">1</property>
        </transition>
    </tractor>

<TMPL_ELSE>

    <producer id="producer<TMPL_VAR NAME="IDX">">
<TMPL_LOOP PRODUCER_PROPERTIES>
        <property name="<TMPL_VAR NAME="NAME">"><TMPL_VAR NAME="VALUE" ESCAPE="HTML"></property></TMPL_LOOP>
    </producer>

<TMPL_IF LOOPED>
    <playlist id="producer<TMPL_VAR NAME="IDX">_looped" force_loop="1">
        <entry producer="producer<TMPL_VAR NAME="IDX">" <TMPL_IF LOOPED_IN>in="<TMPL_VAR NAME="LOOPED_IN">"</TMPL_IF> <TMPL_IF LOOPED_OUT>out="<TMPL_VAR NAME="LOOPED_OUT">"</TMPL_IF> />
    </playlist></TMPL_IF>

    <playlist id="playlist<TMPL_VAR NAME="IDX">">
<TMPL_IF BLANK_IN>        <blank length="<TMPL_VAR NAME="BLANK_IN">"/></TMPL_IF>
<TMPL_LOOP ENTRIES>
        <entry producer="producer<TMPL_VAR NAME="IDX"><TMPL_IF LOOPED>_looped</TMPL_IF>" in="<TMPL_VAR NAME="IN">" out="<TMPL_VAR NAME="OUT">"/></TMPL_LOOP>
<TMPL_IF BLANK_OUT2>        <blank length="<TMPL_VAR NAME="BLANK_OUT2">"/></TMPL_IF>
<TMPL_IF BLANK_OUT>        <blank length="<TMPL_VAR NAME="BLANK_OUT">"/></TMPL_IF>
    </playlist>
</TMPL_IF>

    <tractor id="tractor<TMPL_VAR NAME="IDX">">
        <multitrack>
<TMPL_IF PREV_IDX_SET>
            <track producer="tractor<TMPL_VAR NAME="PREV_IDX">"/><TMPL_ELSE>
            <track producer="clip"/></TMPL_IF>
            <track producer="playlist<TMPL_VAR NAME="IDX">"/>
        </multitrack>
        <transition in="<TMPL_VAR NAME="TRANSITION_IN">" out="<TMPL_VAR NAME="TRANSITION_OUT">">
<TMPL_LOOP TRANSITION_PROPERTIES>
            <property name="<TMPL_VAR NAME="NAME">"><TMPL_VAR NAME="VALUE" ESCAPE="HTML"></property></TMPL_LOOP>
            <property name="mlt_service">composite</property>
            <property name="a_track">0</property>
            <property name="b_track">1</property>
            <property name="nofit">1</property>
            <property name="sliced_composite">1</property>
        </transition>
    </tractor>

</TMPL_LOOP>

</mlt>
<TMPL_LOOP SHM><!-- cache_shm_id="<TMPL_VAR NAME="cache_shm_id" ESCAPE="HTML">" value="<TMPL_VAR NAME="value" ESCAPE="HTML">" cache="<TMPL_VAR NAME="cache" ESCAPE="HTML">" -->
</TMPL_LOOP>
EOT

$Frontier::RPC2::scalars{'i8'} = 1;

sub get_lib_item
{
    my %item;
    my ($r, $srv);
    my ($lib, $name, $fps) = @_;

    print STDERR "# " . timestamp_now() . " get_lib_item(" . $name . ")\n";

    $srv =  Frontier::Client->new('url' => 'http://' . $lib . '/RPC2');

    print STDERR "# " . timestamp_now() . " TheCoreLib.LibBasenameItem($name)\n";
    $r = $srv->call("TheCoreLib.LibBasenameItem", $name);
    print STDERR "# " . timestamp_now() . " done\n";
    return undef unless $r;
    return undef unless !$r->{result};

    $item{'object'} = '' . encode( 'UTF-8', $r->{'object'});
    $item{'abs'} = '' . encode( 'UTF-8', $r->{'value'});
    $item{'cache'} = '' . encode( 'UTF-8', $r->{'cache'});

    push(@SHM, { 'cache_shm_id' => $r->{'cache_shm_id'}, 'value' => $item{'abs'}, 'cache' => $item{'cache'} })
        unless ($r->{'value'} =~ m/--/);

    print STDERR "# " . timestamp_now() . " TheCoreLib.LibMetaItem(" . $item{'object'} . ")\n";
    $r = $srv->call("TheCoreLib.LibMetaItem", $item{'object'});
    print STDERR "# " . timestamp_now() . " done\n";

    return undef unless $r;
    return undef unless !$r->{result};

    $r->{'item'}->{'duration_v'} = 86400.0
        if(defined($r->{'item'}->{'duration_v'}) && !int($r->{'item'}->{'duration_v'}));
    $item{'duration'} = int($r->{'item'}->{'duration_v'} * $fps->[0] / $fps->[1]);

    $item{'display_aspect_ratio'} = $r->{'item'}->{'display_aspect_ratio'};
    $item{'sample_aspect_ratio'} = $r->{'item'}->{'sample_aspect_ratio'};

#warn "name=[$name]\n";
#warn "r->{'item'}->{'duration'}=" . $r->{'item'}->{'duration'} . "\n";
#warn "r->{'item'}->{'duration_v'}=" . $r->{'item'}->{'duration_v'} . "\n";
#warn "fps->[0]=[" . $fps->[0] . "], fps->[1]=[" . $fps->[1] . "]\n";
#warn "item{duration}=" . $item{'duration'} . "\n";

    return \%item;
};

sub load_asset
{
    my $r = 0;
    my ($filename) = @_;
    my %ATTRS;

    if(!open(INFILE, "<", $filename))
    {
        warn "load_asset: Failed to read file $filename\n";
        return undef;
    };

    while (<INFILE>)
    {
        chomp;

        $_ =~ s/\r//g;

        next unless ($_ =~ m/^([^:^ ^#]+)[: ]*(.*)/);

        $r++;

        if($1 eq "key")
        {
            my ($k, $v) = ($1, $2);

            if($v =~ m/^([^ ]+)[ ]*([^ ]+)$/)
            {
                my $keys;
                my @KEYS = ();

                if(defined($ATTRS{$k}))
                {
                    $keys = $ATTRS{$k};
                }
                else
                {
                    $ATTRS{$k} = \@KEYS;
                    $keys = \@KEYS;
                };

                push(@{$keys}, {OFFSET => $1, GEOM => $2});
            }
            else
            {
                warn sprintf("Unrecognized key [%s]\n", $v);
            };

        }
        # parse oxtel keyframes
        elsif($1 eq "\@")
        {
            my ($k, $v) = ($1, $2);

            if($v =~ m/^([-]?[0-9]+) (.*)$/)
            {
                my $keys;
                my @KEYS = ();

                if(defined($ATTRS{$k}))
                {
                    $keys = $ATTRS{$k};
                }
                else
                {
                    $ATTRS{$k} = \@KEYS;
                    $keys = \@KEYS;
                };

                push(@{$keys}, {OFFSET => $1, CMD => $2});
            }
            else
            {
                warn sprintf("Unrecognized keyframes [%s]\n", $v);
            };

        }
        elsif($1 eq "import" || $1 eq "offset")
        {
            my @list = split(' ', $2);
            $ATTRS{$1} = \@list;
        }
        else
        {
            $ATTRS{$1} = $2;
        };
    };

    close (INFILE);

    return undef if(0 == $r);

    return \%ATTRS;
};

sub readdir_rec
{
    my @ITEMS;
    my ($conf, $item) = @_;
    my $path = $conf->{'assets_path'} . '/' . $item;

    if(opendir(D, $path))
    {
        my @list = sort { $a <=> $b } readdir(D);
        closedir(D);

        foreach my $i (@list)
        {
            my $p = $path . '/' . $i;
            my $n = $item . '/' . $i;

            next if (($i eq '.') || ($i eq '..'));

            push(@ITEMS, (-d $p)?readdir_rec($conf, $n):$n);
        };
    };

    return @ITEMS;
};

sub expand_asset_group
{
    my @ITEMS = ();
    my ($conf, $item, $assets_counter) = @_;

    $assets_counter->{$item}++ if(defined($assets_counter->{$item}));
    $assets_counter->{$item} = 0 unless(defined($assets_counter->{$item}));

    my $filename = $conf->{'assets_path'} . '/' . $item;

    if(-d $filename)
    {
        my @I = readdir_rec($conf, $item);
#print "I=" . dump(\@I) . "\n";

        my @S = map
        {
            {
                'name'      => $item,
                'file'      => $_,
                'idx'       => $assets_counter->{$item},
            },
        } @I;
#print "S=" . dump(\@S) . "\n";

        push(@ITEMS, @S);
    }
    else
    {
        unless ('@' eq substr($item, 0, 1))
        {
            push
            (
                @ITEMS,
                {
                    'name'      => $item,
                    'file'      => $item,
                    'idx'       => $assets_counter->{$item},
                }
            );
        }
        else
        {
            if(!open(INFILE, "<", $filename))
            {
                warn "expand_asset_group: Failed to read file $filename\n";
            }
            else
            {
                while(<INFILE>)
                {
                    chomp;
                    push
                    (
                        @ITEMS,
                        {
                            'file'      => $_,
                            'name'      => $item,
                            'idx'       => $assets_counter->{$item},
                        }
                    );
                };

                close INFILE;
            };
        };
    };

    return @ITEMS;
};

sub oxtel_template
{
    my ($a) = @_;
    my @OXT = ();

    print STDERR "# " . timestamp_now() . " oxtel: a=" . dump($a) . "\n";

    $a->{'preroll'} = 0
        unless defined($a->{'preroll'});

    push
    (
        @OXT,
        map
        {
            {
                POS => $_->{OFFSET} + $a->{in} - $a->{preroll},
                SEQ => $_->{CMD}
            }
        } @{$a->{"\@"}}
    );

    return defined($OXT[0]) ? \@OXT : undef;
};

sub build_template
{
    my ($conf, $clip_name, $clip_in, $clip_out, $assets_complex, $meta) = @_;

    my ($resource);

    # ask for clip first
    my $clip_data;
    unless($clip_name =~ /^\#(.*)/)
    {
        $clip_data = get_lib_item($conf->{'lib'}, $clip_name, $conf->{'fps'});
        unless($clip_data)
        {
            warn "failed to get_lib_item($clip_name)";
            return undef;
        };
        $resource = $clip_data->{'abs'};
    }
    else
    {
        $resource = $1;
    };

    # def clip in
    $clip_in = 0 unless defined($clip_in);

    # def clip out
    $clip_out = ($clip_data->{'duration'} - 1)
        if(!defined($clip_out) && defined($clip_data));
    $clip_out = ($clip_data->{'duration'} - 1)
        if(defined($clip_data) && defined($clip_data->{'duration'}) && $clip_out >= $clip_data->{'duration'});

    # calc dur of material
    my $clip_dur = $clip_out - $clip_in + 1;
#warn "clip_in=[$clip_in], clip_out=[$clip_out], clip_dur=[$clip_dur]\n";
    # open templats
    my $tmpl_main = HTML::Template->new_scalar_ref(\$tmpl_main_txt);
    $tmpl_main->param
    (
        resource        =>      $resource,
        CLIP_OUT        =>      $clip_out,
        CLIP_IN         =>      $clip_in,
    );

    # setup clip attrs
    # force aspect
    if(defined($meta->{'current.clip.ar'}))
    {
        if('*' eq $meta->{'current.clip.ar'})
        {
            my $display_aspect_ratio = $conf->{'display_aspect'}->[0] . ':' . $conf->{'display_aspect'}->[1];

            if($clip_data->{'display_aspect_ratio'} eq $display_aspect_ratio)
            {
                my $force_aspect_ratio = '@' . join('/', split(':', $clip_data->{'sample_aspect_ratio'}));

                $tmpl_main->param(force_aspect_ratio => $force_aspect_ratio);
            }
            else
            {
#                $tmpl_main->param(force_aspect_ratio => $meta->{'TheCoreIO.mode_par'});
                $tmpl_main->param(force_aspect_ratio => "\@64/45");
            };
        }
        elsif('PAL43' eq $meta->{'current.clip.ar'})
        {
            $tmpl_main->param(force_aspect_ratio => "\@16/15");
        }
        elsif('PAL169' eq $meta->{'current.clip.ar'})
        {
            $tmpl_main->param(force_aspect_ratio => "\@64/45");
        }
        elsif('U' eq $meta->{'current.clip.ar'})
        {
            # this is a special case for handling
            # letterbox of SD picuture to HD upscaling
            $tmpl_main->param(undo_letterbox => "1");
            $tmpl_main->param(force_aspect_ratio => "\@64/45");
        };
    };
    # progressive
    $tmpl_main->param(force_progressive => "yes")
        if(defined($meta->{'TheCoreIO.progressive'}) && '0' == $meta->{'TheCoreIO.progressive'});
    # audio amp
    $tmpl_main->param(audioamp_volume => $meta->{'current.clip.audio_amp'})
        if(defined($meta->{'current.clip.audio_amp'}));
    # audio mapping
    if(defined($meta->{'current.clip.audio_map'}))
    {
        $tmpl_main->param(audiomap_enable => 1);
        my @map = split('', $meta->{'current.clip.audio_map'});
        for(my $i = 0; $i < 8; $i++)
        {
            my $c = 0;
            if(defined($map[$i]))
            {
                # get char code
                $c = ord($map[$i]);

                # normalize
                if($c <= ord('9') && $c >= ord('0'))
                {
                    $c -= ord('0');
                }
                elsif($c <= ord('Z') && $c >= ord('A'))
                {
                    $c -= ord('A');
                    $c += 10;
                }
                else
                {
                    $c = 0;
                };
            };
            $tmpl_main->param(('audiomap_' . $i) => $c);
        };
    };

    # commercials handler filter parameters
    {
        $tmpl_main->param(commercials_handler_enable                    => 1);
        $tmpl_main->param(commercials_handler_curr_ply_duration         => $meta->{'curr.ply.duration'});
        $tmpl_main->param(commercials_handler_next_ply_duration         => $meta->{'next.ply.duration'});
        $tmpl_main->param(commercials_handler_curr_ply_type             => $meta->{'curr.ply.type'});
        $tmpl_main->param(commercials_handler_next_ply_type             => $meta->{'next.ply.type'});
        $tmpl_main->param(commercials_handler_curr_ply_start_time       => $meta->{'curr.ply.start_time'});
        $tmpl_main->param(commercials_handler_next_ply_start_time       => $meta->{'next.ply.start_time'});
        $tmpl_main->param(commercials_handler_nextC0_ply_start_time     => $meta->{'nextC0.ply.start_time'});
    };

    my @STEREOS = ();
    for(my ($idx, $cnt0, $cnt1) = (1, 0, 1); $idx <= 4; $idx++)
    {
        if(defined($meta->{'current.meta.STEREO_' . $idx}))
        {
            my $stereo_data = get_lib_item($conf->{'lib'}, $meta->{'current.meta.STEREO_' . $idx}, $conf->{'fps'});
            print STDERR "# " . timestamp_now() . " stereo  $idx  data=" . dump($stereo_data) . "\n";

            next if !$stereo_data;

            push(@STEREOS, {
                res             => $stereo_data->{'abs'},
                idx             => $idx,
                cnt0            => $cnt0,
                cnt1            => $cnt1,
                CLIP_OUT        => $clip_out,
                CLIP_IN         => $clip_in,
                CLIP_DUR        => $clip_out - $clip_in,
            });

            $cnt0++;
            $cnt1++;
        };
    };
    $tmpl_main->param(STEREOS => \@STEREOS)
        if defined $STEREOS[0];

    # DTMF sequence filter enable
    if(defined($meta->{'current.meta.DTMF'}))
    {
        my @map = split('@', $meta->{'current.meta.DTMF'});
        if(defined($map[0]) && defined($map[1]))
        {
            $tmpl_main->param(dtmf_filter_enable => 1);
            $tmpl_main->param(dtmf_filter_offset => $map[1]);
            $tmpl_main->param(dtmf_filter_seq => $map[0]);
        };
    };

    # OXTEL sequence filter
    my @oxtel_cmd_list = ();
    if(defined($meta->{'current.meta.OXTEL'}))
    {
        my $oxt = undef;
        my @oxts = ();
        my $src = shift;
        my @dst = split(':', $meta->{'current.meta.OXTEL'});

        foreach my $item (@dst)
        {
            if($item =~ /[@](\d+)/m)
            {
                $oxt = { POS => $1, SEQ => []};
                push(@oxts, $oxt);
            }
            else
            {
                push(@{$oxt->{'SEQ'}}, $item . ':') if(defined($oxt));
            };
        };

        @oxts = map
        {
            {
                POS => $_->{POS},
                SEQ => join('', @{$_->{SEQ}})
            }
        } @oxts;

        push(@oxtel_cmd_list, @oxts);
    }

    # expand assets groups
    my %assets_counter;
    my @assets_mapped;
    foreach my $a (@{$assets_complex})
    {
        my @d = expand_asset_group($conf, $a, \%assets_counter);
        push(@assets_mapped, @d);
    };

    my $asset_idx = 0;
    my @ASSETS = ();
    foreach my $a_obj (@assets_mapped)
    {
#warn "";
        my $ASSET =
        {
            IDX                         => $asset_idx,
            PRODUCER_PROPERTIES         => [],
            PRODUCER_MATTE_PROPERTIES   => [],
            ENTRIES                     => [],
            TRANSITION_PROPERTIES       => [],
        };

        if ($asset_idx > 0)
        {
            $ASSET->{'PREV_IDX'} = ($asset_idx - 1);
            $ASSET->{'PREV_IDX_SET'} = 1;
        };

        my $a = load_asset($conf->{'assets_path'} . '/' . $a_obj->{'file'});
        next unless defined($a);

#warn "a_obj->{'file'}=" . $a_obj->{'file'} . "\n";

        # replace template vars
        for my $f (qw(text resource resource_matte fgcolour aux1 aux2))
        {
            # ignore not used fields
            next unless defined($a->{$f});

            foreach my $w (keys %{$meta})
            {
                my $t = $w;

                # check for modifier
                if($w =~ m/([^@]+)@([^#]+)$/)
                {
                    next unless($2 eq $a_obj->{'name'});
                    $t = $1;
                }
                elsif($w =~ m/([^@]+)@([^#]+)#(\d+)/)
                {
                    next unless(($2 eq $a_obj->{'name'}) && ($3 == $a_obj->{'idx'}));
                    $t = $1;
                };

                my $s = sprintf("<%s/>", $t);
                my $r = $meta->{$w};

                $a->{$f} =~ s/$s/$r/g;
            };

            # cleanup fields
            $a->{$f} =~ s/<([a-zA-Z0-9_.])+\/>//g;
        };

        # in
        $a->{'in'} = 0
            unless defined($a->{'in'});
        $a->{'in'} = $clip_dur + $a->{'in'}
            if($a->{'in'} < 0);
        $a->{'in'} = 0
            if($a->{'in'} >= $clip_dur || $a->{'in'} < 0);

        # other
        ($a->{'dur'} , $a->{'out'}) = ($clip_dur, $clip_out)
            unless(defined($a->{'out'}) || defined($a->{'dur'}));

#warn("[0] a->{'in'}=[" . $a->{'in'} . "] a->{'out'}=[" . $a->{'out'} . "] a->{'dur'}=[" . $a->{'dur'} . "]");

        # out
        $a->{'out'} = $a->{'in'} + $a->{'dur'} - 1
            if(!defined($a->{'out'}));
        $a->{'out'} = $clip_dur + $a->{'out'} - 1
            if(defined($a->{'out'}) && $a->{'out'} < 0);
        $a->{'out'} = $clip_dur - 1
            if($a->{'out'} >= $clip_dur);
        $a->{'out'} = 0
            if($a->{'out'} < 0);

#warn("[1] a->{'in'}=[" . $a->{'in'} . "] a->{'out'}=[" . $a->{'out'} . "] a->{'dur'}=[" . $a->{'dur'} . "]");

        # override in/out from metadata
        my ($p1_in, $p1_out, $p2_in, $p2_out) =
        (
            "current.meta.in\@" . $a_obj->{'name'} . "#" . $a_obj->{'idx'},
            "current.meta.out\@" . $a_obj->{'name'} . "#" . $a_obj->{'idx'},
            "current.meta.in\@" . $a_obj->{'name'},
            "current.meta.out\@" . $a_obj->{'name'}
        );
#warn("[U0] p1_in=[$p1_in], p1_out=[$p1_out], p2_in=[$p2_in], p2_out=[$p2_out]");
#warn("[U0] meta=" . dump($meta));
        if(defined($meta->{$p1_in}) && defined($meta->{$p1_out}))
        {
            $a->{'in'} = $meta->{$p1_in};
            $a->{'out'} = $meta->{$p1_out};
#warn("[U1] meta->{p1_in}=[" . $meta->{$p1_in} . "], meta->{p1_out}=[".$meta->{$p1_out}."]\n");
        }
        elsif(defined($meta->{$p2_in}) && defined($meta->{$p2_out}))
        {
            $a->{'in'} = $meta->{$p2_in};
            $a->{'out'} = $meta->{$p2_out};
#warn("[U2] meta->{p2_in}=[" . $meta->{$p2_in} . "], meta->{p2_out}=[" . $meta->{$p2_out} . "]\n");
        }
        elsif(defined($meta->{$p1_in}) && !defined($meta->{$p1_out}))
        {
            my $o = $meta->{$p1_in};

            $o = ($clip_dur + $o - 1) if($o < 0);

            $a->{'in'} += $o;
            $a->{'out'} += $o;
#warn("[U3] meta->{p1_in}=[" . $meta->{$p1_in} . "]\n");
        }

        # check for out trim
        $a->{'out'} = $clip_dur - 1
            if($a->{'out'} >= $clip_dur);

#warn("[2] a->{'in'}=[" . $a->{'in'} . "] a->{'out'}=[" . $a->{'out'} . "] a->{'dur'}=[" . $a->{'dur'} . "]");

        # dur
        $a->{'dur'} = $a->{'out'} - $a->{'in'} + 1;
#warn("[3] a->{'in'}=[" . $a->{'in'} . "] a->{'out'}=[" . $a->{'out'} . "] a->{'dur'}=[" . $a->{'dur'} . "]");

        # oxtel commands template
        if(defined($a->{'oxtel'}))
        {
            my $r;

            $r = oxtel_template($a);

            push(@oxtel_cmd_list, @{$r}) if defined($r);

            next;
        };

        # type processing
        if(defined($a->{'text'}))
        {
            $a->{'text_target'} = 'text' unless defined($a->{'text_target'});
            push(@{$ASSET->{'PRODUCER_PROPERTIES'}}, {NAME => 'text', VALUE => $a->{'text'}})
                if($a->{'text_target'} eq 'text');
            push(@{$ASSET->{'PRODUCER_PROPERTIES'}}, {NAME => 'markup', VALUE => $a->{'text'}})
                if($a->{'text_target'} eq 'markup');
            push(@{$ASSET->{'PRODUCER_PROPERTIES'}}, {NAME => 'resource', VALUE => $a->{'text'}})
                if($a->{'text_target'} eq 'file');

            foreach my $at
            (
                'size', 'align', 'pad',
                'bgcolour', 'fgcolour', 'olcolour', 'rotate',
                'width_fit', 'width_crop', 'wrap_width', 'wrap_type',
                'weight', 'stretch',
                'line_spacing',
                'family', 'style', 'outline'
            )
            {
                next unless defined($a->{$at});
                push
                (
                    @{$ASSET->{'PRODUCER_PROPERTIES'}},
                    {
                        NAME    => $at,
                        VALUE   => $a->{$at}
                    }
                );
            };
            push
            (
                @{$ASSET->{'PRODUCER_PROPERTIES'}},
#                {
#                    NAME    => 'force_aspect_ratio',
#                    VALUE   => $meta->{'TheCoreIO.mode_par'},
#                },
                {
                    NAME    => 'mlt_service',
                    VALUE   => 'pango'
                }
            );

            # very simple in/out
            push
            (
                @{$ASSET->{'ENTRIES'}},
                {
                    IDX     => $asset_idx,
                    IN      => 0,
                    OUT     => $a->{'dur'} - 1,
                }
            );

            # blanks
            $ASSET->{BLANK_IN} = $a->{'in'} if($a->{'in'} > 0);
            $ASSET->{BLANK_OUT} = ($clip_dur - $a->{'out'} - 1) if(($clip_dur - $a->{'out'} - 1) > 0);
        }
        elsif(defined($a->{'resource'}))
        {
            # resolve resorce
            my $asset_data = get_lib_item($conf->{'lib'}, $a->{'resource'}, $conf->{'fps'});
#warn "res=[" . $a->{'resource'} . "\n";

            $a->{'resource_behave'} = 0
                unless defined($a->{'resource_behave'});

            unless($asset_data)
            {
                warn "get_lib_item(" . $a->{'resource'} . ") is false, will try workaround\n";
                if($a->{'resource'} =~ /^!(.*)/)
                {
                    $asset_data = { 'abs' => $1 };
                    push(@{$ASSET->{'PRODUCER_PROPERTIES'}}, {NAME => 'live', VALUE => '1' });
                }
                elsif($a->{'resource'} =~ /^\#(.*)/)
                {
                    $asset_data = { 'abs' => $1 };
                };
            };

            # audio_index = -1
            push(@{$ASSET->{'PRODUCER_PROPERTIES'}}, { NAME    => 'audio_index', VALUE   => '-1' });
            push(@{$ASSET->{'PRODUCER_MATTE_PROPERTIES'}}, { NAME    => 'audio_index', VALUE   => '-1' })
                if(defined($a->{'resource_matte'}));

            foreach my $at ('force_aspect_ratio', 'audio_index')
            {
                next unless defined($a->{$at});
                push
                (
                    @{$ASSET->{'PRODUCER_PROPERTIES'}},
                    {
                        NAME    => $at,
                        VALUE   => $a->{$at}
                    }
                );
                push
                (
                    @{$ASSET->{'PRODUCER_MATTE_PROPERTIES'}},
                    {
                        NAME    => $at,
                        VALUE   => $a->{$at}
                    }
                ) if(defined($a->{'resource_matte'}));
            };

            push
            (
                @{$ASSET->{'PRODUCER_PROPERTIES'}},
                {
                    NAME    => 'force_aspect_ratio',
                    VALUE   => $meta->{'TheCoreIO.mode_par'},
                },
                {
                    NAME    => 'resource',
                    VALUE   => $asset_data->{'abs'}
                }
            );

            push
            (
                @{$ASSET->{'PRODUCER_PROPERTIES'}},
                {
                    NAME    => 'mlt_service',
                    VALUE   => 'hold'
                },
                {
                    NAME    => 'frame',
                    VALUE   => $a->{'resource_in'}
                }
            ) if('1' eq $a->{'resource_behave'});

            $ASSET->{'LOOPED'} = 1
                if('2' eq $a->{'resource_behave'});

            if(defined($a->{'resource_matte'}))
            {
                my $asset_matte_data = get_lib_item($conf->{'lib'}, $a->{'resource_matte'}, $conf->{'fps'});

                $ASSET->{'MATTE'} = 1;

                push
                (
                    @{$ASSET->{'PRODUCER_MATTE_PROPERTIES'}},
                    {
                        NAME    => 'resource',
                        VALUE   => $asset_matte_data->{'abs'}
                    },
                    {
                        NAME    => 'force_aspect_ratio',
                        VALUE   => $meta->{'TheCoreIO.mode_par'},
                    },
                ) if defined($asset_matte_data->{'abs'});

                push
                (
                    @{$ASSET->{'PRODUCER_MATTE_PROPERTIES'}},
                    {
                        NAME    => 'mlt_service',
                        VALUE   => 'hold'
                    },
                    {
                        NAME    => 'frame',
                        VALUE   => $a->{'resource_in'}
                    }
                ) if('1' eq $a->{'resource_behave'});
            };

            # blanks-in-out

            # single playback
            if('0' eq $a->{'resource_behave'})
            {
                my ($r_in, $r_out);

                $r_in = defined($a->{'resource_in'})?$a->{'resource_in'}:0;
                $r_out = $r_in + $a->{'dur'} - 1;
                $r_out = $a->{'resource_out'}
                    if(defined($a->{'resource_out'}) && $r_out > $a->{'resource_out'});

                # playback entry
                push
                (
                    @{$ASSET->{'ENTRIES'}},
                    {
                        IDX     => $asset_idx,
                        IN      => $r_in,
                        OUT     => $r_out,
                    }
                );

                $ASSET->{BLANK_OUT2} = ($a->{'dur'} - ($r_out - $r_in + 1))
                    if($a->{'dur'} > ($r_out - $r_in + 1));

                $ASSET->{BLANK_IN} = $a->{'in'}
                    if($a->{'in'} > 0);

                $ASSET->{BLANK_OUT} = ($clip_dur - $a->{'out'} - 1)
                    if(($clip_dur - $a->{'out'} - 1) > 0);
            }
            # still playback
            elsif('1' eq $a->{'resource_behave'})
            {
                # playback entry
                push
                (
                    @{$ASSET->{'ENTRIES'}},
                    {
                        IDX     => $asset_idx,
                        IN      => 0,
                        OUT     => $a->{'dur'} - 1,
                    }
                );
                # blanks
                $ASSET->{BLANK_IN} = $a->{'in'} if($a->{'in'} > 0);
                $ASSET->{BLANK_OUT} = ($clip_dur - $a->{'out'} - 1) if(($clip_dur - $a->{'out'} - 1) > 0);
            }
            # loop playback
            elsif('2' eq $a->{'resource_behave'})
            {
##                warn "asset_data->duration=" . $asset_data->{'duration'};

                if(defined($a->{'resource_in'}) || defined($a->{'resource_out'}))
                {
                    my $res_dur = $asset_data->{'duration'};

                    my $res_in = defined($a->{'resource_in'}) ? ($a->{'resource_in'}) : 0;
                    $res_in = 0 if ($res_dur <= $res_in);

                    my $res_out = defined($a->{'resource_out'}) ? ($a->{'resource_out'}) : ($res_dur - 1);
                    $res_out = ($res_dur - 1) if ($res_dur <= $res_out);

                    my $head_dur = $res_in;
                    my $tail_dur = $res_dur - $res_out;
                    my $area_dur = $a->{'out'} - $a->{'in'} + 1;

                    # head
                    if($head_dur > 0 && $head_dur <= $area_dur)
                    {
                        push
                        (
                            @{$ASSET->{'ENTRIES'}},
                            {
                                IDX     => $asset_idx,
                                IN      => 0,
                                OUT     => $res_in - 1,
                            }
                        );

                        $area_dur -= $head_dur;
                    };

                    # looppo
                    if($area_dur > 0)
                    {
                        my $loop_dur = ($area_dur > $tail_dur) ? ($area_dur - $tail_dur) : $area_dur;

                        push
                        (
                            @{$ASSET->{'ENTRIES'}},
                            {
                                IDX     => $asset_idx,
                                IN      => 0,
                                OUT     => $loop_dur - 1,
                                LOOPED  => 1,
                            }
                        );

                        $ASSET->{'LOOPED_IN'} = $res_in;
                        $ASSET->{'LOOPED_OUT'} = $res_out;

                        $area_dur -= $loop_dur;
                    };

                    # tail
                    if($tail_dur > 0 && $tail_dur <= $area_dur)
                    {
                        push
                        (
                            @{$ASSET->{'ENTRIES'}},
                            {
                                IDX     => $asset_idx,
                                IN      => $res_out + 1,
                                OUT     => $res_dur - 1,
                            }
                        );

                        $area_dur -= $head_dur;
                    };

                    # blanks
                    $ASSET->{BLANK_IN} = $a->{'in'} if($a->{'in'} > 0);
                    $ASSET->{BLANK_OUT} = ($clip_dur - $a->{'out'} - 1) if(($clip_dur - $a->{'out'} - 1) > 0);
                }
                else
                {
                    # playback entry
                    push
                    (
                        @{$ASSET->{'ENTRIES'}},
                        {
                            IDX     => $asset_idx,
                            IN      => 0,
                            OUT     => $a->{'dur'} - 1,
                            LOOPED  => 1,
                        }
                    );

                    # blanks
                    $ASSET->{BLANK_IN} = $a->{'in'} if($a->{'in'} > 0);
                    $ASSET->{BLANK_OUT} = ($clip_dur - $a->{'out'} - 1) if(($clip_dur - $a->{'out'} - 1) > 0);
                };
            }
            # unreal condition
            else
            {
                warn "Unreal condition";
            };
        }
        else
        {
            print "not used = " . dump($a) . "\n";
        };


        # tractor
        foreach my $at ('valign', 'halign', 'progressive', 'distort', 'crop', 'titles')
        {
            next unless defined($a->{$at});
            push
            (
                @{$ASSET->{'TRANSITION_PROPERTIES'}},
                {
                    NAME    => $at,
                    VALUE   => $a->{$at}
                }
            );
        };
        foreach my $k (@{$a->{'key'}})
        {
            my $kp;

            # resolve key
            if($k->{OFFSET} eq 'end')
            {
                $kp = $a->{'dur'} - 1;
            }
            elsif($k->{OFFSET} eq 'start')
            {
                $kp = 0;
            }
            elsif($k->{OFFSET} < 0)
            {
                $kp = $a->{'dur'} + $k->{OFFSET};
            }
            else
            {
                $kp = $k->{OFFSET};
            };

            # clip
            $kp = 0 if($kp < 0);
            $kp = ($a->{'dur'} - 1) if($kp >= $a->{'dur'});

            # store
            push
            (
                @{$ASSET->{'TRANSITION_PROPERTIES'}},
                {
                    NAME    => "key[$kp]",
                    VALUE   => $k->{GEOM},
                }
            );
        };
        $ASSET->{'TRANSITION_IN'} = $a->{'in'};
        $ASSET->{'TRANSITION_OUT'} = $a->{'out'};
        push(@ASSETS, $ASSET);
        $asset_idx++;
    };

    $tmpl_main->param
    (
        LIST          =>      \@ASSETS,
    );

    $tmpl_main->param
    (
        SHM             =>      \@SHM,
    );

    $tmpl_main->param(oxtel_cmd_list => \@oxtel_cmd_list)
        if defined($oxtel_cmd_list[0]);

    return $tmpl_main->output();
};

my $conf =
{
    'lib'               => 'localhost:8000',
    'assets_path'       => '/usr/local/src/2014-03-25/assets',
    'fps'               => [25, 1],
};

my $runtime =
{
    'assets'    =>      [],
    'metas'     =>      {},
};

print STDERR "# " . timestamp_now() . " starting app\n";

print $0 . " \\\n";
foreach my $a (@ARGV)
{
    print "    " . $a . " \\\n";
};

GetOptions
(
    'config-lib=s'      =>      \$conf->{'lib'},
    'config-path=s'     =>      \$conf->{'assets_path'},
    'config-fps-num=i'  =>      \$conf->{'fps'}->[0],
    'config-fps-den=i'  =>      \$conf->{'fps'}->[1],
    'clip-name=s'       =>      \$runtime->{'clip'},
    'clip-in=i'         =>      \$runtime->{'in'},
    'clip-out=i'        =>      \$runtime->{'out'},
    'asset=s'           =>      sub{push @{$runtime->{'assets'}}, $_[1];},
    'meta=s%'           =>      sub{ $runtime->{'metas'}->{$_[1]} = $_[2];},
    'output-file=s'     =>      \$runtime->{'output'},
);

# update for fps accoring to mode
if(defined($runtime->{'metas'}->{'TheCoreIO.mode_name'}))
{
    my $m = $runtime->{'metas'}->{'TheCoreIO.mode_name'};
    if(($m eq 'atsc_720p_50') || ($m eq 'uhd_2160p_50'))
    {
        $conf->{'fps'}->[0] = 50;               # frame_rate_num=25
        $conf->{'fps'}->[1] = 1;                # frame_rate_den=1

        $conf->{'sample_aspect'}->[0] = 1;      # sample_aspect_num=1
        $conf->{'sample_aspect'}->[1] = 1;      # sample_aspect_den=1
        $conf->{'display_aspect'}->[0] = 16;    # display_aspect_num=16
        $conf->{'display_aspect'}->[1] = 9;     # display_aspect_den=9
    }
    elsif($m eq 'atsc_1080i_50')
    {
        $conf->{'sample_aspect'}->[0] = 1;      # sample_aspect_num=1
        $conf->{'sample_aspect'}->[1] = 1;      # sample_aspect_den=1
        $conf->{'display_aspect'}->[0] = 16;    # display_aspect_num=16
        $conf->{'display_aspect'}->[1] = 9;     # display_aspect_den=9
    }
    elsif($m eq 'atsc_1080i_5994')
    {
        $conf->{'fps'}->[0] = 30000;            # frame_rate_num=30000
        $conf->{'fps'}->[1] = 1001;             # frame_rate_den=1001
        $conf->{'sample_aspect'}->[0] = 1;      # sample_aspect_num=1
        $conf->{'sample_aspect'}->[1] = 1;      # sample_aspect_den=1
        $conf->{'display_aspect'}->[0] = 16;    # display_aspect_num=16
        $conf->{'display_aspect'}->[1] = 9;     # display_aspect_den=9
    }
    elsif($m eq 'dv_pal_wide')
    {
        $conf->{'sample_aspect'}->[0] = 64;     # sample_aspect_num=1
        $conf->{'sample_aspect'}->[1] = 45;     # sample_aspect_den=1
        $conf->{'display_aspect'}->[0] = 16;    # display_aspect_num=16
        $conf->{'display_aspect'}->[1] = 9;     # display_aspect_den=9
    }

};

print STDERR "# " . timestamp_now() . " building\n";

my $r = build_template
(
    $conf,
    $runtime->{'clip'}, $runtime->{'in'}, $runtime->{'out'},
    $runtime->{'assets'},
    $runtime->{'metas'}
);

exit 1 unless defined($r);

print STDERR "# " . timestamp_now() . " saving\n";

if(!open(OUTFILE, ">", $runtime->{'output'}))
{
    warn "failed to create output file " . $runtime->{'output'} . "\n";
    exit 1;
};

print OUTFILE $r;

close OUTFILE;

print STDERR "# " . timestamp_now() . " bye\n";

#print dump($conf);
#print dump($runtime);
