Fix crash on (auto)save when using CSRI and video is open

Calling AssFile::Save/Load from multiple threads (even on different
objects) was not safe due to that is uses SubtitleFormat internally,
which was inheriently thread-unsafe. To fix this, change
SubtitleFormat's interface to support immutable implementations, and
make all of the current implementations immutable.

This isn't a perfect solution - making a subtitle format implemented in
lua immutable would be rather difficult - so at some point in the future
SubtitleFormat should probably be changed to a factory which returns new
objects from GetReader/GetWriter.

Originally committed to SVN as r6365.
This commit is contained in:
Thomas Goyne 2012-01-26 20:08:38 +00:00
parent d001d66b12
commit 15a4eca7ce
19 changed files with 221 additions and 313 deletions

View File

@ -88,7 +88,7 @@ void AssFile::Load(const wxString &_filename,wxString charset,bool addToRecent)
}
// Get proper format reader
SubtitleFormat *reader = SubtitleFormat::GetReader(_filename);
const SubtitleFormat *reader = SubtitleFormat::GetReader(_filename);
if (!reader) {
wxMessageBox("Unknown file type","Error loading file",wxICON_ERROR | wxOK);
@ -97,8 +97,7 @@ void AssFile::Load(const wxString &_filename,wxString charset,bool addToRecent)
// Read file
AssFile temp;
reader->SetTarget(&temp);
reader->ReadFile(_filename,charset);
reader->ReadFile(&temp, _filename, charset);
swap(temp);
}
catch (agi::UserCancelException const&) {
@ -156,7 +155,7 @@ void AssFile::Load(const wxString &_filename,wxString charset,bool addToRecent)
}
void AssFile::Save(wxString filename, bool setfilename, bool addToRecent, wxString encoding) {
SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
if (!writer)
throw "Unknown file type.";
@ -168,8 +167,7 @@ void AssFile::Save(wxString filename, bool setfilename, bool addToRecent, wxStri
FileSave();
writer->SetTarget(this);
writer->WriteFile(filename, encoding);
writer->WriteFile(this, filename, encoding);
if (addToRecent) {
AddToRecent(filename);
@ -229,8 +227,7 @@ bool AssFile::CanSave() {
if (ext == ".txt") return false;
// Check if it's a known extension
SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
if (!writer) return false;
if (!SubtitleFormat::GetWriter(filename)) return false;
// Scan through the lines
AssStyle defstyle;

View File

@ -59,8 +59,6 @@ using namespace std::tr1::placeholders;
SubtitleFormat::SubtitleFormat(wxString const& name)
: name(name)
, isCopy(0)
, Line(0)
{
formats.push_back(this);
}
@ -69,12 +67,6 @@ SubtitleFormat::~SubtitleFormat() {
formats.remove(this);
}
void SubtitleFormat::SetTarget(AssFile *file) {
ClearCopy();
Line = file ? &file->Line : 0;
assFile = file;
}
bool SubtitleFormat::CanReadFile(wxString const& filename) const {
return GetReadWildcards().Index(filename.AfterLast('.'), false) != wxNOT_FOUND;
}
@ -83,33 +75,8 @@ bool SubtitleFormat::CanWriteFile(wxString const& filename) const {
return GetWriteWildcards().Index(filename.AfterLast('.'), false) != wxNOT_FOUND;
}
void SubtitleFormat::CreateCopy() {
SetTarget(new AssFile(*assFile));
isCopy = true;
}
void SubtitleFormat::ClearCopy() {
if (isCopy) {
delete assFile;
assFile = NULL;
isCopy = false;
}
}
void SubtitleFormat::Clear() {
assFile->Clear();
}
void SubtitleFormat::LoadDefault(bool defline) {
assFile->LoadDefault(defline);
}
void SubtitleFormat::AddLine(wxString data, int *version, AssAttachment **attach) {
assFile->AddLine(data, version, attach);
}
/// @brief Ask the user to enter the FPS
FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) {
FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) const {
wxArrayString choices;
bool drop = false;
@ -171,20 +138,16 @@ FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) {
return FractionalTime(fps, drop);
}
void SubtitleFormat::SortLines() {
AssFile::Sort(*Line);
}
void SubtitleFormat::StripTags() {
for (std::list<AssEntry*>::iterator cur = Line->begin(); cur != Line->end(); ++cur) {
void SubtitleFormat::StripTags(LineList &lines) const {
for (LineList::iterator cur = lines.begin(); cur != lines.end(); ++cur) {
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
current->StripTags();
}
}
}
void SubtitleFormat::ConvertNewlines(wxString const& newline, bool mergeLineBreaks) {
for (std::list<AssEntry*>::iterator cur = Line->begin(); cur != Line->end(); ++cur) {
void SubtitleFormat::ConvertNewlines(LineList &lines, wxString const& newline, bool mergeLineBreaks) const {
for (LineList::iterator cur = lines.begin(); cur != lines.end(); ++cur) {
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
current->Text.Replace("\\h", " ");
current->Text.Replace("\\n", newline);
@ -196,25 +159,25 @@ void SubtitleFormat::ConvertNewlines(wxString const& newline, bool mergeLineBrea
}
}
void SubtitleFormat::StripComments() {
for (std::list<AssEntry*>::iterator it = Line->begin(); it != Line->end(); ) {
void SubtitleFormat::StripComments(LineList &lines) const {
for (LineList::iterator it = lines.begin(); it != lines.end(); ) {
AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);
if (!diag || (!diag->Comment && diag->Text.size()))
++it;
else {
delete *it;
Line->erase(it++);
lines.erase(it++);
}
}
}
void SubtitleFormat::StripNonDialogue() {
for (std::list<AssEntry*>::iterator it = Line->begin(); it != Line->end(); ) {
void SubtitleFormat::StripNonDialogue(LineList &lines) const {
for (LineList::iterator it = lines.begin(); it != lines.end(); ) {
if (dynamic_cast<AssDialogue*>(*it))
++it;
else {
delete *it;
Line->erase(it++);
lines.erase(it++);
}
}
}
@ -227,11 +190,11 @@ static bool dialog_start_lt(AssEntry *pos, AssDialogue *to_insert) {
/// @brief Split and merge lines so there are no overlapping lines
///
/// Algorithm described at http://devel.aegisub.org/wiki/Technical/SplitMerge
void SubtitleFormat::RecombineOverlaps() {
std::list<AssEntry*>::iterator cur, next = Line->begin();
void SubtitleFormat::RecombineOverlaps(LineList &lines) const {
LineList::iterator cur, next = lines.begin();
cur = next++;
for (; next != Line->end(); cur = next++) {
for (; next != lines.end(); cur = next++) {
AssDialogue *prevdlg = dynamic_cast<AssDialogue*>(*cur);
AssDialogue *curdlg = dynamic_cast<AssDialogue*>(*next);
@ -240,15 +203,15 @@ void SubtitleFormat::RecombineOverlaps() {
// Use names like in the algorithm description and prepare for erasing
// old dialogues from the list
std::list<AssEntry*>::iterator prev = cur;
LineList::iterator prev = cur;
cur = next;
next++;
// std::list::insert() inserts items before the given iterator, so
// we need 'next' for inserting. 'prev' and 'cur' can safely be erased
// from the list now.
Line->erase(prev);
Line->erase(cur);
lines.erase(prev);
lines.erase(cur);
//Is there an A part before the overlap?
if (curdlg->Start > prevdlg->Start) {
@ -258,7 +221,7 @@ void SubtitleFormat::RecombineOverlaps() {
newdlg->End = curdlg->Start;
newdlg->Text = prevdlg->Text;
Line->insert(find_if(next, Line->end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
lines.insert(find_if(next, lines.end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
}
// Overlapping A+B part
@ -269,7 +232,7 @@ void SubtitleFormat::RecombineOverlaps() {
// Put an ASS format hard linewrap between lines
newdlg->Text = curdlg->Text + "\\N" + prevdlg->Text;
Line->insert(find_if(next, Line->end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
lines.insert(find_if(next, lines.end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
}
// Is there an A part after the overlap?
@ -280,7 +243,7 @@ void SubtitleFormat::RecombineOverlaps() {
newdlg->End = prevdlg->End;
newdlg->Text = prevdlg->Text;
Line->insert(find_if(next, Line->end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
lines.insert(find_if(next, lines.end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
}
// Is there a B part after the overlap?
@ -291,7 +254,7 @@ void SubtitleFormat::RecombineOverlaps() {
newdlg->End = curdlg->End;
newdlg->Text = curdlg->Text;
Line->insert(find_if(next, Line->end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
lines.insert(find_if(next, lines.end(), bind(dialog_start_lt, _1, newdlg)), newdlg);
}
next--;
@ -299,11 +262,11 @@ void SubtitleFormat::RecombineOverlaps() {
}
/// @brief Merge identical lines that follow each other
void SubtitleFormat::MergeIdentical() {
std::list<AssEntry*>::iterator cur, next = Line->begin();
void SubtitleFormat::MergeIdentical(LineList &lines) const {
LineList::iterator cur, next = lines.begin();
cur = next++;
for (; next != Line->end(); cur = next++) {
for (; next != lines.end(); cur = next++) {
AssDialogue *curdlg = dynamic_cast<AssDialogue*>(*cur);
AssDialogue *nextdlg = dynamic_cast<AssDialogue*>(*next);
@ -314,7 +277,7 @@ void SubtitleFormat::MergeIdentical() {
// Remove duplicate line
delete *cur;
Line->erase(cur);
lines.erase(cur);
}
}
}
@ -323,14 +286,14 @@ std::list<SubtitleFormat*> SubtitleFormat::formats;
void SubtitleFormat::LoadFormats() {
if (formats.empty()) {
new ASSSubtitleFormat();
new EncoreSubtitleFormat();
new MKVSubtitleFormat();
new MicroDVDSubtitleFormat();
new SRTSubtitleFormat();
new TTXTSubtitleFormat();
new TXTSubtitleFormat();
new TranStationSubtitleFormat();
new ASSSubtitleFormat;
new EncoreSubtitleFormat;
new MKVSubtitleFormat;
new MicroDVDSubtitleFormat;
new SRTSubtitleFormat;
new TTXTSubtitleFormat;
new TXTSubtitleFormat;
new TranStationSubtitleFormat;
}
}
@ -347,12 +310,12 @@ SubtitleFormat *find_or_null(Cont &container, Pred pred) {
return *it;
}
SubtitleFormat *SubtitleFormat::GetReader(wxString const& filename) {
const SubtitleFormat *SubtitleFormat::GetReader(wxString const& filename) {
LoadFormats();
return find_or_null(formats, bind(&SubtitleFormat::CanReadFile, _1, filename));
}
SubtitleFormat *SubtitleFormat::GetWriter(wxString const& filename) {
const SubtitleFormat *SubtitleFormat::GetWriter(wxString const& filename) {
LoadFormats();
return find_or_null(formats, bind(&SubtitleFormat::CanWriteFile, _1, filename));
}

View File

@ -45,7 +45,6 @@
#include <libaegisub/exception.h>
class AssAttachment;
class AssEntry;
class AssFile;
class FractionalTime;
@ -57,8 +56,6 @@ class FractionalTime;
/// DOCME
class SubtitleFormat {
wxString name;
bool isCopy;
AssFile *assFile;
/// Get this format's wildcards for a load dialog
virtual wxArrayString GetReadWildcards() const { return wxArrayString(); }
@ -69,47 +66,28 @@ class SubtitleFormat {
static std::list<SubtitleFormat*> formats;
protected:
std::list<AssEntry*> *Line;
typedef std::list<AssEntry*> LineList;
/// Copy the input subtitles file; must be called before making any changes
void CreateCopy();
/// Delete the subtitle file if we own it; should be called after processing
/// if CreateCopy was used
void ClearCopy();
/// Sort the lines by start time
void SortLines();
/// Strip override tags
void StripTags();
void StripTags(LineList &lines) const;
/// Convert newlines to the specified character(s)
/// @param lineEnd newline character(s)
/// @param mergeLineBreaks Should multiple consecutive line breaks be merged into one?
void ConvertNewlines(wxString const& newline, bool mergeLineBreaks = true);
void ConvertNewlines(LineList &lines, wxString const& newline, bool mergeLineBreaks = true) const;
/// Remove All commented and empty lines
void StripComments();
void StripComments(LineList &lines) const;
/// Remove everything but the dialogue lines
void StripNonDialogue();
void StripNonDialogue(LineList &lines) const;
/// @brief Split and merge lines so there are no overlapping lines
///
/// Algorithm described at http://devel.aegisub.org/wiki/Technical/SplitMerge
void RecombineOverlaps();
void RecombineOverlaps(LineList &lines) const;
/// Merge sequential identical lines
void MergeIdentical();
void MergeIdentical(LineList &lines) const;
/// Clear the subtitle file
void Clear();
/// Load the default file
/// @param defline Add a blank line?
void LoadDefault(bool defline=true);
AssFile *GetAssFile() { return assFile; }
/// Add a line to the output file
/// @param data Full text of ASS line
/// @param[in,out] version ASS version the line was parsed as
/// @param[in,out] attach Accumulator for attachment parsing
void AddLine(wxString data, int *version, AssAttachment **attach);
/// Prompt the user for a framerate to use
/// @param showSMPTE Include SMPTE as an option?
FractionalTime AskForFPS(bool showSMPTE=false);
FractionalTime AskForFPS(bool showSMPTE=false) const;
public:
/// Constructor
@ -120,42 +98,41 @@ public:
/// @note Automatically unregisters the format
virtual ~SubtitleFormat();
/// Set the target file to write
void SetTarget(AssFile *file);
/// Get this format's name
wxString GetName() const { return name; }
/// @brief Check if the given file can be read by this format
///
/// Default implemention simply checks if the file's extension is in the
/// Default implement ion simply checks if the file's extension is in the
/// format's wildcard list
virtual bool CanReadFile(wxString const& filename) const;
/// @brief Check if the given file can be written by this format
///
/// Default implemention simply checks if the file's extension is in the
/// Default implement ion simply checks if the file's extension is in the
/// format's wildcard list
virtual bool CanWriteFile(wxString const& filename) const;
/// Load a subtitle file
/// @param[out] target Destination to read lines into
/// @param filename File to load
/// @param forceEncoding Encoding to use or empty string for default
virtual void ReadFile(wxString const& filename, wxString const& forceEncoding="") { }
virtual void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding="") const { }
/// Save a subtitle file
/// @param src Data to write
/// @param filename File to write to
/// @param forceEncoding Encoding to use or empty string for default
virtual void WriteFile(wxString const& filename, wxString const& encoding="") { }
virtual void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding="") const { }
/// Get the wildcards for a save or load dialog
/// @param mode 0: load 1: save
static wxString GetWildcards(int mode);
/// Get a subtitle format that can read the given file or NULL if none can
static SubtitleFormat *GetReader(wxString const& filename);
static const SubtitleFormat *GetReader(wxString const& filename);
/// Get a subtitle format that can write the given file or NULL if none can
static SubtitleFormat *GetWriter(wxString const& filename);
static const SubtitleFormat *GetWriter(wxString const& filename);
/// Initialize subtitle formats
static void LoadFormats();
/// Deinitialize subtitle formats

View File

@ -39,6 +39,7 @@
#include "subtitle_format_ass.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "compat.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
@ -64,9 +65,11 @@ wxArrayString ASSSubtitleFormat::GetWriteWildcards() const {
return formats;
}
void ASSSubtitleFormat::ReadFile(wxString const& filename, wxString const& encoding) {
void ASSSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
using namespace std;
target->Clear();
TextFileReader file(filename, encoding);
int version = filename.Right(4).Lower() != ".ssa";
AssAttachment *attach = 0;
@ -74,22 +77,22 @@ void ASSSubtitleFormat::ReadFile(wxString const& filename, wxString const& encod
while (file.HasMoreLines()) {
wxString line = file.ReadLineFromFile();
try {
AddLine(line, &version, &attach);
target->AddLine(line, &version, &attach);
}
catch (const char *err) {
Clear();
target->Clear();
throw AssParseError("Error processing line: " + STD_STR(line) + ": " + err, 0);
}
}
}
void ASSSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void ASSSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
TextFileWriter file(filename, encoding);
bool ssa = filename.Right(4).Lower() == ".ssa";
std::list<AssEntry*>::iterator last = Line->end(); --last;
wxString group = Line->front()->group;
for (std::list<AssEntry*>::iterator cur=Line->begin(); cur!=Line->end(); ++cur) {
LineList::const_iterator last = src->Line.end(); --last;
wxString group = src->Line.front()->group;
for (LineList::const_iterator cur = src->Line.begin(); cur != src->Line.end(); ++cur) {
// Add a blank line between each group
if ((*cur)->group != group) {
file.WriteLineToFile("");

View File

@ -48,6 +48,6 @@ public:
wxArrayString GetReadWildcards() const;
wxArrayString GetWriteWildcards() const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void WriteFile(wxString const& filename, wxString const& encoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -39,6 +39,7 @@
#include "subtitle_format_encore.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "text_file_writer.h"
EncoreSubtitleFormat::EncoreSubtitleFormat()
@ -52,20 +53,20 @@ wxArrayString EncoreSubtitleFormat::GetWriteWildcards() const {
return formats;
}
void EncoreSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
FractionalTime ft = AskForFPS(true);
if (!ft.FPS().IsLoaded()) return;
TextFileWriter file(filename, encoding);
// Convert to encore
CreateCopy();
SortLines();
StripComments();
RecombineOverlaps();
MergeIdentical();
StripTags();
ConvertNewlines("\r\n");
AssFile copy(*src);
copy.Sort();
StripComments(copy.Line);
RecombineOverlaps(copy.Line);
MergeIdentical(copy.Line);
StripTags(copy.Line);
ConvertNewlines(copy.Line, "\r\n");
// Write lines
int i = 0;
@ -73,12 +74,10 @@ void EncoreSubtitleFormat::WriteFile(wxString const& filename, wxString const& e
// Encore wants ; instead of : if we're dealing with NTSC dropframe stuff
char sep = ft.IsDrop() ? ';' : ':';
for (std::list<AssEntry*>::iterator cur=Line->begin();cur!=Line->end();cur++) {
for (LineList::const_iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
++i;
file.WriteLineToFile(wxString::Format("%i %s %s %s", i, ft.ToSMPTE(current->Start, sep), ft.ToSMPTE(current->End, sep), current->Text));
}
}
ClearCopy();
}

View File

@ -46,5 +46,5 @@ class EncoreSubtitleFormat : public SubtitleFormat {
public:
EncoreSubtitleFormat();
wxArrayString GetWriteWildcards() const;
void WriteFile(wxString const& filename, wxString const& encoding);
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -36,13 +36,15 @@
#include "config.h"
#include "subtitle_format_microdvd.h"
#ifndef AGI_PRE
#include <wx/regex.h>
#endif
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
#include "subtitle_format_microdvd.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
#include "video_context.h"
@ -76,11 +78,11 @@ bool MicroDVDSubtitleFormat::CanReadFile(wxString const& filename) const {
return false;
}
void MicroDVDSubtitleFormat::ReadFile(wxString const& filename, wxString const& forceEncoding) {
void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
TextFileReader file(filename);
wxRegEx exp("^[\\{\\[]([0-9]+)[\\}\\]][\\{\\[]([0-9]+)[\\}\\]](.*)$", wxRE_ADVANCED);
LoadDefault(false);
target->LoadDefault(false);
agi::vfr::Framerate fps;
@ -117,22 +119,22 @@ void MicroDVDSubtitleFormat::ReadFile(wxString const& filename, wxString const&
diag->Start = fps.TimeAtFrame(f1, agi::vfr::START);
diag->End = fps.TimeAtFrame(f2, agi::vfr::END);
diag->Text = text;
Line->push_back(diag);
target->Line.push_back(diag);
}
}
}
void MicroDVDSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
agi::vfr::Framerate fps = AskForFPS().FPS();
if (!fps.IsLoaded()) return;
CreateCopy();
SortLines();
StripComments();
RecombineOverlaps();
MergeIdentical();
StripTags();
ConvertNewlines("|");
AssFile copy(*src);
copy.Sort();
StripComments(copy.Line);
RecombineOverlaps(copy.Line);
MergeIdentical(copy.Line);
StripTags(copy.Line);
ConvertNewlines(copy.Line, "|");
TextFileWriter file(filename, encoding);
@ -142,7 +144,7 @@ void MicroDVDSubtitleFormat::WriteFile(wxString const& filename, wxString const&
}
// Write lines
for (std::list<AssEntry*>::iterator cur=Line->begin();cur!=Line->end();cur++) {
for (LineList::const_iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
int start = fps.FrameAtTime(current->Start, agi::vfr::START);
int end = fps.FrameAtTime(current->End, agi::vfr::END);
@ -150,6 +152,4 @@ void MicroDVDSubtitleFormat::WriteFile(wxString const& filename, wxString const&
file.WriteLineToFile(wxString::Format("{%i}{%i}%s", start, end, current->Text));
}
}
ClearCopy();
}

View File

@ -49,7 +49,7 @@ public:
wxArrayString GetWriteWildcards() const;
bool CanReadFile(wxString const& filename) const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
void WriteFile(wxString const& filename, wxString const& encoding);
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -53,6 +53,6 @@ wxArrayString MKVSubtitleFormat::GetReadWildcards() const {
return formats;
}
void MKVSubtitleFormat::ReadFile(wxString const& filename, wxString const&) {
MatroskaWrapper::GetSubtitles(filename, GetAssFile());
void MKVSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const&) const {
MatroskaWrapper::GetSubtitles(filename, target);
}

View File

@ -46,5 +46,5 @@ public:
MKVSubtitleFormat();
wxArrayString GetReadWildcards() const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
};

View File

@ -375,11 +375,11 @@ wxArrayString SRTSubtitleFormat::GetWriteWildcards() const {
return GetReadWildcards();
}
void SRTSubtitleFormat::ReadFile(wxString const& filename, wxString const& encoding) {
void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
using namespace std;
TextFileReader file(filename, encoding);
LoadDefault(false);
target->LoadDefault(false);
// See parsing algorithm at <http://devel.aegisub.org/wiki/SubtitleFormats/SRT>
@ -433,7 +433,7 @@ found_timestamps:
line->Start = ReadSRTTime(timestamp_regex.GetMatch(text_line, 1));
line->End = ReadSRTTime(timestamp_regex.GetMatch(text_line, 2));
// store pointer to subtitle, we'll continue working on it
Line->push_back(line);
target->Line.push_back(line);
// next we're reading the text
state = 3;
break;
@ -496,20 +496,20 @@ found_timestamps:
line->Text = tag_parser.ToAss(line->Text);
}
void SRTSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
TextFileWriter file(filename,encoding);
void SRTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
TextFileWriter file(filename, encoding);
// Convert to SRT
CreateCopy();
SortLines();
StripComments();
RecombineOverlaps();
MergeIdentical();
ConvertNewlines("\r\n", false);
AssFile copy(*src);
copy.Sort();
StripComments(copy.Line);
RecombineOverlaps(copy.Line);
MergeIdentical(copy.Line);
ConvertNewlines(copy.Line, "\r\n", false);
// Write lines
int i=1;
for (std::list<AssEntry*>::iterator cur = Line->begin(); cur != Line->end(); ++cur) {
for (LineList::const_iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
file.WriteLineToFile(wxString::Format("%d", i++));
file.WriteLineToFile(WriteSRTTime(current->Start) + " --> " + WriteSRTTime(current->End));
@ -517,11 +517,9 @@ void SRTSubtitleFormat::WriteFile(wxString const& filename, wxString const& enco
file.WriteLineToFile("");
}
}
ClearCopy();
}
wxString SRTSubtitleFormat::ConvertTags(AssDialogue *diag) {
wxString SRTSubtitleFormat::ConvertTags(AssDialogue *diag) const {
wxString final;
std::map<char, bool> tag_states;
tag_states['i'] = false;

View File

@ -42,12 +42,12 @@
///
/// DOCME
class SRTSubtitleFormat : public SubtitleFormat {
wxString ConvertTags(AssDialogue *diag);
wxString ConvertTags(AssDialogue *diag) const;
public:
SRTSubtitleFormat();
wxArrayString GetReadWildcards() const;
wxArrayString GetWriteWildcards() const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void WriteFile(wxString const& filename, wxString const& encoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -59,25 +59,25 @@ wxArrayString TranStationSubtitleFormat::GetWriteWildcards() const {
return formats;
}
void TranStationSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
FractionalTime ft = AskForFPS(true);
if (!ft.FPS().IsLoaded()) return;
TextFileWriter file(filename, encoding);
// Convert to TranStation
CreateCopy();
SortLines();
StripComments();
RecombineOverlaps();
MergeIdentical();
AssFile copy(*src);
copy.Sort();
StripComments(copy.Line);
RecombineOverlaps(copy.Line);
MergeIdentical(copy.Line);
AssDialogue *prev = 0;
for (std::list<AssEntry*>::iterator it = Line->begin(); it != Line->end(); ++it) {
for (std::list<AssEntry*>::iterator it = copy.Line.begin(); it != copy.Line.end(); ++it) {
AssDialogue *cur = dynamic_cast<AssDialogue*>(*it);
if (prev && cur) {
file.WriteLineToFile(ConvertLine(prev, &ft, cur->Start));
file.WriteLineToFile(ConvertLine(&copy, prev, &ft, cur->Start));
file.WriteLineToFile("");
}
@ -87,19 +87,17 @@ void TranStationSubtitleFormat::WriteFile(wxString const& filename, wxString con
// flush last line
if (prev)
file.WriteLineToFile(ConvertLine(prev, &ft, -1));
file.WriteLineToFile(ConvertLine(&copy, prev, &ft, -1));
// Every file must end with this line
file.WriteLineToFile("SUB[");
ClearCopy();
}
wxString TranStationSubtitleFormat::ConvertLine(AssDialogue *current, FractionalTime *ft, int nextl_start) {
wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, FractionalTime *ft, int nextl_start) const {
int valign = 0;
const char *halign = " "; // default is centered
const char *type = "N"; // no special style
if (AssStyle *style = GetAssFile()->GetStyle(current->Style)) {
if (AssStyle *style = file->GetStyle(current->Style)) {
if (style->alignment >= 4) valign = 4;
if (style->alignment >= 7) valign = 9;
if (style->alignment == 1 || style->alignment == 4 || style->alignment == 7) halign = "L";

View File

@ -44,10 +44,10 @@ class AssDialogue;
///
/// DOCME
class TranStationSubtitleFormat : public SubtitleFormat {
wxString ConvertLine(AssDialogue *line, FractionalTime *ft, int nextl_start);
wxString ConvertLine(AssFile *file, AssDialogue *line, FractionalTime *ft, int nextl_start) const;
public:
TranStationSubtitleFormat();
wxArrayString GetWriteWildcards() const;
void WriteFile(wxString const& filename, wxString const& encoding);
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -38,6 +38,10 @@
#include "subtitle_format_ttxt.h"
#ifndef AGI_PRE
#include <wx/xml/xml.h>
#endif
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
@ -61,8 +65,8 @@ wxArrayString TTXTSubtitleFormat::GetWriteWildcards() const {
return GetReadWildcards();
}
void TTXTSubtitleFormat::ReadFile(wxString const& filename, wxString const& forceEncoding) {
LoadDefault(false);
void TTXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
target->LoadDefault(false);
// Load XML document
wxXmlDocument doc;
@ -73,106 +77,96 @@ void TTXTSubtitleFormat::ReadFile(wxString const& filename, wxString const& forc
// Check version
wxString verStr = doc.GetRoot()->GetAttribute("version", "");
version = -1;
if (verStr == "1.0") version = 0;
else if (verStr == "1.1") version = 1;
else throw TTXTParseError("Unknown TTXT version: " + STD_STR(verStr), 0);
int version = -1;
if (verStr == "1.0")
version = 0;
else if (verStr == "1.1")
version = 1;
else
throw TTXTParseError("Unknown TTXT version: " + STD_STR(verStr), 0);
// Get children
diag = NULL;
wxXmlNode *child = doc.GetRoot()->GetChildren();
AssDialogue *diag = 0;
int lines = 0;
while (child) {
for (wxXmlNode *child = doc.GetRoot()->GetChildren(); child; child = child->GetNext()) {
// Line
if (child->GetName() == "TextSample") {
if (ProcessLine(child)) lines++;
if (diag = ProcessLine(child, diag, version)) {
lines++;
target->Line.push_back(diag);
}
}
// Header
else if (child->GetName() == "TextStreamHeader") {
ProcessHeader(child);
}
// Proceed to next child
child = child->GetNext();
}
// No lines?
if (lines == 0) {
AssDialogue *line = new AssDialogue();
line->group = "[Events]";
line->Style = "Default";
line->Start = 0;
line->End = 5000;
Line->push_back(line);
}
if (lines == 0)
target->Line.push_back(new AssDialogue);
}
bool TTXTSubtitleFormat::ProcessLine(wxXmlNode *node) {
AssDialogue *TTXTSubtitleFormat::ProcessLine(wxXmlNode *node, AssDialogue *prev, int version) const {
// Get time
wxString sampleTime = node->GetAttribute("sampleTime", "00:00:00.000");
AssTime time;
time.ParseASS(sampleTime);
// Set end time of last line
if (diag) diag->End = time;
diag = NULL;
if (prev)
prev->End = time;
// Get text
wxString text;
if (version == 0) text = node->GetAttribute("text", "");
else text = node->GetNodeContent();
if (version == 0)
text = node->GetAttribute("text", "");
else
text = node->GetNodeContent();
// Create line
if (!text.IsEmpty()) {
// Create dialogue
diag = new AssDialogue();
diag->Start = time;
diag->End = 36000000-10;
diag->group = "[Events]";
diag->Style = "Default";
diag->Comment = false;
if (text.empty()) return 0;
// Process text for 1.0
if (version == 0) {
wxString finalText;
finalText.Alloc(text.Length());
bool in = false;
bool first = true;
for (size_t i=0;i<text.Length();i++) {
if (text[i] == '\'') {
if (!in && !first) finalText += "\\N";
first = false;
in = !in;
}
else if (in) finalText += text[i];
// Create dialogue
AssDialogue *diag = new AssDialogue;
diag->Start = time;
diag->End = 36000000-10;
// Process text for 1.0
if (version == 0) {
wxString finalText;
finalText.reserve(text.size());
bool in = false;
bool first = true;
for (size_t i = 0; i < text.size(); ++i) {
if (text[i] == '\'') {
if (!in && !first) finalText += "\\N";
first = false;
in = !in;
}
diag->Text = finalText;
else if (in) finalText += text[i];
}
// Process text for 1.1
else {
text.Replace("\r", "");
text.Replace("\n", "\\N");
diag->Text = text;
}
// Insert dialogue
Line->push_back(diag);
return true;
diag->Text = finalText;
}
else return false;
// Process text for 1.1
else {
text.Replace("\r", "");
text.Replace("\n", "\\N");
diag->Text = text;
}
return diag;
}
void TTXTSubtitleFormat::ProcessHeader(wxXmlNode *node) {
void TTXTSubtitleFormat::ProcessHeader(wxXmlNode *node) const {
// TODO
}
void TTXTSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void TTXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
// Convert to TTXT
CreateCopy();
ConvertToTTXT();
AssFile copy(*src);
ConvertToTTXT(copy);
// Create XML structure
wxXmlDocument doc;
@ -184,28 +178,22 @@ void TTXTSubtitleFormat::WriteFile(wxString const& filename, wxString const& enc
WriteHeader(root);
// Create lines
int i=1;
using std::list;
prev = NULL;
for (list<AssEntry*>::iterator cur=Line->begin();cur!=Line->end();cur++) {
AssDialogue *prev = 0;
for (LineList::iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
AssDialogue *current = dynamic_cast<AssDialogue*>(*cur);
if (current && !current->Comment) {
WriteLine(root, current);
i++;
WriteLine(root, prev, current);
prev = current;
}
else
throw TTXTParseError("Unexpected line type in TTXT file", 0);
}
// Save XML
//prevNode->SetNext(NULL);
doc.Save(filename);
// Clear
ClearCopy();
}
void TTXTSubtitleFormat::WriteHeader(wxXmlNode *root) {
void TTXTSubtitleFormat::WriteHeader(wxXmlNode *root) const {
// Write stream header
wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, "TextStreamHeader");
node->AddAttribute("width", "400");
@ -255,7 +243,7 @@ void TTXTSubtitleFormat::WriteHeader(wxXmlNode *root) {
root->AddChild(node);
}
void TTXTSubtitleFormat::WriteLine(wxXmlNode *root, AssDialogue *line) {
void TTXTSubtitleFormat::WriteLine(wxXmlNode *root, AssDialogue *prev, AssDialogue *line) const {
// If it doesn't start at the end of previous, add blank
if (prev && prev->End != line->Start) {
wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, "TextSample");
@ -271,22 +259,19 @@ void TTXTSubtitleFormat::WriteLine(wxXmlNode *root, AssDialogue *line) {
node->AddAttribute("xml:space", "preserve");
root->AddChild(node);
node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, "", line->Text));
// Set as previous
prev = line;
}
void TTXTSubtitleFormat::ConvertToTTXT () {
SortLines();
StripComments();
RecombineOverlaps();
MergeIdentical();
StripTags();
ConvertNewlines("\r\n");
void TTXTSubtitleFormat::ConvertToTTXT(AssFile &file) const {
file.Sort();
StripComments(file.Line);
RecombineOverlaps(file.Line);
MergeIdentical(file.Line);
StripTags(file.Line);
ConvertNewlines(file.Line, "\r\n");
// Find last line
AssTime lastTime;
for (std::list<AssEntry*>::reverse_iterator cur=Line->rbegin();cur!=Line->rend();cur++) {
for (LineList::reverse_iterator cur = file.Line.rbegin(); cur != file.Line.rend(); ++cur) {
if (AssDialogue *prev = dynamic_cast<AssDialogue*>(*cur)) {
lastTime = prev->End;
break;
@ -294,11 +279,11 @@ void TTXTSubtitleFormat::ConvertToTTXT () {
}
// Insert blank line at the end
AssDialogue *diag = new AssDialogue();
AssDialogue *diag = new AssDialogue;
diag->Start = lastTime;
diag->End = lastTime+OPT_GET("Timing/Default Duration")->GetInt();
diag->group = "[Events]";
diag->Style = "Default";
diag->Comment = false;
Line->push_back(diag);
file.Line.push_back(diag);
}

View File

@ -34,14 +34,10 @@
/// @ingroup subtitle_io
///
#ifndef AGI_PRE
#include <wx/xml/xml.h>
#endif
#include "subtitle_format.h"
class AssDialogue;
class wxXmlNode;
/// DOCME
/// @class TTXTSubtitleFormat
@ -49,28 +45,19 @@ class AssDialogue;
///
/// DOCME
class TTXTSubtitleFormat : public SubtitleFormat {
/// DOCME
int version;
AssDialogue *ProcessLine(wxXmlNode *node, AssDialogue *prev, int version) const;
void ProcessHeader(wxXmlNode *node) const;
/// DOCME
AssDialogue *diag;
void WriteHeader(wxXmlNode *root) const;
void WriteLine(wxXmlNode *root, AssDialogue *prev, AssDialogue *line) const;
/// DOCME
AssDialogue *prev;
bool ProcessLine(wxXmlNode *node);
void ProcessHeader(wxXmlNode *node);
void WriteHeader(wxXmlNode *root);
void WriteLine(wxXmlNode *root, AssDialogue *line);
void ConvertToTTXT();
void ConvertToTTXT(AssFile &file) const;
public:
TTXTSubtitleFormat();
wxArrayString GetReadWildcards() const;
wxArrayString GetWriteWildcards() const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void WriteFile(wxString const& filename, wxString const& encoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};

View File

@ -39,6 +39,7 @@
#include "subtitle_format_txt.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "compat.h"
#include "dialog_text_import.h"
#include "main.h"
@ -66,14 +67,14 @@ bool TXTSubtitleFormat::CanWriteFile(wxString const& filename) const {
return (filename.Right(4).Lower() == ".txt" && filename.Right(11).Lower() != ".encore.txt" && filename.Right(16).Lower() != ".transtation.txt");
}
void TXTSubtitleFormat::ReadFile(wxString const& filename, wxString const& encoding) {
void TXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
using namespace std;
DialogTextImport dlg;
if (dlg.ShowModal() == wxID_CANCEL) return;
TextFileReader file(filename, encoding, false);
LoadDefault(false);
target->LoadDefault(false);
wxString actor;
wxString separator = lagi_wxString(OPT_GET("Tool/Import/Text/Actor Separator")->GetString());
@ -123,7 +124,7 @@ void TXTSubtitleFormat::ReadFile(wxString const& filename, wxString const& encod
line->End = 0;
// Adds line
Line->push_back(line);
target->Line.push_back(line);
lines++;
}
@ -131,15 +132,15 @@ void TXTSubtitleFormat::ReadFile(wxString const& filename, wxString const& encod
if (lines == 0) {
AssDialogue *line = new AssDialogue;
line->End = OPT_GET("Timing/Default Duration")->GetInt();
Line->push_back(line);
target->Line.push_back(line);
}
}
void TXTSubtitleFormat::WriteFile(wxString const& filename, wxString const& encoding) {
void TXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
size_t num_actor_names = 0, num_dialogue_lines = 0;
// Detect number of lines with Actor field filled out
for (std::list<AssEntry*>::iterator l = Line->begin(); l != Line->end(); ++l) {
for (LineList::const_iterator l = src->Line.begin(); l != src->Line.end(); ++l) {
AssDialogue *dia = dynamic_cast<AssDialogue*>(*l);
if (dia && !dia->Comment) {
num_dialogue_lines++;
@ -156,7 +157,7 @@ void TXTSubtitleFormat::WriteFile(wxString const& filename, wxString const& enco
file.WriteLineToFile(wxString("# Exported by Aegisub ") + GetAegisubShortVersionString());
// Write the file
for (std::list<AssEntry*>::iterator l = Line->begin(); l != Line->end(); ++l) {
for (LineList::const_iterator l = src->Line.begin(); l != src->Line.end(); ++l) {
AssDialogue *dia = dynamic_cast<AssDialogue*>(*l);
if (dia) {

View File

@ -48,6 +48,6 @@ public:
wxArrayString GetWriteWildcards() const;
bool CanWriteFile(wxString const& filename) const;
void ReadFile(wxString const& filename, wxString const& forceEncoding);
void WriteFile(wxString const& filename, wxString const& encoding);
void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const;
void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
};