diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
index a8f7a20f1..daf68b8e2 100644
--- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
+++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
@@ -1035,6 +1035,14 @@
RelativePath="..\..\src\dialog_export.h"
>
+
+
+
+
@@ -1439,6 +1447,14 @@
RelativePath="..\..\src\subtitle_format_ass.h"
>
+
+
+
+
diff --git a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj
index 11b689c6f..aa7175085 100644
--- a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj
+++ b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj
@@ -109,6 +109,7 @@
+
@@ -170,6 +171,7 @@
+
@@ -293,6 +295,7 @@
+
@@ -360,6 +363,7 @@
+
diff --git a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters
index f1cd9c198..80c12a962 100644
--- a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters
+++ b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters
@@ -297,6 +297,9 @@
Features\Export
+
+ Features\Export
+
Features\Export
@@ -351,6 +354,9 @@
Subtitle formats
+
+ Subtitle formats
+
Subtitle formats
@@ -788,6 +794,9 @@
Subtitle formats
+
+ Subtitle formats
+
Subtitle formats
@@ -881,6 +890,9 @@
Features\Export
+
+ Features\Export
+
Features\Export
diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile
index 820f8c70d..9be3d5163 100644
--- a/aegisub/src/Makefile
+++ b/aegisub/src/Makefile
@@ -166,6 +166,7 @@ SRC += \
dialog_detached_video.cpp \
dialog_dummy_video.cpp \
dialog_export.cpp \
+ dialog_export_ebu3264.cpp \
dialog_fonts_collector.cpp \
dialog_jumpto.cpp \
dialog_kara_timing_copy.cpp \
@@ -221,6 +222,7 @@ SRC += \
subs_preview.cpp \
subtitle_format.cpp \
subtitle_format_ass.cpp \
+ subtitle_format_ebu3264.cpp \
subtitle_format_encore.cpp \
subtitle_format_microdvd.cpp \
subtitle_format_mkv.cpp \
diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h
index ac38444be..b2664a7a4 100644
--- a/aegisub/src/agi_pre.h
+++ b/aegisub/src/agi_pre.h
@@ -193,6 +193,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/aegisub/src/dialog_export_ebu3264.cpp b/aegisub/src/dialog_export_ebu3264.cpp
new file mode 100644
index 000000000..d491360d9
--- /dev/null
+++ b/aegisub/src/dialog_export_ebu3264.cpp
@@ -0,0 +1,258 @@
+// Copyright (c) 2011 Niels Martin Hansen
+// Copyright (c) 2012 Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file dialog_export_ebu3264.cpp
+/// @see dialog_export_ebu3264.h
+/// @ingroup subtitle_io export
+
+#include "config.h"
+
+#include "dialog_export_ebu3264.h"
+
+#include
+
+#include "main.h"
+#include "text_file_writer.h"
+
+#ifndef AGI_PRE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+namespace {
+ const char timecode_regex[] = "([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2})";
+
+ /// Validator for SMPTE timecodes
+ class TimecodeValidator : public wxValidator {
+ wxRegEx re;
+ EbuTimecode *value;
+
+ wxTextCtrl *GetCtrl() const { return dynamic_cast(GetWindow()); }
+
+ bool TransferToWindow() {
+ wxTextCtrl *ctrl = GetCtrl();
+ if (!ctrl) return false;
+ ctrl->SetValue(wxString::Format("%02d:%02d:%02d:%02d", (int)value->h, (int)value->m, (int)value->s, (int)value->f));
+ return true;
+ }
+
+ bool TransferFromWindow() {
+ wxTextCtrl *ctrl = GetCtrl();
+ if (!ctrl) return false;
+
+ wxString str = ctrl->GetValue();
+
+ if (re.Matches(str)) {
+ long h, m, s, f;
+ re.GetMatch(str, 1).ToLong(&h);
+ re.GetMatch(str, 2).ToLong(&m);
+ re.GetMatch(str, 3).ToLong(&s);
+ re.GetMatch(str, 4).ToLong(&f);
+ value->h = h;
+ value->m = m;
+ value->s = s;
+ value->f = f;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool Validate(wxWindow *parent) {
+ wxTextCtrl *ctrl = GetCtrl();
+ if (!ctrl) return false;
+
+ if (!re.Matches(ctrl->GetValue())) {
+ wxMessageBox(_("Time code offset in incorrect format. Ensure it is entered as four groups of two digits separated by colons."), _("EBU STL export"), wxICON_EXCLAMATION);
+ return false;
+ }
+ return true;
+ }
+
+ wxObject *Clone() const { return new TimecodeValidator(*this); }
+
+ public:
+ TimecodeValidator(EbuTimecode *target)
+ : re(timecode_regex)
+ , value(target)
+ {
+ assert(target);
+ }
+
+ TimecodeValidator(TimecodeValidator const& other)
+ : wxValidator()
+ , re(timecode_regex)
+ , value(other.value)
+ {
+ }
+ };
+} // namespace {
+
+EbuExportConfigurationDialog::EbuExportConfigurationDialog(wxWindow *owner, EbuExportSettings &s)
+: wxDialog(owner, -1, _("Export to EBU STL format"))
+{
+ wxString tv_standards[] = {
+ _("23.976 fps (non-standard, STL24.01)"),
+ _("24 fps (non-standard, STL24.01)"),
+ _("25 fps (STL25.01)"),
+ _("29.97 fps (non-dropframe, STL30.01)"),
+ _("29.97 fps (dropframe, STL30.01)"),
+ _("30 fps (STL30.01)")
+ };
+ wxRadioBox *tv_standard_box = new wxRadioBox(this, -1, _("TV standard"), wxDefaultPosition, wxDefaultSize, 6, tv_standards, 0, wxRA_SPECIFY_ROWS);
+
+ wxStaticBox *timecode_control_box = new wxStaticBox(this, -1, _("Time codes"));
+ wxTextCtrl *timecode_offset_entry = new wxTextCtrl(this, -1, "00:00:00:00");
+ wxCheckBox *inclusive_end_times_check = new wxCheckBox(this, -1, _("Out-times are inclusive"));
+
+ wxString text_encodings[] = {
+ _("ISO 6937-2 (Latin/Western Europe)"),
+ _("ISO 8859-5 (Cyrillic)"),
+ _("ISO 8859-6 (Arabic)"),
+ _("ISO 8859-7 (Greek)"),
+ _("ISO 8859-8 (Hebrew)"),
+ _("UTF-8 Unicode (non-standard)")
+ };
+ wxRadioBox *text_encoding_box = new wxRadioBox(this, -1, _("Text encoding"), wxDefaultPosition, wxDefaultSize, 6, text_encodings, 0, wxRA_SPECIFY_ROWS);
+
+ wxString wrap_modes[] = {
+ _("Automatically wrap long lines (ASS)"),
+ _("Automatically wrap long lines (Balanced)"),
+ _("Abort if any lines are too long"),
+ _("Skip lines that are too long")
+ };
+
+ wxStaticBox *text_formatting_box = new wxStaticBox(this, -1, _("Text formatting"));
+ wxSpinCtrl *max_line_length_ctrl = new wxSpinCtrl(this, -1, wxString(), wxDefaultPosition, wxSize(65, -1));
+ wxComboBox *wrap_mode_ctrl = new wxComboBox(this, -1, wrap_modes[0], wxDefaultPosition, wxDefaultSize, 4, wrap_modes, wxCB_DROPDOWN | wxCB_READONLY);
+ wxCheckBox *translate_alignments_check = new wxCheckBox(this, -1, _("Translate alignments"));
+
+ max_line_length_ctrl->SetRange(10, 99);
+
+ wxSizer *max_line_length_labelled = new wxBoxSizer(wxHORIZONTAL);
+ max_line_length_labelled->Add(new wxStaticText(this, -1, _("Max. line length:")), 1, wxALIGN_CENTRE|wxRIGHT, 12);
+ max_line_length_labelled->Add(max_line_length_ctrl, 0, 0, 0);
+
+ wxSizer *timecode_offset_labelled = new wxBoxSizer(wxHORIZONTAL);
+ timecode_offset_labelled->Add(new wxStaticText(this, -1, _("Time code offset:")), 1, wxALIGN_CENTRE|wxRIGHT, 12);
+ timecode_offset_labelled->Add(timecode_offset_entry, 0, 0, 0);
+
+ wxSizer *text_formatting_sizer = new wxStaticBoxSizer(text_formatting_box, wxVERTICAL);
+ text_formatting_sizer->Add(max_line_length_labelled, 0, wxEXPAND | (wxALL & ~wxTOP), 6);
+ text_formatting_sizer->Add(wrap_mode_ctrl, 0, wxEXPAND | (wxALL & ~wxTOP), 6);
+ text_formatting_sizer->Add(translate_alignments_check, 0, wxEXPAND | (wxALL & ~wxTOP), 6);
+
+ wxSizer *timecode_control_sizer = new wxStaticBoxSizer(timecode_control_box, wxVERTICAL);
+ timecode_control_sizer->Add(timecode_offset_labelled, 0, wxEXPAND | (wxALL & ~wxTOP), 6);
+ timecode_control_sizer->Add(inclusive_end_times_check, 0, wxEXPAND | (wxALL & ~wxTOP), 6);
+
+ wxSizer *left_column = new wxBoxSizer(wxVERTICAL);
+ left_column->Add(tv_standard_box, 0, wxEXPAND|wxBOTTOM, 6);
+ left_column->Add(timecode_control_sizer, 0, wxEXPAND, 0);
+
+ wxSizer *right_column = new wxBoxSizer(wxVERTICAL);
+ right_column->Add(text_encoding_box, 0, wxEXPAND|wxBOTTOM, 6);
+ right_column->Add(text_formatting_sizer, 0, wxEXPAND, 0);
+
+ wxSizer *vertical_split_sizer = new wxBoxSizer(wxHORIZONTAL);
+ vertical_split_sizer->Add(left_column, 0, wxRIGHT, 6);
+ vertical_split_sizer->Add(right_column, 0, 0, 0);
+
+ wxSizer *buttons_sizer = new wxBoxSizer(wxHORIZONTAL);
+ // Developers are requested to leave this message in! Intentionally not translatable.
+ wxStaticText *sponsor_label = new wxStaticText(this, -1, "EBU STL format writing sponsored by Bandai");
+ sponsor_label->Enable(false);
+ buttons_sizer->Add(sponsor_label, 1, wxALIGN_BOTTOM, 0);
+ buttons_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxLEFT, 6);
+
+ wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
+ main_sizer->Add(vertical_split_sizer, 0, wxEXPAND|wxALL, 12);
+ main_sizer->Add(buttons_sizer, 0, wxEXPAND | (wxALL & ~wxTOP), 12);
+
+ SetSizerAndFit(main_sizer);
+ CenterOnParent();
+
+ // set up validators to move data in and out
+ tv_standard_box->SetValidator(wxGenericValidator((int*)&s.tv_standard));
+ text_encoding_box->SetValidator(wxGenericValidator((int*)&s.text_encoding));
+ translate_alignments_check->SetValidator(wxGenericValidator(&s.translate_alignments));
+ max_line_length_ctrl->SetValidator(wxGenericValidator(&s.max_line_length));
+ wrap_mode_ctrl->SetValidator(wxGenericValidator((int*)&s.line_wrapping_mode));
+ inclusive_end_times_check->SetValidator(wxGenericValidator(&s.inclusive_end_times));
+ timecode_offset_entry->SetValidator(TimecodeValidator(&s.timecode_offset));
+}
+
+agi::vfr::Framerate EbuExportSettings::GetFramerate() const {
+ switch (tv_standard) {
+ case STL24: return agi::vfr::Framerate(24, 1);
+ case STL25: return agi::vfr::Framerate(25, 1);
+ case STL30: return agi::vfr::Framerate(30, 1);
+ case STL23: return agi::vfr::Framerate(24000, 1001, false);
+ case STL29: return agi::vfr::Framerate(30000, 1001, false);
+ case STL29drop: return agi::vfr::Framerate(30000, 1001);
+ default: return agi::vfr::Framerate(25, 1);
+ }
+}
+
+agi::charset::IconvWrapper *EbuExportSettings::GetTextEncoder() const {
+ switch (text_encoding) {
+ case iso6937_2: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-6937-2");
+ case iso8859_5: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-5");
+ case iso8859_6: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-6");
+ case iso8859_7: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-7");
+ case iso8859_8: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-8");
+ case utf8: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "utf-8");
+ default: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-1");
+ }
+}
+
+EbuExportSettings::EbuExportSettings(std::string const& prefix)
+: prefix(prefix)
+, tv_standard((TvStandard)OPT_GET(prefix + "/TV Standard")->GetInt())
+, text_encoding((TextEncoding)OPT_GET(prefix + "/Text Encoding")->GetInt())
+, max_line_length(OPT_GET(prefix + "/Max Line Length")->GetInt())
+, line_wrapping_mode((LineWrappingMode)OPT_GET(prefix + "/Line Wrapping Mode")->GetInt())
+, translate_alignments(OPT_GET(prefix + "/Translate Alignments")->GetBool())
+, inclusive_end_times(OPT_GET(prefix + "/Inclusive End Times")->GetBool())
+{
+ timecode_offset.h = OPT_GET(prefix + "/Timecode Offset/H")->GetInt();
+ timecode_offset.m = OPT_GET(prefix + "/Timecode Offset/M")->GetInt();
+ timecode_offset.s = OPT_GET(prefix + "/Timecode Offset/S")->GetInt();
+ timecode_offset.f = OPT_GET(prefix + "/Timecode Offset/F")->GetInt();
+}
+
+void EbuExportSettings::Save() const {
+ OPT_SET(prefix + "/TV Standard")->SetInt(tv_standard);
+ OPT_SET(prefix + "/Text Encoding")->SetInt(text_encoding);
+ OPT_SET(prefix + "/Max Line Length")->SetInt(max_line_length);
+ OPT_SET(prefix + "/Line Wrapping Mode")->SetInt(line_wrapping_mode);
+ OPT_SET(prefix + "/Translate Alignments")->SetBool(translate_alignments);
+ OPT_SET(prefix + "/Inclusive End Times")->SetBool(inclusive_end_times);
+ OPT_SET(prefix + "/Timecode Offset/H")->SetInt(timecode_offset.h);
+ OPT_SET(prefix + "/Timecode Offset/M")->SetInt(timecode_offset.m);
+ OPT_SET(prefix + "/Timecode Offset/S")->SetInt(timecode_offset.s);
+ OPT_SET(prefix + "/Timecode Offset/F")->SetInt(timecode_offset.f);
+}
diff --git a/aegisub/src/dialog_export_ebu3264.h b/aegisub/src/dialog_export_ebu3264.h
new file mode 100644
index 000000000..18ee2bccb
--- /dev/null
+++ b/aegisub/src/dialog_export_ebu3264.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2011 Niels Martin Hansen
+// Copyright (c) 2012 Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file dialog_export_ebu3264.h
+/// @see dialog_export_ebu3264.cpp
+/// @ingroup subtitle_io export
+
+#ifndef AGI_PRE
+#include
+#endif
+
+#include
+
+namespace agi { namespace charset { class IconvWrapper; } }
+
+#pragma pack(push, 1)
+/// A binary timecode representation, packed
+struct EbuTimecode {
+ uint8_t h, m, s, f;
+};
+#pragma pack(pop)
+
+/// User configuration for EBU Tech 3264-1991 export
+class EbuExportSettings {
+ /// Prefix used for saving to/loading from options
+ std::string prefix;
+public:
+ /// Frame rate + timecode format
+ enum TvStandard {
+ STL23 = 0, ///< 23.976 fps (non-dropframe) (marked as 24)
+ STL24 = 1, ///< 24 fps (film)
+ STL25 = 2, ///< 25 fps (PAL)
+ STL29 = 3, ///< 29.97 fps (non-dropframe) (marked as 30)
+ STL29drop = 4, ///< 29.97 fps (dropframe) (marked as 30)
+ STL30 = 5, ///< 30 fps (NTSC monochrome)
+ };
+
+ /// Character sets for subtitle data
+ enum TextEncoding {
+ iso6937_2 = 0, ///< latin multibyte
+ iso8859_5 = 1, ///< cyrillic
+ iso8859_6 = 2, ///< arabic
+ iso8859_7 = 3, ///< greek
+ iso8859_8 = 4, ///< hebrew
+ utf8 = 5, ///< nonstandard
+ };
+
+ /// Modes for handling lines over the maximum width
+ enum LineWrappingMode {
+ AutoWrap = 0, ///< Wrap overly-long lines ASS-style
+ AutoWrapBalance = 1, ///< Wrap overly-long lines with balanced lines
+ AbortOverLength = 2, ///< Fail if there are overly-long lines
+ IgnoreOverLength = 3 ///< Skip overly-long lines
+ };
+
+ /// Which TV standard (frame rate + timecode encoding) to use
+ TvStandard tv_standard;
+
+ /// How to encode subtitle text
+ TextEncoding text_encoding;
+
+ /// Maximum length of rows in subtitles (in characters)
+ int max_line_length;
+
+ /// How to deal with over-length rows
+ LineWrappingMode line_wrapping_mode;
+
+ /// Translate SSA alignments?
+ bool translate_alignments;
+
+ /// Timecode which time 0 in Aegisub corresponds to
+ EbuTimecode timecode_offset;
+
+ /// Are end timecodes inclusive or exclusive?
+ bool inclusive_end_times;
+
+ /// Get the frame rate for the current TV Standard
+ agi::vfr::Framerate GetFramerate() const;
+
+ /// Get a charset encoder for the current text encoding
+ agi::charset::IconvWrapper *GetTextEncoder() const;
+
+ /// Load saved export settings from options
+ /// @param prefix Option name prefix
+ EbuExportSettings(std::string const& prefix);
+
+ /// Save export settings to options
+ void Save() const;
+};
+
+/// Dialog box for getting an export configuration for EBU Tech 3264-1991
+class EbuExportConfigurationDialog : public wxDialog {
+public:
+ /// Constructor
+ /// @param owner Parent window of the dialog
+ /// @param s Struct with initial values and to fill with the chosen settings
+ EbuExportConfigurationDialog(wxWindow *owner, EbuExportSettings &s);
+};
diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json
index 4c81c2c75..259627e6f 100644
--- a/aegisub/src/libresrc/default_config.json
+++ b/aegisub/src/libresrc/default_config.json
@@ -385,6 +385,23 @@
}
},
+ "Subtitle Format" : {
+ "EBU STL" : {
+ "Inclusive End Times" : true,
+ "Line Wrapping Mode" : 1,
+ "Max Line Length" : 42,
+ "TV Standard" : 0,
+ "Text Encoding" : 0,
+ "Timecode Offset" : {
+ "H" : 0,
+ "M" : 0,
+ "S" : 0,
+ "F" : 0
+ },
+ "Translate Alignments" : true
+ }
+ },
+
"Timing" : {
"Default Duration" : 2000
},
diff --git a/aegisub/src/subtitle_format.cpp b/aegisub/src/subtitle_format.cpp
index f4758250a..ec59bfa53 100644
--- a/aegisub/src/subtitle_format.cpp
+++ b/aegisub/src/subtitle_format.cpp
@@ -48,6 +48,7 @@
#include "ass_file.h"
#include "ass_style.h"
#include "subtitle_format_ass.h"
+#include "subtitle_format_ebu3264.h"
#include "subtitle_format_encore.h"
#include "subtitle_format_microdvd.h"
#include "subtitle_format_mkv.h"
@@ -100,7 +101,7 @@ bool SubtitleFormat::CanSave(const AssFile *subs) const {
return true;
}
-agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) const {
+agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) {
wxArrayString choices;
// Video FPS
@@ -164,7 +165,7 @@ agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) c
return Framerate();
}
-void SubtitleFormat::StripTags(LineList &lines) const {
+void SubtitleFormat::StripTags(LineList &lines) {
for (LineList::iterator cur = lines.begin(); cur != lines.end(); ++cur) {
if (AssDialogue *current = dynamic_cast(*cur)) {
current->StripTags();
@@ -172,7 +173,7 @@ void SubtitleFormat::StripTags(LineList &lines) const {
}
}
-void SubtitleFormat::ConvertNewlines(LineList &lines, wxString const& newline, bool mergeLineBreaks) const {
+void SubtitleFormat::ConvertNewlines(LineList &lines, wxString const& newline, bool mergeLineBreaks) {
for (LineList::iterator cur = lines.begin(); cur != lines.end(); ++cur) {
if (AssDialogue *current = dynamic_cast(*cur)) {
current->Text.Replace("\\h", " ");
@@ -185,7 +186,7 @@ void SubtitleFormat::ConvertNewlines(LineList &lines, wxString const& newline, b
}
}
-void SubtitleFormat::StripComments(LineList &lines) const {
+void SubtitleFormat::StripComments(LineList &lines) {
for (LineList::iterator it = lines.begin(); it != lines.end(); ) {
AssDialogue *diag = dynamic_cast(*it);
if (!diag || (!diag->Comment && diag->Text.size()))
@@ -197,7 +198,7 @@ void SubtitleFormat::StripComments(LineList &lines) const {
}
}
-void SubtitleFormat::StripNonDialogue(LineList &lines) const {
+void SubtitleFormat::StripNonDialogue(LineList &lines) {
for (LineList::iterator it = lines.begin(); it != lines.end(); ) {
if (dynamic_cast(*it))
++it;
@@ -216,7 +217,7 @@ 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(LineList &lines) const {
+void SubtitleFormat::RecombineOverlaps(LineList &lines) {
LineList::iterator cur, next = lines.begin();
cur = next++;
@@ -288,7 +289,7 @@ void SubtitleFormat::RecombineOverlaps(LineList &lines) const {
}
/// @brief Merge identical lines that follow each other
-void SubtitleFormat::MergeIdentical(LineList &lines) const {
+void SubtitleFormat::MergeIdentical(LineList &lines) {
LineList::iterator cur, next = lines.begin();
cur = next++;
@@ -313,6 +314,7 @@ std::list SubtitleFormat::formats;
void SubtitleFormat::LoadFormats() {
if (formats.empty()) {
new ASSSubtitleFormat;
+ new Ebu3264SubtitleFormat;
new EncoreSubtitleFormat;
new MKVSubtitleFormat;
new MicroDVDSubtitleFormat;
diff --git a/aegisub/src/subtitle_format.h b/aegisub/src/subtitle_format.h
index a43f59a63..6e94a3330 100644
--- a/aegisub/src/subtitle_format.h
+++ b/aegisub/src/subtitle_format.h
@@ -65,32 +65,31 @@ class SubtitleFormat {
/// List of loaded subtitle formats
static std::list formats;
-protected:
+public:
typedef std::list LineList;
/// Strip override tags
- void StripTags(LineList &lines) const;
+ static void StripTags(LineList &lines);
/// 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(LineList &lines, wxString const& newline, bool mergeLineBreaks = true) const;
+ static void ConvertNewlines(LineList &lines, wxString const& newline, bool mergeLineBreaks = true);
/// Remove All commented and empty lines
- void StripComments(LineList &lines) const;
+ static void StripComments(LineList &lines);
/// Remove everything but the dialogue lines
- void StripNonDialogue(LineList &lines) const;
+ static void StripNonDialogue(LineList &lines);
/// @brief Split and merge lines so there are no overlapping lines
///
/// Algorithm described at http://devel.aegisub.org/wiki/Technical/SplitMerge
- void RecombineOverlaps(LineList &lines) const;
+ static void RecombineOverlaps(LineList &lines);
/// Merge sequential identical lines
- void MergeIdentical(LineList &lines) const;
+ static void MergeIdentical(LineList &lines);
/// Prompt the user for a frame rate to use
/// @param allow_vfr Include video frame rate as an option even if it's vfr
/// @param show_smpte Show SMPTE drop frame option
- agi::vfr::Framerate AskForFPS(bool allow_vfr, bool show_smpte) const;
+ static agi::vfr::Framerate AskForFPS(bool allow_vfr, bool show_smpte);
-public:
/// Constructor
/// @param Subtitle format name
/// @note Automatically registers the format
diff --git a/aegisub/src/subtitle_format_ebu3264.cpp b/aegisub/src/subtitle_format_ebu3264.cpp
new file mode 100644
index 000000000..e3034cf27
--- /dev/null
+++ b/aegisub/src/subtitle_format_ebu3264.cpp
@@ -0,0 +1,677 @@
+// Copyright (c) 2011 Niels Martin Hansen
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file subtitle_format_ebu3264.cpp
+/// @see subtitle_format_ebu3264.h
+/// @ingroup subtitle_io
+
+// This implements support for the EBU tech 3264 (1991) subtitling data exchange format.
+// Work on support for this format was sponsored by Bandai.
+
+#include "config.h"
+
+#include "subtitle_format_ebu3264.h"
+
+#ifndef AGI_PRE
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+#include "aegisub_endian.h"
+#include "ass_dialogue.h"
+#include "ass_file.h"
+#include "ass_override.h"
+#include "ass_style.h"
+#include "compat.h"
+#include "dialog_export_ebu3264.h"
+#include "main.h"
+#include "text_file_writer.h"
+
+namespace
+{
+#pragma pack(push, 1)
+ /// General Subtitle Information block as it appears in the file
+ struct BlockGSI
+ {
+ char cpn[3]; ///< code page number
+ char dfc[8]; ///< disk format code
+ char dsc; ///< display standard code
+ char cct[2]; ///< character code table number
+ char lc[2]; ///< language code
+ char opt[32]; ///< original programme title
+ char oet[32]; ///< original episode title
+ char tpt[32]; ///< translated programme title
+ char tet[32]; ///< translated episode title
+ char tn[32]; ///< translator name
+ char tcd[32]; ///< translator contact details
+ char slr[16]; ///< subtitle list reference code
+ char cd[6]; ///< creation date
+ char rd[6]; ///< revision date
+ char rn[2]; ///< revision number
+ char tnb[5]; ///< total number of TTI blocks
+ char tns[5]; ///< total number of subtitles
+ char tng[3]; ///< total number of subtitle groups
+ char mnc[2]; ///< maximum number of displayable characters in a row
+ char mnr[2]; ///< maximum number of displayable rows
+ char tcs; ///< time code: status
+ char tcp[8]; ///< time code: start of programme
+ char tcf[8]; ///< time code: first in-cue
+ char tnd; ///< total number of disks
+ char dsn; ///< disk sequence number
+ char co[3]; ///< country of origin
+ char pub[32]; ///< publisher
+ char en[32]; ///< editor's name
+ char ecd[32]; ///< editor's contact details
+ char unused[75];
+ char uda[576]; ///< user defined area
+ };
+
+ /// Text and Timing Information block as it appears in the file
+ struct BlockTTI
+ {
+ uint8_t sgn; ///< subtitle group number
+ uint16_t sn; ///< subtitle number
+ uint8_t ebn; ///< extension block number
+ uint8_t cs; ///< cumulative status
+ EbuTimecode tci; ///< time code in
+ EbuTimecode tco; ///< time code out
+ uint8_t vp; ///< vertical position
+ uint8_t jc; ///< justification code
+ uint8_t cf; ///< comment flag
+ char tf[112]; ///< text field
+ };
+#pragma pack(pop)
+
+ /// A block of text with basic formatting information
+ struct EbuFormattedText
+ {
+ wxString text; ///< Text in this block
+ bool underline; ///< Is this block underlined?
+ bool italic; ///< Is this block italic?
+ bool word_start; ///< Is it safe to line-wrap between this block and the previous one?
+ EbuFormattedText(wxString const& t, bool u = false, bool i = false, bool ws = true) : text(t), underline(u), italic(i), word_start(ws) { }
+ };
+ typedef std::vector EbuTextRow;
+
+ /// Formatting character constants
+ const char EBU_FORMAT_ITALIC[] = "\x81\x80";
+ const char EBU_FORMAT_UNDERLINE[] = "\x83\x82";
+ const char EBU_FORMAT_BOXING[] = "\x85\x84";
+ const char EBU_FORMAT_LINEBREAK = '\x8a';
+ const char EBU_FORMAT_UNUSED_SPACE = '\x8f';
+
+ /// intermediate format
+ class EbuSubtitle
+ {
+ void ProcessOverrides(AssDialogueBlockOverride *ob, bool &underline, bool &italic, int &align, bool style_underline, bool style_italic)
+ {
+ for (std::vector::iterator tag = ob->Tags.begin(); tag != ob->Tags.end(); ++tag)
+ {
+ AssOverrideTag *t = *tag;
+ if (t->Name == "\\u")
+ underline = t->Params[0]->Get(style_underline);
+ else if (t->Name == "\\i")
+ italic = t->Params[0]->Get(style_italic);
+ else if (t->Name == "\\an")
+ align = t->Params[0]->Get(align);
+ else if (t->Name == "\\a" && !t->Params[0]->omitted)
+ align = AssStyle::SsaToAss(t->Params[0]->Get());
+ }
+ }
+
+ void SetAlignment(int ass_alignment)
+ {
+ if (ass_alignment < 1 || ass_alignment > 9)
+ ass_alignment = 2;
+
+ vertical_position = static_cast(ass_alignment / 3);
+ justification_code = static_cast((ass_alignment - 1) % 3 + 1);
+ }
+
+ public:
+ enum CumulativeStatus
+ {
+ NotCumulative = 0,
+ CumulativeStart = 1,
+ CulumativeMiddle = 2,
+ CumulativeEnd = 3
+ };
+
+ enum JustificationCode
+ {
+ UnchangedPresentation = 0,
+ JustifyLeft = 1,
+ JustifyCentre = 2,
+ JustifyRight = 3
+ };
+
+ // note: not set to constants from spec
+ enum VerticalPosition
+ {
+ PositionTop = 2,
+ PositionMiddle = 1,
+ PositionBottom = 0
+ };
+
+ int group_number; ///< always 0 for compat
+ /// subtitle number is assigned when generating blocks
+ CumulativeStatus cumulative_status; ///< always NotCumulative for compat
+ int time_in; ///< frame number
+ int time_out; ///< frame number
+ bool comment_flag; ///< always false for compat
+ JustificationCode justification_code; ///< never Unchanged presentation for compat
+ VerticalPosition vertical_position; ///< translated to row on tti conversion
+ std::vector text_rows; ///< text split into rows, still unicode
+
+ EbuSubtitle()
+ : group_number(0)
+ , cumulative_status(NotCumulative)
+ , time_in(0)
+ , time_out(0)
+ , comment_flag(false)
+ , justification_code(JustifyCentre)
+ , vertical_position(PositionBottom)
+ , text_rows()
+ {
+ }
+
+ void SplitLines(int max_width, int split_type)
+ {
+ // split_type is an SSA wrap style number
+ if (split_type == 2) return; // no wrapping here!
+ if (split_type < 0) return;
+ if (split_type > 4) return;
+
+ std::vector new_text;
+ new_text.reserve(text_rows.size());
+
+ for (std::vector::iterator row = text_rows.begin(); row != text_rows.end(); ++row)
+ {
+ // Get lengths of each word
+ std::vector word_lengths;
+ for (EbuTextRow::iterator cur_block = row->begin(); cur_block != row->end(); ++cur_block)
+ {
+ if (cur_block->word_start)
+ word_lengths.push_back(0);
+ word_lengths.back() += cur_block->text.size();
+ }
+
+ std::vector split_points = agi::get_wrap_points(word_lengths, (size_t)max_width, (agi::WrapMode)split_type);
+
+ if (split_points.empty())
+ {
+ // Line doesn't need splitting, so copy straight over
+ new_text.push_back(*row);
+ continue;
+ }
+
+ // Apply the splits
+ new_text.push_back(EbuTextRow());
+ size_t cur_word = 0;
+ size_t split_point = 0;
+ for (EbuTextRow::iterator cur_block = row->begin(); cur_block != row->end(); ++cur_block)
+ {
+ if (cur_block->word_start && split_point < split_points.size())
+ {
+ if (split_points[split_point] == cur_word)
+ {
+ new_text.push_back(EbuTextRow());
+ ++split_point;
+ }
+ ++cur_word;
+ }
+
+ new_text.back().push_back(*cur_block);
+ }
+ }
+
+ // replace old text
+ swap(text_rows, new_text);
+ }
+
+ bool CheckLineLengths(int max_width) const
+ {
+ for (std::vector::const_iterator row = text_rows.begin(); row != text_rows.end(); ++row)
+ {
+ int line_length = 0;
+ for (EbuTextRow::const_iterator it = row->begin(); it != row->end(); ++it)
+ line_length += it->text.size();
+
+ if (line_length > max_width)
+ // early return as soon as any line is over length
+ return false;
+ }
+ // no lines failed
+ return true;
+ }
+
+ void SetTextFromAss(AssDialogue *line, bool style_underline, bool style_italic, int align, int wrap_mode)
+ {
+ // Helper for finding special characters
+ wxRegEx special_char_search("\\\\[nN]| ", wxRE_ADVANCED);
+
+ line->ParseASSTags();
+
+ text_rows.clear();
+ text_rows.push_back(EbuTextRow());
+
+ // current row being worked on
+ EbuTextRow *cur_row = &text_rows.back();
+
+ // create initial text part
+ cur_row->push_back(EbuFormattedText("", style_underline, style_italic, true));
+
+ bool underline = style_underline, italic = style_italic;
+
+ for (std::vector::iterator bl = line->Blocks.begin(); bl != line->Blocks.end(); ++bl)
+ {
+ AssDialogueBlock *b = *bl;
+ switch (b->GetType())
+ {
+ case BLOCK_PLAIN:
+ // find special characters and convert them
+ {
+ wxString text = b->GetText();
+
+ // Skip comments
+ if (text.size() > 1 && text[0] =='{' && text.Last() == '}')
+ continue;
+
+ text.Replace("\\t", " ");
+
+ while (special_char_search.Matches(text))
+ {
+ size_t start, len;
+ special_char_search.GetMatch(&start, &len);
+
+ // add first part of text to current part
+ cur_row->back().text.append(text.Left(start));
+
+ // process special character
+ wxString substr = text.Mid(start, len);
+ if (substr == "\\N" || (wrap_mode == 1 && substr == "\\n"))
+ {
+ // create a new row with current style
+ text_rows.push_back(EbuTextRow());
+ cur_row = &text_rows.back();
+ cur_row->push_back(EbuFormattedText("", underline, italic, true));
+ }
+ else // if (substr == " " || substr == "\\h" || substr == "\\n")
+ {
+ cur_row->back().text.append(" ");
+ cur_row->push_back(EbuFormattedText("", underline, italic, true));
+ }
+
+ text = text.Mid(start+len);
+ }
+
+ // add the remaining text
+ cur_row->back().text.append(text);
+
+ // convert \h to regular spaces
+ // done after parsing so that words aren't split on \h
+ cur_row->back().text.Replace("\\h", " ");
+ }
+ break;
+
+ case BLOCK_OVERRIDE:
+ // find relevant tags and process them
+ {
+ AssDialogueBlockOverride *ob = static_cast(b);
+ ob->ParseTags();
+ ProcessOverrides(ob, underline, italic, align, style_underline, style_italic);
+
+ // apply any changes
+ if (underline != cur_row->back().underline || italic != cur_row->back().italic)
+ {
+ if (!cur_row->back().text)
+ {
+ // current part is empty, we can safely change formatting on it
+ cur_row->back().underline = underline;
+ cur_row->back().italic = italic;
+ }
+ else
+ {
+ // create a new empty part with new style
+ cur_row->push_back(EbuFormattedText("", underline, italic, false));
+ }
+ }
+ }
+ break;
+
+ default:
+ // ignore block, we don't want to output it (drawing or unknown)
+ break;
+ }
+ }
+
+ line->ClearBlocks();
+ SetAlignment(align);
+ }
+ };
+
+ std::vector convert_subtitles(AssFile ©, EbuExportSettings const& export_settings)
+ {
+ SubtitleFormat::StripComments(copy.Line);
+ copy.Sort();
+ SubtitleFormat::RecombineOverlaps(copy.Line);
+ SubtitleFormat::MergeIdentical(copy.Line);
+
+ int line_wrap_type = copy.GetScriptInfoAsInt("WrapStyle");
+
+ agi::vfr::Framerate fps = export_settings.GetFramerate();
+ EbuTimecode tcofs = export_settings.timecode_offset;
+ int timecode_bias = fps.FrameAtSmpte(tcofs.h, tcofs.m, tcofs.s, tcofs.s);
+
+ AssStyle default_style;
+ std::vector subs_list;
+ subs_list.reserve(copy.Line.size());
+
+ // convert to intermediate format
+ for (entryIter orgline = copy.Line.begin(); orgline != copy.Line.end(); ++orgline)
+ {
+ AssDialogue *line = dynamic_cast(*orgline);
+ if (!line) continue;
+
+ // add a new subtitle and work on it
+ subs_list.push_back(EbuSubtitle());
+ EbuSubtitle &imline = subs_list.back();
+
+ // some defaults for compatibility
+ imline.group_number = 0;
+ imline.comment_flag = false;
+ imline.cumulative_status = EbuSubtitle::NotCumulative;
+
+ // convert times
+ imline.time_in = fps.FrameAtTime(line->Start) + timecode_bias;
+ imline.time_out = fps.FrameAtTime(line->End) + timecode_bias;
+ if (export_settings.inclusive_end_times)
+ // cheap and possibly wrong way to ensure exclusive times, subtract one frame from end time
+ imline.time_out -= 1;
+
+ // convert alignment from style
+ AssStyle *style = copy.GetStyle(line->Style);
+ if (!style)
+ style = &default_style;
+
+ // add text, translate formatting
+ imline.SetTextFromAss(line, style->underline, style->italic, style->alignment, line_wrap_type);
+
+ // line breaking handling
+ if (export_settings.line_wrapping_mode == EbuExportSettings::AutoWrap)
+ imline.SplitLines(export_settings.max_line_length, line_wrap_type);
+ else if (export_settings.line_wrapping_mode == EbuExportSettings::AutoWrapBalance)
+ imline.SplitLines(export_settings.max_line_length, agi::Wrap_Balanced);
+ else if (!imline.CheckLineLengths(export_settings.max_line_length))
+ {
+ if (export_settings.line_wrapping_mode == EbuExportSettings::AbortOverLength)
+ throw Ebu3264SubtitleFormat::ConversionFailed(STD_STR(wxString::Format(_("Line over maximum length: %s"), line->Text.c_str())), 0);
+ else // skip over-long lines
+ subs_list.pop_back();
+ }
+ }
+
+ // produce an empty line if there are none
+ // (it still has to contain a space to not get ignored)
+ if (subs_list.empty())
+ {
+ subs_list.push_back(EbuSubtitle());
+ subs_list.back().text_rows.push_back(EbuTextRow());
+ subs_list.back().text_rows.back().push_back(EbuFormattedText(" "));
+ }
+
+ return subs_list;
+ }
+
+ inline size_t buffer_size(wxString const& str)
+ {
+#if wxUSE_UNICODE_UTF8
+ return str.utf8_length();
+#else
+ return str.length() * sizeof(wxStringCharType);
+#endif
+ }
+
+ inline const char *wx_str(wxString const& str)
+ {
+ return reinterpret_cast(str.wx_str());
+ }
+
+ std::string convert_subtitle_line(std::vector::const_iterator sub, agi::charset::IconvWrapper *encoder)
+ {
+ std::string fullstring;
+ for (std::vector::const_iterator row = sub->text_rows.begin(); row != sub->text_rows.end(); ++row)
+ {
+ // formatting is reset at the start of every row, so keep track per row
+ bool underline = false, italic = false;
+ for (std::vector::const_iterator block = row->begin(); block != row->end(); ++block)
+ {
+ // insert codes for changed formatting
+ if (underline != block->underline)
+ fullstring += EBU_FORMAT_UNDERLINE[block->underline];
+ if (italic != block->italic)
+ fullstring += EBU_FORMAT_ITALIC[block->italic];
+
+ underline = block->underline;
+ italic = block->italic;
+
+ // convert text to specified encoding
+ fullstring += encoder->Convert(std::string(wx_str(block->text), buffer_size(block->text)));
+ }
+
+ // check that this is not the last row
+ if (row+1 != sub->text_rows.end())
+ // insert linebreak for non-final lines
+ fullstring += EBU_FORMAT_LINEBREAK;
+ }
+ return fullstring;
+ }
+
+ void smpte_at_frame(agi::vfr::Framerate const& fps, int frame, EbuTimecode &tc)
+ {
+ int h=0, m=0, s=0, f=0;
+ fps.SmpteAtFrame(frame, &h, &m, &s, &f);
+ tc.h = h;
+ tc.m = m;
+ tc.s = s;
+ tc.f = f;
+ }
+
+ std::vector create_blocks(std::vector const& subs_list, EbuExportSettings const& export_settings)
+ {
+ agi::scoped_ptr encoder(export_settings.GetTextEncoder());
+ agi::vfr::Framerate fps = export_settings.GetFramerate();
+
+ uint16_t subtitle_number = 0;
+
+ std::vector tti;
+ tti.reserve(subs_list.size());
+ for (std::vector::const_iterator sub = subs_list.begin(); sub != subs_list.end(); ++sub)
+ {
+ std::string fullstring = convert_subtitle_line(sub, encoder.get());
+
+ // construct a base block that can be copied and filled
+ BlockTTI base;
+ base.sgn = sub->group_number;
+ base.sn = Endian::MachineToLittle(subtitle_number++);
+ base.ebn = 255;
+ base.cf = sub->comment_flag;
+ memset(base.tf, EBU_FORMAT_UNUSED_SPACE, sizeof(base.tf));
+ smpte_at_frame(fps, sub->time_in, base.tci);
+ smpte_at_frame(fps, sub->time_out, base.tco);
+ base.cs = sub->cumulative_status;
+
+ if (export_settings.translate_alignments)
+ {
+ // vertical position
+ if (sub->vertical_position == EbuSubtitle::PositionTop)
+ base.vp = 0;
+ else if (sub->vertical_position == EbuSubtitle::PositionMiddle)
+ base.vp = std::min(0, 50 - (10 * sub->text_rows.size()));
+ else //if (sub->vertical_position == EbuSubtitle::PositionBottom)
+ base.vp = 99;
+
+ base.jc = sub->justification_code;
+ }
+ else
+ {
+ base.vp = 99;
+ base.jc = EbuSubtitle::JustifyCentre;
+ }
+
+ // produce blocks from string
+ static const size_t block_size = sizeof(((BlockTTI*)0)->tf);
+ uint8_t num_blocks = 0;
+ for (size_t pos = 0; pos < fullstring.size(); pos += block_size)
+ {
+ size_t bytes_remaining = fullstring.size() - pos;
+
+ tti.push_back(base);
+ // write an extension block number if the remaining text doesn't fit in the block
+ tti.back().ebn = bytes_remaining > block_size ? num_blocks++ : 255;
+
+ std::copy(&fullstring[pos], &fullstring[pos + std::min(block_size, bytes_remaining)], tti.back().tf);
+ }
+ }
+
+ return tti;
+ }
+
+#ifdef _MSC_VER
+#define snprintf _snprintf
+#endif
+
+ BlockGSI create_header(AssFile const& copy, EbuExportSettings const& export_settings)
+ {
+ wxString scriptinfo_title = copy.GetScriptInfo("Title");
+ wxString scriptinfo_translation = copy.GetScriptInfo("Original Translation");
+ wxString scriptinfo_editing = copy.GetScriptInfo("Original Editing");
+
+ agi::charset::IconvWrapper gsi_encoder(wxSTRING_ENCODING, "CP850");
+
+ BlockGSI gsi;
+ memset(&gsi, 0x20, sizeof(gsi)); // fill with spaces
+ memcpy(gsi.cpn, "850", 3);
+ switch (export_settings.tv_standard)
+ {
+ case EbuExportSettings::STL23:
+ case EbuExportSettings::STL24:
+ memcpy(gsi.dfc, "STL24.01", 8);
+ break;
+ case EbuExportSettings::STL29:
+ case EbuExportSettings::STL29drop:
+ case EbuExportSettings::STL30:
+ memcpy(gsi.dfc, "STL30.01", 8);
+ break;
+ case EbuExportSettings::STL25:
+ default:
+ memcpy(gsi.dfc, "STL25.01", 8);
+ break;
+ }
+ gsi.dsc = '0'; // open subtitling
+ gsi.cct[0] = '0';
+ gsi.cct[1] = '0' + (int)export_settings.text_encoding;
+ if (export_settings.text_encoding == EbuExportSettings::utf8)
+ memcpy(gsi.cct, "U8", 2);
+ memcpy(gsi.lc, "00", 2);
+ gsi_encoder.Convert(wx_str(scriptinfo_title), buffer_size(scriptinfo_title), gsi.opt, 32);
+ gsi_encoder.Convert(wx_str(scriptinfo_translation), buffer_size(scriptinfo_translation), gsi.tn, 32);
+ {
+ char buf[20];
+ time_t now;
+ time(&now);
+ tm *thetime = localtime(&now);
+ strftime(buf, 20, "AGI-%y%m%d%H%M%S", thetime);
+ memcpy(gsi.slr, buf, 16);
+ strftime(buf, 20, "%y%m%d", thetime);
+ memcpy(gsi.cd, buf, 6);
+ memcpy(gsi.rd, buf, 6);
+ memcpy(gsi.rn, "00", 2);
+ memcpy(gsi.tng, "001", 3);
+ snprintf(gsi.mnc, 2, "%02u", (unsigned int)export_settings.max_line_length);
+ memcpy(gsi.mnr, "99", 2);
+ gsi.tcs = '1';
+ EbuTimecode tcofs = export_settings.timecode_offset;
+ snprintf(gsi.tcp, 8, "%02u%02u%02u%02u", (unsigned int)tcofs.h, (unsigned int)tcofs.m, (unsigned int)tcofs.s, (unsigned int)tcofs.s);
+ }
+ gsi.tnd = '1';
+ gsi.dsn = '1';
+ memcpy(gsi.co, "NTZ", 3); // neutral zone!
+ gsi_encoder.Convert(wx_str(scriptinfo_editing), buffer_size(scriptinfo_editing), gsi.en, 32);
+ if (export_settings.text_encoding == EbuExportSettings::utf8)
+ strncpy(gsi.uda, "This file was exported by Aegisub using non-standard UTF-8 encoding for the subtitle blocks. The TTI.TF field contains UTF-8-encoded text interspersed with the standard formatting codes, which are not encoded. GSI.CCT is set to 'U8' to signify this.", sizeof(gsi.uda));
+
+ return gsi;
+ }
+
+ EbuExportSettings get_export_config(wxWindow *parent)
+ {
+ EbuExportSettings s("Subtitle Format/EBU STL");
+
+ // Disable the busy cursor set by the exporter while the dialog is visible
+ wxEndBusyCursor();
+ int res = EbuExportConfigurationDialog(parent, s).ShowModal();
+ wxBeginBusyCursor();
+
+ if (res != wxID_OK)
+ throw agi::UserCancelException("EBU/STL export");
+
+ s.Save();
+ return s;
+ }
+
+} // namespace {
+
+Ebu3264SubtitleFormat::Ebu3264SubtitleFormat()
+: SubtitleFormat("EBU subtitling data exchange format (EBU tech 3264, 1991)")
+{
+}
+
+wxArrayString Ebu3264SubtitleFormat::GetWriteWildcards() const
+{
+ wxArrayString formats;
+ formats.Add("stl");
+ return formats;
+}
+
+void Ebu3264SubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const
+{
+ // collect data from user
+ EbuExportSettings export_settings = get_export_config(0);
+ AssFile copy(*src);
+
+ std::vector subs_list = convert_subtitles(copy, export_settings);
+ std::vector tti = create_blocks(subs_list, export_settings);
+ BlockGSI gsi = create_header(copy, export_settings);
+
+ BlockTTI &block0 = tti.front();
+ snprintf(gsi.tcf, 8, "%02u%02u%02u%02u", (unsigned int)block0.tci.h, (unsigned int)block0.tci.m, (unsigned int)block0.tci.s, (unsigned int)block0.tci.f);
+ snprintf(gsi.tnb, 5, "%5u", (unsigned int)tti.size());
+ snprintf(gsi.tns, 5, "%5u", (unsigned int)subs_list.size());
+
+ // write file
+ agi::io::Save f(STD_STR(filename), true);
+ f.Get().write((const char *)&gsi, sizeof(gsi));
+ for (std::vector::iterator block = tti.begin(); block != tti.end(); ++block)
+ {
+ f.Get().write((const char *)&*block, sizeof(*block));
+ }
+}
diff --git a/aegisub/src/subtitle_format_ebu3264.h b/aegisub/src/subtitle_format_ebu3264.h
new file mode 100644
index 000000000..b76d472c0
--- /dev/null
+++ b/aegisub/src/subtitle_format_ebu3264.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 Niels Martin Hansen
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file subtitle_format_ebu3264.h
+/// @see subtitle_format_ebu3264.cpp
+/// @ingroup subtitle_io
+
+#include "subtitle_format.h"
+
+/// @brief Subtitle writer for the EBU tech 3264 (1991) subtitling data exchange format
+///
+/// Based on specifications obtained at
+/// Work on support for this format was sponsored by Bandai.
+class Ebu3264SubtitleFormat : public SubtitleFormat {
+public:
+ Ebu3264SubtitleFormat();
+ wxArrayString GetWriteWildcards() const;
+ void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const;
+
+ DEFINE_SIMPLE_EXCEPTION(ConversionFailed, agi::InvalidInputException, "subtitle_io/ebu3264/conversion_error")
+};