// Copyright (c) 2006, Rodrigo Braz Monteiro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Aegisub Project http://www.aegisub.org/ /// @file subtitle_format.cpp /// @brief Base class for subtitle format handlers /// @ingroup subtitle_io /// #include "config.h" #include "subtitle_format.h" #include #include // Keep this last so wxUSE_CHOICEDLG is set. #include "ass_attachment.h" #include "ass_dialogue.h" #include "ass_file.h" #include "ass_style.h" #include "compat.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" #include "subtitle_format_srt.h" #include "subtitle_format_transtation.h" #include "subtitle_format_ttxt.h" #include "subtitle_format_txt.h" #include "video_context.h" #include #include #include #include #include #include #include using namespace std::placeholders; namespace { std::vector> formats; } SubtitleFormat::SubtitleFormat(std::string name) : name(std::move(name)) { } SubtitleFormat::~SubtitleFormat() { } bool SubtitleFormat::CanReadFile(agi::fs::path const& filename, std::string const&) const { auto wildcards = GetReadWildcards(); return any_of(begin(wildcards), end(wildcards), [&](std::string const& ext) { return agi::fs::HasExtension(filename, ext); }); } bool SubtitleFormat::CanWriteFile(agi::fs::path const& filename) const { auto wildcards = GetWriteWildcards(); return any_of(begin(wildcards), end(wildcards), [&](std::string const& ext) { return agi::fs::HasExtension(filename, ext); }); } bool SubtitleFormat::CanSave(const AssFile *subs) const { AssStyle defstyle; for (auto const& line : subs->Line) { // Check style, if anything non-default is found, return false if (const AssStyle *curstyle = dynamic_cast(&line)) { if (curstyle->GetEntryData() != defstyle.GetEntryData()) return false; } // Check for attachments, if any is found, return false if (dynamic_cast(&line)) return false; // Check dialog if (const AssDialogue *curdiag = dynamic_cast(&line)) { if (curdiag->GetStrippedText() != curdiag->Text) return false; } } return true; } agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) { wxArrayString choices; // Video FPS VideoContext *context = VideoContext::Get(); bool vidLoaded = context->TimecodesLoaded(); if (vidLoaded) { if (!context->FPS().IsVFR()) choices.Add(wxString::Format(_("From video (%g)"), context->FPS().FPS())); else if (allow_vfr) choices.Add(_("From video (VFR)")); else vidLoaded = false; } // Standard FPS values choices.Add(_("15.000 FPS")); choices.Add(_("23.976 FPS (Decimated NTSC)")); choices.Add(_("24.000 FPS (FILM)")); choices.Add(_("25.000 FPS (PAL)")); choices.Add(_("29.970 FPS (NTSC)")); if (show_smpte) choices.Add(_("29.970 FPS (NTSC with SMPTE dropframe)")); choices.Add(_("30.000 FPS")); choices.Add(_("50.000 FPS (PAL x2)")); choices.Add(_("59.940 FPS (NTSC x2)")); choices.Add(_("60.000 FPS")); choices.Add(_("119.880 FPS (NTSC x4)")); choices.Add(_("120.000 FPS")); bool was_busy = wxIsBusy(); if (was_busy) wxEndBusyCursor(); int choice = wxGetSingleChoiceIndex(_("Please choose the appropriate FPS for the subtitles:"), _("FPS"), choices); if (was_busy) wxBeginBusyCursor(); using agi::vfr::Framerate; if (choice == -1) return Framerate(); // Get FPS from choice if (vidLoaded) --choice; if (!show_smpte && choice > 4) --choice; switch (choice) { case -1: return context->FPS(); break; // VIDEO case 0: return Framerate(15, 1); break; case 1: return Framerate(24000, 1001); break; case 2: return Framerate(24, 1); break; case 3: return Framerate(25, 1); break; case 4: return Framerate(30000, 1001); break; case 5: return Framerate(30000, 1001, true); break; case 6: return Framerate(30, 1); break; case 7: return Framerate(50, 1); break; case 8: return Framerate(60000, 1001); break; case 9: return Framerate(60, 1); break; case 10: return Framerate(120000, 1001); break; case 11: return Framerate(120, 1); break; } assert(false); return Framerate(); } void SubtitleFormat::StripTags(AssFile &file) { for (auto current : file.Line | agi::of_type()) current->StripTags(); } void SubtitleFormat::ConvertNewlines(AssFile &file, std::string const& newline, bool mergeLineBreaks) { for (auto current : file.Line | agi::of_type()) { std::string repl = current->Text; boost::replace_all(repl, "\\h", " "); boost::ireplace_all(repl, "\\n", newline); if (mergeLineBreaks) { std::string dbl(newline + newline); size_t pos = 0; while ((pos = repl.find(dbl, pos)) != std::string::npos) boost::replace_all(repl, dbl, newline); } current->Text = repl; } } void SubtitleFormat::StripComments(AssFile &file) { file.Line.remove_and_dispose_if([](AssEntry const& e) { const AssDialogue *diag = dynamic_cast(&e); return diag && (diag->Comment || diag->Text.get().empty()); }, [](AssEntry *e) { delete e; }); } void SubtitleFormat::StripNonDialogue(AssFile &file) { file.Line.remove_and_dispose_if([](AssEntry const& e) { return e.Group() != AssEntryGroup::DIALOGUE; }, [](AssEntry *e) { delete e; }); } static bool dialog_start_lt(AssEntry &pos, AssDialogue *to_insert) { AssDialogue *diag = dynamic_cast(&pos); return diag && diag->Start > to_insert->Start; } /// @brief Split and merge lines so there are no overlapping lines /// /// Algorithm described at http://devel.aegisub.org/wiki/Technical/SplitMerge void SubtitleFormat::RecombineOverlaps(AssFile &file) { entryIter cur, next = file.Line.begin(); cur = next++; for (; next != file.Line.end(); cur = next++) { AssDialogue *prevdlg = dynamic_cast(&*cur); AssDialogue *curdlg = dynamic_cast(&*next); if (!curdlg || !prevdlg) continue; if (prevdlg->End <= curdlg->Start) continue; // Use names like in the algorithm description and prepare for erasing // old dialogues from the list entryIter 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. file.Line.erase(prev); file.Line.erase(cur); //Is there an A part before the overlap? if (curdlg->Start > prevdlg->Start) { // Produce new entry with correct values auto newdlg = new AssDialogue(*prevdlg); newdlg->Start = prevdlg->Start; newdlg->End = curdlg->Start; newdlg->Text = prevdlg->Text; file.Line.insert(find_if(next, file.Line.end(), std::bind(dialog_start_lt, _1, newdlg)), *newdlg); } // Overlapping A+B part { auto newdlg = new AssDialogue(*prevdlg); newdlg->Start = curdlg->Start; newdlg->End = (prevdlg->End < curdlg->End) ? prevdlg->End : curdlg->End; // Put an ASS format hard linewrap between lines newdlg->Text = curdlg->Text.get() + "\\N" + prevdlg->Text.get(); file.Line.insert(find_if(next, file.Line.end(), std::bind(dialog_start_lt, _1, newdlg)), *newdlg); } // Is there an A part after the overlap? if (prevdlg->End > curdlg->End) { // Produce new entry with correct values auto newdlg = new AssDialogue(*prevdlg); newdlg->Start = curdlg->End; newdlg->End = prevdlg->End; newdlg->Text = prevdlg->Text; file.Line.insert(find_if(next, file.Line.end(), std::bind(dialog_start_lt, _1, newdlg)), *newdlg); } // Is there a B part after the overlap? if (curdlg->End > prevdlg->End) { // Produce new entry with correct values auto newdlg = new AssDialogue(*prevdlg); newdlg->Start = prevdlg->End; newdlg->End = curdlg->End; newdlg->Text = curdlg->Text; file.Line.insert(find_if(next, file.Line.end(), std::bind(dialog_start_lt, _1, newdlg)), *newdlg); } next--; } } /// @brief Merge identical lines that follow each other void SubtitleFormat::MergeIdentical(AssFile &file) { entryIter cur, next = file.Line.begin(); cur = next++; for (; next != file.Line.end(); cur = next++) { AssDialogue *curdlg = dynamic_cast(&*cur); AssDialogue *nextdlg = dynamic_cast(&*next); if (curdlg && nextdlg && curdlg->End == nextdlg->Start && curdlg->Text == nextdlg->Text) { // Merge timing nextdlg->Start = std::min(nextdlg->Start, curdlg->Start); nextdlg->End = std::max(nextdlg->End, curdlg->End); // Remove duplicate line delete curdlg; } } } void SubtitleFormat::LoadFormats() { if (formats.empty()) { formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); formats.emplace_back(agi::util::make_unique()); } } template SubtitleFormat *find_or_throw(Cont &container, Pred pred) { auto it = find_if(container.begin(), container.end(), pred); if (it == container.end()) throw UnknownSubtitleFormatError("Subtitle format for extension not found", nullptr); return it->get(); } const SubtitleFormat *SubtitleFormat::GetReader(agi::fs::path const& filename, std::string const& encoding) { LoadFormats(); return find_or_throw(formats, std::bind(&SubtitleFormat::CanReadFile, _1, filename, encoding)); } const SubtitleFormat *SubtitleFormat::GetWriter(agi::fs::path const& filename) { LoadFormats(); return find_or_throw(formats, std::bind(&SubtitleFormat::CanWriteFile, _1, filename)); } std::string SubtitleFormat::GetWildcards(int mode) { LoadFormats(); std::vector all; std::string final; for (auto const& format : formats) { auto cur = mode == 0 ? format->GetReadWildcards() : format->GetWriteWildcards(); if (cur.empty()) continue; for (auto& str : cur) str.insert(0, "*."); all.insert(all.end(), begin(cur), end(cur)); final += "|" + format->GetName() + " (" + boost::join(cur, ",") + ")|" + boost::join(cur, ";"); } return from_wx(_("All Supported Formats")) + " (" + boost::join(all, ",") + ")|" + boost::join(all, ";") + final; }