--- /dev/null
+/*
+ * 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();
+ }
+}
+
+
+
+