2014-03-07 19:58:51 +01:00
|
|
|
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2014-03-07 19:58:51 +01:00
|
|
|
// 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.
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2014-03-07 19:58:51 +01:00
|
|
|
// 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.
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2009-07-29 07:43:02 +02:00
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
2012-10-12 19:16:39 +02:00
|
|
|
#include "ass_file.h"
|
|
|
|
|
2006-06-30 23:59:20 +02:00
|
|
|
#include "ass_attachment.h"
|
2009-09-10 15:06:40 +02:00
|
|
|
#include "ass_dialogue.h"
|
2012-12-08 03:51:09 +01:00
|
|
|
#include "ass_info.h"
|
2009-09-10 15:06:40 +02:00
|
|
|
#include "ass_style.h"
|
2014-05-03 18:35:39 +02:00
|
|
|
#include "ass_style_storage.h"
|
2013-01-07 02:50:09 +01:00
|
|
|
#include "options.h"
|
2014-05-03 18:35:39 +02:00
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
#include <algorithm>
|
|
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
2014-04-29 18:33:22 +02:00
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
2013-01-26 02:57:46 +01:00
|
|
|
#include <boost/filesystem/path.hpp>
|
2014-04-22 19:21:00 +02:00
|
|
|
#include <cassert>
|
2014-09-06 18:16:44 +02:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <unordered_set>
|
2012-11-05 17:20:58 +01:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
AssFile::AssFile() { }
|
|
|
|
|
2006-01-16 22:02:54 +01:00
|
|
|
AssFile::~AssFile() {
|
2014-03-07 19:58:51 +01:00
|
|
|
Styles.clear_and_dispose([](AssStyle *e) { delete e; });
|
|
|
|
Events.clear_and_dispose([](AssDialogue *e) { delete e; });
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-05-04 13:04:48 +02:00
|
|
|
void AssFile::LoadDefault(bool include_dialogue_line, std::string const& style_catalog) {
|
2014-03-08 01:16:38 +01:00
|
|
|
Info.emplace_back("Title", "Default Aegisub file");
|
|
|
|
Info.emplace_back("ScriptType", "v4.00+");
|
|
|
|
Info.emplace_back("WrapStyle", "0");
|
|
|
|
Info.emplace_back("ScaledBorderAndShadow", "yes");
|
2012-01-08 02:04:37 +01:00
|
|
|
if (!OPT_GET("Subtitle/Default Resolution/Auto")->GetBool()) {
|
2014-03-08 01:16:38 +01:00
|
|
|
Info.emplace_back("PlayResX", std::to_string(OPT_GET("Subtitle/Default Resolution/Width")->GetInt()));
|
|
|
|
Info.emplace_back("PlayResY", std::to_string(OPT_GET("Subtitle/Default Resolution/Height")->GetInt()));
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2014-03-08 01:16:38 +01:00
|
|
|
Info.emplace_back("YCbCr Matrix", "None");
|
2012-02-16 22:21:35 +01:00
|
|
|
|
2014-05-03 18:35:39 +02:00
|
|
|
// Add default style
|
2014-03-07 18:02:24 +01:00
|
|
|
Styles.push_back(*new AssStyle);
|
2012-01-08 02:34:30 +01:00
|
|
|
|
2014-05-03 18:35:39 +02:00
|
|
|
// Add/replace any catalog styles requested
|
2014-05-04 13:04:48 +02:00
|
|
|
if (AssStyleStorage::CatalogExists(style_catalog)) {
|
2014-05-03 18:35:39 +02:00
|
|
|
AssStyleStorage catalog;
|
2014-05-04 13:04:48 +02:00
|
|
|
catalog.LoadCatalog(style_catalog);
|
2014-05-03 18:35:39 +02:00
|
|
|
catalog.ReplaceIntoFile(*this);
|
|
|
|
}
|
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
if (include_dialogue_line)
|
2014-03-07 18:02:24 +01:00
|
|
|
Events.push_back(*new AssDialogue);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-03-08 01:16:38 +01:00
|
|
|
AssFile::AssFile(const AssFile &from)
|
|
|
|
: Info(from.Info)
|
2014-03-08 02:13:23 +01:00
|
|
|
, Attachments(from.Attachments)
|
2014-04-22 19:21:00 +02:00
|
|
|
, Extradata(from.Extradata)
|
|
|
|
, next_extradata_id(from.next_extradata_id)
|
2014-03-08 01:16:38 +01:00
|
|
|
{
|
2014-03-08 01:44:49 +01:00
|
|
|
Styles.clone_from(from.Styles,
|
|
|
|
[](AssStyle const& e) { return new AssStyle(e); },
|
|
|
|
[](AssStyle *e) { delete e; });
|
|
|
|
Events.clone_from(from.Events,
|
|
|
|
[](AssDialogue const& e) { return new AssDialogue(e); },
|
|
|
|
[](AssDialogue *e) { delete e; });
|
2010-07-09 09:31:34 +02:00
|
|
|
}
|
|
|
|
|
2014-03-07 18:02:24 +01:00
|
|
|
void AssFile::swap(AssFile& from) throw() {
|
|
|
|
Info.swap(from.Info);
|
|
|
|
Styles.swap(from.Styles);
|
|
|
|
Events.swap(from.Events);
|
|
|
|
Attachments.swap(from.Attachments);
|
2014-04-22 19:21:00 +02:00
|
|
|
Extradata.swap(from.Extradata);
|
2014-05-22 03:32:42 +02:00
|
|
|
std::swap(Properties, from.Properties);
|
2014-04-22 19:21:00 +02:00
|
|
|
std::swap(next_extradata_id, from.next_extradata_id);
|
2010-06-22 02:03:33 +02:00
|
|
|
}
|
2013-10-27 15:15:39 +01:00
|
|
|
|
2010-06-22 02:03:33 +02:00
|
|
|
AssFile& AssFile::operator=(AssFile from) {
|
2014-03-07 18:02:24 +01:00
|
|
|
swap(from);
|
2010-06-22 02:03:33 +02:00
|
|
|
return *this;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-04-04 17:11:09 +02:00
|
|
|
EntryList<AssDialogue>::iterator AssFile::iterator_to(AssDialogue& line) {
|
|
|
|
using l = EntryList<AssDialogue>;
|
|
|
|
bool in_list = !l::node_algorithms::inited(l::value_traits::to_node_ptr(line));
|
|
|
|
return in_list ? Events.iterator_to(line) : Events.end();
|
|
|
|
}
|
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
void AssFile::InsertAttachment(agi::fs::path const& filename) {
|
2013-06-13 00:54:19 +02:00
|
|
|
AssEntryGroup group = AssEntryGroup::GRAPHIC;
|
2006-07-01 06:35:50 +02:00
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
auto ext = boost::to_lower_copy(filename.extension().string());
|
2012-01-08 02:34:30 +01:00
|
|
|
if (ext == ".ttf" || ext == ".ttc" || ext == ".pfb")
|
2013-06-13 00:54:19 +02:00
|
|
|
group = AssEntryGroup::FONT;
|
2012-02-01 01:47:38 +01:00
|
|
|
|
2014-03-08 02:13:23 +01:00
|
|
|
Attachments.emplace_back(filename, group);
|
2012-02-16 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
std::string AssFile::GetScriptInfo(std::string const& key) const {
|
2014-03-07 19:58:51 +01:00
|
|
|
for (auto const& info : Info) {
|
|
|
|
if (boost::iequals(key, info.Key()))
|
|
|
|
return info.Value();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-06-16 08:20:14 +02:00
|
|
|
return "";
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
int AssFile::GetScriptInfoAsInt(std::string const& key) const {
|
|
|
|
return atoi(GetScriptInfo(key).c_str());
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
|
2014-03-08 01:16:38 +01:00
|
|
|
for (auto it = Info.begin(); it != Info.end(); ++it) {
|
|
|
|
if (boost::iequals(key, it->Key())) {
|
2012-12-08 03:51:09 +01:00
|
|
|
if (value.empty())
|
2014-03-08 01:16:38 +01:00
|
|
|
Info.erase(it);
|
2012-12-08 03:51:09 +01:00
|
|
|
else
|
2014-03-08 01:16:38 +01:00
|
|
|
it->SetValue(value);
|
2006-01-16 22:02:54 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2012-02-16 22:21:55 +01:00
|
|
|
|
2012-12-10 02:07:05 +01:00
|
|
|
if (!value.empty())
|
2014-03-08 01:16:38 +01:00
|
|
|
Info.emplace_back(key, value);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
void AssFile::GetResolution(int &sw, int &sh) const {
|
2011-10-25 03:16:56 +02:00
|
|
|
sw = GetScriptInfoAsInt("PlayResX");
|
|
|
|
sh = GetScriptInfoAsInt("PlayResY");
|
2007-05-03 19:19:50 +02:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
// Gabest logic: default is 384x288, assume 1280x1024 if either height or
|
|
|
|
// width are that, otherwise assume 4:3 if only heigh or width are set.
|
|
|
|
// Why 1280x1024? Who the fuck knows. Clearly just Gabest trolling everyone.
|
2007-05-03 19:19:50 +02:00
|
|
|
if (sw == 0 && sh == 0) {
|
|
|
|
sw = 384;
|
|
|
|
sh = 288;
|
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
else if (sw == 0)
|
|
|
|
sw = sh == 1024 ? 1280 : sh * 4 / 3;
|
|
|
|
else if (sh == 0)
|
|
|
|
sh = sw == 1280 ? 1024 : sw * 3 / 4;
|
2006-07-01 09:22:57 +02:00
|
|
|
}
|
|
|
|
|
2012-12-30 01:32:36 +01:00
|
|
|
std::vector<std::string> AssFile::GetStyles() const {
|
|
|
|
std::vector<std::string> styles;
|
2014-03-07 19:58:51 +01:00
|
|
|
for (auto& style : Styles)
|
|
|
|
styles.push_back(style.name);
|
2006-01-16 22:02:54 +01:00
|
|
|
return styles;
|
|
|
|
}
|
|
|
|
|
2012-12-30 01:32:36 +01:00
|
|
|
AssStyle *AssFile::GetStyle(std::string const& name) {
|
2014-03-07 19:58:51 +01:00
|
|
|
for (auto& style : Styles) {
|
|
|
|
if (boost::iequals(style.name, name))
|
|
|
|
return &style;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2012-11-05 17:20:58 +01:00
|
|
|
return nullptr;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
int AssFile::Commit(wxString const& desc, int type, int amend_id, AssDialogue *single_line) {
|
2014-04-01 18:17:30 +02:00
|
|
|
if (type == COMMIT_NEW || (type & COMMIT_DIAG_ADDREM) || (type & COMMIT_ORDER)) {
|
2014-03-31 16:41:57 +02:00
|
|
|
int i = 0;
|
|
|
|
for (auto& event : Events)
|
|
|
|
event.Row = i++;
|
|
|
|
}
|
|
|
|
|
2014-03-04 17:32:29 +01:00
|
|
|
PushState({desc, &amend_id, single_line});
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
std::set<const AssDialogue*> changed_lines;
|
2012-12-17 16:54:19 +01:00
|
|
|
if (single_line)
|
|
|
|
changed_lines.insert(single_line);
|
|
|
|
|
|
|
|
AnnounceCommit(type, changed_lines);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2013-01-26 02:57:46 +01:00
|
|
|
return amend_id;
|
2010-05-19 02:44:44 +02:00
|
|
|
}
|
2007-01-26 01:47:42 +01:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompStart(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.Start < rgt.Start;
|
2010-05-19 02:44:44 +02:00
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompEnd(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.End < rgt.End;
|
2010-05-19 02:44:44 +02:00
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompStyle(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.Style < rgt.Style;
|
2007-01-26 01:47:42 +01:00
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompActor(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.Actor < rgt.Actor;
|
2012-01-31 01:44:34 +01:00
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompEffect(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.Effect < rgt.Effect;
|
2012-01-31 01:44:34 +01:00
|
|
|
}
|
2014-03-07 19:58:51 +01:00
|
|
|
bool AssFile::CompLayer(AssDialogue const& lft, AssDialogue const& rgt) {
|
|
|
|
return lft.Layer < rgt.Layer;
|
2012-07-23 02:44:44 +02:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-03-07 23:41:12 +01:00
|
|
|
void AssFile::Sort(CompFunc comp, std::set<AssDialogue*> const& limit) {
|
2014-03-07 18:02:24 +01:00
|
|
|
Sort(Events, comp, limit);
|
2010-05-19 02:44:44 +02:00
|
|
|
}
|
2012-10-12 19:16:39 +02:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
void AssFile::Sort(EntryList<AssDialogue> &lst, CompFunc comp, std::set<AssDialogue*> const& limit) {
|
|
|
|
if (limit.empty()) {
|
|
|
|
lst.sort(comp);
|
|
|
|
return;
|
|
|
|
}
|
2013-01-26 02:57:46 +01:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
// Sort each selected block separately, leaving everything else untouched
|
|
|
|
for (auto begin = lst.begin(); begin != lst.end(); ++begin) {
|
|
|
|
if (!limit.count(&*begin)) continue;
|
|
|
|
auto end = begin;
|
|
|
|
while (end != lst.end() && limit.count(&*end)) ++end;
|
2010-05-19 02:44:44 +02:00
|
|
|
|
2014-03-07 19:58:51 +01:00
|
|
|
// sort doesn't support only sorting a sublist, so move them to a temp list
|
|
|
|
EntryList<AssDialogue> tmp;
|
2010-05-19 02:44:44 +02:00
|
|
|
tmp.splice(tmp.begin(), lst, begin, end);
|
2014-03-07 19:58:51 +01:00
|
|
|
tmp.sort(comp);
|
2010-05-19 02:44:44 +02:00
|
|
|
lst.splice(end, tmp);
|
|
|
|
|
|
|
|
begin = --end;
|
|
|
|
}
|
|
|
|
}
|
2014-04-22 19:21:00 +02:00
|
|
|
|
|
|
|
uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value) {
|
2014-08-24 21:06:22 +02:00
|
|
|
for (auto const& data : Extradata) {
|
|
|
|
// perform brute-force deduplication by simple key and value comparison
|
2014-09-06 18:16:44 +02:00
|
|
|
if (key == data.key && value == data.value) {
|
|
|
|
return data.id;
|
2014-08-24 21:06:22 +02:00
|
|
|
}
|
|
|
|
}
|
2014-09-06 18:16:44 +02:00
|
|
|
Extradata.push_back(ExtradataEntry{next_extradata_id, key, value});
|
2014-04-22 19:21:00 +02:00
|
|
|
return next_extradata_id++; // return old value, then post-increment
|
|
|
|
}
|
|
|
|
|
2014-09-06 18:16:44 +02:00
|
|
|
namespace {
|
|
|
|
struct extradata_id_cmp {
|
|
|
|
bool operator()(ExtradataEntry const& e, uint32_t id) {
|
|
|
|
return e.id < id;
|
|
|
|
}
|
|
|
|
bool operator()(uint32_t id, ExtradataEntry const& e) {
|
|
|
|
return id < e.id;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename ExtradataType, typename Func>
|
|
|
|
void enumerate_extradata(ExtradataType&& extradata, std::vector<uint32_t> const& id_list, Func&& f) {
|
|
|
|
auto begin = extradata.begin(), end = extradata.end();
|
2014-04-22 19:21:00 +02:00
|
|
|
for (auto id : id_list) {
|
2014-09-06 18:16:44 +02:00
|
|
|
auto it = lower_bound(begin, end, id, extradata_id_cmp{});
|
|
|
|
if (it != end) {
|
|
|
|
f(*it);
|
|
|
|
begin = it;
|
|
|
|
}
|
2014-04-22 19:21:00 +02:00
|
|
|
}
|
2014-09-06 18:16:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename K, typename V>
|
|
|
|
using reference_map = std::unordered_map<std::reference_wrapper<const K>, V, std::hash<K>, std::equal_to<K>>;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ExtradataEntry> AssFile::GetExtradata(std::vector<uint32_t> const& id_list) const {
|
|
|
|
std::vector<ExtradataEntry> result;
|
|
|
|
enumerate_extradata(Extradata, id_list, [&](ExtradataEntry const& e) {
|
|
|
|
result.push_back(e);
|
|
|
|
});
|
2014-04-22 19:21:00 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssFile::CleanExtradata() {
|
2014-09-06 18:16:44 +02:00
|
|
|
if (Extradata.empty()) return;
|
|
|
|
|
|
|
|
std::unordered_set<uint32_t> ids_used;
|
2014-04-22 19:21:00 +02:00
|
|
|
for (auto& line : Events) {
|
2014-09-06 18:16:44 +02:00
|
|
|
if (line.ExtradataIds.get().empty()) continue;
|
|
|
|
|
2014-04-22 19:21:00 +02:00
|
|
|
// Find the ID for each unique key in the line
|
2014-09-06 18:16:44 +02:00
|
|
|
reference_map<std::string, uint32_t> keys_used;
|
|
|
|
enumerate_extradata(Extradata, line.ExtradataIds.get(), [&](ExtradataEntry const& e) {
|
|
|
|
keys_used[e.key] = e.id;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (auto const& used : keys_used)
|
|
|
|
ids_used.insert(used.second);
|
|
|
|
|
|
|
|
// If any keys were duplicated or missing, update the id list
|
|
|
|
if (keys_used.size() != line.ExtradataIds.get().size()) {
|
|
|
|
std::vector<uint32_t> ids;
|
|
|
|
ids.reserve(keys_used.size());
|
|
|
|
for (auto const& used : keys_used)
|
|
|
|
ids.push_back(used.second);
|
|
|
|
std::sort(begin(ids), end(ids));
|
|
|
|
line.ExtradataIds = std::move(ids);
|
2014-04-22 19:21:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-06 18:16:44 +02:00
|
|
|
if (ids_used.size() != Extradata.size()) {
|
|
|
|
// Erase all no-longer-used extradata entries
|
|
|
|
Extradata.erase(std::remove_if(begin(Extradata), end(Extradata), [&](ExtradataEntry const& e) {
|
|
|
|
return !ids_used.count(e.id);
|
|
|
|
}), end(Extradata));
|
2014-04-22 19:21:00 +02:00
|
|
|
}
|
|
|
|
}
|