Redesign how project metadata is stored in the file

Remove it from the script info section and put it in its own section
that isn't tracked by undo and make it not stringly typed. Removes the
need for the gross hack where changes are slipped in just before saving
to circumvent the undo system, cuts down on the uses of string literals
to identify fields, and probably improves performance a little.
This commit is contained in:
Thomas Goyne 2014-05-21 18:32:42 -07:00
parent 19e8f19e52
commit 9c7119fdc2
21 changed files with 240 additions and 180 deletions

View File

@ -84,6 +84,7 @@ void AssFile::swap(AssFile& from) throw() {
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);
}
@ -121,22 +122,6 @@ int AssFile::GetScriptInfoAsInt(std::string const& key) const {
return atoi(GetScriptInfo(key).c_str());
}
std::string AssFile::GetUIState(std::string const& key) const {
auto value = GetScriptInfo("Aegisub " + key);
if (value.empty())
value = GetScriptInfo(key);
return value;
}
int AssFile::GetUIStateAsInt(std::string const& key) const {
return atoi(GetUIState(key).c_str());
}
void AssFile::SaveUIState(std::string const& key, std::string const& value) {
if (OPT_GET("App/Save UI State")->GetBool())
SetScriptInfo("Aegisub " + key, value);
}
void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
for (auto it = Info.begin(); it != Info.end(); ++it) {
if (boost::iequals(key, it->Key())) {
@ -300,4 +285,3 @@ void AssFile::CleanExtradata() {
Extradata.erase(id);
}
}

View File

@ -27,11 +27,6 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file ass_file.h
/// @see ass_file.cpp
/// @ingroup subs_storage
///
#include "ass_entry.h"
#include <libaegisub/fs_fwd.h>
@ -58,6 +53,26 @@ struct AssFileCommit {
AssDialogue *single_line;
};
struct ProjectProperties {
std::string automation_scripts;
std::string export_filters;
std::string export_encoding;
std::string style_storage;
std::string audio_file;
std::string video_file;
std::string timecodes_file;
std::string keyframes_file;
std::map<std::string, std::string> automation_settings;
// UI State
double video_zoom = 0.;
double ar_value = 0.;
int scroll_position = 0;
int active_row = 0;
int ar_mode = 0;
int video_position = 0;
};
class AssFile {
/// A set of changes has been committed to the file (AssFile::COMMITType)
agi::signal::Signal<int, std::set<const AssDialogue*> const&> AnnounceCommit;
@ -69,6 +84,7 @@ public:
EntryList<AssDialogue> Events;
std::vector<AssAttachment> Attachments;
AegisubExtradataMap Extradata;
ProjectProperties Properties;
uint32_t next_extradata_id = 0;
@ -104,9 +120,6 @@ public:
std::string GetScriptInfo(std::string const& key) const;
/// Set the value of a [Script Info] key. Adds it if it doesn't exist.
void SetScriptInfo(std::string const& key, std::string const& value);
std::string GetUIState(std::string const& key) const;
int GetUIStateAsInt(std::string const& key) const;
void SaveUIState(std::string const& key, std::string const& value);
/// @brief Add a new extradata entry
/// @param key Class identifier/owner for the extradata

View File

@ -24,6 +24,7 @@
#include <libaegisub/ass/uuencode.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/util.h>
#include <algorithm>
#include <boost/algorithm/string/case_conv.hpp>
@ -31,9 +32,71 @@
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/variant.hpp>
#include <unordered_map>
class AssParser::HeaderToProperty {
using field = boost::variant<
std::string ProjectProperties::*,
int ProjectProperties::*,
double ProjectProperties::*
>;
std::unordered_map<std::string, field> fields;
public:
HeaderToProperty()
: fields({
{"Automation Scripts", &ProjectProperties::automation_scripts},
{"Export Filters", &ProjectProperties::export_filters},
{"Export Encoding", &ProjectProperties::export_encoding},
{"Last Style Storage", &ProjectProperties::style_storage},
{"Audio URI", &ProjectProperties::audio_file},
{"Audio File", &ProjectProperties::audio_file},
{"Video File", &ProjectProperties::video_file},
{"Timecodes File", &ProjectProperties::timecodes_file},
{"Keyframes File", &ProjectProperties::keyframes_file},
{"Video Zoom Percent", &ProjectProperties::video_zoom},
{"Scroll Position", &ProjectProperties::scroll_position},
{"Active Line", &ProjectProperties::active_row},
{"Video Position", &ProjectProperties::video_position},
{"Video AR Mode", &ProjectProperties::ar_mode},
{"Video AR Value", &ProjectProperties::ar_value},
{"Aegisub Video Zoom Percent", &ProjectProperties::video_zoom},
{"Aegisub Scroll Position", &ProjectProperties::scroll_position},
{"Aegisub Active Line", &ProjectProperties::active_row},
{"Aegisub Video Position", &ProjectProperties::video_position}
})
{
}
bool ProcessProperty(AssFile *target, std::string const& key, std::string const& value) {
auto it = fields.find(key);
if (it != end(fields)) {
using namespace agi::util;
struct {
using result_type = void;
ProjectProperties &obj;
std::string const& value;
void operator()(std::string ProjectProperties::*f) const { obj.*f = value; }
void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
} visitor {target->Properties, value};
boost::apply_visitor(visitor, it->second);
return true;
}
if (boost::starts_with(key, "Automation Settings ")) {
target->Properties.automation_settings[key.substr(strlen("Automation Settings"))] = value;
return true;
}
return false;
}
};
AssParser::AssParser(AssFile *target, int version)
: target(target)
: property_handler(new HeaderToProperty)
, target(target)
, version(version)
, state(&AssParser::ParseScriptInfoLine)
{
@ -94,7 +157,23 @@ void AssParser::ParseScriptInfoLine(std::string const& data) {
size_t pos = data.find(':');
if (pos == data.npos) return;
target->Info.push_back(*new AssInfo(data.substr(0, pos), boost::trim_left_copy(data.substr(pos + 1))));
auto key = data.substr(0, pos);
auto value = data.substr(pos + 1);
boost::trim_left(value);
if (!property_handler->ProcessProperty(target, key, value))
target->Info.push_back(*new AssInfo(std::move(key), std::move(value)));
}
void AssParser::ParseMetadataLine(std::string const& data) {
size_t pos = data.find(':');
if (pos == data.npos) return;
auto key = data.substr(0, pos);
auto value = data.substr(pos + 1);
boost::trim_left(value);
property_handler->ProcessProperty(target, key, value);
}
void AssParser::ParseEventLine(std::string const& data) {
@ -171,12 +250,14 @@ void AssParser::AddLine(std::string const& data) {
state = &AssParser::ParseEventLine;
else if (low == "[script info]")
state = &AssParser::ParseScriptInfoLine;
else if (low == "[aegisub project garbage]")
state = &AssParser::ParseMetadataLine;
else if (low == "[aegisub extradata]")
state = &AssParser::ParseExtradataLine;
else if (low == "[graphics]")
state = &AssParser::ParseGraphicsLine;
else if (low == "[fonts]")
state = &AssParser::ParseFontLine;
else if (low == "[aegisub extradata]")
state = &AssParser::ParseExtradataLine;
else
state = &AssParser::UnknownLine;
return;

View File

@ -20,6 +20,9 @@ class AssAttachment;
class AssFile;
class AssParser {
class HeaderToProperty;
std::unique_ptr<HeaderToProperty> property_handler;
AssFile *target;
int version;
std::unique_ptr<AssAttachment> attach;
@ -29,6 +32,7 @@ class AssParser {
void ParseEventLine(std::string const& data);
void ParseStyleLine(std::string const& data);
void ParseScriptInfoLine(std::string const& data);
void ParseMetadataLine(std::string const& data);
void ParseFontLine(std::string const& data);
void ParseGraphicsLine(std::string const& data);
void ParseExtradataLine(std::string const &data);

View File

@ -182,14 +182,14 @@ namespace Automation4 {
std::string ExportFilter::GetScriptSettingsIdentifier()
{
return inline_string_encode("Automation Settings " + GetName());
return inline_string_encode(GetName());
}
wxWindow* ExportFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
config_dialog = GenerateConfigDialog(parent, c);
if (config_dialog) {
std::string val = c->ass->GetScriptInfo(GetScriptSettingsIdentifier());
std::string const& val = c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()];
if (!val.empty())
config_dialog->Unserialise(val);
return config_dialog->CreateWindow(parent);
@ -199,11 +199,8 @@ namespace Automation4 {
}
void ExportFilter::LoadSettings(bool is_default, agi::Context *c) {
if (config_dialog) {
std::string val = config_dialog->Serialise();
if (!val.empty())
c->ass->SetScriptInfo(GetScriptSettingsIdentifier(), val);
}
if (config_dialog)
c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()] = config_dialog->Serialise();
}
// ProgressSink
@ -285,10 +282,6 @@ namespace Automation4 {
}
// ScriptManager
ScriptManager::~ScriptManager()
{
}
void ScriptManager::Add(std::unique_ptr<Script> script)
{
if (find(scripts.begin(), scripts.end(), script) == scripts.end())
@ -373,18 +366,16 @@ namespace Automation4 {
LocalScriptManager::LocalScriptManager(agi::Context *c)
: context(c)
, connections(agi::signal::make_vector({
c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this),
c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this),
}))
, file_open_connection(c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this))
{
AddScriptChangeListener(&LocalScriptManager::SaveLoadedList, this);
}
void LocalScriptManager::Reload()
{
scripts.clear();
auto local_scripts = context->ass->GetScriptInfo("Automation Scripts");
auto const& local_scripts = context->ass->Properties.automation_scripts;
if (local_scripts.empty()) {
ScriptsChanged();
return;
@ -421,7 +412,7 @@ namespace Automation4 {
ScriptsChanged();
}
void LocalScriptManager::OnSubtitlesSave()
void LocalScriptManager::SaveLoadedList()
{
// Store Automation script data
// Algorithm:
@ -450,7 +441,7 @@ namespace Automation4 {
scripts_string += scriptfn;
}
context->ass->SetScriptInfo("Automation Scripts", scripts_string);
context->ass->Properties.automation_scripts = std::move(scripts_string);
}
// ScriptFactory

View File

@ -190,7 +190,7 @@ namespace Automation4 {
public:
/// Deletes all scripts managed
virtual ~ScriptManager();
virtual ~ScriptManager() = default;
/// Add a script to the manager.
void Add(std::unique_ptr<Script> script);
/// Remove a script from the manager, and delete the Script object.
@ -215,9 +215,9 @@ namespace Automation4 {
/// Manager for scripts specified by a subtitle file
class LocalScriptManager final : public ScriptManager {
agi::Context *context;
std::vector<agi::signal::Connection> connections;
agi::signal::Connection file_open_connection;
void OnSubtitlesSave();
void SaveLoadedList();
public:
LocalScriptManager(agi::Context *context);
void Reload() override;

View File

@ -127,7 +127,6 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
connections = agi::signal::make_vector({
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this),
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this),
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this),
context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this),
context->selectionController->AddSelectionListener([&]{ Refresh(false); }),
@ -184,11 +183,7 @@ void BaseGrid::OnSubtitlesCommit(int type) {
}
void BaseGrid::OnSubtitlesOpen() {
ScrollTo(context->ass->GetUIStateAsInt("Scroll Position"));
}
void BaseGrid::OnSubtitlesSave() {
context->ass->SaveUIState("Scroll Position", std::to_string(yPos));
ScrollTo(context->ass->Properties.scroll_position);
}
void BaseGrid::OnShowColMenu(wxCommandEvent &event) {
@ -444,7 +439,7 @@ void BaseGrid::OnSize(wxSizeEvent &) {
void BaseGrid::OnScroll(wxScrollEvent &event) {
int newPos = event.GetPosition();
if (yPos != newPos) {
yPos = newPos;
context->ass->Properties.scroll_position = yPos = newPos;
Refresh(false);
}
}
@ -580,7 +575,7 @@ void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
void BaseGrid::ScrollTo(int y) {
int nextY = mid(0, y, GetRows() - 1);
if (yPos != nextY) {
yPos = nextY;
context->ass->Properties.scroll_position = yPos = nextY;
scrollBar->SetThumbPosition(yPos);
Refresh(false);
}
@ -605,7 +600,7 @@ void BaseGrid::AdjustScrollbar() {
int drawPerScreen = clientSize.GetHeight() / lineHeight;
int rows = GetRows();
yPos = mid(0, yPos, rows - 1);
context->ass->Properties.scroll_position = yPos = mid(0, yPos, rows - 1);
scrollBar->SetScrollbar(yPos, drawPerScreen, rows + drawPerScreen - 1, drawPerScreen - 2, true);
scrollBar->Thaw();

View File

@ -94,7 +94,6 @@ class BaseGrid final : public wxWindow {
void OnSize(wxSizeEvent &event);
void OnSubtitlesCommit(int type);
void OnSubtitlesOpen();
void OnSubtitlesSave();
void OnActiveLineChanged(AssDialogue *);
void ScrollTo(int y);

View File

@ -87,7 +87,7 @@ DialogExport::DialogExport(agi::Context *c)
filter_list->Bind(wxEVT_LISTBOX, &DialogExport::OnChange, this);
// Get selected filters
std::string selected = c->ass->GetScriptInfo("Export filters");
std::string const& selected = c->ass->Properties.export_filters;
boost::char_separator<char> sep("|");
for (auto const& token : boost::tokenizer<boost::char_separator<char>>(selected, sep)) {
auto it = find(begin(filters), end(filters), token);
@ -119,7 +119,7 @@ DialogExport::DialogExport(agi::Context *c)
wxSizer *charset_list_sizer = new wxBoxSizer(wxHORIZONTAL);
charset_list_sizer->Add(charset_list_label, wxSizerFlags().Center().Border(wxRIGHT));
charset_list_sizer->Add(charset_list, wxSizerFlags(1).Expand());
if (!charset_list->SetStringSelection(to_wx(c->ass->GetScriptInfo("Export Encoding"))))
if (!charset_list->SetStringSelection(to_wx(c->ass->Properties.export_encoding)))
charset_list->SetStringSelection("Unicode (UTF-8)");
wxSizer *top_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Filters"));
@ -148,15 +148,14 @@ DialogExport::DialogExport(agi::Context *c)
}
DialogExport::~DialogExport() {
std::string infoList;
c->ass->Properties.export_filters.clear();
for (size_t i = 0; i < filter_list->GetCount(); ++i) {
if (filter_list->IsChecked(i)) {
if (!infoList.empty())
infoList += "|";
infoList += from_wx(filter_list->GetString(i));
if (!c->ass->Properties.export_filters.empty())
c->ass->Properties.export_filters += "|";
c->ass->Properties.export_filters += from_wx(filter_list->GetString(i));
}
}
c->ass->SetScriptInfo("Export filters", infoList);
}
void DialogExport::OnProcess(wxCommandEvent &) {
@ -172,7 +171,7 @@ void DialogExport::OnProcess(wxCommandEvent &) {
try {
wxBusyCursor busy;
c->ass->SetScriptInfo("Export Encoding", from_wx(charset_list->GetStringSelection()));
c->ass->Properties.export_encoding = from_wx(charset_list->GetStringSelection());
exporter->Export(filename, from_wx(charset_list->GetStringSelection()), this);
}
catch (agi::UserCancelException const&) {

View File

@ -319,7 +319,7 @@ void DialogStyleManager::UpdateStorage() {
void DialogStyleManager::OnChangeCatalog() {
std::string catalog(from_wx(CatalogList->GetStringSelection()));
c->ass->SetScriptInfo("Last Style Storage", catalog);
c->ass->Properties.style_storage = catalog;
Store.LoadCatalog(catalog);
UpdateStorage();
}
@ -341,15 +341,12 @@ void DialogStyleManager::LoadCatalog() {
}
// Set to default if available
std::string pickStyle = c->ass->GetScriptInfo("Last Style Storage");
std::string pickStyle = c->ass->Properties.style_storage;
if (pickStyle.empty())
pickStyle = "Default";
int opt = CatalogList->FindString(to_wx(pickStyle), false);
if (opt != wxNOT_FOUND)
CatalogList->SetSelection(opt);
else
CatalogList->SetSelection(0);
CatalogList->SetSelection(opt == wxNOT_FOUND ? 0 : opt);
OnChangeCatalog();
}

View File

@ -55,20 +55,15 @@ Project::Project(agi::Context *c) : context(c) {
OPT_SUB("Subtitle/Provider", &Project::ReloadVideo, this);
OPT_SUB("Video/Force BT.601", &Project::ReloadVideo, this);
OPT_SUB("Video/Provider", &Project::ReloadVideo, this);
c->subsController->AddFileSaveListener(&Project::OnSubtitlesSave, this);
}
Project::~Project() { }
void Project::OnSubtitlesSave() {
context->ass->SetScriptInfo("Audio File",
config::path->MakeRelative(audio_file, "?script").generic_string());
context->ass->SetScriptInfo("Video File",
config::path->MakeRelative(video_file, "?script").generic_string());
context->ass->SetScriptInfo("VFR File",
config::path->MakeRelative(timecodes_file, "?script").generic_string());
context->ass->SetScriptInfo("Keyframes File",
config::path->MakeRelative(keyframes_file, "?script").generic_string());
void Project::UpdateRelativePaths() {
context->ass->Properties.audio_file = config::path->MakeRelative(audio_file, "?script").generic_string();
context->ass->Properties.video_file = config::path->MakeRelative(video_file, "?script").generic_string();
context->ass->Properties.timecodes_file = config::path->MakeRelative(timecodes_file, "?script").generic_string();
context->ass->Properties.keyframes_file = config::path->MakeRelative(keyframes_file, "?script").generic_string();
}
void Project::ReloadAudio() {
@ -89,6 +84,15 @@ void Project::ShowError(std::string const& message) {
ShowError(to_wx(message));
}
void Project::SetPath(agi::fs::path& var, const char *token, const char *mru, agi::fs::path const& value) {
var = value;
if (*token)
config::path->SetToken(token, value);
if (*mru)
config::mru->Add(mru, value);
UpdateRelativePaths();
}
void Project::DoLoadSubtitles(agi::fs::path const& path, std::string encoding) {
try {
if (encoding.empty())
@ -141,10 +145,10 @@ void Project::LoadUnloadFiles() {
auto load_linked = OPT_GET("App/Auto/Load Linked Files")->GetInt();
if (!load_linked) return;
auto audio = config::path->MakeAbsolute(context->ass->GetScriptInfo("Audio File"), "?script");
auto video = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
auto timecodes = config::path->MakeAbsolute(context->ass->GetScriptInfo("VFR File"), "?script");
auto keyframes = config::path->MakeAbsolute(context->ass->GetScriptInfo("Keyframes File"), "?script");
auto audio = config::path->MakeAbsolute(context->ass->Properties.audio_file, "?script");
auto video = config::path->MakeAbsolute(context->ass->Properties.video_file, "?script");
auto timecodes = config::path->MakeAbsolute(context->ass->Properties.timecodes_file, "?script");
auto keyframes = config::path->MakeAbsolute(context->ass->Properties.keyframes_file, "?script");
if (video == video_file && audio == audio_file && keyframes == keyframes_file && timecodes == timecodes_file)
return;
@ -160,23 +164,14 @@ void Project::LoadUnloadFiles() {
CloseVideo();
else if ((loaded_video = DoLoadVideo(video))) {
auto vc = context->videoController.get();
vc->JumpToFrame(context->ass->GetUIStateAsInt("Video Position"));
vc->JumpToFrame(context->ass->Properties.video_position);
std::string arString = context->ass->GetUIState("Video Aspect Ratio");
if (boost::starts_with(arString, "c")) {
double ar = 0.;
agi::util::try_parse(arString.substr(1), &ar);
vc->SetAspectRatio(ar);
}
else {
int ar = 0;
if (agi::util::try_parse(arString, &ar) && ar >= 0 && ar < 4)
vc->SetAspectRatio((AspectRatio)ar);
}
double videoZoom = 0.;
if (agi::util::try_parse(context->ass->GetUIState("Video Zoom Percent"), &videoZoom))
context->videoDisplay->SetZoom(videoZoom);
auto ar_mode = static_cast<AspectRatio>(context->ass->Properties.ar_mode);
if (ar_mode == AspectRatio::Custom)
vc->SetAspectRatio(context->ass->Properties.ar_value);
else
vc->SetAspectRatio(ar_mode);
context->videoDisplay->SetZoom(context->ass->Properties.video_zoom);
}
}
@ -225,9 +220,7 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
return ShowError(e.GetChainedMessage());
}
audio_file = path;
config::path->SetToken("?audio", path);
config::mru->Add("Audio", path);
SetPath(audio_file, "?audio", "Audio", path);
AnnounceAudioProviderModified(audio_provider.get());
}
@ -238,8 +231,7 @@ void Project::LoadAudio(agi::fs::path const& path) {
void Project::CloseAudio() {
AnnounceAudioProviderModified(nullptr);
audio_provider.reset();
audio_file.clear();
config::path->SetToken("?audio", "");
SetPath(audio_file, "?audio", "", "");
}
bool Project::DoLoadVideo(agi::fs::path const& path) {
@ -266,12 +258,10 @@ bool Project::DoLoadVideo(agi::fs::path const& path) {
timecodes = video_provider->GetFPS();
keyframes = video_provider->GetKeyFrames();
timecodes_file.clear();
keyframes_file.clear();
video_file = path;
config::mru->Add("Video", path);
config::path->SetToken("?video", path);
SetPath(video_file, "?video", "Video", path);
std::string warning = video_provider->GetWarning();
if (!warning.empty())
@ -296,15 +286,13 @@ void Project::LoadVideo(agi::fs::path const& path) {
void Project::CloseVideo() {
AnnounceVideoProviderModified(nullptr);
video_provider.reset();
video_file.clear();
config::path->SetToken("?video", "");
SetPath(video_file, "?video", "", "");
video_has_subtitles = false;
}
void Project::DoLoadTimecodes(agi::fs::path const& path) {
timecodes = agi::vfr::Framerate(path);
timecodes_file = path;
config::mru->Add("Timecodes", path);
SetPath(timecodes_file, "", "Timecodes", path);
AnnounceTimecodesModified(timecodes);
}
@ -324,14 +312,13 @@ void Project::LoadTimecodes(agi::fs::path const& path) {
void Project::CloseTimecodes() {
timecodes = video_provider ? video_provider->GetFPS() : agi::vfr::Framerate{};
timecodes_file.clear();
SetPath(timecodes_file, "", "", "");
AnnounceTimecodesModified(timecodes);
}
void Project::DoLoadKeyframes(agi::fs::path const& path) {
keyframes = agi::keyframe::Load(path);
keyframes_file = path;
config::mru->Add("Keyframes", path);
SetPath(keyframes_file, "", "Keyframes", path);
AnnounceKeyframesModified(keyframes);
}
@ -351,7 +338,7 @@ void Project::LoadKeyframes(agi::fs::path const& path) {
void Project::CloseKeyframes() {
keyframes = video_provider ? video_provider->GetKeyFrames() : std::vector<int>{};
keyframes_file.clear();
SetPath(keyframes_file, "", "", "");
AnnounceKeyframesModified(keyframes);
}
@ -431,8 +418,12 @@ void Project::LoadList(std::vector<agi::fs::path> const& files) {
if (!subs.empty())
DoLoadSubtitles(subs);
if (!video.empty())
if (!video.empty()) {
auto rel_audio_file = context->ass->Properties.audio_file;
DoLoadVideo(video);
if (!rel_audio_file.empty() && context->ass->Properties.audio_file.empty())
context->ass->Properties.audio_file.swap(rel_audio_file);
}
if (!audio.empty())
DoLoadAudio(audio, false);
else if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)

View File

@ -29,7 +29,6 @@ class wxString;
namespace agi { struct Context; }
class Project {
// Things owned by this
std::unique_ptr<AudioProvider> audio_provider;
std::unique_ptr<AsyncVideoProvider> video_provider;
agi::vfr::Framerate timecodes;
@ -46,10 +45,7 @@ class Project {
agi::signal::Signal<std::vector<int> const&> AnnounceKeyframesModified;
bool video_has_subtitles = false;
DialogProgress *progress = nullptr;
// Things not
agi::Context *context = nullptr;
void ShowError(wxString const& message);
@ -62,11 +58,12 @@ class Project {
void DoLoadKeyframes(agi::fs::path const& path);
void LoadUnloadFiles();
void OnSubtitlesSave();
void UpdateRelativePaths();
void ReloadAudio();
void ReloadVideo();
void SetPath(agi::fs::path& var, const char *token, const char *mru, agi::fs::path const& value);
public:
Project(agi::Context *context);
~Project();

View File

@ -27,7 +27,6 @@
SelectionController::SelectionController(agi::Context *c)
: context(c)
, open_connection(c->subsController->AddFileOpenListener(&SelectionController::OnSubtitlesOpen, this))
, save_connection(c->subsController->AddFileSaveListener(&SelectionController::OnSubtitlesSave, this))
{
}
@ -35,7 +34,7 @@ void SelectionController::OnSubtitlesOpen() {
selection.clear();
active_line = nullptr;
if (!context->ass->Events.empty()) {
int row = mid<int>(0, context->ass->GetUIStateAsInt("Active Line"), context->ass->Events.size() - 1);
int row = mid<int>(0, context->ass->Properties.active_row, context->ass->Events.size() - 1);
active_line = &*std::next(context->ass->Events.begin(), row);
selection.insert(active_line);
}
@ -43,11 +42,6 @@ void SelectionController::OnSubtitlesOpen() {
AnnounceActiveLineChanged(active_line);
}
void SelectionController::OnSubtitlesSave() {
if (active_line)
context->ass->SaveUIState("Active Line", std::to_string(active_line->Row));
}
void SelectionController::SetSelectedSet(Selection new_selection) {
selection = std::move(new_selection);
AnnounceSelectedSetChanged();
@ -56,6 +50,8 @@ void SelectionController::SetSelectedSet(Selection new_selection) {
void SelectionController::SetActiveLine(AssDialogue *new_line) {
if (new_line != active_line) {
active_line = new_line;
if (active_line)
context->ass->Properties.active_row = active_line->Row;
AnnounceActiveLineChanged(new_line);
}
}
@ -64,6 +60,8 @@ void SelectionController::SetSelectionAndActive(Selection new_selection, AssDial
bool active_line_changed = new_line != active_line;
selection = std::move(new_selection);
active_line = new_line;
if (active_line)
context->ass->Properties.active_row = active_line->Row;
AnnounceSelectedSetChanged();
if (active_line_changed)

View File

@ -47,7 +47,6 @@ class SelectionController {
AssDialogue *active_line = nullptr; ///< The currently active line or 0 if none
agi::signal::Connection open_connection;
agi::signal::Connection save_connection;
void OnSubtitlesOpen();
void OnSubtitlesSave();

View File

@ -220,11 +220,9 @@ void SubsController::Save(agi::fs::path const& filename, std::string const& enco
this->filename = filename;
config::path->SetToken("?script", filename.parent_path());
FileSave();
context->ass->CleanExtradata();
writer->WriteFile(context->ass.get(), filename, 0, encoding);
FileSave();
}
catch (...) {
autosaved_commit_id = old_autosaved_commit_id;

View File

@ -52,11 +52,12 @@ class SubsController {
/// A new file has been opened (filename)
agi::signal::Signal<agi::fs::path> FileOpen;
/// The file is about to be saved
/// This signal is intended for adding metadata such as video filename,
/// frame number, etc. Ideally this would all be done immediately rather
/// than waiting for a save, but that causes (more) issues with undo
/// The file has been saved
agi::signal::Signal<> FileSave;
/// The file is about to be saved
/// This signal is intended for adding metadata which is awkward or
/// expensive to always keep up to date
agi::signal::Signal<> UpdateProperties;
/// The filename of the currently open file, if any
agi::fs::path filename;

View File

@ -22,6 +22,7 @@
#include "ass_file.h"
#include "ass_style.h"
#include "ass_parser.h"
#include "options.h"
#include "string_codec.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
@ -93,6 +94,41 @@ struct Writer {
}
}
void Write(ProjectProperties const& properties) {
file.WriteLineToFile("");
file.WriteLineToFile("[Aegisub Project Garbage]");
WriteIfNotEmpty("Automation Scripts: ", properties.automation_scripts);
WriteIfNotEmpty("Export Filters: ", properties.export_filters);
WriteIfNotEmpty("Export Encoding: ", properties.export_encoding);
WriteIfNotEmpty("Last Style Storage: ", properties.style_storage);
WriteIfNotEmpty("Audio File: ", properties.audio_file);
WriteIfNotEmpty("Video File: ", properties.video_file);
WriteIfNotEmpty("Timecodes File: ", properties.timecodes_file);
WriteIfNotEmpty("Keyframes File: ", properties.keyframes_file);
WriteIfNotZero("Video AR Mode: ", properties.ar_mode);
WriteIfNotZero("Video AR Value: ", properties.ar_value);
if (OPT_GET("App/Save UI State")->GetBool()) {
WriteIfNotZero("Video Zoom Percent: ", properties.video_zoom);
WriteIfNotZero("Scroll Position: ", properties.scroll_position);
WriteIfNotZero("Active Line: ", properties.active_row);
WriteIfNotZero("Video Position: ", properties.video_position);
}
}
void WriteIfNotEmpty(const char *key, std::string const& value) {
if (!value.empty())
file.WriteLineToFile(key + value);
}
template<typename Number>
void WriteIfNotZero(const char *key, Number n) {
if (n != Number{})
file.WriteLineToFile(key + std::to_string(n));
}
void WriteExtradata(AegisubExtradataMap const& extradata) {
if (extradata.size() == 0)
return;
@ -125,6 +161,7 @@ struct Writer {
void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, agi::vfr::Framerate const& fps, std::string const& encoding) const {
Writer writer(filename, encoding);
writer.Write(src->Info);
writer.Write(src->Properties);
writer.Write(src->Styles);
writer.Write(src->Attachments);
writer.Write(src->Events);

View File

@ -61,7 +61,6 @@ VideoController::VideoController(agi::Context *c)
context->ass->AddCommitListener(&VideoController::OnSubtitlesCommit, this),
context->project->AddVideoProviderListener(&VideoController::OnNewVideoProvider, this),
context->selectionController->AddActiveLineListener(&VideoController::OnActiveLineChanged, this),
context->subsController->AddFileSaveListener(&VideoController::OnSubtitlesSave, this),
}))
{
Bind(EVT_VIDEO_ERROR, &VideoController::OnVideoError, this);
@ -76,6 +75,9 @@ void VideoController::OnNewVideoProvider(AsyncVideoProvider *new_provider) {
provider = new_provider;
if (!provider) {
color_matrix.clear();
context->ass->Properties.ar_mode = 0;
context->ass->Properties.ar_value = 0.0;
context->ass->Properties.video_position = 0;
return;
}
@ -98,33 +100,12 @@ void VideoController::OnSubtitlesCommit(int type, std::set<const AssDialogue *>
}
}
if (changed.empty() || no_amend)
if (changed.empty())
provider->LoadSubtitles(context->ass.get());
else
provider->UpdateSubtitles(context->ass.get(), changed);
if (!IsPlaying())
provider->GetFrame(frame_n, TimeAtFrame(frame_n));
no_amend = false;
}
void VideoController::OnSubtitlesSave() {
no_amend = true;
if (!provider) {
context->ass->SaveUIState("Video Aspect Ratio", "");
context->ass->SaveUIState("Video Position", "");
return;
}
std::string ar;
if (ar_type == AspectRatio::Custom)
ar = "c" + std::to_string(ar_value);
else
ar = std::to_string((int)ar_type);
context->ass->SaveUIState("Video Aspect Ratio", ar);
context->ass->SaveUIState("Video Position", std::to_string(frame_n));
}
void VideoController::OnActiveLineChanged(AssDialogue *line) {
@ -133,6 +114,7 @@ void VideoController::OnActiveLineChanged(AssDialogue *line) {
}
void VideoController::RequestFrame() {
context->ass->Properties.video_position = frame_n;
provider->RequestFrame(frame_n, TimeAtFrame(frame_n));
}
@ -243,12 +225,16 @@ double VideoController::GetARFromType(AspectRatio type) const {
void VideoController::SetAspectRatio(double value) {
ar_type = AspectRatio::Custom;
ar_value = mid(.5, value, 5.);
context->ass->Properties.ar_mode = (int)ar_type;
context->ass->Properties.ar_value = ar_value;
ARChange(ar_type, ar_value);
}
void VideoController::SetAspectRatio(AspectRatio type) {
ar_value = mid(.5, GetARFromType(type), 5.);
ar_type = type;
context->ass->Properties.ar_mode = (int)ar_type;
context->ass->Properties.ar_value = ar_value;
ARChange(ar_type, ar_value);
}

View File

@ -101,19 +101,12 @@ class VideoController final : public wxEvtHandler {
std::vector<agi::signal::Connection> connections;
/// Amending the frame source's copy of the subtitle file requires that it
/// be kept in perfect sync. Saving the file can add lines to the file
/// without a commit, breaking this sync, so force a non-amend after each
/// save.
bool no_amend = false;
void OnPlayTimer(wxTimerEvent &event);
void OnVideoError(VideoProviderErrorEvent const& err);
void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
void OnSubtitlesSave();
void OnNewVideoProvider(AsyncVideoProvider *provider);
void OnActiveLineChanged(AssDialogue *line);

View File

@ -109,7 +109,6 @@ VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBo
connections = agi::signal::make_vector({
con->project->AddVideoProviderListener(&VideoDisplay::UpdateSize, this),
con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this),
con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this),
});
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
@ -345,6 +344,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
PositionVideo();
zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight();
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
con->ass->Properties.video_zoom = zoomValue;
}
else {
PositionVideo();
@ -385,11 +385,13 @@ void VideoDisplay::OnKeyDown(wxKeyEvent &event) {
}
void VideoDisplay::SetZoom(double value) {
if (value == 0) return;
zoomValue = std::max(value, .125);
size_t selIndex = zoomValue / .125 - 1;
if (selIndex < zoomBox->GetCount())
zoomBox->SetSelection(selIndex);
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
con->ass->Properties.video_zoom = zoomValue;
UpdateSize();
}
@ -397,6 +399,7 @@ void VideoDisplay::SetZoomFromBox(wxCommandEvent &) {
int sel = zoomBox->GetSelection();
if (sel != wxNOT_FOUND) {
zoomValue = (sel + 1) * .125;
con->ass->Properties.video_zoom = zoomValue;
UpdateSize();
}
}
@ -444,7 +447,3 @@ void VideoDisplay::Unload() {
tool.reset();
pending_frame.reset();
}
void VideoDisplay::OnSubtitlesSave() {
con->ass->SaveUIState("Video Zoom Percent", std::to_string(zoomValue));
}

View File

@ -141,8 +141,6 @@ class VideoDisplay final : public wxGLCanvas {
void OnSizeEvent(wxSizeEvent &event);
void OnContextMenu(wxContextMenuEvent&);
void OnSubtitlesSave();
public:
/// @brief Constructor
VideoDisplay(