// 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 #include ////////////////////// AssDialogue ////////////////////// // Constructs AssDialogue AssDialogue::AssDialogue() { Type = ENTRY_DIALOGUE; 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) { Type = ENTRY_DIALOGUE; group = _T("[Events]"); data = _data; Valid = Parse(IsSSA); if (!Valid) { throw _T("Failed parsing line."); } UpdateData(); } ////////////// // Destructor AssDialogue::~AssDialogue () { Clear(); } ///////// // Clear void AssDialogue::Clear () { using std::vector; for (vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { delete *cur; } Blocks.clear(); } ////////////////// // Parse ASS Data bool AssDialogue::Parse(bool IsSSA) { size_t pos = 0; wxString temp; // Get type if (data.substr(pos,9) == _T("Dialogue:")) { Comment = false; pos = 10; } else if (data.substr(pos,8) == _T("Comment:")) { Comment = true; pos = 9; } else return false; wxStringTokenizer tkn(data.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); // Get text Text = tkn.GetNextToken(); while (tkn.HasMoreTokens()) { Text += _T(","); Text += tkn.GetNextToken(); } return true; } ////////////////////////////////// // Update AssDialogue's data line void AssDialogue::UpdateData () { // Prepare data = _T(""); // Write all data if (Comment) data += _T("Comment: "); else data += _T("Dialogue: "); data += wxString::Format(_T("%01i"),Layer); data += _T(","); data += Start.GetASSFormated() + _T(","); data += End.GetASSFormated() + _T(","); Style.Replace(_T(","),_T(";")); Actor.Replace(_T(","),_T(";")); data += Style + _T(","); data += Actor + _T(","); data += GetMarginString(1); data += _T(","); data += GetMarginString(2); data += _T(","); data += GetMarginString(3); data += _T(","); Effect.Replace(_T(","),_T(";")); data += Effect + _T(","); data += Text; } /////////////////////////////// // 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(""),_T("{\\i1}")); total += Text.Replace(_T(""),_T("{\\i0}")); total += Text.Replace(_T(""),_T("{\\b1}")); total += Text.Replace(_T(""),_T("{\\b0}")); total += Text.Replace(_T(""),_T("{\\u1}")); total += Text.Replace(_T(""),_T("{\\u0}")); total += Text.Replace(_T(""),_T("{\\s1}")); total += Text.Replace(_T(""),_T("{\\s0}")); // Process 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(""),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(" 0) UpdateText(); UpdateData(); } ////////////////// // Parse ASS tags void AssDialogue::ParseASSTags () { // Clear blocks for (size_t i=0;iparent = this; block->text = work; block->ParseTags(); Blocks.push_back(block); // Look for \p in block std::vector::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; vector::iterator next; for (vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur=next) { next = cur; next++; if ((*cur)->type == BLOCK_OVERRIDE) { delete *cur; Blocks.erase(cur); } } UpdateText(); UpdateData(); } /////////////////////// // 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 for (size_t i=0;iTags.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(""); } if (!temp && isItalic) { isItalic = false; final += _T(""); } } // Underline if (curTag->Name == _T("\\u")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isUnder) { isUnder = true; final += _T(""); } if (!temp && isUnder) { isUnder = false; final += _T(""); } } // Strikeout if (curTag->Name == _T("\\s")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isStrike) { isStrike = true; final += _T(""); } if (!temp && isStrike) { isStrike = false; final += _T(""); } } // Bold if (curTag->Name == _T("\\b")) { temp = curTag->Params.at(0)->AsBool(); if (temp && !isBold) { isBold = true; final += _T(""); } if (!temp && isBold) { isBold = false; final += _T(""); } } } } } // Plain text else { curPlain = AssDialogueBlock::GetAsPlain(Blocks.at(i)); if (curPlain) { final += curPlain->GetText(); } } } Text = final; UpdateData(); } ////////////////////////// // Updates text from tags void AssDialogue::UpdateText () { using std::vector; Text = _T(""); for (vector::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 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; for (std::vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { if ((*cur)->type == BLOCK_OVERRIDE) { curBlock = static_cast (*cur); curBlock->ProcessParameters(callback,userData); } } } /////////////////////////////// // 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)); } ////////////////////// 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 (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 (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 (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; }