/* * subtitle editor * * http://kitone.free.fr/subtitleeditor/ * * Copyright @ 2005-2006, kitone * * Contact: kitone at free dot fr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * See gpl.txt for more information regarding the GNU General Public License. * * * \file * \brief * \author kitone (kitone at free dot fr) */ #include "SubtitleASS.h" #include "Color.h" #include "utility.h" #include #include #include #include #include #include #include "RegEx.h" /* * */ Glib::ustring SubtitleASS::get_name() { return "Advanced Sub Station Alpha"; } /* * */ Glib::ustring SubtitleASS::get_extension() { return "ass"; } /* * */ bool SubtitleASS::check(const std::string &line) { static RegEx ex("^ScriptType:\\s+(v|V)4.00\\+"); return ex.exec(line); } /* * HACK ! réecrire ça ! */ bool str2int(const std::string &str); std::string bool2str(const bool &boolean); bool SubtitleASS::str2bool(const std::string &str) { if(str=="0") return false; return true; } /* * hack ! */ Glib::ustring SubtitleASS::clean_style_name(const Glib::ustring &name) { Glib::ustring str = name; Glib::ustring::size_type n = str.find('*'); if(n != Glib::ustring::npos) str.erase(n,1); return str; } /* * recupere dans un tableau le format a partir de la line */ std::vector SubtitleASS::build_format(const std::string &text, int column, bool remove_space) { std::string line; if(remove_space) { for(unsigned int i=7; i array; std::string::size_type s=0, e=0; int i=0; do { if(column > 0 && i+1 == column) { array.push_back(line.substr(s,std::string::npos)); break; } e = line.find(",", s); array.push_back(line.substr(s,e-s)); s=e+1; ++i; }while(e != std::string::npos); return array; } /* * construtor */ SubtitleASS::SubtitleASS(Document* doc) :SubtitleFormat(doc) { se_debug(SE_DEBUG_LOADER | SE_DEBUG_SAVER); } /* * */ SubtitleASS::~SubtitleASS() { se_debug(SE_DEBUG_LOADER | SE_DEBUG_SAVER); } /* * read subtitle file */ bool SubtitleASS::open(const Glib::ustring &filename, const Glib::ustring &encoding) { se_debug(SE_DEBUG_LOADER); SubtitleFormat::open(filename, encoding); std::ifstream file(filename.c_str()); if(!file) { throw SubtitleException("SubtitleASS", _("I can't open this file.")); } std::string line; enum TYPE { UNKNOWN = 0, SCRIPT_INFO, STYLES, EVENTS }; TYPE type = UNKNOWN; while(!file.eof() && std::getline(file, line)) { if(line.find("[Script Info]") != std::string::npos) type = SCRIPT_INFO; else if(line.find("[V4+ Styles]") != std::string::npos) type = STYLES; else if(line.find("[Events]") != std::string::npos) type = EVENTS; switch(type) { case UNKNOWN: break; case SCRIPT_INFO: readScripInfo(line); break; case STYLES: readStyles(line); break; case EVENTS: readEvents(line); break; } } file.close(); return true; } /* * Sauvegarde */ bool SubtitleASS::save(const Glib::ustring &filename, const Glib::ustring &encoding) { se_debug(SE_DEBUG_SAVER); SubtitleFormat::save(filename, encoding); std::ofstream file(filename.c_str()); if(!file) { throw SubtitleException("SubtitleASS", _("I can't open this file.")); } se_debug_message(SE_DEBUG_SAVER, "save ScriptInfo"); // ScriptInfo { file << utf8_to_charset("[Script Info]") << std::endl; file << utf8_to_charset("; This script was created by subtitleeditor ") << utf8_to_charset(VERSION) << std::endl; file << utf8_to_charset("; http://kitone.free.fr/subtitleeditor/") << std::endl; #define CHECK(label, key) if(m_scriptInfo->key.size() > 0) { file << utf8_to_charset(label) << utf8_to_charset(m_scriptInfo->key) << std::endl; } m_scriptInfo->ScriptType="V4.00+"; CHECK("Title: ", Title); CHECK("Original Script: ", OriginalScript); CHECK("Original Translation: ", OriginalTranslation); CHECK("Original Editing: ", OriginalEditing); CHECK("Synch Point: ", SynchPoint); CHECK("Script Updated By: ", ScriptUpdatedBy); CHECK("Update Details: ", UpdateDetails); CHECK("ScriptType: ", ScriptType); CHECK("Collisions: ", Collisions); CHECK("PlayResX: ", PlayResX); CHECK("PlayResY: ", PlayResY); CHECK("Timer: ", Timer); CHECK("WrapStyle: ", WrapStyle); CHECK("Dialogue: ", Dialogue); CHECK("Comment: ", Comment); CHECK("Picture: ", Picture); CHECK("Sound: ", Sound); CHECK("Movie: ", Movie); #undef CHECK file << std::endl; } se_debug_message(SE_DEBUG_SAVER, "save Style"); // Style { StyleColumnRecorder column; file << "[V4+ Styles]" << std::endl; file << "Format: " << "Name, " << "Fontname, " << "Fontsize, " << "PrimaryColour, " << "SecondaryColour, " << "OutlineColour, " << "BackColour, " << "Bold, " << "Italic, " << "Underline, " << "StrikeOut, " << "ScaleX, " << "ScaleY, " << "Spacing, " << "Angle, " << "BorderStyle, " << "Outline, " << "Shadow, " << "Alignment, " << "MarginL, " << "MarginR, " << "MarginV, " << "Encoding" << std::endl; Gtk::TreeNodeChildren rows = m_styleModel->children(); for(Gtk::TreeIter it = rows.begin(); it; ++it) { std::ostringstream oss; oss << "Style: " << (*it)[column.name] << "," << (*it)[column.font_name] << "," << (*it)[column.font_size] << "," << color_to_ass_color((*it)[column.primary_colour]) << "," << color_to_ass_color((*it)[column.secondary_colour]) << "," << color_to_ass_color((*it)[column.outline_colour]) << "," << color_to_ass_color((*it)[column.shadow_colour]) << "," << bool2str((*it)[column.bold]) << "," << bool2str((*it)[column.italic]) << "," << bool2str((*it)[column.underline]) << "," << bool2str((*it)[column.strikeout]) << "," << (*it)[column.scale_x] << "," << (*it)[column.scale_y] << "," << (*it)[column.spacing] << "," << (*it)[column.angle] << "," << (*it)[column.border_style] << "," << (*it)[column.outline] << "," << (*it)[column.shadow] << "," << (*it)[column.alignment] << "," << (*it)[column.margin_l] << "," << (*it)[column.margin_r] << "," << (*it)[column.margin_v] << "," << (*it)[column.encoding] << std::endl; file << utf8_to_charset(oss.str()); } file << std::endl; } se_debug_message(SE_DEBUG_SAVER, "save Event"); // Event { file << "[Events]" << std::endl; // format: file << "Format: " << "Layer, " << "Start, " << "End, " << "Style, " << "Actor, " << "MarginL, " << "MarginR, " << "MarginV, " << "Effect, " << "Text" << std::endl; // dialog: Gtk::TreeNodeChildren rows = m_subtitleModel->children(); for(Gtk::TreeIter it = rows.begin(); it; ++it) { SubtitleModifier subtitle(it); std::ostringstream oss; Glib::ustring text = subtitle.get_text(); newline_to_characters(text, "\\n"); oss << "Dialogue: " << subtitle.get_layer() << "," << subtitletime_to_ass_time(subtitle.get_start()) << "," << subtitletime_to_ass_time(subtitle.get_end()) << "," << utf8_to_charset(subtitle.get_style()) << "," << utf8_to_charset(subtitle.get_name()) << "," << std::setw(4) << std::setfill('0') << subtitle.get_margin_l() << "," << std::setw(4) << std::setfill('0') << subtitle.get_margin_r() << "," << std::setw(4) << std::setfill('0') << subtitle.get_margin_v() << "," << utf8_to_charset(subtitle.get_effect()) << "," << utf8_to_charset(text) << std::endl; file << oss.str(); } } file.close(); return true; } /* * READ BLOCK */ /* * lecture du block [ScriptInfo] */ bool SubtitleASS::readScripInfo(const std::string &_line) { se_debug_message(SE_DEBUG_LOADER, "read ScriptInfo"); std::string line = check_end_char(_line); #define CHECK(label, key) \ if(line.find(label) != std::string::npos) \ { line.erase(0, (std::string(label)).size()); m_scriptInfo->key = check_end_char(charset_to_utf8(line)); return true; } // else std::cerr << "CHECK not found: " << label << std::endl; CHECK("Title: ", Title); CHECK("Original Script: ", OriginalScript); CHECK("Original Translation: ", OriginalTranslation); CHECK("Original Editing: ", OriginalEditing); CHECK("Synch Point: ", SynchPoint); CHECK("Script Updated By: ", ScriptUpdatedBy); CHECK("Update Details: ", UpdateDetails); CHECK("ScriptType: ", ScriptType); CHECK("Collisions: ", Collisions); CHECK("PlayResX: ", PlayResX); CHECK("PlayResY: ", PlayResY); CHECK("Timer: ", Timer); CHECK("WrapStyle: ", WrapStyle); CHECK("Dialogue: ", Dialogue); CHECK("Comment: ", Comment); CHECK("Picture: ", Picture); CHECK("Sound: ", Sound); CHECK("Movie: ", Movie); #undef CHECK return true; } /* * lecture du block [V4+ Styles] */ bool SubtitleASS::readStyles(const std::string &_line) { se_debug_message(SE_DEBUG_LOADER, "read Style"); std::string line = check_end_char(_line); if(line.find("Style: ") != std::string::npos) { StyleColumnRecorder column; // on donne la ligne sans "Style: " = size - 7 std::vector fmt = build_format(line.substr(7, line.size()-7), formats.size(), false); Gtk::TreeIter it = m_styleModel->append(); // on supprime '*' si il existe (*it)[column.name] = clean_style_name( charset_to_utf8(fmt[ map["Name"] ]) ); (*it)[column.font_name] = fmt[ map["Fontname"] ]; (*it)[column.font_size] = str2int( fmt[ map["Fontsize"] ] ); // color (*it)[column.primary_colour] = ass_color_to_color( fmt[ map["PrimaryColour"] ]); (*it)[column.secondary_colour] = ass_color_to_color( fmt[ map["SecondaryColour"] ]); (*it)[column.outline_colour] = ass_color_to_color( fmt[ map["OutlineColour"] ]); (*it)[column.shadow_colour] = ass_color_to_color( fmt[ map["BackColour"] ]); (*it)[column.bold] = str2bool( fmt[ map["Bold"] ] ); (*it)[column.italic] = str2bool( fmt[ map["Italic"] ] ); (*it)[column.underline] = str2bool( fmt[ map["Underline"] ] ); (*it)[column.strikeout] = str2bool( fmt[ map["StrikeOut"] ] ); (*it)[column.scale_x] = str2int( fmt[ map["ScaleX"] ] ); (*it)[column.scale_y] = str2int( fmt[ map["ScaleY"] ] ); (*it)[column.spacing] = str2int( fmt[ map["Spacing"] ] ); (*it)[column.angle] = str2int( fmt[ map["Angle"] ] ); (*it)[column.border_style] = str2int( fmt[ map["BorderStyle"] ] ); (*it)[column.outline] = str2int( fmt[ map["Outline"] ] ); (*it)[column.shadow] = str2int( fmt[ map["Shadow"] ]); (*it)[column.alignment] = str2int( fmt[ map["Alignment"] ]); (*it)[column.margin_l] = str2int( fmt[ map["MarginL"] ]); (*it)[column.margin_r] = str2int( fmt[ map["MarginR"] ]); (*it)[column.margin_v] = str2int( fmt[ map["MarginV"] ]); (*it)[column.encoding] = str2int( fmt[ map["Encoding"] ]); } else if(line.find("Format: ") != std::string::npos) { formats = build_format(line, -1, true); for(unsigned int i=0; i array = build(line, 10); SubtitleModifier subtitle(m_subtitleModel->append()); // marked/layer subtitle.set_layer(array[0]); subtitle.set_start_and_end( ass_time_to_subtitletime(array[1]) , ass_time_to_subtitletime(array[2]) ); subtitle.set_style(clean_style_name( charset_to_utf8(array[3]) )); subtitle.set_name(charset_to_utf8(array[4])); subtitle.set_margin_l(array[5]); subtitle.set_margin_r(array[6]); subtitle.set_margin_v(array[7]); subtitle.set_effect(charset_to_utf8(array[8])); Glib::ustring text = check_end_char(charset_to_utf8(array[9])); characters_to_newline(text, "\\n"); characters_to_newline(text, "\\N"); subtitle.set_text(text); } return true; } /* * */ std::vector< std::string > SubtitleASS::build(const std::string &line, unsigned int column) { std::vector< std::string > array; std::string::size_type s=0, e=0; do { if(column > 0 && array.size()+1 == column) { array.push_back(line.substr(s,std::string::npos)); break; } e = line.find(",", s); array.push_back(line.substr(s,e-s)); s=e+1; }while(e != std::string::npos); return array; } /* * convertir le temps utiliser par subtitle editor en temps valide pour le format SSA * 0:00:00.000 -> 0:00:00.00 */ Glib::ustring SubtitleASS::subtitletime_to_ass_time(const SubtitleTime &time) { int msecs = (int)(time.msecs * 0.1); gchar *tmp = g_strdup_printf("%01i:%02i:%02i.%02i", time.hours, time.mins, time.secs, msecs); Glib::ustring res(tmp); g_free(tmp); return res; } /* * convertir un temps ASS en SubtitleTime (string) */ Glib::ustring SubtitleASS::ass_time_to_subtitletime(const Glib::ustring &text) { if(SubtitleTime::validate(text)) return SubtitleTime(text).str(); std::cerr << "SubtitleASS::ass_time_to_subtitletime error > " << text << std::endl; return SubtitleTime::null(); } /* * convertir une couleur en couleur ASS pour la sauvegarde */ Glib::ustring SubtitleASS::color_to_ass_color(const Color& color) { Color c = color; unsigned int r = c.getR(); unsigned int g = c.getG(); unsigned int b = c.getB(); unsigned int a = c.getA(); unsigned int abgr = /*a << 24 |*/ b << 16 | g << 8 | r << 0; gchar *tmp = g_strdup_printf("&H%08X", abgr); Glib::ustring str(tmp); g_free(tmp); return str; } /* * convertir une couleur ASS en Color */ Color SubtitleASS::ass_color_to_color(const Glib::ustring &str) { try { Glib::ustring value = str; if(value.size() > 2) { if(value[0] == '&') value.erase(0,1); if(value[0] == 'h' || value[0] == 'H') value.erase(0,1); if(value[value.size() ] == '&') value.erase(value.size() -1, 1); } long temp[4] = {0,0,0,0}; for(int i=0; i<4; ++i) { if(value.size() > 0) { Glib::ustring tmp = value.substr(value.size() - 2, 2); temp[i] = strtoll(tmp.c_str(), NULL, 16); value = value.substr(0, value.size() -2); } } return Color(temp[0], temp[1], temp[2], 255);// temp[3]); } catch(...) { //std::cerr << "SubtitleASS::ass_color_to_color error > " << str << std::endl; } return Color(0,0,0,255); }