Aegisub/src/ass_file.cpp

290 lines
8.7 KiB
C++
Raw Normal View History

// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
2006-01-16 22:02:54 +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
//
// 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
//
// Aegisub Project http://www.aegisub.org/
#include "ass_file.h"
#include "ass_attachment.h"
#include "ass_dialogue.h"
#include "ass_info.h"
#include "ass_style.h"
#include "ass_style_storage.h"
#include "options.h"
#include <algorithm>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/path.hpp>
#include <cassert>
AssFile::AssFile() { }
2006-01-16 22:02:54 +01:00
AssFile::~AssFile() {
Styles.clear_and_dispose([](AssStyle *e) { delete e; });
Events.clear_and_dispose([](AssDialogue *e) { delete e; });
2006-01-16 22:02:54 +01: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");
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");
// Add default style
Styles.push_back(*new AssStyle);
// Add/replace any catalog styles requested
if (AssStyleStorage::CatalogExists(style_catalog)) {
AssStyleStorage catalog;
catalog.LoadCatalog(style_catalog);
catalog.ReplaceIntoFile(*this);
}
if (include_dialogue_line)
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)
, 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; });
}
void AssFile::swap(AssFile& from) throw() {
Info.swap(from.Info);
Styles.swap(from.Styles);
Events.swap(from.Events);
Attachments.swap(from.Attachments);
Extradata.swap(from.Extradata);
std::swap(Properties, from.Properties);
std::swap(next_extradata_id, from.next_extradata_id);
}
2013-10-27 15:15:39 +01:00
AssFile& AssFile::operator=(AssFile from) {
swap(from);
return *this;
2006-01-16 22:02:54 +01: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();
}
void AssFile::InsertAttachment(agi::fs::path const& filename) {
2013-06-13 00:54:19 +02:00
AssEntryGroup group = AssEntryGroup::GRAPHIC;
auto ext = boost::to_lower_copy(filename.extension().string());
if (ext == ".ttf" || ext == ".ttc" || ext == ".pfb")
2013-06-13 00:54:19 +02:00
group = AssEntryGroup::FONT;
2014-03-08 02:13:23 +01:00
Attachments.emplace_back(filename, group);
}
std::string AssFile::GetScriptInfo(std::string const& key) const {
for (auto const& info : Info) {
if (boost::iequals(key, info.Key()))
return info.Value();
2006-01-16 22:02:54 +01:00
}
return "";
2006-01-16 22:02:54 +01:00
}
int AssFile::GetScriptInfoAsInt(std::string const& key) const {
return atoi(GetScriptInfo(key).c_str());
2006-01-16 22:02:54 +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())) {
if (value.empty())
2014-03-08 01:16:38 +01:00
Info.erase(it);
else
2014-03-08 01:16:38 +01:00
it->SetValue(value);
2006-01-16 22:02:54 +01:00
return;
}
}
if (!value.empty())
2014-03-08 01:16:38 +01:00
Info.emplace_back(key, value);
2006-01-16 22:02:54 +01:00
}
void AssFile::GetResolution(int &sw, int &sh) const {
sw = GetScriptInfoAsInt("PlayResX");
sh = GetScriptInfoAsInt("PlayResY");
// 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.
if (sw == 0 && sh == 0) {
sw = 384;
sh = 288;
}
else if (sw == 0)
sw = sh == 1024 ? 1280 : sh * 4 / 3;
else if (sh == 0)
sh = sw == 1280 ? 1024 : sw * 3 / 4;
}
2012-12-30 01:32:36 +01:00
std::vector<std::string> AssFile::GetStyles() const {
std::vector<std::string> styles;
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) {
for (auto& style : Styles) {
if (boost::iequals(style.name, name))
return &style;
2006-01-16 22:02:54 +01:00
}
return nullptr;
2006-01-16 22:02:54 +01:00
}
int AssFile::Commit(wxString const& desc, int type, int amend_id, AssDialogue *single_line) {
if (type == COMMIT_NEW || (type & COMMIT_DIAG_ADDREM) || (type & COMMIT_ORDER)) {
int i = 0;
for (auto& event : Events)
event.Row = i++;
}
PushState({desc, &amend_id, single_line});
2006-01-16 22:02:54 +01:00
std::set<const AssDialogue*> changed_lines;
if (single_line)
changed_lines.insert(single_line);
AnnounceCommit(type, changed_lines);
2006-01-16 22:02:54 +01:00
return amend_id;
}
2007-01-26 01:47:42 +01:00
bool AssFile::CompStart(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.Start < rgt.Start;
}
bool AssFile::CompEnd(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.End < rgt.End;
}
bool AssFile::CompStyle(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.Style < rgt.Style;
2007-01-26 01:47:42 +01:00
}
bool AssFile::CompActor(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.Actor < rgt.Actor;
}
bool AssFile::CompEffect(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.Effect < rgt.Effect;
}
bool AssFile::CompLayer(AssDialogue const& lft, AssDialogue const& rgt) {
return lft.Layer < rgt.Layer;
}
2006-01-16 22:02:54 +01:00
void AssFile::Sort(CompFunc comp, std::set<AssDialogue*> const& limit) {
Sort(Events, comp, limit);
}
void AssFile::Sort(EntryList<AssDialogue> &lst, CompFunc comp, std::set<AssDialogue*> const& limit) {
if (limit.empty()) {
lst.sort(comp);
return;
}
// 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;
// sort doesn't support only sorting a sublist, so move them to a temp list
EntryList<AssDialogue> tmp;
tmp.splice(tmp.begin(), lst, begin, end);
tmp.sort(comp);
lst.splice(end, tmp);
begin = --end;
}
}
uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value) {
for (auto const& data : Extradata) {
// perform brute-force deduplication by simple key and value comparison
if (key == data.second.first && value == data.second.second) {
return data.first;
}
}
// next_extradata_id must not exist
assert(Extradata.find(next_extradata_id) == Extradata.end());
Extradata[next_extradata_id] = {key, value};
return next_extradata_id++; // return old value, then post-increment
}
std::map<std::string, std::string> AssFile::GetExtradata(std::vector<uint32_t> const& id_list) const {
// If multiple IDs have the same key name, the last ID wins
std::map<std::string, std::string> result;
for (auto id : id_list) {
auto it = Extradata.find(id);
if (it != Extradata.end())
result[it->second.first] = it->second.second;
}
return result;
}
void AssFile::CleanExtradata() {
// Collect all IDs existing in the database
// Then remove all IDs found to be in use from this list
// Remaining is then all garbage IDs
std::vector<uint32_t> ids;
for (auto& it : Extradata)
ids.push_back(it.first);
if (ids.empty()) return;
// For each line, find which IDs it actually uses and remove them from the unused-list
for (auto& line : Events) {
// Find the ID for each unique key in the line
std::map<std::string, uint32_t> key_ids;
for (auto id : line.ExtradataIds.get()) {
auto ed_it = Extradata.find(id);
if (ed_it == Extradata.end())
continue;
key_ids[ed_it->second.first] = id;
}
// Update the line's ID list to only contain the actual ID for any duplicate keys
// Also mark found IDs as used in the cleaning list
std::vector<uint32_t> new_ids;
for (auto& keyid : key_ids) {
new_ids.push_back(keyid.second);
ids.erase(remove(begin(ids), end(ids), keyid.second), end(ids));
}
line.ExtradataIds = new_ids;
}
// The ids list should contain only unused IDs now
for (auto id : ids) {
Extradata.erase(id);
}
}