// Copyright (c) 2005, Rodrigo Braz Monteiro // 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 // //////////// // Includes #include "ass_dialogue.h" #include "ass_override.h" #include "vfr.h" #include "utils.h" #include <fstream> #include <wx/tokenzr.h> #ifndef NO_FEX #include "../FexTrackerSource/FexTracker.h" #include "../FexTrackerSource/FexMovement.h" #endif ////////////////////// AssDialogue ////////////////////// // Constructs AssDialogue AssDialogue::AssDialogue() { #ifndef NO_FEX Tracker = 0; Movement = 0; #endif group = _T("[Events]"); Valid = true; Start.SetMS(0); End.SetMS(5000); StartMS = 0; Layer = 0; MarginR = MarginL = MarginV = 0; Text = _T(""); Style = _T("Default"); Actor = _T(""); Effect = _T(""); Comment = false; UpdateData(); } AssDialogue::AssDialogue(wxString _data,bool IsSSA) { #ifndef NO_FEX Tracker = 0; Movement = 0; #endif group = _T("[Events]"); Valid = Parse(_data,IsSSA); if (!Valid) { throw _T("Failed parsing line."); } UpdateData(); } ////////////// // Destructor AssDialogue::~AssDialogue () { Clear(); } ///////// // Clear void AssDialogue::Clear () { ClearBlocks(); #ifndef NO_FEX if( Tracker ) { delete Tracker; Tracker = 0; } if( Movement ) { DeleteMovement( Movement ); Movement = 0; } #endif } //////////////// // Clear blocks void AssDialogue::ClearBlocks() { using std::vector; for (vector<AssDialogueBlock*>::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { delete *cur; } Blocks.clear(); } ////////////////// // Parse ASS Data bool AssDialogue::Parse(wxString rawData, bool IsSSA) { size_t pos = 0; wxString temp; // Get type if (rawData.substr(pos,9) == _T("Dialogue:")) { Comment = false; pos = 10; } else if (rawData.substr(pos,8) == _T("Comment:")) { Comment = true; pos = 9; } else return false; wxStringTokenizer tkn(rawData.Mid(pos),_T(","),wxTOKEN_RET_EMPTY_ALL); // Get layer number if (!tkn.HasMoreTokens()) return false; temp = tkn.GetNextToken().Trim(false).Trim(true); if (IsSSA) Layer = 0; else { long templ; temp.ToLong(&templ); Layer = templ; } // Get start time if (!tkn.HasMoreTokens()) return false; Start.ParseASS(tkn.GetNextToken()); StartMS = Start.GetMS(); // Get end time if (!tkn.HasMoreTokens()) return false; End.ParseASS(tkn.GetNextToken()); // Get style if (!tkn.HasMoreTokens()) return false; Style = tkn.GetNextToken(); Style.Trim(true); Style.Trim(false); // Get actor if (!tkn.HasMoreTokens()) return false; Actor = tkn.GetNextToken(); Actor.Trim(true); Actor.Trim(false); // Get left margin if (!tkn.HasMoreTokens()) return false; SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),1); // Get right margin if (!tkn.HasMoreTokens()) return false; SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),2); // Get vertical margin if (!tkn.HasMoreTokens()) return false; SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),3); // Get effect if (!tkn.HasMoreTokens()) return false; Effect = tkn.GetNextToken(); Effect.Trim(true); Effect.Trim(false); #ifndef NO_FEX if( Effect.BeforeFirst(':')==_T("FexMovement") ) { if( Movement ) DeleteMovement( Movement ); Movement = CreateMovement(); LoadMovement( Movement, Effect.AfterFirst(':').c_str() ); } #endif // Get text Text = tkn.GetNextToken(); while (tkn.HasMoreTokens()) { Text += _T(","); Text += tkn.GetNextToken(); } return true; } ////////////////// // Keep data flag bool AssDialogue::keepData = true; ///////////// // Make data wxString AssDialogue::MakeData() { // Prepare wxString final = _T(""); // Write all final if (Comment) final += _T("Comment: "); else final += _T("Dialogue: "); final += wxString::Format(_T("%01i"),Layer); final += _T(","); final += Start.GetASSFormated() + _T(","); final += End.GetASSFormated() + _T(","); Style.Replace(_T(","),_T(";")); Actor.Replace(_T(","),_T(";")); final += Style + _T(","); final += Actor + _T(","); final += GetMarginString(1); final += _T(","); final += GetMarginString(2); final += _T(","); final += GetMarginString(3); final += _T(","); Effect.Replace(_T(","),_T(";")); final += Effect + _T(","); final += Text; return final; } ////////////////////////////////// // Update AssDialogue's data line void AssDialogue::UpdateData () { if (keepData) SetEntryData(MakeData()); } ////////////////// // Get entry data const wxString AssDialogue::GetEntryData() { if (keepData) return AssEntry::GetEntryData(); return MakeData(); } ////////////////// // Set entry data void AssDialogue::SetEntryData(wxString newData) { if (keepData) AssEntry::SetEntryData(newData); } /////////////////////////////// // Get SSA version of Dialogue wxString AssDialogue::GetSSAText () { // Prepare wxString work = _T(""); // Write all work if (Comment) work += _T("Comment: "); else work += _T("Dialogue: "); work += _T("Marked=0,"); work += Start.GetASSFormated() + _T(","); work += End.GetASSFormated() + _T(","); Style.Replace(_T(","),_T(";")); Actor.Replace(_T(","),_T(";")); work += Style + _T(","); work += Actor + _T(","); work += GetMarginString(1); work += _T(","); work += GetMarginString(2); work += _T(","); work += GetMarginString(3); work += _T(","); Effect.Replace(_T(","),_T(";")); work += Effect + _T(","); work += Text; return work; } ////////////////// // Parse SRT tags // -------------- // Yea, I convert to ASS tags, then parse that. So sue me. void AssDialogue::ParseSRTTags () { // Search and replace size_t total = 0; total += Text.Replace(_T("<i>"),_T("{\\i1}")); total += Text.Replace(_T("</i>"),_T("{\\i0}")); total += Text.Replace(_T("<b>"),_T("{\\b1}")); total += Text.Replace(_T("</b>"),_T("{\\b0}")); total += Text.Replace(_T("<u>"),_T("{\\u1}")); total += Text.Replace(_T("</u>"),_T("{\\u0}")); total += Text.Replace(_T("<s>"),_T("{\\s1}")); total += Text.Replace(_T("</s>"),_T("{\\s0}")); // Process <font> tag wxString work = Text; work.UpperCase(); size_t pos_open = 0; size_t pos_close = 0; size_t pos = 0; size_t end = 0; size_t start = 0; bool isOpen; // Iterate pos_open = work.find(_T("<FONT"),0); pos_close = work.find(_T("</FONT"),0); while (pos_open != wxString::npos || pos_close != wxString::npos) { // Determine if it's an open or close tag if (pos_open < pos_close) { start = pos_open; isOpen = true; } else { start = pos_close; isOpen = false; } end = work.find(_T(">"),start)+1; //if (end == wxString::npos) continue; // Open tag if (isOpen) { wxString replaced = _T(""); // Color tag if ((pos = work.find(_T("COLOR=\""),start)) != wxString::npos) { if (pos < end) { pos += 7; size_t end_tag = Text.find(_T("\""),pos); if (end_tag != wxString::npos) { if (end_tag-pos == 7) { replaced += _T("{\\c&H"); replaced += work.substr(pos+5,2); replaced += work.substr(pos+3,2); replaced += work.substr(pos+1,2); replaced += _T("&}"); total++; } } } } // Face tag if ((pos = work.find(_T("FACE=\""),start)) != wxString::npos) { if (pos < end) { pos += 6; size_t end_tag = work.find(_T("\""),pos); if (end_tag != wxString::npos) { replaced += _T("{\\fn"); replaced += work.substr(pos,end_tag-pos); replaced += _T("}"); total++; } } } // Size tag if ((pos = work.find(_T("SIZE=\""),start)) != wxString::npos) { if (pos < end) { pos += 6; size_t end_tag = Text.find(_T("\""),pos); if (end_tag != wxString::npos) { replaced += _T("{\\fs"); replaced += work.substr(pos,end_tag-pos); replaced += _T("}"); total++; } } } // Replace whole tag Text = Text.substr(0,start) + replaced + Text.substr(end); total++; } // Close tag else { // Find if it's italic, bold, underline, and strikeout wxString prev = Text.Left(start); bool isItalic=false,isBold=false,isUnder=false,isStrike=false; if (CountMatches(prev,_T("{\\i1}")) > CountMatches(prev,_T("{\\i0}"))) isItalic = true; if (CountMatches(prev,_T("{\\b1}")) > CountMatches(prev,_T("{\\b0}"))) isBold = true; if (CountMatches(prev,_T("{\\u1}")) > CountMatches(prev,_T("{\\u0}"))) isUnder = true; if (CountMatches(prev,_T("{\\s1}")) > CountMatches(prev,_T("{\\s0}"))) isStrike = true; // Generate new tag, by reseting and then restoring flags wxString replaced = _T("{\\r"); if (isItalic) replaced += _T("\\i1"); if (isBold) replaced += _T("\\b1"); if (isUnder) replaced += _T("\\u1"); if (isStrike) replaced += _T("\\s1"); replaced += _T("}"); // Replace Text = Text.substr(0,start) + replaced + Text.substr(end); total++; } // Get next work = Text; work.UpperCase(); pos_open = work.find(_T("<FONT"),0); pos_close = work.find(_T("</FONT"),0); } // Remove double tagging Text.Replace(_T("}{"),_T("")); // Update all stuff //if (total > 0) UpdateText(); UpdateData(); } ////////////////// // Parse ASS tags void AssDialogue::ParseASSTags () { // Clear blocks ClearBlocks(); // Is drawing? int drawingLevel = 0; // Loop through const size_t len = Text.size(); size_t cur = 0; size_t end = 0; while (cur < len) { // Overrides block if (Text[cur] == '{') { // Get contents of block wxString work; end = Text.find(_T("}"),cur); if (end == wxString::npos) { work = Text.substr(cur); end = len; } else work = Text.substr(cur,end-cur); work = Text.substr(cur+1,end-cur-1); // Create block AssDialogueBlockOverride *block = new AssDialogueBlockOverride; block->parent = this; block->text = work; block->ParseTags(); Blocks.push_back(block); // Look for \p in block std::vector<AssOverrideTag*>::iterator curTag; for (curTag = block->Tags.begin();curTag != block->Tags.end();curTag++) { AssOverrideTag *tag = *curTag; if (tag->Name == _T("\\p")) { drawingLevel = tag->Params.at(0)->AsInt(); } } // Increase cur = end+1; } // Plain-text/drawing block else { wxString work; end = Text.find(_T("{"),cur); if (end == wxString::npos) { work = Text.substr(cur); end = len; } else work = Text.substr(cur,end-cur); // Plain-text if (drawingLevel == 0) { AssDialogueBlockPlain *block = new AssDialogueBlockPlain; block->text = work; Blocks.push_back(block); } // Drawing else { AssDialogueBlockDrawing *block = new AssDialogueBlockDrawing; block->text = work; block->Scale = drawingLevel; Blocks.push_back(block); } cur = end; } } // Empty line, make an empty block if (len == 0) { AssDialogueBlockPlain *block = new AssDialogueBlockPlain; block->text = _T(""); Blocks.push_back(block); } } ////////////// // Strip tags void AssDialogue::StripTags () { using std::list; using std::vector; ParseASSTags(); vector<AssDialogueBlock*>::iterator next; for (vector<AssDialogueBlock*>::iterator cur=Blocks.begin();cur!=Blocks.end();cur=next) { next = cur; next++; if ((*cur)->type == BLOCK_OVERRIDE) { delete *cur; Blocks.erase(cur); } } UpdateText(); UpdateData(); ClearBlocks(); } /////////////////////// // Convert tags to SRT // ------------------- // TODO: Improve this code // void AssDialogue::ConvertTagsToSRT () { // Setup using std::list; using std::vector; AssDialogueBlockOverride* curBlock; AssDialogueBlockPlain *curPlain; AssOverrideTag* curTag; wxString final = _T(""); bool isItalic=false,isBold=false,isUnder=false,isStrike=false; bool temp; // Iterate through blocks ParseASSTags(); for (size_t i=0;i<Blocks.size();i++) { curBlock = AssDialogueBlock::GetAsOverride(Blocks.at(i)); if (curBlock) { // Iterate through overrides for (size_t j=0;j<curBlock->Tags.size();j++) { curTag = curBlock->Tags.at(j); if (curTag->IsValid()) { // Italics if (curTag->Name == _T("\\i")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isItalic) { isItalic = true; final += _T("<i>"); } if (!temp && isItalic) { isItalic = false; final += _T("</i>"); } } // Underline if (curTag->Name == _T("\\u")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isUnder) { isUnder = true; final += _T("<u>"); } if (!temp && isUnder) { isUnder = false; final += _T("</u>"); } } // Strikeout if (curTag->Name == _T("\\s")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isStrike) { isStrike = true; final += _T("<s>"); } if (!temp && isStrike) { isStrike = false; final += _T("</s>"); } } // Bold if (curTag->Name == _T("\\b")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isBold) { isBold = true; final += _T("<b>"); } if (!temp && isBold) { isBold = false; final += _T("</b>"); } } } } } // Plain text else { curPlain = AssDialogueBlock::GetAsPlain(Blocks.at(i)); if (curPlain) { final += curPlain->GetText(); } } } Text = final; UpdateData(); ClearBlocks(); } ////////////////////////// // Updates text from tags void AssDialogue::UpdateText () { using std::vector; Text = _T(""); for (vector<AssDialogueBlock*>::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { if ((*cur)->type == BLOCK_OVERRIDE) { Text += _T("{"); Text += (*cur)->GetText(); Text += _T("}"); } else Text += (*cur)->GetText(); } } ///////////////////////////// // Sets margin from a string void AssDialogue::SetMarginString(const wxString origvalue,int which) { // Make it numeric wxString strvalue = origvalue; if (!strvalue.IsNumber()) { strvalue = _T(""); for (size_t i=0;i<origvalue.Length();i++) { if (origvalue.Mid(i,1).IsNumber()) { strvalue += origvalue.Mid(i,1); } } } // Get value long value; strvalue.ToLong(&value); // Cap it if (value < 0) value = 0; if (value > 9999) value = 9999; // Assign switch (which) { case 1: MarginL = value; break; case 2: MarginR = value; break; case 3: MarginV = value; break; default: throw _T("Invalid margin"); } } ////////////////////////// // Gets string for margin wxString AssDialogue::GetMarginString(int which) { int value; switch (which) { case 1: value = MarginL; break; case 2: value = MarginR; break; case 3: value = MarginV; break; default: throw _T("Invalid margin"); } wxString result = wxString::Format(_T("%04i"),value); return result; } /////////////////////////////////// // Process parameters via callback void AssDialogue::ProcessParameters(void (*callback)(wxString tagName,int par_n,AssOverrideParameter *param,void *userData),void *userData) { // Apply for all override blocks AssDialogueBlockOverride *curBlock; //ParseASSTags(); for (std::vector<AssDialogueBlock*>::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { if ((*cur)->type == BLOCK_OVERRIDE) { curBlock = static_cast<AssDialogueBlockOverride*> (*cur); curBlock->ProcessParameters(callback,userData); } } //ClearBlocks(); } /////////////////////////////// // Checks if two lines collide bool AssDialogue::CollidesWith(AssDialogue *target) { if (!target) return false; int a = Start.GetMS(); int b = End.GetMS(); int c = target->Start.GetMS(); int d = target->End.GetMS(); return ((a < c) ? (c < b) : (a < d)); } ///////// // Clone AssEntry *AssDialogue::Clone() { // Create clone AssDialogue *final = new AssDialogue(); // Copy data final->group = group; final->StartMS = StartMS; final->Valid = Valid; final->Actor = Actor; final->Comment = Comment; final->Effect = Effect; final->End = End; final->Layer = Layer; final->MarginL = MarginL; final->MarginR = MarginR; final->MarginV = MarginV; final->Start = Start; final->StartMS = final->StartMS; final->Style = Style; final->Text = Text; final->SetEntryData(GetEntryData()); // Return return final; } ////////////////////// AssDialogueBlock ////////////////////// /////////////// // Constructor AssDialogueBlock::AssDialogueBlock () { type = BLOCK_BASE; } ////////////// // Destructor AssDialogueBlock::~AssDialogueBlock () { } //////////////////////////// // Returns as a plain block // ---------------------- // If it isn't a plain block, returns NULL AssDialogueBlockPlain *AssDialogueBlock::GetAsPlain(AssDialogueBlock *base) { if (!base) return NULL; if (base->type == BLOCK_PLAIN) { return static_cast<AssDialogueBlockPlain*> (base); } return NULL; } //////////////////////////////// // Returns as an override block // ---------------------------- // If it isn't an override block, returns NULL AssDialogueBlockOverride *AssDialogueBlock::GetAsOverride(AssDialogueBlock *base) { if (!base) return NULL; if (base->type == BLOCK_OVERRIDE) { return static_cast<AssDialogueBlockOverride*> (base); } return NULL; } ////////////////////////////// // Returns as a drawing block // ---------------------------- // If it isn't an drawing block, returns NULL AssDialogueBlockDrawing *AssDialogueBlock::GetAsDrawing(AssDialogueBlock *base) { if (!base) return NULL; if (base->type == BLOCK_DRAWING) { return static_cast<AssDialogueBlockDrawing*> (base); } return NULL; } ////////////////////// AssDialogueBlockPlain ////////////////////// /////////////// // Constructor AssDialogueBlockPlain::AssDialogueBlockPlain () { type = BLOCK_PLAIN; } /////////////////// // Return the text wxString AssDialogueBlockPlain::GetText() { return text; } ////////////////////// AssDialogueBlockDrawing ////////////////////// /////////////// // Constructor AssDialogueBlockDrawing::AssDialogueBlockDrawing () { type = BLOCK_DRAWING; } /////////////////// // Return the text wxString AssDialogueBlockDrawing::GetText() { return text; } //////////////////////// // Multiply coordinates void AssDialogueBlockDrawing::MultiplyCoords(double x,double y) { // HACK: Implement a proper parser ffs!! wxStringTokenizer tkn(GetText(),_T(" "),wxTOKEN_DEFAULT); wxString cur; wxString final; bool isX = true; long temp; // Process tokens while (tkn.HasMoreTokens()) { cur = tkn.GetNextToken().Lower(); // Number, process it if (cur.IsNumber()) { // Multiply it cur.ToLong(&temp); if (isX) temp = temp*x + 0.5; else temp = temp*y + 0.5; // Write back to list final += wxString::Format(_T("%i "),temp); // Toggle X/Y isX = !isX; } // Text else { if (cur == _T("m") || cur == _T("n") || cur == _T("l") || cur == _T("b") || cur == _T("s") || cur == _T("p") || cur == _T("c")) isX = true; final += cur + _T(" "); } } // Write back final final = final.Left(final.Length()-1); text = final; }