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") +};