/* * Copyright (C) 2004-2005 Vadim Berezniker * http://www.kryptolus.com * * 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, 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "common.h" #include "sabbu.h" #include "stringutils.h" #include "gui_status_bar.h" #include "kryString.h" #include "kryTextFileReader.h" #include "krySubSSA.h" #include "krySubReader.h" #include "krySubReaderSSA.h" extern struct sabbu app; krySubReaderSSA::krySubReaderSSA(char *filename) : krySubReader(filename) { } void krySubReaderSSA::ParseData(char *buffer, kryList *column_list, void *object) { char *ptr = buffer; char *var; ptr = string_ignore_whitespce(ptr); kryListIterator iter; column_list->GetIterator(&iter); while(TRUE) { struct krySubSSAColumnInfo *column_info = iter.GetNext(); if(!column_info) { ptr = string_ignore_whitespce(ptr); if(ptr[0] != 0) throw new kryTextParseError(-1, _("Extra data at the end of line. Line not loaded.")); break; } ptr = string_ignore_whitespce(ptr); var = (column_info->type == TOKEN_STR_END ? string_read_token_end(&ptr) : string_read_token(&ptr, ',', FALSE)); if(!var) throw new kryTextParseError(-1, _("Missing data while trying to read '%s' column. Line not loaded."), column_info->name); if(column_info->type == TOKEN_STR || column_info->type == TOKEN_STR_END) { column_info->func_read(object, var); } else if(column_info->type == TOKEN_DOUBLE) { ((SubSsaReadColumnFuncDouble) column_info->func_read)(object, atof(var)); } else if(column_info->type == TOKEN_INT) { if(column_info->func_transform_read) column_info->func_read(object, column_info->func_transform_read(var)); else column_info->func_read(object, ((void *) atoi(var))); } kry_free(var); if(column_info->type == TOKEN_STR_END) break; } } kryStyle *krySubReaderSSA::ParseStyleLine(char *buffer, kryList *style_column_list, gboolean script_ass) { kryStyle *style = new kryStyle(); char *ptr = buffer; ptr += 6; try { this->ParseData(ptr, style_column_list, style); } catch(kryTextParseError *error) { delete style; throw error; } if(style->GetBorderStyle() == 1) style->SetBorderStyle(BORDER_OUTLINE); else if(style->GetBorderStyle() == 3) style->SetBorderStyle(BORDER_OPAQUE); if(!script_ass) { if(style->GetAlignment() >= 9 && style->GetAlignment() <= 11) style->SetAlignment(style->GetAlignment() - 5); else if(style->GetAlignment() >= 5 && style->GetAlignment() <= 7) style->SetAlignment(style->GetAlignment() + 2); } style->SetMarginBottom(style->GetMarginTop()); return style; } kryEventDetailed *krySubReaderSSA::ParseEventLine(char *buffer, char *type, kryList *event_column_list) { kryEventDetailed *line = new kryEventDetailed(); if(!strcmp(type, "Dialogue")) { line->SetType(kryEvent::EVENT_DIALOG); } else if(!strcmp(type, "Comment")) { line->SetType(kryEvent::EVENT_COMMENT_DIALOG); } else if(!strcmp(type, "Blank")) { char *sub_type = string_read_token_end(&buffer); line->SetType(kryEvent::EVENT_BLANK); line->SetStyle(""); line->SetText(""); line->SetEffect(""); if(!strcmp(sub_type, "Dialogue")) line->SetTypeSub(kryEvent::EVENT_DIALOG); else if(!strcmp(sub_type, "Comment")) line->SetTypeSub(kryEvent::EVENT_COMMENT_DIALOG); else if(!strcmp(sub_type, "Blank")) line->SetTypeSub(kryEvent::EVENT_BLANK); kry_free(sub_type); return line; } else { delete line; throw new kryTextParseError(-1, _("Unknown event type '%s'. Line ignored."), type); } try { this->ParseData(buffer, event_column_list, line); } catch(kryTextParseError *error) { delete line; throw error; } return line; } void krySubReaderSSA::FreeColumnList(kryList *list) { list->ForEach((GFunc) kry_free_minimal, NULL); } kryList *krySubReaderSSA::BuildColumnList(char *buffer, int mask) { char *ptr = buffer; char *var; char *ptr_prev = NULL; gboolean lastStr = FALSE; kryList *columns = new kryList(); ptr += 7; ptr = string_ignore_whitespce(ptr); while(!lastStr) { ptr = string_ignore_whitespce(ptr); var = string_read_token(&ptr, ','); if(!var && ptr_prev && ptr_prev[0] != 0) { lastStr = TRUE; ptr_prev = string_ignore_whitespce(ptr_prev); var = kry_strdup(ptr_prev); } else if(!var) { var = kry_strdup(""); } for(int i = strlen(var) - 1; i >= 0 && var[i] == ' '; i--) var[i] = 0; struct krySubSSAColumnInfo *info_orig = sub_ssa_save_find_column(var, mask); if(info_orig) { struct krySubSSAColumnInfo *info = kry_new(struct krySubSSAColumnInfo, 1); *info = *info_orig; columns->Append(info); } else { this->AddError(new kryTextParseError(-1, _("Unknown column '%s' in column list. Column ignored."), var)); } ptr_prev = ptr; kry_free(var); } return columns; } /* * Reads SSA data from the given file. * filename: the file to read * subscript: pointer to the structure where the data will be stored (structure must be created via script_new functin) * * Returns * SCRIPT_OK if the file is successfully read. * SCRIPT_WRONGFORMAT if the file doesn't seem to be in SSA format. * SCRIPT_ERROR if a fatal error occured while parsing the file. */ enum krySubReader::retval krySubReaderSSA::ReadScript(kryScript *script, kryList *list) { char *buffer, *buffer_orig = NULL; enum sub_ssa_section sub_ssa_section = SECTION_NONE; double prev_progress = 0; gboolean infoNotFound = TRUE; kryList *style_columns = NULL, *event_columns = NULL; gboolean info_key_found = FALSE; kryStyle *style_empty_line_end = NULL; kryString info_empty_line_end; script->SetFilename(this->GetFilename()); if(!this->Open()) return SCRIPT_ERROR; this->m_errors = list; this->m_script = script; GTimer *timer = g_timer_new(); g_timer_start(timer); while(TRUE) { int len; if(buffer_orig) { kry_free(buffer_orig); buffer_orig = NULL; } buffer = this->GetLine(); if(buffer == NULL) break; buffer_orig = buffer; len = strlen(buffer); // don't update too often if(g_timer_elapsed(timer, NULL) > 1.0) { gui_status_bar_set_progress(app.ui.status_bar, this->GetProgress()); prev_progress = this->GetProgress(); g_timer_start(timer); } while(len > 0 && (buffer[0] == ' ') || buffer[0] == '\t') { buffer++; len--; } if(len > 0 && buffer[0] == ';') { if(sub_ssa_section == SECTION_EVENTS) { kryEventDetailed *event = new kryEventDetailed(); event->SetType(kryEvent::EVENT_COMMENT_STRING); event->SetText(buffer + 1); event->SetStyle(""); char tmp = buffer[1]; buffer[1] = 0; event->SetPrefix(buffer_orig); buffer[1] = tmp; this->AddEvent(event); } else if(sub_ssa_section == SECTION_V4_STYLES || sub_ssa_section == SECTION_V4P_STYLES) { kryStyle *style = new kryStyle(); style->SetType(KRY_STYLE_COMMENT); // a user cannot create styles with a comma in it so if we use a comma // we can be sure we won't conflict with user styles. we use the line number // to make sure this name is unique char *name = kry_strdup_printf(KRY_LOC ",%d", this->GetLineNumber()); style->SetName(name); style->SetPrefix(buffer_orig); script->AddStyle(style); style_empty_line_end = NULL; kry_free(name); } else if(sub_ssa_section == SECTION_INFO && info_key_found) { // a user cannot create properties with a colon in it so if we use a colon // we can be sure we won't conflict with user styles. we use the line number // to make sure this name is unique char *name = kry_strdup_printf(KRY_LOC ":%d", this->GetLineNumber()); script->SetProperty(name, kry_strdup(buffer_orig)); info_empty_line_end = ""; } continue; } if(len == 0) { if(sub_ssa_section == SECTION_EVENTS) { kryEventDetailed *event = new kryEventDetailed(); event->SetType(kryEvent::EVENT_COMMENT_BLANK); event->SetText(""); event->SetStyle(""); event->SetPrefix(buffer_orig); this->AddEvent(event); } else if(sub_ssa_section == SECTION_V4_STYLES || sub_ssa_section == SECTION_V4P_STYLES) { kryStyle *style = new kryStyle(); style->SetType(KRY_STYLE_COMMENT); // a user cannot create styles with a comma in it so if we use a comma // we can be sure we won't conflict with user styles. we use the line number // to make sure this name is unique char *name = kry_strdup_printf(KRY_LOC ",%d", this->GetLineNumber()); style->SetName(name); style->SetPrefix(buffer_orig); script->AddStyle(style); style_empty_line_end = style; } else if(sub_ssa_section == SECTION_INFO && info_key_found) { // a user cannot create properties with a colon in it so if we use a colon // we can be sure we won't conflict with user styles. we use the line number // to make sure this name is unique char *name = kry_strdup_printf(KRY_LOC ":%d", this->GetLineNumber()); script->SetProperty(name, kry_strdup(buffer_orig)); info_empty_line_end = name; } continue; } // if line starts a new section, we process it and continue to next line if(len > 3 && buffer[0] == '[' && buffer[len - 1] == ']') { char *ptr = buffer; char *section; ptr++; section = string_read_token(&ptr, ']'); if(!section) { g_warning("Error extraction section name"); continue; } if(!strcmp(section, "Script Info")) sub_ssa_section = SECTION_INFO; else if(!strcmp(section, "V4 Styles")) sub_ssa_section = SECTION_V4_STYLES; else if(!strcmp(section, "V4+ Styles")) sub_ssa_section = SECTION_V4P_STYLES; else if(!strcmp(section, "Events")) sub_ssa_section = SECTION_EVENTS; else { sub_ssa_section = SECTION_NONE; this->AddError(new kryTextParseError(this->GetLineNumber(), _("Unknown section: '%s'. WARNING: This section will be discarded on save."), section)); } kry_free(section); continue; } if(infoNotFound && sub_ssa_section == SECTION_INFO) infoNotFound = FALSE; if(infoNotFound) { kry_free(buffer_orig); this->Close(); return SCRIPT_WRONGFORMAT; } if(sub_ssa_section == SECTION_INFO) { char *buffer_copy = kry_strdup(buffer); char *ptr_orig = buffer_copy; char *key = string_read_token(&buffer_copy, ':'); char *value; if(!key) { kry_free(ptr_orig); continue; } buffer_copy = string_ignore_whitespce(buffer_copy); value = string_read_token_end(&buffer_copy); if(!value) { kry_free(ptr_orig); kry_free(key); continue; } script->SetProperty(key, value); info_key_found = TRUE; info_empty_line_end = ""; kry_free(ptr_orig); /* check data */ continue; } if((sub_ssa_section == SECTION_V4_STYLES || sub_ssa_section == SECTION_V4P_STYLES) && len >= 7 && !strncmp(buffer, "Format:", 7)) { if(sub_ssa_section == SECTION_V4_STYLES) style_columns = this->BuildColumnList(buffer, COLUMN_STYLE_ANY | COLUMN_STYLE_SSA | COLUMN_EVENT_ANY_READONLY); else style_columns = this->BuildColumnList(buffer, COLUMN_STYLE_ANY | COLUMN_STYLE_ASS | COLUMN_EVENT_ANY_READONLY); kryListIterator iter; style_columns->GetIterator(&iter); while(krySubSSAColumnInfo *info = iter.GetNext()) { if(sub_ssa_section == SECTION_V4_STYLES && ( !strcmp(info->name, "OutlineColour") || !strcmp(info->name, "Underline") || !strcmp(info->name, "StrikeOut") || !strcmp(info->name, "ScaleX") || !strcmp(info->name, "ScaleY") || !strcmp(info->name, "Spacing") || !strcmp(info->name, "Angle") )) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("An ASS column (%s) has been listed in an SSA style format"), info->name)); } else if(sub_ssa_section == SECTION_V4P_STYLES && (!strcmp(info->name, "TertiaryColour") || !strcmp(info->name, "AlphaLevel"))) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("An SSA column (%s) has been listed in an ASS style format"), info->name)); } } continue; } if((sub_ssa_section == SECTION_V4_STYLES || sub_ssa_section == SECTION_V4P_STYLES) && len >= 6 && !strncmp(buffer, "Style:", 6)) { try { kryStyle *style = this->ParseStyleLine(buffer, style_columns, (sub_ssa_section == SECTION_V4P_STYLES)); if(sub_ssa_section == SECTION_V4P_STYLES) { script->SetType(KRY_FORMAT_ASS); style->SetType(KRY_STYLE_ASS); } script->AddStyle(style); style_empty_line_end = NULL; } catch(kryTextParseError * error) { error->SetLine(this->GetLineNumber()); this->AddError(error); } continue; } if((sub_ssa_section == SECTION_V4_STYLES || sub_ssa_section == SECTION_V4P_STYLES)) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("Unknown line. WARNING: Will be discarded on save."))); continue; } if((sub_ssa_section == SECTION_EVENTS) && len >= 7 && !strncmp(buffer, "Format:", 7)) { event_columns = this->BuildColumnList(buffer, COLUMN_EVENT_ANY | COLUMN_EVENT_SSA | COLUMN_EVENT_ASS | COLUMN_EVENT_ANY_READONLY); kryListIterator iter; event_columns->GetIterator(&iter); while(krySubSSAColumnInfo *info = iter.GetNext()) { if(script->GetType() == KRY_FORMAT_SSA && info->column_type & COLUMN_EVENT_ASS) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("An ASS column (%s) has been listed in an SSA style format"), info->name)); } else if(script->GetType() == KRY_FORMAT_ASS && info->column_type & COLUMN_EVENT_SSA) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("An SSA column (%s) has been listed in an ASS event format"), info->name)); } } continue; } if(sub_ssa_section == SECTION_EVENTS) { char *ptr = string_ignore_whitespce(buffer); char *type = string_read_token(&ptr, ':'); kryEventDetailed *line; if(!type) { this->AddError(new kryTextParseError(this->GetLineNumber(), _("Unknown line. WARNING: Will be discarded on save."))); continue; } try { line = this->ParseEventLine(ptr+1, type, event_columns); if(line) { this->AddEvent(line); if(line->GetLayer() != 0) script->SetType(KRY_FORMAT_ASS); } } catch(kryTextParseError *error) { error->SetLine(this->GetLineNumber()); this->AddError(error); } kry_free(type); continue; } } if(buffer_orig) kry_free(buffer_orig); if(event_columns) { this->FreeColumnList(event_columns); delete event_columns; } if(style_columns) { this->FreeColumnList(style_columns); delete style_columns; } if(infoNotFound) { this->Close(); return SCRIPT_WRONGFORMAT; } if(style_empty_line_end) script->RemoveStyle(style_empty_line_end->GetName()); if(!info_empty_line_end.IsEmpty()) script->SetProperty(info_empty_line_end.GetBuffer(), NULL); kryListIterator iter; script->GetEventIterator(&iter); int index = 0, index_empty_line = -1; while(kryEvent *event = iter.GetNext()) { if(event->GetType() == kryEvent::EVENT_COMMENT_BLANK) { if(index_empty_line == -1) index_empty_line = index; } else { index_empty_line = -1; } index++; } if(index_empty_line != -1) { kryList *> delete_list; kryList & list = script->GetEventList(); list.GetNthIterator(&iter, index_empty_line); while(iter.GetNext()) iter.Remove(); } if(!script->GetStyle("Default")) { script->AddDefaultStyle(); script->GetStyle("Default")->SetType( (script->GetType() == KRY_FORMAT_ASS) ? KRY_STYLE_ASS : KRY_STYLE_SSA); } script->SetEncoding(this->GetEncoding()); this->m_errors = NULL; this->Close(); return SCRIPT_OK; }