Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / cinelerra / filescene.C
diff --git a/cinelerra-5.1/cinelerra/filescene.C b/cinelerra-5.1/cinelerra/filescene.C
new file mode 100644 (file)
index 0000000..548e679
--- /dev/null
@@ -0,0 +1,1919 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2011 Adam Williams <broadcast at earthling dot net>
+ * 
+ * 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
+ * 
+ */
+
+
+
+#include "asset.h"
+#include "bcsignals.h"
+#include "clip.h"
+#include "file.h"
+#include "filescene.h"
+#include "filesystem.h"
+#include "libmjpeg.h"
+#include "scenegraph.h"
+
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+
+extern "C"
+{
+#include <uuid.h>
+}
+
+
+// Paths relative to the exe path
+#define FESTIVAL_PATH "/festival"
+#define FESTIVAL_LIB_PATH "/lib/"
+#define ASSET_PATH "/models/"
+#define FREAD_SIZE 0x10000
+#define WAVHEADER 44
+#define FESTIVAL_SAMPLERATE 16000
+#define PAUSE_SAMPLES FESTIVAL_SAMPLERATE
+// Amount to truncate if ...
+#define ADVANCE_SAMPLES FESTIVAL_SAMPLERATE
+
+// Maximum characters in a line of dialog is limited by festival
+#define MAX_CHARS 65536
+
+#define STRIP_LINE(string) \
+/* Strip linefeeds */ \
+       while(len > 0 && (string[len - 1] == '\n' || \
+               string[len - 1] == ' ')) \
+       { \
+               string[len - 1] = 0; \
+               len--; \
+       } \
+ \
+/* Strip comments */ \
+       for(i = 0; i < len; i++) \
+       { \
+               if(string[i] == '#') \
+               { \
+                       string[i] = 0; \
+                       i = len; \
+               } \
+       }
+
+
+
+
+#define STRING_PARAMETER(title, need_char, dst) \
+if(!strncmp(command, title, strlen(title))) \
+{ \
+/* advance to argument */ \
+       i += strlen(title); \
+       while(string[i] != 0 && string[i] == ' ') \
+               i++; \
+ \
+       if(current_char || !need_char) \
+       { \
+/* printf("STRING_PARAMETER %s %s %p\n", title, string + i, dst); */ \
+               strcpy(dst, string + i); \
+       } \
+       else \
+       { \
+               printf("FileScene::read_script %d Line %d: %s but no current character\n",  \
+                       __LINE__, \
+                       current_line, \
+                       title); \
+       } \
+ \
+       i = len; \
+}
+
+static int read_parameter(char *string,
+       int *i,
+       const char *title, 
+       char *dst_string, 
+       float *dst_float0, 
+       float *dst_float1, 
+       float *dst_float2)
+{
+       char *command = string + *i;
+       char *arg_text[3];
+
+       if(!strncmp(command, title, strlen(title)))
+       {
+               *i += strlen(title);
+
+               for(int j = 0; j < 4; j++)
+               {
+/* skip to start of argument */
+                       while(string[*i] != 0 && string[*i] == ' ')
+                               (*i)++;
+
+                       if(string[*i] != 0)
+                       {
+                               arg_text[j] = string + *i;
+                               while(string[*i] != 0 && string[*i] != ' ')
+                                       (*i)++;
+                       }
+                       else
+                       {
+                               arg_text[j] = 0;
+                       }
+               }
+
+// printf("read_parameter %d %s %s %s %s\n", 
+// __LINE__, 
+// title, 
+// arg_text[0], 
+// arg_text[1], 
+// arg_text[2]);
+
+               if(arg_text[0])
+               {
+                       if(dst_string)
+                       {
+                               char *ptr1 = dst_string;
+                               char *ptr2 = arg_text[0];
+                               while(*ptr2 != 0 && *ptr2 != ' ')
+                                       *ptr1++ = *ptr2++;
+                               *ptr1 = 0;
+                       }
+                       else
+                       if(dst_float0)
+                       {
+                               *dst_float0 = atof(arg_text[0]);
+                       }
+               }
+
+               if(arg_text[1])
+               {
+                       if(dst_float1)
+                       {
+                               *dst_float1 = atof(arg_text[1]);
+                       }
+               }
+
+               if(arg_text[2])
+               {
+                       if(dst_float2)
+                       {
+                               *dst_float2 = atof(arg_text[2]);
+                       }
+               }
+
+               return 1;
+       }
+
+       return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+FileScene::FileScene(Asset *asset, File *file)
+ : FileBase(asset, file)
+{
+       reset_parameters();
+       get_exe_path(exe_path);
+}
+
+
+FileScene::~FileScene()
+{
+       close_file();
+}
+
+
+
+int FileScene::open_file(int rd, int wr)
+{
+// Load the script to get character count
+       read_script();
+       
+
+
+// Set asset format
+       asset->format = FILE_SCENE;
+       asset->video_data = 1;
+// Should be set in the scene file
+       asset->layers = 1;
+       asset->width = 1280;
+       asset->height = 720;
+// Dictated by character animations
+       asset->frame_rate = (double)30000 / 1001;
+// Some arbitrary default length.
+// Hard to calculate without rendering it.
+       asset->video_length = -1;
+
+
+
+       asset->audio_data = 1;
+// The speech synthesizer outputs different samplerates depending on the speaker.
+// The heroine voices are all 16khz
+       asset->sample_rate = FESTIVAL_SAMPLERATE;
+// Mono voice placement for now
+// Maybe a different track for each voice in the future or 3D positioning
+       asset->channels = script->total_characters();
+       asset->audio_length = -1;
+
+
+       return 0;
+}
+
+int FileScene::close_file()
+{
+       delete script;
+       delete [] audio_temp;
+//     delete overlayer;
+//     delete affine;
+       FileBase::close_file();
+       return 0;
+}
+
+
+int FileScene::check_sig(Asset *asset, char *test)
+{
+       if(!strncmp(test, "TEXT2MOVIE", 10)) return 1;
+
+       return 0;
+}
+
+
+
+int FileScene::set_video_position(int64_t x)
+{
+       return 0;
+}
+
+
+int FileScene::set_audio_position(int64_t x)
+{
+       return 0;
+}
+
+
+int FileScene::read_frame(VFrame *frame)
+{
+// Everything is timed based on speech, so render the audio for this frame.
+       const int debug = 0;
+       
+       
+       frame->clear_frame();
+       int64_t audio_position1 = (int64_t)(file->current_frame * 
+               asset->sample_rate /
+               asset->frame_rate);
+       int64_t audio_position2 = (int64_t)(audio_position1 + 
+               asset->sample_rate /
+               asset->frame_rate);
+
+       if(debug) printf("FileScene::read_frame %d frame=%jd"
+               " frame_rate=%f sample_rate=%d audio_position1=%jd"
+               " audio_position2=%jd\n", __LINE__,
+               file->current_frame, asset->frame_rate,
+               asset->sample_rate, audio_position1,
+               audio_position2);
+
+       render_chunks(audio_position1, 
+               audio_position2 - audio_position1,
+               1);
+       if(!script) return 1;
+       if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+
+//     script->dump();
+
+
+// Determine lip position from amplitude
+       double accum = 0;
+       for(int i = 0; i < audio_position2 - audio_position1; i++)
+       {
+               double sample_float = fabs((double)audio_temp[i] / 32768);
+               if(sample_float > accum) accum = sample_float;
+       }
+       if(debug) printf("FileScene::read_frame %d accum=%f\n", __LINE__, accum);
+
+// Reset cameras
+       for(int i = 0; i < script->total_characters(); i++)
+       {
+               SceneChar *character = script->get_character(i);
+               character->current_camera = SceneChar::CAMERA_WIDE;
+               character->is_speeking = 0;
+               character->max = 0;
+       }
+
+// Now determine most recent character which is speaking from the sample times.
+       int64_t current_sample = 0;
+       SceneChar *speeking_character = 0;
+// Sample relative to start of chunk
+       int64_t chunk_sample = 0;
+       for(int i = 0; 
+               i < script->total_chunks() && current_sample < audio_position2; 
+               i++)
+       {
+               SceneChunk *chunk = script->get_chunk(i);
+               int samples = chunk->audio_size / 2;
+
+
+               if(audio_position1 >= current_sample && 
+                       audio_position1 < current_sample + samples)
+               {
+                       speeking_character = chunk->character;
+                       speeking_character->max = chunk->max;
+                       chunk->character->is_speeking = 1;
+                       chunk_sample = audio_position1 - current_sample;
+//                     break;
+               }
+               else
+               if(!chunk->character->is_speeking)
+               {
+                       chunk->character->increment_camera();
+               }
+
+               current_sample += chunk->advance_samples;
+       }
+
+
+
+       if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+       
+// Render the scene.
+// Store component placement in a scene graph.
+       SceneGraph scene;
+       //SceneNode *speeking_node = 0;
+
+// Scale for the entire scene
+       SceneCamera *camera = new SceneCamera;
+       scene.append_camera(camera);
+       camera->at_x = 0;
+       camera->at_y = 0;
+
+// Render background
+       if(script->background[0])
+       {
+               script->render_background(&scene);
+       }
+
+       if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+
+// Render characters
+       for(int i = 0; i < script->total_characters(); i++)
+       {
+               SceneChar *character = script->get_character(i);
+
+               character->read_model();
+
+// Nodes for the character
+               SceneNode *character_node = new SceneNode(character->name);
+               scene.append(character_node);
+               character_node->sx = character_node->sy = character->scale;
+
+               SceneNode *head_node = new SceneNode("head");
+
+               SceneNode *body_node = new SceneNode("body");
+
+// Render in the order listed in the file
+               if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+               for(int j = 0; j < 2; j++)
+               {
+                       if(debug) printf("FileScene::read_frame %d j=%d head_order=%d body_order=%d\n", 
+                               __LINE__, 
+                               j,
+                               character->head_order,
+                               character->body_order);
+                       if(j == character->head_order) character_node->append(head_node);
+                       else
+                       if(j == character->body_order) character_node->append(body_node);
+               }
+
+               SceneNode *eye_node = 0;
+               if(character->eyes.size())
+               {
+                       eye_node = new SceneNode("eyes");
+                       head_node->append(eye_node);
+               }
+
+               SceneNode *mouth_node = new SceneNode("mouth");
+               head_node->append(mouth_node);
+
+// Logical character placement
+               switch(script->total_characters())
+               {
+                       case 1:
+                               if(speeking_character == character &&
+                                       speeking_character->current_camera == SceneChar::CAMERA_CU)
+                               {
+                                       camera->at_x = asset->width / 4;
+                               }
+
+                               character_node->x = (int)(asset->width / 2 - 
+                                       character->w * character->scale / 2);
+                               character_node->y = (int)(asset->height - 
+                                       character->h * character->scale);
+                               break;
+
+                       case 2:
+                               if(i == 0)
+                               {
+                                       character_node->x = 0;
+                                       character_node->y = (int)(asset->height - 
+                                               character->h * character->scale);
+
+                                       if(speeking_character == character &&
+                                               speeking_character->current_camera == SceneChar::CAMERA_CU)
+                                       {
+                                               camera->at_y = character_node->y;
+                                               camera->at_x = character_node->x + 
+                                                       (character->head->x + character->head->image->get_w() / 2) * character->scale - 
+                                                       asset->width / 4;
+                                               CLAMP(camera->at_x, 0, asset->width / 2);
+                                               CLAMP(camera->at_y, 0, asset->height);
+//printf("FileScene::read_frame %d camera->at_x=%f camera->at_y=%f\n", __LINE__, camera->at_x, camera->at_y);
+                                       }
+
+                                       if(character->faces_left)
+                                       {
+                                               character_node->flip = 1;
+                                               character_node->x = asset->width - 
+                                                       character->w * character->scale;
+                                       }
+                               }
+                               else
+                               {
+
+                                       character_node->x = (int)(asset->width - 
+                                               character->w * character->scale);
+                                       character_node->y = (int)(asset->height - 
+                                               character->h * character->scale);
+
+                                       if(speeking_character == character &&
+                                               speeking_character->current_camera == SceneChar::CAMERA_CU)
+                                       {
+                                               camera->at_x = character_node->x + 
+                                                       character->head->x * character->scale - 
+                                                       asset->width / 4;
+                                               CLAMP(camera->at_x, 0, asset->width / 2);
+                                       }
+
+                                       if(!character->faces_left) 
+                                       {
+                                               character_node->flip = 1;
+                                               character_node->x = 0;
+                                       }
+                               }
+                               break;
+
+                       case 3:
+                               if(i == 0)
+                               {
+                                       character_node->x = 0;
+                                       character_node->y = (int)(asset->height - 
+                                               character->h * character->scale);
+                                       if(character->faces_left)
+                                       {
+                                               character_node->flip = 1;
+                                       }
+                               }
+                               else
+                               if(i == 1)
+                               {
+                                       if(speeking_character == character &&
+                                               speeking_character->current_camera == SceneChar::CAMERA_CU)
+                                       {
+                                               camera->at_x = asset->width / 4;
+                                       }
+                                       character_node->x = (int)(asset->width / 2 - 
+                                               character->w * character->scale / 2);
+                                       character_node->y = (int)(asset->height - 
+                                               character->h * character->scale);
+                                       if(character->faces_left)
+                                       {
+                                               character_node->flip = 1;
+                                       }
+                               }
+                               else
+                               {
+                                       if(speeking_character == character &&
+                                               speeking_character->current_camera == SceneChar::CAMERA_CU)
+                                       {
+                                               camera->at_x = asset->width / 2;
+                                       }
+                                       character_node->x = (int)(asset->width - 
+                                               character->w * character->scale);
+                                       character_node->y = (int)(asset->height - 
+                                               character->h * character->scale);
+                                       if(!character->faces_left)
+                                       {
+                                               character_node->flip = 1;
+                                               character_node->x = 0;
+                                       }
+                               }
+                               break;
+               }
+
+// Clamp the head
+               if(character_node->y < 0) character_node->y = 0;
+
+// Add remaining parts
+               body_node->copy_ref(character->body);
+               head_node->copy_ref(character->head);
+
+// Speeker head rotation
+               if(speeking_character == character)
+               {
+                       //speeking_node = character_node;
+               
+                       int head_time = (chunk_sample / asset->sample_rate / 2) % 2;
+
+                       if(head_time > 0)
+                       {
+                               double temp;
+                               double anim_position = modf((double)chunk_sample / asset->sample_rate / 2, &temp);
+                               double anim_length = 0.1;
+// printf("FileScene::read_frame %d %d %f\n", 
+// __LINE__, 
+// head_time,
+// anim_position);
+
+                               if(anim_position < anim_length)
+                                       head_node->ry = -5 * anim_position / anim_length;
+                               else
+                               if(anim_position > 1.0 - anim_length)
+                                       head_node->ry = -5 * (1.0 - anim_position) / anim_length;
+                               else
+                                       head_node->ry = -5;
+                       }
+               }
+
+//head_node->ry = -5;
+
+// Eyes
+               double intpart;
+               if(character->eyes.size())
+               {
+                       if(modf((file->current_frame / asset->frame_rate + script->get_char_number(character)) / 5, &intpart) <= 
+                               0.1 / 5 &&
+                               file->current_frame / asset->frame_rate > 1)
+                       {
+                               eye_node->copy_ref(character->eyes.get(0));
+                       }
+                       else
+                               eye_node->copy_ref(character->eyes.get(1));
+               }
+
+// Compute the mouth image
+               int fraction = 0;
+               if(character->is_speeking && character->max > 0)
+               {
+                       fraction = (int)(accum * character->mouths.size() / character->max);
+                       if(fraction >= character->mouths.size())
+                               fraction = character->mouths.size() - 1;
+               }
+
+               mouth_node->copy_ref(character->mouths.get(fraction));
+
+
+// camera->scale = 2;
+// camera->at_x = asset->width / 2;
+// camera->at_y = 0;
+
+// Compute camera
+               if(speeking_character == character &&
+                       speeking_character->current_camera == SceneChar::CAMERA_CU)
+               {
+// If closeup, increase scene scale
+                       camera->scale = 2;
+               }
+       }
+
+
+
+
+       if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+
+
+
+// Render scene graph
+       scene.render(frame, file->cpus);
+               
+               
+               
+       if(debug) printf("FileScene::read_frame %d\n", __LINE__);
+
+
+       return 0;
+}
+
+
+int FileScene::read_samples(double *buffer, int64_t len)
+{
+// Speech rendering
+// Everything is timed based on speech, so we have to do this for video, too.
+       render_chunks(file->current_sample, len, 0);
+
+//     script->dump();
+
+
+// Convert temp to output
+       for(int i = 0; i < len; i++)
+       {
+               buffer[i] = (double)audio_temp[i] / 32768;
+       }
+
+       return 0;
+}
+
+
+int64_t FileScene::get_memory_usage()
+{
+//PRINT_TRACE
+       int total = 0x100000;
+       if(script)
+       {
+               total += script->get_memory_usage();
+               
+       }
+//PRINT_TRACE
+       return total;
+}
+
+
+
+int FileScene::get_best_colormodel(Asset *asset, int driver)
+{
+       return 0;
+}
+
+
+int FileScene::colormodel_supported(int colormodel)
+{
+       return BC_RGBA8888;
+}
+
+
+int FileScene::can_copy_from(Asset *asset, int64_t position)
+{
+       return 0;
+}
+
+
+int FileScene::reset_parameters_derived()
+{
+       script = 0;
+       audio_temp = 0;
+       temp_allocated = 0;
+//     overlayer = 0;
+//     affine = 0;
+       return 0;
+}
+
+
+
+void FileScene::render_chunks(int64_t start_position, 
+       int64_t len, 
+       int all_channels)
+{
+       int64_t end_position = start_position + len;
+       const int debug = 0;
+
+
+       if(debug) printf("FileScene::render_chunks %d start_position=%jd"
+               " len=%jd\n", __LINE__, start_position, len);
+
+// Update script
+       read_script();
+
+       if(!script) return;
+
+       if(debug) PRINT_TRACE
+
+// Reallocate temp buffer
+       if(len > temp_allocated)
+       {
+               delete [] audio_temp;
+               audio_temp = new int16_t[len];
+               temp_allocated = len;
+       }
+       bzero(audio_temp, sizeof(int16_t) * len);
+
+       if(debug) PRINT_TRACE
+
+
+
+
+// Find start_position in script output.
+// Must know length of every chunk before end of buffer
+       int64_t current_sample = 0;
+       for(int i = 0; i < script->total_chunks() &&
+               current_sample < end_position; i++)
+       {
+               SceneChunk *chunk = script->get_chunk(i);
+               chunk->used = 0;
+               if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
+
+// If extent of audio output hasn't been calculated, render it
+               if(!chunk->audio_size)
+               {
+                       if(debug) printf("FileScene::render_chunks %d i=%d\n", __LINE__, i);
+                       chunk->render();
+               }
+
+// Dialog chunk is inside output buffer
+               if(current_sample + chunk->audio_size / 2 > start_position &&
+                       current_sample < end_position)
+               {
+                       if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
+
+// If no audio output exists, render it
+                       if(!chunk->audio)
+                       {
+                               if(debug) printf("FileScene::render_chunks %d rerendering audio\n", __LINE__);
+                               chunk->render();
+                       }
+
+                       if(debug) printf("FileScene::render_chunks %d: Using \"%s\" samples=%d\n",
+                               __LINE__,
+                               chunk->text,
+                               chunk->audio_size / 2);
+                       if(debug) printf("FileScene::render_chunks %d: start_position=%jd"
+                                       " current_sample=%jd\n", __LINE__,
+                                       start_position, current_sample);
+
+// Memcpy it.
+// TODO: allow characters to talk simultaneously
+                       int64_t src_offset = start_position - current_sample;
+                       int64_t dst_offset = 0;
+                       int64_t src_len = chunk->audio_size / 2 - src_offset;
+
+                       if(debug) printf("FileScene::render_chunks %d: src_offset=%jd"
+                               " dst_offset=%jd src_len=%jd\n", __LINE__,
+                               src_offset, dst_offset, src_len);
+
+                       if(src_offset < 0)
+                       {
+                               dst_offset -= src_offset;
+                               src_len += src_offset;
+                               src_offset = 0;
+                       }
+
+                       if(dst_offset + src_len > len)
+                       {
+                               src_len = len - dst_offset;
+                       }
+
+                       if(debug) printf("FileScene::render_chunks %d: src_offset=%jd"
+                               " dst_offset=%jd src_len=%jd\n", __LINE__,
+                               src_offset, dst_offset, src_len);
+
+// Transfer if right channel
+                       if(all_channels || 
+                               file->current_channel == script->get_char_number(chunk->character))
+                       {
+                               for(int j = 0; j < src_len; j++)
+                               {
+                                       audio_temp[dst_offset + j] = 
+                                               chunk->audio[(src_offset + j) * 2] | 
+                                               (chunk->audio[(src_offset + j) * 2 + 1] << 8);
+                               }
+                       }
+
+                       if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
+                       chunk->used = 1;
+               }
+
+               current_sample += chunk->advance_samples;
+       }
+
+// Erase unused dialog chunks
+       if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
+       for(int i = 0; i < script->total_chunks(); i++)
+       {
+               SceneChunk *chunk = script->get_chunk(i);
+               if(!chunk->used && chunk->audio)
+               {
+                       if(debug) printf("FileScene::render_chunks %d erasing unused audio\n", __LINE__);
+                       delete [] chunk->audio;
+                       chunk->audio = 0;
+                       chunk->audio_allocated = 0;
+               }
+       }
+       if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
+       
+       
+}
+
+
+
+int FileScene::read_script()
+{
+       const int debug = 0;
+       struct stat ostat;
+       if(stat(asset->path, &ostat))
+       {
+               printf("FileScene::read_script %d: %s\n", __LINE__, strerror(errno));
+               return 1;
+       }
+
+// Test timestamp
+       if(script)
+       {
+               if(ostat.st_mtime == script->timestamp)
+               {
+                       if(debug) printf("FileScene::read_script %d: script unchanged\n", __LINE__);
+                       return 0;
+               }
+       }
+
+
+// Read new script
+       delete script;
+       script = 0;
+       if(!script) script = new SceneTokens(this, file->cpus);
+       script->timestamp = ostat.st_mtime;
+
+       script->read_script(asset->path);
+
+       return 1;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+SceneChar::SceneChar(SceneTokens *script)
+{
+       this->script = script;
+       name[0] = 0;
+       voice[0] = 0;
+       body = 0;
+       head = 0;
+       body_order = 0;
+       head_order = 1;
+       sprintf(model, "heroine01");
+       current_camera = CAMERA_WIDE;
+       faces_left = 0;
+       scale = 1;
+       max = 0;
+       is_speeking = 0;
+}
+
+SceneChar::~SceneChar()
+{
+       mouths.remove_all_objects();
+       eyes.remove_all_objects();
+       delete body;
+       delete head;
+}
+
+
+void SceneChar::increment_camera()
+{
+       current_camera++;
+       if(current_camera >= CAMERA_TOTAL) 
+               current_camera = 0;
+}
+
+
+int SceneChar::read_model()
+{
+// Read descriptor file
+       const int debug = 0;
+       int current_line = 0;
+       float x, y;
+       int i;
+       int current_order = 0;
+       char path[BCTEXTLEN];
+
+// Already read it
+       if(body) return 0;
+
+       script->convert_path(path, model);
+       FILE *fd = fopen(path, "r");
+       
+
+// Read assets
+       if(fd)
+       {
+// Read 1 line
+               char string[BCTEXTLEN];
+               char string2[BCTEXTLEN];
+
+               while(!feof(fd))
+               {
+                       char *result = fgets(string, BCTEXTLEN, fd);
+                       
+                       if(result)
+                       {
+                               int len = strlen(string);
+
+                               STRIP_LINE(string);
+
+                               if(debug) printf("SceneChar::read_model %d: %s\n", 
+                                       __LINE__, 
+                                       string);
+                       
+                               for(i = 0; i < len; i++)
+                               {
+                                       if(isalnum(string[i]))
+                                       {
+                                               string2[0] = 0;
+                                               
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "width:", 
+                                                       0, 
+                                                       &w, 
+                                                       0, 
+                                                       0))
+                                               {
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "height:", 
+                                                       0, 
+                                                       &h, 
+                                                       0, 
+                                                       0))
+                                               {
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "body:", 
+                                                       string2, 
+                                                       0, 
+                                                       &x, 
+                                                       &y))
+                                               {
+// Load image
+                                                       body = new SceneNode(script->load_image(string2), 1, x, y);
+                                                       body_order = current_order++;
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "head:", 
+                                                       string2, 
+                                                       0, 
+                                                       &x, 
+                                                       &y))
+                                               {
+// Load image
+                                                       head = new SceneNode(script->load_image(string2), 1, x, y);
+                                                       head_order = current_order++;
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "mouth:", 
+                                                       string2, 
+                                                       0, 
+                                                       &x, 
+                                                       &y))
+                                               {
+// Load image
+                                                       SceneNode *mouth;
+                                                       mouths.append(mouth = new SceneNode(script->load_image(string2), 1, x, y));
+// Make coordinates relative to head
+                                                       if(head)
+                                                       {
+                                                               mouth->x -= head->x;
+                                                               mouth->y -= head->y;
+                                                       }
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "eyes:", 
+                                                       string2, 
+                                                       0, 
+                                                       &x, 
+                                                       &y))
+                                               {
+// Load image
+                                                       SceneNode *eye;
+                                                       eyes.append(eye = new SceneNode(script->load_image(string2), 1, x, y));
+// Make coordinates relative to head
+                                                       if(head)
+                                                       {
+                                                               eye->x -= head->x;
+                                                               eye->y -= head->y;
+                                                       }
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "faces_left", 
+                                                       0, 
+                                                       0, 
+                                                       0, 
+                                                       0))
+                                               {
+                                                       faces_left = 1;
+                                               }
+                                               else
+                                               if(read_parameter(string,
+                                                       &i,
+                                                       "scale:", 
+                                                       0, 
+                                                       &scale, 
+                                                       0, 
+                                                       0))
+                                               {
+                                               }
+                                               
+                                               i = len;
+                                       }
+                               }
+                       }
+                       
+                       current_line++;
+               }
+               
+               fclose(fd);
+               if(debug) dump();
+               return 0;
+       }
+       
+       printf("SceneChar::read_model %d: %s %s\n", __LINE__, path, strerror(errno));
+       return 1;
+}
+
+int SceneChar::get_memory_usage()
+{
+       int total = 0;
+       if(body) total += body->get_memory_usage();
+       if(head) total += head->get_memory_usage();
+       for(int i = 0; i < mouths.size(); i++)
+               total += mouths.get(i)->get_memory_usage();
+       return total;
+}
+
+void SceneChar::dump()
+{
+       printf("SceneChar::dump %d: %p name=%s voice=%s model=%s body=%p eyes=%d mouths=%d\n", 
+               __LINE__, 
+               this,
+               name, 
+               voice, 
+               model,
+               body,
+               eyes.size(),
+               mouths.size());
+       printf("SceneChar::dump %d: w=%f h=%f\n", __LINE__, w, h);
+
+
+       for(int i = 0; i < mouths.size(); i++)
+       {
+               SceneNode *node = mouths.get(i);
+               printf("    mouth=%p x=%f y=%f\n", node, node->x, node->y);
+       }
+
+       for(int i = 0; i < eyes.size(); i++)
+       {
+               SceneNode *node = eyes.get(i);
+               printf("    eyes=%p x=%f y=%f\n", node, node->x, node->y);
+       }
+}
+
+
+
+
+
+
+
+
+
+// Dialog from a single character
+SceneChunk::SceneChunk(SceneTokens *script)
+{
+       text = 0;
+       character = 0;
+       audio = 0;
+       audio_size = 0;
+       audio_allocated = 0;
+       advance_samples = 0;
+       used = 0;
+       max = 0;
+       command = NO_COMMAND;
+       this->script = script;
+}
+
+SceneChunk::~SceneChunk()
+{
+       delete [] text;
+       delete [] audio;
+}
+
+void SceneChunk::dump()
+{
+       printf("SceneChunk::dump %d: character=%s command=%d text=%s\n", 
+               __LINE__, 
+               character->name,
+               command,
+               text);
+       printf("SceneChunk::dump %d: audio=%p audio_size=%d advance_samples=%d\n",
+               __LINE__,
+               audio,
+               audio_size,
+               advance_samples);
+}
+
+int SceneChunk::get_memory_usage()
+{
+       return audio_allocated;
+}
+
+
+void SceneChunk::append_text(char *new_text)
+{
+       char string[BCTEXTLEN];
+
+// Replace "
+// Convert ' to \'
+       char *ptr = string;
+       int len = strlen(new_text);
+       for(int i = 0; i < len; i++)
+       {
+//             if(new_text[i] == '"')
+//                     *ptr++ = ' ';
+//             else
+//             if(new_text[i] == '\'')
+//             {
+//                     *ptr++ = '\\';
+//                     *ptr++ = '\'';
+//                     *ptr++ = '\'';
+//             }
+//             else
+                       *ptr++ = new_text[i];
+       }
+       *ptr++ = 0;
+       
+       int len2 = strlen(string);
+       if(text)
+       {
+               int len1 = strlen(text);
+               int len3 = 1;
+               int need_space = 0;
+
+//             if(len1 > 0 && isalnum(text[len1 - 1]))
+               if(len1 > 0 && text[len1 - 1] != ' ')
+               {
+                       need_space = 1;
+                       len3++;
+               }
+
+               text = (char*)realloc(text, len1 + len2 + len3);
+
+// Append space
+               if(need_space)
+               {
+                       text[len1] = ' ';
+                       text[len1 + 1] = 0;
+               }
+       }
+       else
+       {
+               text = new char[len2 + 1];
+               text[0] = 0;
+       }
+
+       strcat(text, string);
+}
+
+
+
+
+void SceneChunk::render()
+{
+       const int debug = 0;
+       int len = 0;
+       char command_line[BCTEXTLEN];
+       char string2[MAX_CHARS];
+       char script_path[BCTEXTLEN];
+       
+       //int total_args = 0;
+       if(text) len = strlen(text);
+       char *text_end = text + len;
+       char *text_ptr = text;
+
+       audio_size = 0;
+
+       if(!character)
+       {
+               printf("SceneChunk::render %d: no character defined.\n", __LINE__);
+       }
+       
+       if(len > MAX_CHARS)
+       {
+               printf("SceneChunk::render %d: text '%s' exceeds festival's maximum line length of %d chars.\n",
+                       __LINE__,
+                       text,
+                       MAX_CHARS);
+       }
+
+// Process command
+       switch(command)
+       {
+               case SceneChunk::PAUSE_COMMAND:
+                       audio_allocated = PAUSE_SAMPLES * 2;
+                       audio_size = PAUSE_SAMPLES * 2;
+                       advance_samples = PAUSE_SAMPLES;
+                       audio = (unsigned char*)realloc(audio, audio_allocated);
+                       bzero(audio, audio_size);
+                       max = 0;
+                       break;
+       }
+
+       while(text && text_ptr < text_end)
+       {
+// Copy at most MAX_CHARS of data into string2
+               char *ptr = string2;
+               for(int i = 0; i < MAX_CHARS && text_ptr < text_end; i++)
+               {
+                       *ptr++ = *text_ptr++;
+               }
+               *ptr++ = 0;
+
+
+// Rewind to white space if still more text
+               if(text_ptr < text_end)
+               {
+                       ptr--;
+                       text_ptr--;
+                       while(*text_ptr != ' ' && 
+                               *text_ptr != '\n' && 
+                               text_ptr > text)
+                       {
+                               text_ptr--;
+                               ptr--;
+                       }
+
+
+// Truncate string2 at white space
+                       *ptr = 0;
+// If no white space, abort.
+                       if(text_ptr <= text)
+                       {
+                               break;
+                       }
+
+               }
+
+               uuid_t temp_id;
+               sprintf(script_path, "/tmp/cinelerra.");
+               uuid_generate(temp_id);
+               uuid_unparse(temp_id, script_path + strlen(script_path));
+               FILE *script_fd = fopen(script_path, "w");
+
+               sprintf(command_line, "%s%s --libdir %s%s -b %s", 
+                       script->file->exe_path,
+                       FESTIVAL_PATH,
+                       script->file->exe_path,
+                       FESTIVAL_LIB_PATH,
+                       script_path);
+
+// Create script.
+// The maximum text length is limited with the command line
+
+               fprintf(script_fd, 
+                       "(voice_%s)\n"
+                       "(set! text (Utterance Text \"%s\"))\n"
+                       "(utt.synth text)"
+                       "(utt.save.wave text \"-\")",
+                       character->voice,
+                       string2);
+               fclose(script_fd);
+
+
+               if(debug)
+               {
+                       printf("SceneChunk::render %d %s\n", 
+                               __LINE__,
+                               command_line);
+                       
+                       FILE *script_fd = fopen(script_path, "r");
+                       while(!feof(script_fd))
+                               fputc(fgetc(script_fd), stdout);
+                       printf("\n");
+                       fclose(script_fd);
+               }
+
+// popen only does half duplex
+               FILE *fd = popen(command_line, "r");
+
+// Capture output
+               if(fd)
+               {
+                       int audio_start = audio_size;
+
+               
+                       if(debug) printf("SceneChunk::render %d\n", 
+                               __LINE__);
+                       while(!feof(fd))
+                       {
+                               if(debug) printf("SceneChunk::render %d\n", 
+                                       __LINE__);
+                               if(audio_size + FREAD_SIZE > audio_allocated)
+                               {
+                                       audio_allocated += FREAD_SIZE;
+                                       audio = (unsigned char*)realloc(audio, audio_allocated);
+                               }
+
+
+                               if(debug) printf("SceneChunk::render %d audio=%p audio_size=%d\n", 
+                                       __LINE__,
+                                       audio,
+                                       audio_size);
+
+                               int bytes_read = fread(audio + audio_size, 1, FREAD_SIZE, fd);
+                               if(debug) printf("SceneChunk::render %d bytes_read=%d\n", 
+                                       __LINE__,
+                                       bytes_read);
+                               audio_size += bytes_read;
+                               if(bytes_read < FREAD_SIZE)
+                               {
+                                       break;
+                               }
+                       }
+
+
+                       pclose(fd);
+
+                       if(debug) printf("SceneChunk::render %d audio=%p audio_size=%d audio_allocated=%d\n", 
+                               __LINE__,
+                               audio,
+                               audio_size,
+                               audio_allocated);
+
+// Strip WAV header
+
+                       if(audio_size - audio_start > WAVHEADER)
+                       {
+// for(int i = 0; i < 128; i++)
+// {
+// printf("%c ", audio[audio_start + i]);
+// if(!((i + 1) % 16)) printf("\n");
+// }
+// printf("\n");
+// Find header after error messages
+                               int header_size = WAVHEADER;
+                               for(int i = audio_start; i < audio_start + audio_size - 4; i++)
+                               {
+                                       if(audio[i] == 'R' &&
+                                               audio[i + 1] == 'I' &&
+                                               audio[i + 2] == 'F' &&
+                                               audio[i + 3] == 'F')
+                                       {
+                                               header_size += i - audio_start;
+                                               break;
+                                       }
+                               }
+
+                               memcpy(audio + audio_start, 
+                                       audio + audio_start + header_size, 
+                                       audio_size - audio_start - header_size);
+                               audio_size -= header_size;
+                               if(debug) printf("SceneChunk::render %d: audio_size=%d\n",
+                                       __LINE__,
+                                       audio_size);
+                       }
+                       
+                       advance_samples = audio_size / 2;
+               }
+               else
+               {
+                       printf("SceneChunk::render %d: Couldn't run %s: %s\n",
+                               __LINE__,
+                               command_line,
+                               strerror(errno));
+               }
+
+               remove(script_path);
+               if(debug) printf("SceneChunk::render %d max=%f\n", 
+                       __LINE__,
+                       max);
+       }
+
+
+       if(text)
+       {
+
+// Truncate if ...
+               text_ptr = text + len - 1;
+               while(text_ptr > text && 
+                       (*text_ptr == ' ' ||
+                       *text_ptr == '\n'))
+                       text_ptr--;
+               if(text_ptr > text + 3 &&
+                       *(text_ptr - 0) == '.' &&
+                       *(text_ptr - 1) == '.' &&
+                       *(text_ptr - 2) == '.')
+               {
+                       advance_samples -= ADVANCE_SAMPLES;
+                       if(advance_samples < 0) advance_samples = 0;
+               }
+
+// Calculate loudest part
+               max = 0;
+               for(int i = 0; i < audio_size; i += 2)
+               {
+                       int16_t sample = audio[i] |
+                               (audio[i + 1] << 8);
+                       double sample_float = fabs((double)sample / 32768);
+                       if(sample_float > max) max = sample_float;
+               }
+       }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+SceneTokens::SceneTokens(FileScene *file, int cpus)
+{
+       background[0] = 0;
+       background_image = 0;
+       timestamp = 0;
+       this->cpus = cpus;
+       this->file = file;
+//     overlayer = 0;
+}
+
+SceneTokens::~SceneTokens()
+{
+       chunks.remove_all_objects();
+       characters.remove_all_objects();
+       delete background_image;
+//     delete overlayer;
+}
+
+SceneChar* SceneTokens::get_character(char *name)
+{
+       const int debug = 0;
+       if(debug) printf("SceneTokens::get_character %d %d\n",
+               __LINE__,
+               characters.size());
+
+       for(int i = 0; i < characters.size(); i++)
+       {
+               if(!strcmp(characters.get(i)->name, name)) return characters.get(i);
+       }
+       if(debug) printf("SceneTokens::get_character %d %d\n",
+               __LINE__,
+               characters.size());
+       
+       SceneChar *result = new SceneChar(this);
+       if(debug) printf("SceneTokens::get_character %d %d this=%p\n",
+               __LINE__,
+               characters.size(),
+               &characters);
+
+       characters.append(result);
+       if(debug) printf("SceneTokens::get_character %d %d\n",
+               __LINE__,
+               characters.size());
+
+       strcpy(result->name, name);
+       if(debug) printf("SceneTokens::get_character %d %d\n",
+               __LINE__,
+               characters.size());
+
+       if(debug) printf("SceneTokens::get_character %d %d\n",
+               __LINE__,
+               characters.size());
+
+       return result;
+}
+
+SceneChar* SceneTokens::get_character(int number)
+{
+       return characters.get(number);
+}
+
+int SceneTokens::get_char_number(SceneChar *ptr)
+{
+       for(int i = 0; i < characters.size(); i++)
+               if(ptr == characters.get(i)) return i;
+       return 0;
+}
+
+SceneChunk* SceneTokens::new_chunk()
+{
+       SceneChunk *result = new SceneChunk(this);
+       chunks.append(result);
+       return result;
+}
+
+int SceneTokens::total_chunks()
+{
+       return chunks.size();
+}
+
+int SceneTokens::total_characters()
+{
+       return characters.size();
+}
+
+SceneChunk* SceneTokens::get_chunk(int number)
+{
+       return chunks.get(number);
+}
+
+int SceneTokens::read_script(char *path)
+{
+       const int debug = 0;
+
+       strcpy(this->path, path);
+
+       FILE *fd = fopen(path, "r");
+       if(fd)
+       {
+// Read 1 line
+               char string[BCTEXTLEN];
+// Current character name
+               char char_name[BCTEXTLEN];
+               SceneChar *current_char = 0;
+               char_name[0] = 0;
+               SceneChunk *current_chunk = 0;
+               int current_line = 0;
+               int i;
+
+               while(!feof(fd))
+               {
+                       char *result = fgets(string, BCTEXTLEN, fd);
+                       current_line++;
+
+                       if(result)
+                       {
+                               int len = strlen(string);
+                               STRIP_LINE(string)
+
+                               if(debug) printf("SceneTokens::read_script %d: %s\n", 
+                                       __LINE__, 
+                                       string);
+
+// Skip the file ID & empty lines
+                               if(string[0] == 0 ||
+                                       !strncmp(string, "TEXT2MOVIE", 10))
+                                       continue;
+
+                               int got_it = 0;
+                               for(i = 0; i < len; i++)
+                               {
+                                       if(isalnum(string[i]))
+                                       {
+                                               got_it = 1;
+                                               i = len;
+                                       }
+                               }
+
+                               if(!got_it) continue;
+
+// A line all in caps is a character name
+                               got_it = 1;
+                               for(i = 0; i < len; i++)
+                               {
+                                       if(islower(string[i]))
+                                       {
+                                               got_it = 0;
+                                               i = len;
+                                       }
+                               }
+
+                               if(got_it)
+                               {
+                                       strcpy(char_name, string);
+
+                                       if(debug) printf("SceneTokens::read_script %d: char_name=%s\n", 
+                                               __LINE__, 
+                                               char_name);
+
+                                       current_char = get_character(char_name);
+
+                                       if(debug) printf("SceneTokens::read_script %d current_char=%p\n", 
+                                               __LINE__, 
+                                               current_char);
+
+// Reset the current chunk pointer
+                                       current_chunk = 0;
+                                       i = len;
+                               }
+                               else
+                                       i = 0;
+
+// Certain words are commands
+                               for(; i < len; i++)
+                               {
+                                       if(string[i] == '[' || isalnum(string[i]))
+                                       {
+                                               char *command = string + i;
+
+                                               STRING_PARAMETER("voice:", 1, current_char->voice)
+                                               else
+                                               STRING_PARAMETER("model:", 1, current_char->model)
+                                               else
+                                               STRING_PARAMETER("background:", 0, background)
+                                               else
+// Default is dialogue
+                                               {
+                                                       if(!current_char)
+                                                       {
+                                                               printf("SceneTokens::read_script %d Line %d: dialogue text but no current character\n", 
+                                                                       __LINE__,
+                                                                       current_line);
+                                                       }
+                                                       else
+                                                       {
+                                                               if(!current_chunk)
+                                                               {
+                                                                       current_chunk = new_chunk();
+                                                                       current_chunk->character = current_char;
+                                                               }
+
+// Append dialogue to current chunk
+                                                               current_chunk->append_text(string + i);
+                                                       }
+                                                       
+                                                       i = len;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
+               
+               
+               fclose(fd);
+
+               if(debug) printf("SceneTokens::read_script %d total_chunks=%d\n", 
+                       __LINE__,
+                       total_chunks());
+// Parse commands in dialogue
+               for(i = 0; i < total_chunks(); i++)
+               {
+                       SceneChunk *chunk = get_chunk(i);
+                       if(chunk->text)
+                       {
+                               char *ptr = chunk->text;
+                               char *end = chunk->text + strlen(chunk->text);
+                               int got_text = 0;
+                               if(debug) printf("SceneTokens::read_script %d %s\n", __LINE__, chunk->text);
+                               while(ptr < end)
+                               {
+// Start of command
+                                       if(*ptr == '[')
+                                       {
+                                               if(debug) printf("SceneTokens::read_script %d [\n", __LINE__);
+// Split chunk
+                                               if(got_text)
+                                               {
+                                                       SceneChunk *new_chunk = new SceneChunk(this);
+                                                       new_chunk->character = chunk->character;
+                                                       chunks.insert(new_chunk, i + 1);
+                                                       
+// Move text from start of command to new chunk.
+                                                       new_chunk->append_text(ptr);
+// Truncate current chunk
+                                                       *ptr = 0;
+// Advance to next chunk
+                                                       ptr = new_chunk->text;
+                                                       end = new_chunk->text + strlen(new_chunk->text);
+                                                       chunk = new_chunk;
+                                                       i++;
+                                               }
+                                               if(debug) printf("SceneTokens::read_script %d\n", __LINE__);
+
+// Read command
+                                               while(ptr < end && 
+                                                       (*ptr == '[' || *ptr == ' ' || *ptr == '\n')) 
+                                                       ptr++;
+
+                                               if(debug) printf("SceneTokens::read_script %d\n", __LINE__);
+
+                                               char *ptr2 = string;
+                                               char *string_end = string + BCTEXTLEN;
+                                               while(*ptr != ']' && 
+                                                       *ptr != ' ' &&
+                                                       *ptr != '\n' &&
+                                                       ptr < end && 
+                                                       ptr2 < string_end - 1)
+                                               {
+                                                       *ptr2++ = *ptr++;
+                                               }
+                                               *ptr2 = 0;
+                                               if(debug) printf("SceneTokens::read_script %d command=%s\n", __LINE__, string);
+
+// Convert command to code
+                                               if(!strcasecmp(string, "pause"))
+                                               {
+                                                       chunk->command = SceneChunk::PAUSE_COMMAND;
+                                               }
+                                               else
+                                               {
+// TODO: line numbers
+                                                       printf("SceneTokens::read_script %d: Unknown command '%s'\n", 
+                                                               __LINE__, 
+                                                               string);
+                                               }
+                                               if(debug) printf("SceneTokens::read_script %d\n", __LINE__);
+
+// Search for more text
+                                               while(ptr < end && 
+                                                       (*ptr == ']' || *ptr == ' ' || *ptr == '\n')) 
+                                                       ptr++;
+
+// Create new chunk for rest of text
+                                               if(ptr < end)
+                                               {
+                                                       SceneChunk *new_chunk = new SceneChunk(this);
+                                                       new_chunk->character = chunk->character;
+                                                       chunks.insert(new_chunk, i + 1);
+// Move text from end of command to new chunk.
+                                                       new_chunk->append_text(ptr);
+// Parse next chunk in next iteration
+                                                       ptr = end;
+//                                                     i++;
+                                               }
+                                               if(debug) printf("SceneTokens::read_script %d\n", __LINE__);
+
+// Truncate current chunk
+                                               chunk->text[0] = 0;
+                                       }
+                                       else
+// Got non whitespace
+                                       if(*ptr != ' ' &&
+                                               *ptr != '\n')
+                                       {
+                                               got_text = 1;
+                                       }
+
+                                       ptr++;
+                               }
+                       }
+               }
+               
+               
+               if(debug) dump();
+               return 0;
+       }
+
+       printf("SceneTokens::read_script %d: %s %s\n", __LINE__, path, strerror(errno));
+       return 1;
+}
+
+void SceneTokens::convert_path(char *dst, char *src)
+{
+// Absolute path in src
+       if(src[0] == '/')
+       {
+               strcpy(dst, src);
+       }
+       else
+// Relative path
+       {
+// Try directory of script
+               FileSystem fs;
+               fs.extract_dir(dst, path);
+               strcat(dst, src);
+               
+               struct stat ostat;
+               if(stat(dst, &ostat))
+               {
+// Try cinelerra directory
+                       get_exe_path(dst);
+                       strcat(dst, ASSET_PATH);
+                       strcat(dst, src);
+//printf("SceneTokens::convert_path %d %s\n", __LINE__, dst);
+               }
+       }
+}
+
+VFrame* SceneTokens::load_image(char *path)
+{
+       VFrame *result = 0;
+       char complete_path[BCTEXTLEN];
+       convert_path(complete_path, path);
+
+       int64_t size = FileSystem::get_size(complete_path);
+       if(!size)
+       {
+               printf("SceneTokens::load_image %d: Couldn't open %s\n",
+                       __LINE__,
+                       complete_path);
+               return 0;
+       }
+
+       unsigned char *data = new unsigned char[size + 4];
+       FILE *fd = fopen(complete_path, "r");
+       (void)fread(data + 4, 1, size, fd);
+       data[0] = (size >> 24) & 0xff;
+       data[1] = (size >> 16) & 0xff;
+       data[2] = (size >> 8) & 0xff;
+       data[3] = size & 0xff;
+       result = new VFramePng(data, 1.);
+       delete [] data;
+
+
+       if(!BC_CModels::has_alpha(result->get_color_model()) )
+               printf("SceneTokens::load_image %d: image %s has no alpha channel\n", 
+                       __LINE__,
+                       path);
+       return result;
+}
+
+void SceneTokens::render_background(SceneGraph *scene)
+{
+// Decompress background image
+       if(!background_image)
+       {
+               background_image = load_image(background);
+       }
+       
+       if(background_image)
+       {
+               SceneNode *node = new SceneNode("background");
+               scene->append(node);
+               node->image = background_image;
+               node->x = 0;
+               node->y = 0;
+               node->sx = 1;
+               node->sy = 1;
+       }
+}
+
+int SceneTokens::get_memory_usage()
+{
+       int total = 0;
+       if(background_image) total += background_image->get_memory_usage();
+       for(int i = 0; i < total_chunks(); i++)
+       {
+               total += get_chunk(i)->get_memory_usage();
+       }
+       for(int i = 0; i < total_characters(); i++)
+       {
+               total += get_character(i)->get_memory_usage();
+       }
+       return total;
+}
+
+
+void SceneTokens::dump()
+{
+       printf("SceneTokens::dump %d background=%s\n", __LINE__, background);
+       for(int i = 0; i < characters.size(); i++)
+       {
+               characters.get(i)->dump();
+       }
+
+       
+       for(int i = 0; i < chunks.size(); i++)
+       {
+               chunks.get(i)->dump();
+       }
+}
+
+
+
+