// Copyright (c) 2005, 2006, 2007, 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:jiifurusu@gmail.com // #ifdef WITH_AUTOMATION #ifdef WITH_AUTO3 #if __VISUALC__ >= 1200 #pragma comment(lib,"aegisub-auto3.lib") #endif #include #include #include "auto4_auto3.h" #include "auto4_auto3_factory.h" #include "../auto3/auto3.h" #include "options.h" #include "string_codec.h" #include "vfr.h" #include "ass_override.h" namespace Automation4 { // Auto3ProgressSink void Auto3ProgressSink::SetStatus(void *cbdata, const char *msg) { Auto3ProgressSink *ps = (Auto3ProgressSink*)cbdata; ps->SetTask(wxString(msg, wxConvUTF8)); } void Auto3ProgressSink::OutputDebug(void *cbdata, const char *msg) { Auto3ProgressSink *ps = (Auto3ProgressSink*)cbdata; ps->AddDebugOutput(wxString(msg, wxConvUTF8)); ps->AddDebugOutput(_T("\n")); } void Auto3ProgressSink::ReportProgress(void *cbdata, float progress) { Auto3ProgressSink *ps = (Auto3ProgressSink*)cbdata; ps->SetProgress(progress); } Auto3ProgressSink::Auto3ProgressSink(Auto3Interpreter *_script, wxWindow *parent) : ProgressSink(parent) , script(_script) { script->cb.logdata = this; script->cb.log_error = OutputDebug; script->cb.log_message = OutputDebug; script->cb.set_progress = ReportProgress; script->cb.set_status = SetStatus; } Auto3ProgressSink::~Auto3ProgressSink() { script->cb.logdata = 0; script->cb.log_error = 0; script->cb.log_message = 0; script->cb.set_progress = 0; script->cb.set_status = 0; } // Auto3ConfigDialog wxWindow* Auto3ConfigDialog::CreateWindow(wxWindow *parent) { if (options->name == 0) return 0; wxPanel *res = new wxPanel(parent, -1); wxFlexGridSizer *sizer = new wxFlexGridSizer(2, 5, 5); for (Auto3ConfigOption *opt = options; opt->name; opt++) { if (opt->kind == COK_INVALID) continue; Control control; control.option = opt; switch (opt->kind) { case COK_LABEL: control.control = new wxStaticText(res, -1, wxString(opt->label, wxConvUTF8)); break; case COK_TEXT: control.control = new wxTextCtrl(res, -1, wxString(opt->value.stringval, wxConvUTF8)); break; case COK_INT: control.control = new wxSpinCtrl(res, -1); if (opt->min.valid && opt->max.valid) { ((wxSpinCtrl*)control.control)->SetRange(opt->min.intval, opt->max.intval); } else if (opt->min.valid) { ((wxSpinCtrl*)control.control)->SetRange(opt->min.intval, 0x7fff); } else if (opt->max.valid) { ((wxSpinCtrl*)control.control)->SetRange(-0x7fff, opt->max.intval); } else { ((wxSpinCtrl*)control.control)->SetRange(-0x7fff, 0x7fff); } ((wxSpinCtrl*)control.control)->SetValue(opt->value.intval); break; case COK_FLOAT: control.control = new wxTextCtrl(res, -1, wxString::Format(_T("%f"), opt->value.floatval)); break; case COK_BOOL: control.control = new wxCheckBox(res, -1, wxString(opt->label, wxConvUTF8)); ((wxCheckBox*)control.control)->SetValue(!!opt->value.intval); break; case COK_COLOUR: // *FIXME* what to do here? // just put a stupid edit box for now control.control = new wxTextCtrl(res, -1, wxString(opt->value.stringval, wxConvUTF8)); break; case COK_STYLE: control.control = new wxChoice(res, -1, wxDefaultPosition, wxDefaultSize, AssFile::top->GetStyles()); ((wxChoice*)control.control)->Insert(_T(""), 0); break; case COK_INVALID: break; } if (opt->kind != COK_LABEL && opt->kind != COK_BOOL) { control.label = new wxStaticText(res, -1, wxString(opt->label, wxConvUTF8)); sizer->Add(control.label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL); } else { control.label = 0; sizer->AddSpacer(0); } control.control->SetToolTip(wxString(opt->hint, wxConvUTF8)); sizer->Add(control.control, 1, wxEXPAND); controls.push_back(control); } res->SetSizerAndFit(sizer); return res; } Auto3ConfigDialog::Auto3ConfigDialog(Auto3Interpreter *script) { options = script->config; } Auto3ConfigDialog::~Auto3ConfigDialog() { // Nothing to do here } void Auto3ConfigDialog::ReadBack() { for (std::vector::iterator ctl = controls.begin(); ctl != controls.end(); ctl++) { switch (ctl->option->kind) { case COK_TEXT: Auto3Free(ctl->option->value.stringval); ctl->option->value.stringval = Auto3Strdup(((wxTextCtrl*)ctl->control)->GetValue().mb_str(wxConvUTF8)); break; case COK_INT: ctl->option->value.intval = ((wxSpinCtrl*)ctl->control)->GetValue(); break; case COK_FLOAT: { double v; if (!((wxTextCtrl*)ctl->control)->GetValue().ToDouble(&v)) { wxLogWarning( _T("The value entered for field '%s' (%s) could not be converted to a floating-point number. Default value (%f) substituted for the entered value."), wxString(ctl->option->label, wxConvUTF8).c_str(), ((wxTextCtrl*)ctl->control)->GetValue().c_str(), ctl->option->default_val.floatval); ctl->option->value.floatval = ctl->option->default_val.floatval; } ctl->option->value.floatval = v; break; } case COK_BOOL: ctl->option->value.intval = (int)((wxCheckBox*)ctl->control)->GetValue(); break; case COK_COLOUR: // *FIXME* needs to be updated to use a proper color control Auto3Free(ctl->option->value.stringval); ctl->option->value.stringval = Auto3Strdup(((wxTextCtrl*)ctl->control)->GetValue().mb_str(wxConvUTF8)); break; case COK_STYLE: Auto3Free(ctl->option->value.stringval); ctl->option->value.stringval = Auto3Strdup(((wxChoice*)ctl->control)->GetStringSelection().mb_str(wxConvUTF8)); break; case COK_LABEL: case COK_INVALID: break; } } } wxString Auto3ConfigDialog::Serialise() { if (options->name == 0) return _T(""); wxString result; for (Auto3ConfigOption *opt = options; opt->name; opt++) { wxString optname(opt->name, wxConvUTF8); switch (opt->kind) { case COK_TEXT: case COK_STYLE: case COK_COLOUR: { wxString optstrval(opt->value.stringval, wxConvUTF8); result << wxString::Format(_T("%s:%s|"), optname.c_str(), inline_string_encode(optstrval).c_str()); break; } case COK_INT: case COK_BOOL: result << wxString::Format(_T("%s:%d|"), optname.c_str(), opt->value.intval); break; case COK_FLOAT: result << wxString::Format(_T("%s:%e|"), optname.c_str(), opt->value.floatval); break; default: // The rest aren't stored break; } } if (!result.IsEmpty() && result.Last() == _T('|')) result.RemoveLast(); return result; } void Auto3ConfigDialog::Unserialise(const wxString &settings) { wxStringTokenizer toker(settings, _T("|"), wxTOKEN_STRTOK); while (toker.HasMoreTokens()) { // get the parts of this setting wxString setting = toker.GetNextToken(); wxString optname = setting.BeforeFirst(_T(':')); wxString optval = setting.AfterFirst(_T(':')); // find the setting in the list loaded from the script Auto3ConfigOption *opt = options; while (opt->name && wxString(opt->name, wxConvUTF8) != optname) opt ++; if (opt->name) { // ok, found the option! switch (opt->kind) { case COK_TEXT: case COK_STYLE: case COK_COLOUR: Auto3Free(opt->value.stringval); opt->value.stringval = Auto3Strdup(inline_string_decode(optval).mb_str(wxConvUTF8)); break; case COK_INT: case COK_BOOL: { long n; optval.ToLong(&n, 10); opt->value.intval = n; break; } case COK_FLOAT: { double v; optval.ToDouble(&v); opt->value.floatval = (float)v; break; } case COK_LABEL: case COK_INVALID: break; } } } } // Auto3Filter Auto3Filter::Auto3Filter(const wxString &_name, const wxString &_description, Auto3Interpreter *_script) : Feature(SCRIPTFEATURE_FILTER, _name) , FeatureFilter(_name, _description, 0) , script(_script) { // Nothing more to do } ScriptConfigDialog* Auto3Filter::GenerateConfigDialog(wxWindow *parent) { config = new Auto3ConfigDialog(script); return config; } void Auto3Filter::Init() { // Nothing to do here } void Auto3Filter::ProcessSubs(AssFile *subs, wxWindow *export_dialog) { Auto3ProgressSink *sink = new Auto3ProgressSink(script, export_dialog); sink->SetTitle(GetName()); Auto3ThreadedProcessor thread(script, subs, config, sink); sink->ShowModal(); thread.Wait(); delete sink; } // Auto3ThreadedProcessor Auto3ThreadedProcessor::Auto3ThreadedProcessor(Auto3Interpreter *_script, AssFile *_file, Auto3ConfigDialog *_config, Auto3ProgressSink *_sink) : wxThread(wxTHREAD_JOINABLE) , script(_script) , file(_file) , config(_config) , sink(_sink) { // Pure copypasta int prio = Options.AsInt(_T("Automation Thread Priority")); if (prio == 0) prio = 50; // normal else if (prio == 1) prio = 30; // below normal else if (prio == 2) prio = 10; // lowest else prio = 50; // fallback normal Create(); SetPriority(prio); Run(); } wxThread::ExitCode Auto3ThreadedProcessor::Entry() { script->cb.rwdata = this; script->cb.reset_style_pointer = ResetStylePointer; script->cb.reset_subs_pointer = ResetSubsPointer; script->cb.get_meta_info = GetMetaInfo; script->cb.get_next_style = GetNextStyle; script->cb.get_next_sub = GetNextSub; script->cb.start_subs_write = StartSubsWrite; script->cb.write_sub = WriteSub; int res = RunAuto3Script(script); script->cb.rwdata = 0; script->cb.reset_style_pointer = 0; script->cb.reset_subs_pointer = 0; script->cb.get_meta_info = 0; script->cb.get_next_style = 0; script->cb.get_next_sub = 0; script->cb.start_subs_write = 0; script->cb.write_sub = 0; sink->script_finished = true; wxWakeUpIdle(); if (res) return (wxThread::ExitCode) 1; else return 0; } void Auto3ThreadedProcessor::ResetStylePointer(void *cbdata) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; self->style_pointer = self->file->Line.begin(); } void Auto3ThreadedProcessor::ResetSubsPointer(void *cbdata) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; self->subs_pointer = self->file->Line.begin(); } void Auto3ThreadedProcessor::GetMetaInfo(void *cbdata, int *res_x, int *res_y) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; self->file->GetResolution(*res_x, *res_y); } int Auto3ThreadedProcessor::GetNextStyle( void *cbdata, char **name, char **fontname, int *fontsize, char **color1, char **color2, char **color3, char **color4, int *bold, int *italic, int *underline, int *strikeout, float *scale_x, float *scale_y, float *spacing, float *angle, int *borderstyle, float *outline, float *shadow, int *align, int *margin_l, int *margin_r, int *margin_v, int *encoding) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; while (self->style_pointer != self->file->Line.end()) { AssStyle *style = AssEntry::GetAsStyle(*self->style_pointer); // Increase iterator before we test for and return style data, since it needs to be done either way // The iterator should always point to the next line to be examined self->style_pointer++; if (style) { // Put strings into buffers self->stylename = style->name.mb_str(wxConvUTF8); self->stylefont = style->font.mb_str(wxConvUTF8); self->stylecolor[0] = style->primary.GetASSFormatted(true, false, true).mb_str(wxConvUTF8); self->stylecolor[1] = style->secondary.GetASSFormatted(true, false, true).mb_str(wxConvUTF8); self->stylecolor[2] = style->outline.GetASSFormatted(true, false, true).mb_str(wxConvUTF8); self->stylecolor[3] = style->shadow.GetASSFormatted(true, false, true).mb_str(wxConvUTF8); // Store data to lib *name = self->stylename.data(); *fontname = self->stylefont.data(); *fontsize = (int)style->fontsize; *color1 = self->stylecolor[0].data(); *color2 = self->stylecolor[1].data(); *color3 = self->stylecolor[2].data(); *color4 = self->stylecolor[3].data(); *bold = (int)style->bold; *italic = (int)style->italic; *underline = (int)style->italic; *strikeout = (int)style->strikeout; *scale_x = style->scalex; *scale_y = style->scaley; *spacing = style->spacing; *angle = style->angle; *borderstyle = style->borderstyle; *outline = style->outline_w; *shadow = style->shadow_w; *align = style->alignment; *margin_l = style->Margin[0]; *margin_r = style->Margin[1]; *margin_v = style->Margin[2]; *encoding = style->encoding; // and return success return 1; } } return 0; } int Auto3ThreadedProcessor::GetNextSub(void *cbdata, int *layer, int *start_time, int *end_time, char **style, char **actor, int *margin_l, int *margin_r, int *margin_v, char **effect, char **text, int *comment) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; while (self->subs_pointer != self->file->Line.end()) { AssDialogue *dia = AssEntry::GetAsDialogue(*self->subs_pointer); self->subs_pointer++; if (dia) { // Put strings into buffers self->diagstyle = dia->Style.mb_str(wxConvUTF8); self->diagactor = dia->Actor.mb_str(wxConvUTF8); self->diageffect = dia->Effect.mb_str(wxConvUTF8); self->diagtext = dia->Text.mb_str(wxConvUTF8); // Store data to lib *comment = (int)dia->Comment; *layer = dia->Layer; *start_time = dia->Start.GetMS()/10; *end_time = dia->End.GetMS()/10; *style = self->diagstyle.data(); *actor = self->diagactor.data(); *margin_l = dia->Margin[0]; *margin_r = dia->Margin[1]; *margin_v = dia->Margin[2]; *effect = self->diageffect.data(); *text = self->diagtext.data(); // return success return 1; } } return 0; } void Auto3ThreadedProcessor::StartSubsWrite(void *cbdata) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; // clear all dialogue lines std::list::iterator line = self->file->Line.begin(); while (line != self->file->Line.end()) { std::list::iterator cur = line; line++; if (AssEntry::GetAsDialogue(*cur)) { delete *cur; self->file->Line.erase(cur); } } } void Auto3ThreadedProcessor::WriteSub(void *cbdata, int layer, int start_time, int end_time, const char *style, const char *actor, int margin_l, int margin_r, int margin_v, const char *effect, const char *text, int comment) { Auto3ThreadedProcessor *self = (Auto3ThreadedProcessor*)cbdata; // Construct dialogue object AssDialogue *dia = new AssDialogue(); dia->Comment = !!comment; dia->Layer = layer; dia->Start.SetMS(start_time*10); dia->End.SetMS(end_time*10); dia->Style = wxString(style, wxConvUTF8); dia->Actor = wxString(actor, wxConvUTF8); dia->Margin[0] = margin_l; dia->Margin[1] = margin_r; dia->Margin[2] = dia->Margin[3] = margin_v; dia->Effect = wxString(effect, wxConvUTF8); dia->Text = wxString(text, wxConvUTF8); // Append to file self->file->Line.push_back(dia); } // Auto3Script Auto3Script::Auto3Script(const wxString &filename) : Script(filename) , filter(0) , script(0) { try { Create(); } catch (wxChar *e) { description = e; loaded = false; } } Auto3Script::~Auto3Script() { if (script) Destroy(); } void Auto3Script::TextExtents(void *cbdata, const char *text, const char *fontname, int fontsize, int bold, int italic, int spacing, float scale_x, float scale_y, int encoding, float *out_width, float *out_height, float *out_descent, float *out_extlead) { double resx, resy, resd, resl; wxString intext(text, wxConvUTF8); AssStyle st; st.font = wxString(fontname, wxConvUTF8); st.fontsize = fontsize; st.bold = !!bold; st.italic = !!italic; st.underline = false; st.strikeout = false; st.scalex = scale_x; st.scaley = scale_y; st.spacing = spacing; st.encoding = encoding; CalculateTextExtents(&st, intext, resx, resy, resd, resl); // So no error checking here... FIXME? *out_width = resx; *out_height = resy; *out_descent = resd; *out_extlead = resl; } filename_t Auto3Script::ResolveInclude(void *cbdata, const char *incname) { Auto3Script *s = (Auto3Script*)cbdata; wxString fnames(incname, wxConvUTF8); wxFileName fname(fnames); if (fname.GetDirCount() == 0) { // filename only fname = s->include_path.FindAbsoluteValidPath(fnames); } else if (fname.IsRelative()) { // relative path wxFileName sfname(s->GetFilename()); fname.MakeAbsolute(sfname.GetPath(true)); } else { // absolute path, do nothing } if (!fname.IsOk() || !fname.FileExists()) { return 0; } #ifdef WIN32 // Get number of widechars in filename string size_t wfnlen = wcslen(fname.GetFullPath().wc_str()); // Alloc memory to hold string filename_t wfn = (filename_t)Auto3Malloc((wfnlen+1) * sizeof(wchar_t)); // And copy string into memory wcsncpy(wfn, fname.GetFullPath().wc_str(), wfnlen); wfn[wfnlen] = 0; return wfn; #else return Auto3Strdup(fname.GetFullPath().fn_str()); #endif } int Auto3Script::FrameFromMs(void *cbdata, int ms) { if (VFR_Output.IsLoaded()) { return VFR_Output.GetFrameAtTime(ms, true); } else { return 0; } } int Auto3Script::MsFromFrame(void *cbdata, int frame) { if (VFR_Output.IsLoaded()) { return VFR_Output.GetTimeAtFrame(frame, true); } else { return 0; } } void Auto3Script::Create() { Destroy(); // Fill callbacks struct Auto3Callbacks cb; // Logging, implemented only during script execution cb.logdata = 0; cb.log_error = 0; cb.log_message = 0; cb.set_progress = 0; cb.set_status = 0; // Read/write, also only during execution cb.rwdata = 0; cb.get_meta_info = 0; cb.get_next_style = 0; cb.get_next_sub = 0; cb.reset_style_pointer = 0; cb.reset_subs_pointer = 0; cb.start_subs_write = 0; cb.write_sub = 0; // Misc, implemented all the time cb.rundata = this; cb.resolve_include = ResolveInclude; cb.text_extents = TextExtents; cb.frame_from_ms = FrameFromMs; cb.ms_from_frame = MsFromFrame; char *errormsg = 0; // Why oh why... GCC wants fn_str() to be dereffed with .data() but MSVC hates that... // If anyone can FIXME to something more sensible, please do so #ifdef WIN32 script = CreateAuto3Script((const filename_t)GetFilename().fn_str(), GetPrettyFilename().mb_str(wxConvUTF8), &cb, &errormsg); #else script = CreateAuto3Script((const filename_t)GetFilename().fn_str().data(), GetPrettyFilename().mb_str(wxConvUTF8).data(), &cb, &errormsg); #endif if (script) { assert(errormsg == 0); loaded = true; name = wxString(script->name, wxConvUTF8); description = wxString(script->description, wxConvUTF8); author = _T(""); version = _T(""); filter = new Auto3Filter(name, description, script); } else { loaded = false; name = GetPrettyFilename(); if (errormsg) { description = wxString(errormsg, wxConvUTF8); Auto3Free(errormsg); } else { description = _T("Unknown error (auto3 library returned NULL error message)"); } } } void Auto3Script::Destroy() { if (!script) return; if (filter) { delete filter; filter = 0; } DestroyAuto3Script(script); script = 0; loaded = false; } void Auto3Script::Reload() { Destroy(); Create(); } Auto3ScriptFactory::Auto3ScriptFactory() { engine_name = _T("Legacy Automation 3"); filename_pattern = _T("*.auto3"); Register(this); } Auto3ScriptFactory::~Auto3ScriptFactory() { } Script* Auto3ScriptFactory::Produce(const wxString &filename) const { if (filename.Right(6).Lower() == _T(".auto3")) { return new Auto3Script(filename); } else { return 0; } } }; #endif // WITH_AUTO3 #endif // WITH_AUTOMATION