2006-01-16 22:02:54 +01:00
// Copyright (c) 2005, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//
# include <wx/file.h>
# include <wx/gdicmn.h>
# include <wx/dcmemory.h>
# include <wx/filename.h>
# include <wx/tokenzr.h>
# include "automation.h"
# include "ass_file.h"
# include "ass_entry.h"
# include "ass_dialogue.h"
# include "ass_style.h"
# include "options.h"
# include "string_codec.h"
# include "vfr.h"
2006-01-31 22:37:02 +01:00
# ifdef WIN32
2006-01-16 22:02:54 +01:00
# include <windows.h>
2006-01-31 22:37:02 +01:00
# include <wchar.h>
2006-01-16 22:02:54 +01:00
# else
# include <ft2build.h>
# include FT_FREETYPE_H
# endif
extern " C " {
# include <lualib.h>
# include <lauxlib.h>
}
int L_callfunc ( lua_State * L , int nargs , int nresults ) ;
void L_settable ( lua_State * L , int table , wxString & key , lua_Number val ) ;
void L_settable ( lua_State * L , int table , wxString & key , wxString val ) ;
void L_settable_bool ( lua_State * L , int table , wxString & key , bool val ) ;
void L_settable ( lua_State * L , int table , const char * key , lua_Number val ) ;
void L_settable ( lua_State * L , int table , const char * key , wxString val ) ;
void L_settable_bool ( lua_State * L , int table , const char * key , bool val ) ;
void L_settable_kara ( lua_State * L , int table , int index , int duration , wxString & kind , wxString & text , wxString & text_stripped ) ;
// these two assume the table to get from is on the top of the stack
lua_Number L_gettableN ( lua_State * L , const char * key ) ;
wxString L_gettableS ( lua_State * L , const char * key ) ;
bool L_gettableB ( lua_State * L , const char * key ) ;
namespace AutomationHelper {
// helper functions for the scripts
// expect a pointer to the automation script object to be on the private stack
/*
Helper function helper . . .
Get the AutomationScript object associated with a Lua state .
*/
AutomationScript * GetScriptObject ( lua_State * L )
{
lua_pushstring ( L , " aegisub " ) ;
lua_rawget ( L , LUA_REGISTRYINDEX ) ;
AutomationScript * s = ( AutomationScript * ) ( lua_touserdata ( L , - 1 ) ) ;
if ( ! s ) {
lua_pushstring ( L , " Unable to retrieve AutomationScript object from the registry. This should never happen! " ) ;
lua_error ( L ) ; // never returns
}
lua_pop ( L , 1 ) ;
return s ;
}
/*
" Debug hook " function , used for checking if the interpreter has been asked to cancel .
If it has , a Lua error is reported .
*/
void hookfunc ( lua_State * L , lua_Debug * ar )
{
AutomationScript * script = GetScriptObject ( L ) ;
if ( script - > force_cancel ) {
if ( ar - > currentline < 0 ) {
lua_pushstring ( L , " Script forcibly terminated at an unknown line " ) ;
} else {
lua_pushstring ( L , " Script forcibly terminated at line " ) ;
lua_pushnumber ( L , ar - > currentline ) ;
lua_concat ( L , 2 ) ;
}
lua_error ( L ) ;
}
}
/*
function aegisub . output_debug ( text )
Output text to a debug console .
@ text
String . The text to output .
Returns : nothing .
*/
int output_debug ( lua_State * L )
{
AutomationScript * script = GetScriptObject ( L ) ;
// check we were passed a string
if ( lua_gettop ( L ) < 1 ) {
// idiot user (nothing on the stack)
lua_pushstring ( L , " output_debug called with no arguments " ) ;
lua_error ( L ) ; // never returns
} else if ( ! lua_isstring ( L , - 1 ) ) {
// idiot user (didn't pass a string)
lua_pushstring ( L , " output_debug called with non string-compatible argument " ) ;
lua_error ( L ) ; // never returns
}
script - > OutputDebugString ( wxString ( lua_tostring ( L , 1 ) , wxConvUTF8 ) , true ) ;
return 0 ;
}
/*
function aegisub . set_status ( text )
Sets the current status - message . ( Used for progress - reporting . )
@ text
String . The status message .
Returns : nothing .
*/
int set_status ( lua_State * L )
{
AutomationScript * script = GetScriptObject ( L ) ;
// check we were passed a string
if ( lua_gettop ( L ) < 1 ) {
// idiot user (nothing on the stack)
lua_pushstring ( L , " output_debug called with no arguments " ) ;
lua_error ( L ) ; // never returns
} else if ( ! lua_isstring ( L , - 1 ) ) {
// idiot user (didn't pass a string)
lua_pushstring ( L , " output_debug called with non string-compatible argument " ) ;
lua_error ( L ) ; // never returns
}
script - > OutputDebugString ( wxString ( lua_tostring ( L , 1 ) , wxConvUTF8 ) , false ) ;
return 0 ;
}
/*
function aegisub . colorstring_to_rgb ( colorstring )
Convert an ASS color - string to a set of RGB values .
@ colorstring
String . The color - string to convert .
Returns : Four values , all numbers , being the color components in this
order : Red , Green , Blue , Alpha - channel
*/
int colorstring_to_rgb ( lua_State * L )
{
if ( lua_gettop ( L ) < 1 ) {
lua_pushstring ( L , " colorstring_to_rgb called without arguments " ) ;
lua_error ( L ) ;
}
if ( ! lua_isstring ( L , 1 ) ) {
lua_pushstring ( L , " colorstring_to_rgb requires a string type argument " ) ;
lua_error ( L ) ;
}
wxString colorstring ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_pop ( L , 1 ) ;
AssColor rgb ;
rgb . ParseASS ( colorstring ) ;
lua_pushnumber ( L , rgb . r ) ;
lua_pushnumber ( L , rgb . g ) ;
lua_pushnumber ( L , rgb . b ) ;
lua_pushnumber ( L , rgb . a ) ;
return 4 ;
}
/*
function aegisub . report_progress ( percent )
Report the progress of the processing .
@ percent
Number . How much of the data have been processed so far .
Returns : nothing .
*/
int report_progress ( lua_State * L )
{
AutomationScript * script = GetScriptObject ( L ) ;
// check we were passed a string
if ( lua_gettop ( L ) < 1 ) {
// idiot user (nothing on the stack)
lua_pushstring ( L , " report_progress called with no arguments " ) ;
lua_error ( L ) ; // never returns
} else if ( ! lua_isnumber ( L , - 1 ) ) {
// idiot user (didn't pass a string)
lua_pushstring ( L , " report_progress requires a numeric argument " ) ;
lua_error ( L ) ; // never returns
}
lua_Number p = lua_tonumber ( L , - 1 ) ;
if ( p < 0 ) p = 0 ;
if ( p > 100 ) p = 100 ;
p = ( p + 100 ) / 3 ;
script - > ReportProgress ( p ) ;
return 0 ;
}
/*
function aegisub . text_extents ( style , text )
Calculate the on - screen pixel size of the given text using the given style .
@ style
Table . A single style definition like those passed to process_lines .
@ text
String . The text to calculate the extents for . This should not contain
formatting codes , as they will be treated as part of the text .
Returns 4 values :
1 : Number . Width of the text , in pixels .
2 : Number . Height of the text , in pixels .
3 : Number . Descent of the text , in pixels .
4 : Number . External leading for the text , in pixels .
*/
int text_extents ( lua_State * L )
{
// vars for the result
int resx = 0 , resy = 0 , resd = 0 , resl = 0 ;
// get the input
// no error checking for the moment
wxString intext ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
// leave only style table
lua_settop ( L , - 2 ) ;
// read out the relevant parts of style
wxString fontname ( L_gettableS ( L , " fontname " ) ) ;
double fontsize = L_gettableN ( L , " fontsize " ) ;
bool bold = L_gettableB ( L , " bold " ) ;
bool italic = L_gettableB ( L , " italic " ) ;
bool underline = L_gettableB ( L , " underline " ) ;
bool strikeout = L_gettableB ( L , " strikeout " ) ;
double scale_x = L_gettableN ( L , " scale_x " ) ;
double scale_y = L_gettableN ( L , " scale_y " ) ;
int spacing = ( int ) L_gettableN ( L , " spacing " ) ;
2006-01-31 22:37:02 +01:00
int charset = ( int ) L_gettableN ( L , " encoding " ) ;
wxLogDebug ( _T ( " text_extents for: %s:%f:%d%d%d%d:%f:%f:%d:%d " ) , fontname , fontsize , bold , italic , underline , strikeout , scale_x , scale_y , spacing , charset ) ;
# ifdef WIN32
HDC thedc = CreateCompatibleDC ( 0 ) ;
if ( ! thedc ) return 0 ;
SetMapMode ( thedc , MM_TEXT ) ;
fontsize = - MulDiv ( ( int ) ( fontsize + 0.5 ) , GetDeviceCaps ( thedc , LOGPIXELSY ) , 72 ) ;
LOGFONT lf ;
ZeroMemory ( & lf , sizeof ( lf ) ) ;
lf . lfHeight = fontsize ;
lf . lfWeight = bold ? FW_BOLD : FW_NORMAL ;
lf . lfItalic = italic ;
lf . lfUnderline = underline ;
lf . lfStrikeOut = strikeout ;
lf . lfCharSet = charset ;
lf . lfOutPrecision = OUT_TT_PRECIS ;
lf . lfClipPrecision = CLIP_DEFAULT_PRECIS ;
lf . lfQuality = ANTIALIASED_QUALITY ;
lf . lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE ;
wcsncpy ( lf . lfFaceName , fontname . wc_str ( ) , 32 ) ;
HFONT thefont = CreateFontIndirect ( & lf ) ;
if ( ! thefont ) return 0 ;
SelectObject ( thedc , thefont ) ;
SIZE sz ;
if ( spacing ) {
resx = 0 ;
for ( unsigned int i = 0 ; i < intext . length ( ) ; i + + ) {
wchar_t c = intext [ i ] ;
GetTextExtentPoint32 ( thedc , & c , 1 , & sz ) ;
resx + = sz . cx + spacing ;
resy = sz . cy ;
}
} else {
GetTextExtentPoint32 ( thedc , intext . wc_str ( ) , intext . Length ( ) , & sz ) ;
resx = sz . cx ;
resy = sz . cy ;
}
// HACKISH FIX! This seems to work, but why? It shouldn't be needed?!?
fontsize = L_gettableN ( L , " fontsize " ) ;
resx = ( int ) ( resx * fontsize / resy + 0.5 ) ;
resy = ( int ) ( fontsize + 0.5 ) ;
TEXTMETRIC tm ;
GetTextMetrics ( thedc , & tm ) ;
resd = tm . tmDescent ;
resl = tm . tmExternalLeading ;
DeleteObject ( thedc ) ;
DeleteObject ( thefont ) ;
2006-01-16 22:02:54 +01:00
2006-01-31 22:37:02 +01:00
# else // not WIN32
2006-01-16 22:02:54 +01:00
wxMemoryDC thedc ;
// fix fontsize to be 72 DPI
fontsize = - FT_MulDiv ( ( int ) ( fontsize + 0.5 ) , 72 , thedc . GetPPI ( ) . y ) ;
// now try to get a font!
// use the font list to get some caching... (chance is the script will need the same font very often)
2006-01-31 22:37:02 +01:00
// USING wxTheFontList SEEMS TO CAUSE BAD LEAKS!
2006-01-16 22:02:54 +01:00
//wxFont *thefont = wxTheFontList->FindOrCreateFont(
wxFont thefont (
fontsize ,
wxFONTFAMILY_DEFAULT ,
italic ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL ,
bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL ,
underline ,
fontname ,
wxFONTENCODING_SYSTEM ) ;
thedc . SetFont ( thefont ) ;
2006-01-31 22:37:02 +01:00
if ( spacing ) {
// If there's inter-character spacing, kerning info must not be used, so calculate width per character
for ( unsigned int i = 0 ; i < intext . length ( ) ; i + + ) {
int a , b , c , d ;
thedc . GetTextExtent ( intext [ i ] , & a , & b , & c , & d ) ;
resx + = a + spacing ;
resy = b > resy ? b : resy ;
resd = c > resd ? c : resd ;
resl = d > resl ? d : resl ;
}
} else {
// If the inter-character spacing should be zero, kerning info can (and must) be used, so calculate everything in one go
thedc . GetTextExtent ( intext , & resx , & resy , & resd , & resl ) ;
2006-01-16 22:02:54 +01:00
}
2006-01-31 22:37:02 +01:00
# endif
2006-01-16 22:02:54 +01:00
2006-01-31 22:37:02 +01:00
// Compensate for scaling
resx = ( int ) ( scale_x / 100 * resx + 0.5 ) ;
2006-01-16 22:02:54 +01:00
resy = ( int ) ( scale_y / 100 * resy + 0.5 ) ;
resd = ( int ) ( scale_y / 100 * resd + 0.5 ) ;
resl = ( int ) ( scale_y / 100 * resl + 0.5 ) ;
lua_pushnumber ( L , resx ) ;
lua_pushnumber ( L , resy ) ;
lua_pushnumber ( L , resd ) ;
lua_pushnumber ( L , resl ) ;
return 4 ;
}
/*
function aegisub . frame_from_ms ( ms )
Return the video frame - number for the given time .
@ ms
Number . Time in miliseconds to get the frame number for .
Returns : A number , the frame numer . If there is no framerate data , returns
nil .
*/
int frame_from_ms ( lua_State * L )
{
int ms = ( int ) lua_tonumber ( L , - 1 ) ;
lua_pop ( L , 1 ) ;
if ( VFR_Output . loaded ) {
lua_pushnumber ( L , VFR_Output . CorrectFrameAtTime ( ms , true ) ) ;
return 1 ;
} else {
lua_pushnil ( L ) ;
return 1 ;
}
}
/*
function aegisub . ms_from_frame ( frame )
Returns the start - time for the given video frame - number .
@ frame
Number . Frame - number to get start - time from .
Returns : A number , the start - time of the frame . If there is no framerate
data , returns nil .
*/
int ms_from_frame ( lua_State * L )
{
int frame = ( int ) lua_tonumber ( L , - 1 ) ;
lua_pop ( L , 1 ) ;
if ( VFR_Output . loaded ) {
lua_pushnumber ( L , VFR_Output . CorrectTimeAtFrame ( frame , true ) ) ;
return 1 ;
} else {
lua_pushnil ( L ) ;
return 1 ;
}
}
/*
function include ( filename )
@ filename
String . Name of the file to include .
Returns : Depends on the script included .
*/
int include ( lua_State * L )
{
AutomationScript * script = GetScriptObject ( L ) ;
if ( ! lua_isstring ( L , 1 ) ) {
lua_pushstring ( L , " First argument to the include function must be a string. " ) ;
lua_error ( L ) ;
}
wxString fnames ( lua_tostring ( L , 1 ) , wxConvUTF8 ) ;
wxFileName fname ( fnames ) ;
if ( fname . GetDirCount ( ) = = 0 ) {
// filename only
fname = script - > include_path . FindAbsoluteValidPath ( fnames ) ;
} else if ( fname . IsRelative ( ) ) {
// relative path
wxFileName sfname ( script - > filename ) ;
fname . MakeAbsolute ( sfname . GetPath ( true ) ) ;
} else {
// absolute path, invalid
lua_pushstring ( L , " Filename passed to include seems to have an absolute path, which is not allowed. " ) ;
lua_error ( L ) ;
}
if ( ! fname . IsOk ( ) | | ! fname . FileExists ( ) ) {
{
// need to make a new scope here, so the char buffer can go out of scope before lua_error() makes a longjmp
wxCharBuffer errmsg = wxString : : Format ( _T ( " The file could not be included, not found. \" %s \" " ) , fnames . c_str ( ) ) . mb_str ( wxConvUTF8 ) ;
lua_pushstring ( L , errmsg . data ( ) ) ;
}
lua_error ( L ) ;
}
AutomationScriptFile * sfile ;
sfile = AutomationScriptFile : : CreateFromFile ( fname . GetFullPath ( ) ) ;
wxCharBuffer fnamebuf = fname . GetFullName ( ) . mb_str ( wxConvUTF8 ) ;
switch ( luaL_loadbuffer ( L , sfile - > scriptdata , sfile - > scriptlen , fnamebuf . data ( ) ) ) {
// FIXME: these should be made into lua_error() things instead... probably
case 0 :
// success!
break ;
case LUA_ERRSYNTAX :
throw AutomationError ( wxString : : Format ( _T ( " Lua syntax error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
case LUA_ERRMEM :
throw AutomationError ( wxString : : Format ( _T ( " Lua memory allocation error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
default :
throw AutomationError ( wxString : : Format ( _T ( " Lua unknown error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
}
delete sfile ;
// top of stack before the call (correct for the function itself being on stack)
int pretop = lua_gettop ( L ) - 1 ;
// call the loaded script
lua_call ( L , 0 , LUA_MULTRET ) ;
// calculate the number of results the script produced
return lua_gettop ( L ) - pretop ;
}
}
/*
Call a Lua function without risking killing the entire program
Just throw a C + + exception instead : )
Really just a thin wrapper around lua_pcall
*/
inline int L_callfunc ( lua_State * L , int nargs , int nresults )
{
int res = lua_pcall ( L , nargs , nresults , 0 ) ;
switch ( res ) {
case LUA_ERRRUN :
throw AutomationError ( wxString : : Format ( _T ( " Lua runtime error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
case LUA_ERRMEM :
throw AutomationError ( wxString : : Format ( _T ( " Lua memory allocation error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
case LUA_ERRERR :
// shouldn't happen as an error handling function isn't being used
throw AutomationError ( wxString : : Format ( _T ( " Lua error calling error handler: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
default :
// success!
return res ;
}
}
inline void L_settable ( lua_State * L , int table , wxString & key , lua_Number val )
{
L_settable ( L , table , key . mb_str ( wxConvUTF8 ) , val ) ;
}
inline void L_settable ( lua_State * L , int table , wxString & key , wxString val )
{
//wxLogMessage(_T("Wrapping adding of string at index '%s': %s"), key, val);
L_settable ( L , table , key . mb_str ( wxConvUTF8 ) , val ) ;
}
inline void L_settable_bool ( lua_State * L , int table , wxString & key , bool val )
{
L_settable_bool ( L , table , key . mb_str ( wxConvUTF8 ) , val ) ;
}
inline void L_settable ( lua_State * L , int table , const char * key , lua_Number val )
{
lua_pushstring ( L , key ) ;
lua_pushnumber ( L , val ) ;
if ( table > 0 | | table < - 100 ) {
lua_settable ( L , table ) ;
} else {
lua_settable ( L , table - 2 ) ;
}
}
inline void L_settable ( lua_State * L , int table , const char * key , wxString val )
{
//wxLogMessage(_T("Adding string at index '%s': %s"), wxString(key, wxConvUTF8), val);
lua_pushstring ( L , key ) ;
lua_pushstring ( L , val . mb_str ( wxConvUTF8 ) ) ;
if ( table > 0 | | table < - 100 ) {
lua_settable ( L , table ) ;
} else {
lua_settable ( L , table - 2 ) ;
}
}
inline void L_settable_bool ( lua_State * L , int table , const char * key , bool val )
{
lua_pushstring ( L , key ) ;
lua_pushboolean ( L , val ? 1 : 0 ) ;
if ( table > 0 | | table < - 100 ) {
lua_settable ( L , table ) ;
} else {
lua_settable ( L , table - 2 ) ;
}
}
inline void L_settable_kara ( lua_State * L , int table , int index , int duration , wxString & kind , wxString & text , wxString & text_stripped )
{
lua_newtable ( L ) ;
L_settable ( L , - 1 , " duration " , duration ) ;
L_settable ( L , - 1 , " kind " , kind ) ;
L_settable ( L , - 1 , " text " , text ) ;
L_settable ( L , - 1 , " text_stripped " , text_stripped ) ;
if ( table > 0 | | table < - 100 ) {
lua_rawseti ( L , table , index ) ;
} else {
lua_rawseti ( L , table - 1 , index ) ;
}
}
lua_Number L_gettableN ( lua_State * L , const char * key )
{
lua_pushstring ( L , key ) ;
lua_gettable ( L , - 2 ) ;
lua_Number res = lua_tonumber ( L , - 1 ) ;
lua_settop ( L , - 2 ) ;
return res ;
}
wxString L_gettableS ( lua_State * L , const char * key )
{
lua_pushstring ( L , key ) ;
lua_gettable ( L , - 2 ) ;
wxString res ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_settop ( L , - 2 ) ;
return res ;
}
bool L_gettableB ( lua_State * L , const char * key )
{
lua_pushstring ( L , key ) ;
lua_gettable ( L , - 2 ) ;
bool res = lua_toboolean ( L , - 1 ) ! = 0 ;
lua_settop ( L , - 2 ) ;
return res ;
}
AutomationError : : AutomationError ( wxString msg )
: message ( msg )
{
// nothing to do here...
}
wxString AutomationScriptConfiguration : : serialize ( )
{
wxString result ;
for ( std : : vector < AutomationScriptConfigurationOption > : : iterator opt = options . begin ( ) ; opt ! = options . end ( ) ; opt + + ) {
switch ( opt - > kind ) {
case COK_TEXT :
case COK_STYLE :
result < < wxString : : Format ( _T ( " %s:%s| " ) , opt - > name . c_str ( ) , inline_string_encode ( opt - > value . stringval ) . c_str ( ) ) ;
break ;
case COK_INT :
result < < wxString : : Format ( _T ( " %s:%d| " ) , opt - > name . c_str ( ) , opt - > value . intval ) ;
break ;
case COK_FLOAT :
result < < wxString : : Format ( _T ( " %s:%e| " ) , opt - > name . c_str ( ) , opt - > value . floatval ) ;
break ;
case COK_BOOL :
result < < wxString : : Format ( _T ( " %s:%d| " ) , opt - > name . c_str ( ) , opt - > value . boolval ? 1 : 0 ) ;
break ;
case COK_COLOUR :
result < < wxString : : Format ( _T ( " %s:%s| " ) , opt - > name . c_str ( ) , opt - > value . colourval . GetASSFormatted ( false ) . c_str ( ) ) ;
break ;
default :
// The rest aren't stored
break ;
}
}
if ( result . Last ( ) = = _T ( ' | ' ) )
result . RemoveLast ( ) ;
return result ;
}
void AutomationScriptConfiguration : : unserialize ( wxString & settings )
{
//wxLogMessage(_T("Unserializing config string: %s"), settings);
wxStringTokenizer toker ( settings , _T ( " | " ) , wxTOKEN_STRTOK ) ;
while ( toker . HasMoreTokens ( ) ) {
// get the parts of this setting
wxString setting = toker . GetNextToken ( ) ;
//wxLogMessage(_T("Got token: %s"), setting);
wxString optname = setting . BeforeFirst ( _T ( ' : ' ) ) ;
wxString optval = setting . AfterFirst ( _T ( ' : ' ) ) ;
//wxLogMessage(_T("Split into: \"%s\" and \"%s\""), optname, optval);
// find the setting in the list loaded from the script
std : : vector < AutomationScriptConfigurationOption > : : iterator opt = options . begin ( ) ;
while ( opt ! = options . end ( ) & & opt - > name ! = optname )
opt + + ;
if ( opt ! = options . end ( ) ) {
//wxLogMessage(_T("Found the option!"));
// ok, found the option!
switch ( opt - > kind ) {
case COK_TEXT :
case COK_STYLE :
opt - > value . stringval = inline_string_decode ( optval ) ;
//wxLogMessage(_T("Decoded string to: %s"), opt->value.stringval);
break ;
case COK_INT :
{
long n ;
optval . ToLong ( & n , 10 ) ;
opt - > value . intval = n ;
}
break ;
case COK_FLOAT :
optval . ToDouble ( & opt - > value . floatval ) ;
break ;
case COK_BOOL :
opt - > value . boolval = optval = = _T ( " 1 " ) ;
break ;
case COK_COLOUR :
opt - > value . colourval . ParseASS ( optval ) ;
break ;
}
}
}
}
void AutomationScriptConfiguration : : load_from_lua ( lua_State * L )
{
//wxLogMessage(_T("Loading configuration options from script"));
present = false ;
if ( ! lua_istable ( L , - 1 ) ) {
return ;
}
//wxLogMessage(_T("The script does have config options, good!"));
int i = 1 ;
while ( true ) {
// get an element from the array
//wxLogMessage(_T("Getting option %d (stacktop is %d)"), i, lua_gettop(L));
lua_pushnumber ( L , i ) ;
lua_gettable ( L , - 2 ) ;
// check if it was a table
if ( ! lua_istable ( L , - 1 ) ) {
//wxLogMessage(_T("Damn! Not an option... breaking out (actual type was %d)"), lua_type(L, -1));
lua_pop ( L , 1 ) ;
break ;
}
//wxLogMessage(_T("Yay! It was an option, adding another blank option thing to the list"));
// add a new config option and fill it
{
AutomationScriptConfigurationOption opt ;
options . push_back ( opt ) ;
}
AutomationScriptConfigurationOption & opt = options . back ( ) ;
// get the "kind"
lua_pushstring ( L , " kind " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isstring ( L , - 1 ) ) {
// use C standard lib functions here, as it's probably faster than messing around with unicode
// lua is known to always properly null-terminate strings, and the strings are known to be pure ascii
const char * kind = lua_tostring ( L , - 1 ) ;
if ( strcmp ( kind , " label " ) = = 0 ) {
opt . kind = COK_LABEL ;
} else if ( strcmp ( kind , " text " ) = = 0 ) {
opt . kind = COK_TEXT ;
} else if ( strcmp ( kind , " int " ) = = 0 ) {
opt . kind = COK_INT ;
} else if ( strcmp ( kind , " float " ) = = 0 ) {
opt . kind = COK_FLOAT ;
} else if ( strcmp ( kind , " bool " ) = = 0 ) {
opt . kind = COK_BOOL ;
} else if ( strcmp ( kind , " colour " ) = = 0 ) {
opt . kind = COK_COLOUR ;
} else if ( strcmp ( kind , " style " ) = = 0 ) {
opt . kind = COK_STYLE ;
} else {
opt . kind = COK_INVALID ;
}
} else {
opt . kind = COK_INVALID ;
}
//wxLogMessage(_T("Got kind: %d"), opt.kind);
// remove "kind" string from stack again
lua_pop ( L , 1 ) ;
// no need to check for rest if this one is already deemed invalid
if ( opt . kind ! = COK_INVALID ) {
// name
lua_pushstring ( L , " name " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isstring ( L , - 1 ) ) {
opt . name = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_pop ( L , 1 ) ;
} else {
lua_pop ( L , 1 ) ;
// no name means invalid option
opt . kind = COK_INVALID ;
goto continue_invalid_option ;
}
//wxLogMessage(_T("Got name: %s"), opt.name);
// label
lua_pushstring ( L , " label " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isstring ( L , - 1 ) ) {
opt . label = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_pop ( L , 1 ) ;
} else {
lua_pop ( L , 1 ) ;
// label is also required
opt . kind = COK_INVALID ;
goto continue_invalid_option ;
}
assert ( opt . kind ! = COK_INVALID ) ;
// hint
lua_pushstring ( L , " hint " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isstring ( L , - 1 ) ) {
opt . hint = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
} else {
opt . hint = _T ( " " ) ;
}
lua_pop ( L , 1 ) ;
// min
lua_pushstring ( L , " min " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isnumber ( L , - 1 ) ) {
opt . min . isset = true ;
opt . min . floatval = lua_tonumber ( L , - 1 ) ;
opt . min . intval = ( int ) opt . min . floatval ;
} else {
opt . min . isset = false ;
}
lua_pop ( L , 1 ) ;
// max
lua_pushstring ( L , " max " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isnumber ( L , - 1 ) ) {
opt . max . isset = true ;
opt . max . floatval = lua_tonumber ( L , - 1 ) ;
opt . max . intval = ( int ) opt . max . floatval ;
} else {
opt . max . isset = false ;
}
lua_pop ( L , 1 ) ;
// default (this is going to kill me)
lua_pushstring ( L , " default " ) ;
lua_gettable ( L , - 2 ) ;
switch ( opt . kind ) {
case COK_LABEL :
// nothing to do, nothing expected
break ;
case COK_TEXT :
case COK_STYLE :
// expect it to be a string
if ( lua_isstring ( L , - 1 ) ) {
opt . default_val . stringval = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
} else {
// not a string, baaaad scripter
opt . kind = COK_INVALID ;
}
break ;
case COK_INT :
case COK_FLOAT :
// expect it to be a number
if ( lua_isnumber ( L , - 1 ) ) {
opt . default_val . floatval = lua_tonumber ( L , - 1 ) ;
opt . default_val . intval = ( int ) opt . default_val . floatval ;
} else {
opt . kind = COK_INVALID ;
}
break ;
case COK_BOOL :
// expect it to be a bool
if ( lua_isboolean ( L , - 1 ) ) {
opt . default_val . boolval = lua_toboolean ( L , - 1 ) ! = 0 ;
} else {
opt . kind = COK_INVALID ;
}
break ;
case COK_COLOUR :
// expect it to be a ass hex colour formatted string
if ( lua_isstring ( L , - 1 ) ) {
opt . default_val . stringval = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
opt . default_val . colourval . ParseASS ( opt . default_val . stringval ) ; // and hope this goes well!
} else {
opt . kind = COK_INVALID ;
}
break ;
}
opt . value = opt . default_val ;
lua_pop ( L , 1 ) ;
}
// so we successfully got an option added, so at least there is a configuration present now
present = true ;
continue_invalid_option :
// clean up and prepare for next iteration
lua_pop ( L , 1 ) ;
i + + ;
}
}
void AutomationScriptConfiguration : : store_to_lua ( lua_State * L )
{
// we'll always need a new table, no matter what
lua_newtable ( L ) ;
//wxLogMessage(_T("Created table for configuration data (top=%d)"), lua_gettop(L));
for ( std : : vector < AutomationScriptConfigurationOption > : : iterator opt = options . begin ( ) ; opt ! = options . end ( ) ; opt + + ) {
//wxLogMessage(_T("Storing option named '%s' (top=%d)"), opt->name, lua_gettop(L));
switch ( opt - > kind ) {
case COK_INVALID :
case COK_LABEL :
//wxLogMessage(_T("Nothing to store"));
break ;
case COK_TEXT :
case COK_STYLE :
//wxLogMessage(_T("Storing string value"));
L_settable ( L , - 1 , opt - > name , opt - > value . stringval ) ;
break ;
case COK_INT :
//wxLogMessage(_T("Storing int value"));
L_settable ( L , - 1 , opt - > name , opt - > value . intval ) ;
break ;
case COK_FLOAT :
//wxLogMessage(_T("Storing float value"));
L_settable ( L , - 1 , opt - > name , opt - > value . floatval ) ;
break ;
case COK_BOOL :
//wxLogMessage(_T("Storing bool value"));
L_settable_bool ( L , - 1 , opt - > name , opt - > value . boolval ) ;
break ;
case COK_COLOUR :
//wxLogMessage(_T("Storing colourvalue"));
L_settable ( L , - 1 , opt - > name , opt - > value . colourval . GetASSFormatted ( false , false ) ) ;
break ;
default :
//wxLogMessage(_T("Felt into default handler?!?"));
break ;
}
}
//wxLogMessage(_T("Finished storing configuration data (top=%d)"), lua_gettop(L));
}
AutomationScript : : AutomationScript ( AutomationScriptFile * script )
{
force_cancel = false ;
filename = script - > filename ;
progress_reporter = 0 ;
debug_reporter = 0 ;
// get a lua object
L = lua_open ( ) ;
// put a pointer to this object in the registry index
lua_pushstring ( L , " aegisub " ) ;
lua_pushlightuserdata ( L , this ) ;
lua_rawset ( L , LUA_REGISTRYINDEX ) ;
// set up the cancelling hook, call every 100 instructions
lua_sethook ( L , AutomationHelper : : hookfunc , LUA_MASKCOUNT , 100 ) ;
// provide some standard libraries
luaopen_base ( L ) ;
luaopen_string ( L ) ;
luaopen_table ( L ) ;
luaopen_math ( L ) ;
# ifdef _DEBUG
luaopen_debug ( L ) ;
# endif
// but no I/O, OS or debug facilities, those aren't safe
// disable the dofile() function
lua_pushstring ( L , " dofile " ) ;
lua_pushnil ( L ) ;
lua_settable ( L , LUA_GLOBALSINDEX ) ;
// create an include function better suited aegisub later
// the path object is needed for this
include_path . EnsureFileAccessible ( script - > filename ) ;
{
wxStringTokenizer toker ( Options . AsText ( _T ( " Automation Include Path " ) ) , _T ( " | " ) , false ) ;
while ( toker . HasMoreTokens ( ) ) {
wxFileName path ( toker . GetNextToken ( ) ) ;
if ( ! path . IsOk ( ) ) continue ;
if ( path . IsRelative ( ) ) continue ;
if ( ! path . DirExists ( ) ) continue ;
if ( include_path . Member ( path . GetLongPath ( ) ) ) continue ;
include_path . Add ( path . GetLongPath ( ) ) ;
}
}
// add the include function to the global environment
lua_pushstring ( L , " include " ) ;
lua_pushcfunction ( L , AutomationHelper : : include ) ;
lua_settable ( L , LUA_GLOBALSINDEX ) ;
// create the "aegisub" table and fill it with some function pointers
// create the table and add it to the global environment
lua_pushstring ( L , " aegisub " ) ;
lua_newtable ( L ) ;
lua_settable ( L , LUA_GLOBALSINDEX ) ;
// get it back onto the stack
lua_pushstring ( L , " aegisub " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
// get the index of the table on the stack
int tabid = lua_gettop ( L ) ;
// now register some functions!
lua_pushstring ( L , " output_debug " ) ;
lua_pushcfunction ( L , AutomationHelper : : output_debug ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " set_status " ) ;
lua_pushcfunction ( L , AutomationHelper : : set_status ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " report_progress " ) ;
lua_pushcfunction ( L , AutomationHelper : : report_progress ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " colorstring_to_rgb " ) ;
lua_pushcfunction ( L , AutomationHelper : : colorstring_to_rgb ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " text_extents " ) ;
lua_pushcfunction ( L , AutomationHelper : : text_extents ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " frame_from_ms " ) ;
lua_pushcfunction ( L , AutomationHelper : : frame_from_ms ) ;
lua_settable ( L , tabid ) ;
lua_pushstring ( L , " ms_from_frame " ) ;
lua_pushcfunction ( L , AutomationHelper : : ms_from_frame ) ;
lua_settable ( L , tabid ) ;
// and no more need for that tabid
lua_settop ( L , tabid - 1 ) ;
// ok, finally the environment is set up!
// now try to load the script
{
switch ( luaL_loadbuffer ( L , script - > scriptdata , script - > scriptlen , " user script " ) ) {
case 0 :
// success!
break ;
case LUA_ERRSYNTAX :
throw AutomationError ( wxString : : Format ( _T ( " Lua syntax error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
case LUA_ERRMEM :
throw AutomationError ( wxString : : Format ( _T ( " Lua memory allocation error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
default :
throw AutomationError ( wxString : : Format ( _T ( " Lua unknown error: %s " ) , wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . c_str ( ) ) ) ;
break ;
}
}
// it's loaded and is now a function at the top of the stack
// it doesn't take any arguments and doesn't return anything
// so let's try executing it by calling!
L_callfunc ( L , 0 , 0 ) ;
// so, the script should be loaded
// now try to get the script data!
// first the version
lua_pushstring ( L , " version " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
if ( ! lua_isnumber ( L , - 1 ) ) {
throw AutomationError ( wxString ( _T ( " Script error: 'version' value not found or not a number " ) ) ) ;
}
version = lua_tonumber ( L , - 1 ) ;
lua_settop ( L , - 2 ) ;
if ( version < 3 | | version > 4 ) {
// invalid version
throw AutomationError ( wxString ( _T ( " Script error: 'version' value invalid for this version of Automation " ) ) ) ;
}
// kind
lua_pushstring ( L , " kind " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
if ( ! lua_isstring ( L , - 1 ) ) {
throw AutomationError ( wxString ( _T ( " Script error: 'kind' value not found or not a string " ) ) ) ;
}
kind = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_settop ( L , - 2 ) ;
// name
lua_pushstring ( L , " name " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
if ( ! lua_isstring ( L , - 1 ) ) {
throw AutomationError ( wxString ( _T ( " Script error: 'name' value not found or not a string " ) ) ) ;
}
name = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_settop ( L , - 2 ) ;
// description (optional)
lua_pushstring ( L , " description " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
if ( lua_isstring ( L , - 1 ) ) {
description = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_settop ( L , - 2 ) ;
} else {
description = _T ( " " ) ;
}
// process_lines (just check if it's there, no need to save it anywhere)
lua_pushstring ( L , " process_lines " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
if ( ! lua_isfunction ( L , - 1 ) ) {
throw AutomationError ( wxString ( _T ( " Script error: No 'process_lines' function provided " ) ) ) ;
}
lua_settop ( L , - 2 ) ;
// configuration (let the config object do all the loading)
lua_pushstring ( L , " configuration " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
//wxLogMessage(_T("Calling configuration.load_from_lua()"));
configuration . load_from_lua ( L ) ;
lua_settop ( L , - 2 ) ;
// done!
}
AutomationScript : : ~ AutomationScript ( )
{
lua_close ( L ) ;
}
void AutomationScript : : OutputDebugString ( wxString str , bool isdebug )
{
//wxLogMessage(_T("automation message: ") + str);
if ( debug_reporter ) {
debug_reporter ( str , isdebug , this , debug_target ) ;
}
return ;
}
void AutomationScript : : ReportProgress ( float progress )
{
//wxLogMessage(wxString::Format(_T("automation progress: %.1f%%"), progress));
if ( progress_reporter ) {
progress_reporter ( progress , this , progress_target ) ;
}
return ;
}
int AutomationScript : : L_panicfunc ( lua_State * L )
{
wxLogError ( _T ( " Lua produced an error. Attempting to recover. " ) ) ;
longjmp ( AutomationHelper : : GetScriptObject ( L ) - > panicjmp , lua_gettop ( L ) ) ;
}
void AutomationScript : : process_lines ( AssFile * input )
{
// prepare for panic...
if ( int ret = setjmp ( panicjmp ) ) {
wxLogError ( wxString : : Format ( _T ( " Returned out of Lua environment. Size of stack before: %d " ) , ret ) ) ;
# ifdef _DEBUG
wxLogError (
# else
wxLogFatalError (
# endif
_T ( " Due to an internal error in the Lua engine, the internal state of Aegisub might have become inconsistent " )
_T ( " and cannot continue. If you can reproduce this error, please report it to the developers. " ) ) ;
lua_close ( L ) ;
return ;
} else {
lua_atpanic ( L , AutomationScript : : L_panicfunc ) ;
}
// start by generating lua representations of the data...
// maybe it's safest to start by making plenty of space on the stack
if ( ! lua_checkstack ( L , 100 ) ) {
throw AutomationError ( wxString ( _T ( " Lua error: Unable to allocate stack space " ) ) ) ;
}
OutputDebugString ( wxString ( _T ( " Preparing subtitle data " ) ) ) ;
// first put the function itself on the stack
lua_pushstring ( L , " process_lines " ) ;
lua_gettable ( L , LUA_GLOBALSINDEX ) ;
// now put the three arguments on the stack
// first argument: the metadata table
lua_newtable ( L ) ;
L_settable ( L , - 1 , " res_x " , input - > GetScriptInfoAsInt ( _T ( " PlayResX " ) ) ) ;
L_settable ( L , - 1 , " res_y " , input - > GetScriptInfoAsInt ( _T ( " PlayResY " ) ) ) ;
// second and third arguments: styles and events tables
lua_newtable ( L ) ;
int styletab = lua_gettop ( L ) ;
lua_newtable ( L ) ;
int eventtab = lua_gettop ( L ) ;
int numstyles = 0 , numevents = 0 ;
// fill the styles and events tables
int processed_lines = 1 ;
for ( std : : list < AssEntry * > : : iterator i = input - > Line . begin ( ) ; i ! = input - > Line . end ( ) ; i + + , processed_lines + + ) {
AssEntry * e = * i ;
if ( ! e - > Valid ) continue ;
if ( e - > Type = = ENTRY_STYLE ) {
AssStyle * style = e - > GetAsStyle ( e ) ;
// gonna need a table to put the style data into
lua_newtable ( L ) ;
// put the table into index N in the style table
lua_pushvalue ( L , - 1 ) ;
lua_rawseti ( L , styletab , numstyles ) ;
// and put it into its named index
lua_pushstring ( L , style - > name . mb_str ( wxConvUTF8 ) ) ;
lua_pushvalue ( L , - 2 ) ;
lua_settable ( L , styletab ) ;
// so now the table is regged and stuff, put some data into it
L_settable ( L , - 1 , " name " , style - > name ) ;
L_settable ( L , - 1 , " fontname " , style - > font ) ;
L_settable ( L , - 1 , " fontsize " , style - > fontsize ) ;
L_settable ( L , - 1 , " color1 " , style - > primary . GetASSFormatted ( true , true ) ) ;
L_settable ( L , - 1 , " color2 " , style - > secondary . GetASSFormatted ( true , true ) ) ;
L_settable ( L , - 1 , " color3 " , style - > outline . GetASSFormatted ( true , true ) ) ;
L_settable ( L , - 1 , " color4 " , style - > shadow . GetASSFormatted ( true , true ) ) ;
L_settable_bool ( L , - 1 , " bold " , style - > bold ) ;
L_settable_bool ( L , - 1 , " italic " , style - > italic ) ;
L_settable_bool ( L , - 1 , " underline " , style - > underline ) ;
L_settable_bool ( L , - 1 , " strikeout " , style - > strikeout ) ;
L_settable ( L , - 1 , " scale_x " , style - > scalex ) ;
L_settable ( L , - 1 , " scale_y " , style - > scaley ) ;
L_settable ( L , - 1 , " spacing " , style - > spacing ) ;
L_settable ( L , - 1 , " angle " , style - > angle ) ;
L_settable ( L , - 1 , " borderstyle " , style - > borderstyle ) ;
L_settable ( L , - 1 , " outline " , style - > outline_w ) ;
L_settable ( L , - 1 , " shadow " , style - > shadow_w ) ;
L_settable ( L , - 1 , " align " , style - > alignment ) ;
L_settable ( L , - 1 , " margin_l " , style - > MarginL ) ;
L_settable ( L , - 1 , " margin_r " , style - > MarginR ) ;
L_settable ( L , - 1 , " margin_v " , style - > MarginV ) ;
L_settable ( L , - 1 , " encoding " , style - > encoding ) ;
// and get that table off the stack again
lua_settop ( L , - 2 ) ;
numstyles + + ;
} else if ( e - > group = = _T ( " [Events] " ) ) {
if ( e - > Type ! = ENTRY_DIALOGUE ) {
// not a dialogue/comment event
// start checking for a blank line
2006-02-20 22:32:58 +01:00
if ( e - > data . IsEmpty ( ) ) {
2006-01-16 22:02:54 +01:00
lua_newtable ( L ) ;
L_settable ( L , - 1 , " kind " , wxString ( _T ( " blank " ) ) ) ;
} else if ( e - > data [ 0 ] = = _T ( ' ; ' ) ) {
// semicolon comment
lua_newtable ( L ) ;
L_settable ( L , - 1 , " kind " , wxString ( _T ( " scomment " ) ) ) ;
L_settable ( L , - 1 , " text " , e - > data . Mid ( 1 ) ) ;
} else {
// not a blank line and not a semicolon comment
// just skip...
continue ;
}
} else {
// ok, so it is a dialogue/comment event
// massive handling :(
lua_newtable ( L ) ;
assert ( e - > Type = = ENTRY_DIALOGUE ) ;
AssDialogue * dia = e - > GetAsDialogue ( e ) ;
// kind of line
if ( dia - > Comment ) {
L_settable ( L , - 1 , " kind " , wxString ( _T ( " comment " ) ) ) ;
} else {
L_settable ( L , - 1 , " kind " , wxString ( _T ( " dialogue " ) ) ) ;
}
L_settable ( L , - 1 , " layer " , dia - > Layer ) ;
L_settable ( L , - 1 , " start_time " , dia - > Start . GetMS ( ) / 10 ) ;
L_settable ( L , - 1 , " end_time " , dia - > End . GetMS ( ) / 10 ) ;
L_settable ( L , - 1 , " style " , dia - > Style ) ;
L_settable ( L , - 1 , " name " , dia - > Actor ) ;
L_settable ( L , - 1 , " margin_l " , dia - > MarginL ) ;
L_settable ( L , - 1 , " margin_r " , dia - > MarginR ) ;
L_settable ( L , - 1 , " margin_v " , dia - > MarginV ) ;
L_settable ( L , - 1 , " effect " , dia - > Effect ) ;
L_settable ( L , - 1 , " text " , dia - > Text ) ;
// so that's the easy part
// now for the stripped text and *ugh* the karaoke!
// prepare for stripped text
wxString text_stripped = _T ( " " ) ;
L_settable ( L , - 1 , " text_stripped " , 0 ) ; // dummy item
// prepare karaoke table
lua_newtable ( L ) ;
lua_pushstring ( L , " karaoke " ) ;
lua_pushvalue ( L , - 2 ) ;
lua_settable ( L , - 4 ) ;
// now the top of the stack is the karaoke table, and it's present in the dialogue table
int kcount = 0 ;
int kdur = 0 ;
wxString kkind = _T ( " " ) ;
wxString ktext = _T ( " " ) ;
wxString ktext_stripped = _T ( " " ) ;
for ( std : : vector < AssDialogueBlock * > : : iterator block = dia - > Blocks . begin ( ) ; block ! = dia - > Blocks . end ( ) ; block + + ) {
switch ( ( * block ) - > type ) {
case BLOCK_BASE :
throw _T ( " BLOCK_BASE found processing dialogue blocks. This should never happen. " ) ;
case BLOCK_PLAIN :
ktext + = ( * block ) - > text ;
ktext_stripped + = ( * block ) - > text ;
text_stripped + = ( * block ) - > text ;
break ;
case BLOCK_DRAWING :
ktext + = ( * block ) - > text ;
break ;
case BLOCK_OVERRIDE : {
bool brackets_open = false ;
std : : vector < AssOverrideTag * > & tags = ( * block ) - > GetAsOverride ( * block ) - > Tags ;
for ( std : : vector < AssOverrideTag * > : : iterator tag = tags . begin ( ) ; tag ! = tags . end ( ) ; tag + + ) {
if ( ! ( * tag ) - > Name . Mid ( 0 , 2 ) . CmpNoCase ( _T ( " \\ k " ) ) & & ( * tag ) - > IsValid ( ) ) {
// it's a karaoke tag
if ( brackets_open ) {
ktext + = _T ( " } " ) ;
brackets_open = false ;
}
L_settable_kara ( L , - 1 , kcount , kdur , kkind , ktext , ktext_stripped ) ;
kcount + + ;
kdur = ( * tag ) - > Params [ 0 ] - > AsInt ( ) ; // no error checking; this should always be int
kkind = ( * tag ) - > Name . Mid ( 1 ) ;
ktext = _T ( " " ) ;
ktext_stripped = _T ( " " ) ;
} else {
// it's something else
// don't care if it's a valid tag or not
if ( ! brackets_open ) {
ktext + = _T ( " { " ) ;
brackets_open = true ;
}
ktext + = ( * tag ) - > ToString ( ) ;
}
}
if ( brackets_open ) {
ktext + = _T ( " } " ) ;
}
break ; }
}
}
// add the final karaoke block to the table
// (even if there's no karaoke in the line, there's always at least one karaoke block)
// even if the line ends in {\k10} with no text after, an empty block should still be inserted
// (otherwise data are lost)
L_settable_kara ( L , - 1 , kcount , kdur , kkind , ktext , ktext_stripped ) ;
kcount + + ;
L_settable ( L , - 1 , " n " , kcount ) ; // number of syllables in the karaoke
lua_settop ( L , - 2 ) ; // remove karaoke table from the stack again
L_settable ( L , - 1 , " text_stripped " , text_stripped ) ; // store the real stripped text
}
// now the entry table has been created and placed on top of the stack
// now all that's missing it to insert it into the event table
lua_rawseti ( L , eventtab , numevents ) ;
numevents + + ;
} else {
// not really a line type automation needs to take care of... ignore it
}
ReportProgress ( 100.0f * processed_lines / input - > Line . size ( ) / 3 ) ;
}
// finally add the counter elements to the styles and events tables
lua_pushnumber ( L , numstyles ) ;
lua_rawseti ( L , styletab , - 1 ) ;
L_settable ( L , eventtab , " n " , numevents ) ;
// and let the config object create a table for the @config argument
//wxLogMessage(_T("Calling configuration.store_to_lua()"));
configuration . store_to_lua ( L ) ;
// so now the metadata, styles and events tables are filled with data
// ready to call the processing function!
OutputDebugString ( wxString ( _T ( " Running script for processing " ) ) ) ;
ReportProgress ( 100.0f / 3 ) ;
L_callfunc ( L , 4 , 1 ) ;
ReportProgress ( 200.0f / 3 ) ;
OutputDebugString ( wxString ( _T ( " Reading back data from script " ) ) ) ;
// phew, survived the call =)
// time to read back the results
wxLogDebug ( _T ( " Returned from Lua script call " ) ) ;
if ( ! lua_istable ( L , - 1 ) ) {
throw AutomationError ( wxString ( _T ( " The script function did not return a table as expected. Unable to process results. (Nothing was changed.) " ))) ;
}
// but start by removing all events
{
std : : list < AssEntry * > : : iterator cur , next ;
next = input - > Line . begin ( ) ;
while ( next ! = input - > Line . end ( ) ) {
cur = next + + ;
if ( ( * cur ) - > group = = _T ( " [Events] " ) ) {
if ( ( * cur ) - > data = = _T ( " [Events] " ) ) {
// skip the section header
continue ;
}
if ( ( * cur ) - > Type ! = ENTRY_DIALOGUE & &
( * cur ) - > data . Mid ( 0 , 1 ) ! = _T ( " ; " ) & &
( * cur ) - > data . Trim ( ) ! = _T ( " " ) ) {
// skip non-dialogue non-semicolon comment lines (such as Format)
continue ;
}
delete ( * cur ) ;
input - > Line . erase ( cur ) ;
}
}
}
wxLogDebug ( _T ( " Finished removing old events from subtitles " ) ) ;
// so anyway, there is a single table on the stack now
// that table contains a lot of events...
// and it ought to contain an "n" key as well, telling how many events
// but be lenient, and don't expect one to be there, but rather count from zero and let it be nil-terminated
// if the "n" key is there, use it as a progress indicator hint, though
int output_line_count ;
lua_pushstring ( L , " n " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isnumber ( L , - 1 ) ) {
output_line_count = ( int ) lua_tonumber ( L , - 1 ) ;
} else {
// assume number of output lines == number of input lines
output_line_count = processed_lines ;
}
lua_settop ( L , - 2 ) ;
wxLogDebug ( _T ( " Retrieved number of lines in result: %d " ) , output_line_count ) ;
// loop through the stack and report back the type of each element
wxLogDebug ( _T ( " Size of Lua stack: %d " ) , lua_gettop ( L ) ) ;
for ( int si = lua_gettop ( L ) ; si > 0 ; si - - ) {
wxString type = wxString ( lua_typename ( L , lua_type ( L , si ) ) , wxConvUTF8 ) ;
wxLogDebug ( _T ( " Stack index %d, type %s " ) , si , type . c_str ( ) ) ;
}
wxLogDebug ( _T ( " Stack dump finished " ) ) ;
int outline = 0 ;
int faketime = input - > Line . back ( ) - > StartMS ;
// If there's nothing at index 0, start at index 1 instead, to support both zero and one based indexing
lua_pushnumber ( L , outline ) ;
lua_gettable ( L , - 2 ) ;
if ( ! lua_istable ( L , - 1 ) ) {
outline + + ;
output_line_count + + ;
}
lua_pop ( L , 1 ) ;
while ( lua_pushnumber ( L , outline ) , lua_gettable ( L , - 2 ) , lua_istable ( L , - 1 ) ) {
// top of the stack is a table, hopefully with an AssEntry in it
wxLogDebug ( _T ( " Processing output line %d " ) , outline ) ;
// start by getting the kind
lua_pushstring ( L , " kind " ) ;
lua_gettable ( L , - 2 ) ;
if ( ! lua_isstring ( L , - 1 ) ) {
OutputDebugString ( wxString : : Format ( _T ( " The output data at index %d is mising a valid 'kind' field, and has been skipped " ) , outline ) ) ;
lua_settop ( L , - 2 ) ;
} else {
wxString kind = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) . Lower ( ) ;
// remove "kind" from stack again
lua_settop ( L , - 2 ) ;
wxLogDebug ( _T ( " Kind of line: %s " ) , kind . c_str ( ) ) ;
if ( kind = = _T ( " dialogue " ) | | kind = = _T ( " comment " ) ) {
lua_pushstring ( L , " layer " ) ;
lua_gettable ( L , - 2 ) ;
lua_pushstring ( L , " start_time " ) ;
lua_gettable ( L , - 3 ) ;
lua_pushstring ( L , " end_time " ) ;
lua_gettable ( L , - 4 ) ;
lua_pushstring ( L , " style " ) ;
lua_gettable ( L , - 5 ) ;
lua_pushstring ( L , " name " ) ;
lua_gettable ( L , - 6 ) ;
lua_pushstring ( L , " margin_l " ) ;
lua_gettable ( L , - 7 ) ;
lua_pushstring ( L , " margin_r " ) ;
lua_gettable ( L , - 8 ) ;
lua_pushstring ( L , " margin_v " ) ;
lua_gettable ( L , - 9 ) ;
lua_pushstring ( L , " effect " ) ;
lua_gettable ( L , - 10 ) ;
lua_pushstring ( L , " text " ) ;
lua_gettable ( L , - 11 ) ;
wxLogDebug ( _T ( " Read out all fields for dialogue event " ) ) ;
if ( lua_isnumber ( L , - 10 ) & & lua_isnumber ( L , - 9 ) & & lua_isnumber ( L , - 8 ) & &
lua_isstring ( L , - 7 ) & & lua_isstring ( L , - 6 ) & & lua_isnumber ( L , - 5 ) & &
lua_isnumber ( L , - 4 ) & & lua_isnumber ( L , - 3 ) & & lua_isstring ( L , - 2 ) & &
lua_isstring ( L , - 1 ) )
{
AssDialogue * e = new AssDialogue ( ) ;
e - > Layer = ( int ) lua_tonumber ( L , - 10 ) ;
e - > Start . SetMS ( 10 * ( int ) lua_tonumber ( L , - 9 ) ) ;
e - > End . SetMS ( 10 * ( int ) lua_tonumber ( L , - 8 ) ) ;
e - > Style = wxString ( lua_tostring ( L , - 7 ) , wxConvUTF8 ) ;
e - > Actor = wxString ( lua_tostring ( L , - 6 ) , wxConvUTF8 ) ;
e - > MarginL = ( int ) lua_tonumber ( L , - 5 ) ;
e - > MarginR = ( int ) lua_tonumber ( L , - 4 ) ;
e - > MarginV = ( int ) lua_tonumber ( L , - 3 ) ;
e - > Effect = wxString ( lua_tostring ( L , - 2 ) , wxConvUTF8 ) ;
e - > Text = wxString ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
e - > Comment = kind = = _T ( " comment " ) ;
lua_settop ( L , - 11 ) ;
e - > StartMS = e - > Start . GetMS ( ) ;
e - > ParseASSTags ( ) ;
e - > UpdateData ( ) ;
input - > Line . push_back ( e ) ;
wxLogDebug ( _T ( " Produced new dialogue event in output subs " ) ) ;
} else {
OutputDebugString ( wxString : : Format ( _T ( " The output data at index %d (kind '%s') has one or more missing/invalid fields, and has been skipped " ) , outline , kind . c_str ( ) ) ) ;
}
} else if ( kind = = _T ( " scomment " ) ) {
lua_pushstring ( L , " text " ) ;
lua_gettable ( L , - 2 ) ;
if ( lua_isstring ( L , - 1 ) ) {
wxString text ( lua_tostring ( L , - 1 ) , wxConvUTF8 ) ;
lua_settop ( L , - 2 ) ;
AssEntry * e = new AssEntry ( wxString ( _T ( " ; " ) ) + text ) ;
e - > StartMS = faketime ;
input - > Line . push_back ( e ) ;
wxLogDebug ( _T ( " Produced new semicolon comment in output subs " ) ) ;
} else {
OutputDebugString ( wxString : : Format ( _T ( " The output data at index %d (kind 'scomment') is missing a valid 'text' field, and has been skipped " ) , outline ) ) ;
}
} else if ( kind = = _T ( " blank " ) ) {
AssEntry * e = new AssEntry ( _T ( " " ) ) ;
e - > StartMS = faketime ;
input - > Line . push_back ( e ) ;
wxLogDebug ( _T ( " Produced new blank line in output subs " ) ) ;
} else {
OutputDebugString ( wxString : : Format ( _T ( " The output data at index %d has an invalid value in the 'kind' field, and has been skipped " ) , outline ) ) ;
}
}
// remove table again
lua_settop ( L , - 2 ) ;
// progress report
if ( outline > = output_line_count ) {
ReportProgress ( 99.9f ) ;
} else {
ReportProgress ( ( 200.0f + 100.0f * outline / output_line_count ) / 3 ) ;
}
outline + + ;
wxLogDebug ( _T ( " Size of Lua stack: %d " ) , lua_gettop ( L ) ) ;
}
ReportProgress ( 100 ) ;
OutputDebugString ( wxString ( _T ( " Script execution complete " ) ) ) ;
return ;
}
AutomationScriptFile : : ~ AutomationScriptFile ( )
{
// if file had a bom
if ( utf8bom )
scriptdata - = 3 ;
delete scriptdata ;
}
AutomationScriptFile * AutomationScriptFile : : CreateFromString ( wxString & script )
{
wxCharBuffer rawscript = script . mb_str ( wxConvUTF8 ) ;
AutomationScriptFile * res = new AutomationScriptFile ( ) ;
res - > scriptlen = strlen ( rawscript ) ;
res - > scriptdata = new char [ res - > scriptlen ] ;
res - > filename = _T ( " " ) ;
return res ;
}
AutomationScriptFile * AutomationScriptFile : : CreateFromFile ( wxString filename )
{
wxFile file ( filename ) ;
if ( ! file . IsOpened ( ) ) {
return 0 ;
}
// prepare variables
AutomationScriptFile * res = new AutomationScriptFile ( ) ;
res - > filename = filename ;
res - > scriptlen = file . Length ( ) ;
res - > scriptdata = new char [ res - > scriptlen ] ;
// read from file
file . Read ( res - > scriptdata , res - > scriptlen ) ;
// check for UTF-8 BOM
res - > utf8bom = ( ( res - > scriptdata [ 0 ] & 0xFF ) = = 0xEF ) & & ( ( res - > scriptdata [ 1 ] & 0xFF ) = = 0xBB ) & & ( ( res - > scriptdata [ 2 ] & 0xFF ) = = 0xBF ) ;
if ( res - > utf8bom ) {
// skip it if found
res - > scriptdata + = 3 ;
res - > scriptlen - = 3 ;
}
return res ;
}