Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • 1a2847016c7f73f5ba44b661346459c8a408be26
  • master par défaut
  • 1-baka-export
  • meson
  • assdraw
  • old-master
  • v3.2.2
  • v3.2.1
  • v3.2.0
  • v3.1.3
  • v3.1.2
  • v3.1.1
  • v3.1.0
  • v3.0.4
  • v3.0.3
  • v3.0.2
  • v3.0.1
  • v3.0.0
  • v2.1.3
  • v2.1.4
  • v2.1.5
  • v2.1.6
  • v2.1.0
  • v2.1.1
  • v2.1.2
  • v2.1.7
26 résultats

subtitle_format.cpp

Blame
  • subtitle_format.cpp 12,11 Kio
    // 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 <wx/intl.h>
    #include <wx/choicdlg.h> // 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 <libaegisub/fs.h>
    #include <libaegisub/of_type_adaptor.h>
    #include <libaegisub/util.h>
    
    #include <algorithm>
    #include <boost/algorithm/string/join.hpp>
    #include <boost/algorithm/string/predicate.hpp>
    #include <boost/algorithm/string/replace.hpp>
    
    using namespace std::placeholders;
    
    namespace {
    	std::vector<std::unique_ptr<SubtitleFormat>> 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<const AssStyle*>(&line)) {
    			if (curstyle->GetEntryData() != defstyle.GetEntryData())
    				return false;
    		}
    
    		// Check for attachments, if any is found, return false
    		if (dynamic_cast<const AssAttachment*>(&line)) return false;
    
    		// Check dialog
    		if (const AssDialogue *curdiag = dynamic_cast<const AssDialogue*>(&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<AssDialogue>())
    		current->StripTags();
    }
    
    void SubtitleFormat::ConvertNewlines(AssFile &file, std::string const& newline, bool mergeLineBreaks) {
    	for (auto current : file.Line | agi::of_type<AssDialogue>()) {
    		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<const AssDialogue*>(&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<AssDialogue*>(&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<AssDialogue*>(&*cur);
    		AssDialogue *curdlg = dynamic_cast<AssDialogue*>(&*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<AssDialogue*>(&*cur);
    		AssDialogue *nextdlg = dynamic_cast<AssDialogue*>(&*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<AssSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<Ebu3264SubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<EncoreSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<MKVSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<MicroDVDSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<SRTSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<TTXTSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<TXTSubtitleFormat>());
    		formats.emplace_back(agi::util::make_unique<TranStationSubtitleFormat>());
    	}
    }
    
    template<class Cont, class Pred>
    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<std::string> 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;
    }