mirror of https://github.com/odrling/Aegisub
Redesign project file handling
Add a new Project class which is responsible for everything related to opening and closing audio, video, subtitles, timecodes and keyframes. This pulls almost everything not directly related to playing audio/video out of the audio and video controllers, pulls more crap out of FrameMain, and happens to make things a little simpler in the process.
This commit is contained in:
parent
a345b8c4d5
commit
19e8f19e52
|
@ -205,6 +205,7 @@
|
||||||
<ClInclude Include="$(SrcDir)placeholder_ctrl.h" />
|
<ClInclude Include="$(SrcDir)placeholder_ctrl.h" />
|
||||||
<ClInclude Include="$(SrcDir)preferences.h" />
|
<ClInclude Include="$(SrcDir)preferences.h" />
|
||||||
<ClInclude Include="$(SrcDir)preferences_base.h" />
|
<ClInclude Include="$(SrcDir)preferences_base.h" />
|
||||||
|
<ClInclude Include="$(SrcDir)project.h" />
|
||||||
<ClInclude Include="$(SrcDir)resolution_resampler.h" />
|
<ClInclude Include="$(SrcDir)resolution_resampler.h" />
|
||||||
<ClInclude Include="$(SrcDir)scintilla_text_ctrl.h" />
|
<ClInclude Include="$(SrcDir)scintilla_text_ctrl.h" />
|
||||||
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
|
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
|
||||||
|
@ -234,7 +235,7 @@
|
||||||
<ClInclude Include="$(SrcDir)text_file_writer.h" />
|
<ClInclude Include="$(SrcDir)text_file_writer.h" />
|
||||||
<ClInclude Include="$(SrcDir)text_selection_controller.h" />
|
<ClInclude Include="$(SrcDir)text_selection_controller.h" />
|
||||||
<ClInclude Include="$(SrcDir)thesaurus.h" />
|
<ClInclude Include="$(SrcDir)thesaurus.h" />
|
||||||
<ClInclude Include="$(SrcDir)threaded_frame_source.h" />
|
<ClInclude Include="$(SrcDir)async_video_provider.h" />
|
||||||
<ClInclude Include="$(SrcDir)time_range.h" />
|
<ClInclude Include="$(SrcDir)time_range.h" />
|
||||||
<ClInclude Include="$(SrcDir)timeedit_ctrl.h" />
|
<ClInclude Include="$(SrcDir)timeedit_ctrl.h" />
|
||||||
<ClInclude Include="$(SrcDir)toggle_bitmap.h" />
|
<ClInclude Include="$(SrcDir)toggle_bitmap.h" />
|
||||||
|
@ -244,7 +245,7 @@
|
||||||
<ClInclude Include="$(SrcDir)vector2d.h" />
|
<ClInclude Include="$(SrcDir)vector2d.h" />
|
||||||
<ClInclude Include="$(SrcDir)version.h" />
|
<ClInclude Include="$(SrcDir)version.h" />
|
||||||
<ClInclude Include="$(SrcDir)video_box.h" />
|
<ClInclude Include="$(SrcDir)video_box.h" />
|
||||||
<ClInclude Include="$(SrcDir)video_context.h" />
|
<ClInclude Include="$(SrcDir)video_controller.h" />
|
||||||
<ClInclude Include="$(SrcDir)video_display.h" />
|
<ClInclude Include="$(SrcDir)video_display.h" />
|
||||||
<ClInclude Include="$(SrcDir)video_frame.h" />
|
<ClInclude Include="$(SrcDir)video_frame.h" />
|
||||||
<ClInclude Include="$(SrcDir)video_out_gl.h" />
|
<ClInclude Include="$(SrcDir)video_out_gl.h" />
|
||||||
|
@ -282,6 +283,7 @@
|
||||||
<ClCompile Include="$(SrcDir)ass_style.cpp" />
|
<ClCompile Include="$(SrcDir)ass_style.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)ass_style_storage.cpp" />
|
<ClCompile Include="$(SrcDir)ass_style_storage.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)ass_time.cpp" />
|
<ClCompile Include="$(SrcDir)ass_time.cpp" />
|
||||||
|
<ClCompile Include="$(SrcDir)async_video_provider.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)audio_box.cpp" />
|
<ClCompile Include="$(SrcDir)audio_box.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)audio_colorscheme.cpp" />
|
<ClCompile Include="$(SrcDir)audio_colorscheme.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)audio_controller.cpp" />
|
<ClCompile Include="$(SrcDir)audio_controller.cpp" />
|
||||||
|
@ -395,6 +397,7 @@
|
||||||
<ClCompile Include="$(SrcDir)persist_location.cpp" />
|
<ClCompile Include="$(SrcDir)persist_location.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)preferences.cpp" />
|
<ClCompile Include="$(SrcDir)preferences.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)preferences_base.cpp" />
|
<ClCompile Include="$(SrcDir)preferences_base.cpp" />
|
||||||
|
<ClCompile Include="$(SrcDir)project.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)resolution_resampler.cpp" />
|
<ClCompile Include="$(SrcDir)resolution_resampler.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
|
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
|
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
|
||||||
|
@ -426,7 +429,6 @@
|
||||||
<ClCompile Include="$(SrcDir)text_file_writer.cpp" />
|
<ClCompile Include="$(SrcDir)text_file_writer.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)text_selection_controller.cpp" />
|
<ClCompile Include="$(SrcDir)text_selection_controller.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)thesaurus.cpp" />
|
<ClCompile Include="$(SrcDir)thesaurus.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp" />
|
|
||||||
<ClCompile Include="$(SrcDir)timeedit_ctrl.cpp" />
|
<ClCompile Include="$(SrcDir)timeedit_ctrl.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)toggle_bitmap.cpp" />
|
<ClCompile Include="$(SrcDir)toggle_bitmap.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)toolbar.cpp" />
|
<ClCompile Include="$(SrcDir)toolbar.cpp" />
|
||||||
|
@ -436,7 +438,7 @@
|
||||||
<ClCompile Include="$(SrcDir)vector2d.cpp" />
|
<ClCompile Include="$(SrcDir)vector2d.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)version.cpp" />
|
<ClCompile Include="$(SrcDir)version.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)video_box.cpp" />
|
<ClCompile Include="$(SrcDir)video_box.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)video_context.cpp" />
|
<ClCompile Include="$(SrcDir)video_controller.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)video_display.cpp" />
|
<ClCompile Include="$(SrcDir)video_display.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)video_frame.cpp" />
|
<ClCompile Include="$(SrcDir)video_frame.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)video_out_gl.cpp" />
|
<ClCompile Include="$(SrcDir)video_out_gl.cpp" />
|
||||||
|
|
|
@ -348,10 +348,7 @@
|
||||||
<ClInclude Include="$(SrcDir)dialog_version_check.h">
|
<ClInclude Include="$(SrcDir)dialog_version_check.h">
|
||||||
<Filter>Features\Update checker</Filter>
|
<Filter>Features\Update checker</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="$(SrcDir)threaded_frame_source.h">
|
<ClInclude Include="$(SrcDir)video_controller.h">
|
||||||
<Filter>Video\Providers</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="$(SrcDir)video_context.h">
|
|
||||||
<Filter>Video</Filter>
|
<Filter>Video</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="$(SrcDir)video_frame.h">
|
<ClInclude Include="$(SrcDir)video_frame.h">
|
||||||
|
@ -624,6 +621,12 @@
|
||||||
<ClInclude Include="$(SrcDir)dialog_video_properties.h">
|
<ClInclude Include="$(SrcDir)dialog_video_properties.h">
|
||||||
<Filter>Features\Resolution resampler</Filter>
|
<Filter>Features\Resolution resampler</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="$(SrcDir)async_video_provider.h">
|
||||||
|
<Filter>Video\Providers</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="$(SrcDir)project.h">
|
||||||
|
<Filter>Main UI</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
|
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
|
||||||
|
@ -950,10 +953,7 @@
|
||||||
<ClCompile Include="$(SrcDir)dialog_version_check.cpp">
|
<ClCompile Include="$(SrcDir)dialog_version_check.cpp">
|
||||||
<Filter>Features\Update checker</Filter>
|
<Filter>Features\Update checker</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp">
|
<ClCompile Include="$(SrcDir)video_controller.cpp">
|
||||||
<Filter>Video\Providers</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="$(SrcDir)video_context.cpp">
|
|
||||||
<Filter>Video</Filter>
|
<Filter>Video</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="$(SrcDir)video_slider.cpp">
|
<ClCompile Include="$(SrcDir)video_slider.cpp">
|
||||||
|
@ -1181,9 +1181,15 @@
|
||||||
<ClCompile Include="$(SrcDir)dialog_video_properties.cpp">
|
<ClCompile Include="$(SrcDir)dialog_video_properties.cpp">
|
||||||
<Filter>Features\Resolution resampler</Filter>
|
<Filter>Features\Resolution resampler</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="$(SrcDir)async_video_provider.cpp">
|
||||||
|
<Filter>Video\Providers</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="$(SrcDir)project.cpp">
|
||||||
|
<Filter>Main UI</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="$(SrcDir)res\res.rc" />
|
<ResourceCompile Include="$(SrcDir)res\res.rc" />
|
||||||
<ResourceCompile Include="$(SrcDir)res\strings.rc" />
|
<ResourceCompile Include="$(SrcDir)res\strings.rc" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
/// @file signal.h
|
|
||||||
/// @brief
|
|
||||||
/// @ingroup libaegisub
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <boost/container/map.hpp>
|
#include <boost/container/map.hpp>
|
||||||
|
@ -45,11 +41,29 @@ namespace detail {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A connection which is not automatically closed
|
||||||
|
///
|
||||||
|
/// Connections initially start out owned by the signal. If a slot knows that it
|
||||||
|
/// will outlive a signal and does not need to be able to block a connection, it
|
||||||
|
/// can simply ignore the return value of Connect.
|
||||||
|
///
|
||||||
|
/// If a slot needs to be able to disconnect from a signal, it should store the
|
||||||
|
/// returned connection in a Connection, which transfers ownership of the
|
||||||
|
/// connection to the slot. If there is any chance that the signal will outlive
|
||||||
|
/// the slot, this must be done.
|
||||||
|
class UnscopedConnection {
|
||||||
|
friend class Connection;
|
||||||
|
detail::ConnectionToken *token;
|
||||||
|
public:
|
||||||
|
UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
|
||||||
|
};
|
||||||
|
|
||||||
/// Object representing a connection to a signal
|
/// Object representing a connection to a signal
|
||||||
class Connection {
|
class Connection {
|
||||||
std::unique_ptr<detail::ConnectionToken> token;
|
std::unique_ptr<detail::ConnectionToken> token;
|
||||||
public:
|
public:
|
||||||
Connection() = default;
|
Connection() = default;
|
||||||
|
Connection(UnscopedConnection src) BOOST_NOEXCEPT : token(src.token) { token->claimed = true; }
|
||||||
Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { }
|
Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { }
|
||||||
Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; }
|
Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; }
|
||||||
Connection& operator=(Connection&& that) BOOST_NOEXCEPT { token = std::move(that.token); return *this; }
|
Connection& operator=(Connection&& that) BOOST_NOEXCEPT { token = std::move(that.token); return *this; }
|
||||||
|
@ -69,23 +83,6 @@ public:
|
||||||
void Unblock() { if (token) token->blocked = false; }
|
void Unblock() { if (token) token->blocked = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A connection which is not automatically closed
|
|
||||||
///
|
|
||||||
/// Connections initially start out owned by the signal. If a slot knows that it
|
|
||||||
/// will outlive a signal and does not need to be able to block a connection, it
|
|
||||||
/// can simply ignore the return value of Connect.
|
|
||||||
///
|
|
||||||
/// If a slot needs to be able to disconnect from a signal, it should store the
|
|
||||||
/// returned connection in a Connection, which transfers ownership of the
|
|
||||||
/// connection to the slot. If there is any chance that the signal will outlive
|
|
||||||
/// the slot, this must be done.
|
|
||||||
class UnscopedConnection {
|
|
||||||
detail::ConnectionToken *token;
|
|
||||||
public:
|
|
||||||
UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
|
|
||||||
operator Connection() { return Connection(token); }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
/// Polymorphic base class for slots
|
/// Polymorphic base class for slots
|
||||||
///
|
///
|
||||||
|
@ -198,6 +195,15 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Create a vector of scoped connections from an initializer list
|
||||||
|
///
|
||||||
|
/// Required due to that initializer lists copy their input, and trying to pass
|
||||||
|
/// an initializer list directly to a vector results in a
|
||||||
|
/// std::initializer_list<Connection>, which can't be copied.
|
||||||
|
inline std::vector<Connection> make_vector(std::initializer_list<UnscopedConnection> connections) {
|
||||||
|
return std::vector<Connection>(std::begin(connections), std::end(connections));
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
||||||
/// @brief Define functions which forward their arguments to the connect method
|
/// @brief Define functions which forward their arguments to the connect method
|
||||||
|
|
|
@ -122,6 +122,7 @@ SRC += \
|
||||||
ass_style.cpp \
|
ass_style.cpp \
|
||||||
ass_style_storage.cpp \
|
ass_style_storage.cpp \
|
||||||
ass_time.cpp \
|
ass_time.cpp \
|
||||||
|
async_video_provider.cpp \
|
||||||
audio_box.cpp \
|
audio_box.cpp \
|
||||||
audio_colorscheme.cpp \
|
audio_colorscheme.cpp \
|
||||||
audio_controller.cpp \
|
audio_controller.cpp \
|
||||||
|
@ -205,6 +206,7 @@ SRC += \
|
||||||
persist_location.cpp \
|
persist_location.cpp \
|
||||||
preferences.cpp \
|
preferences.cpp \
|
||||||
preferences_base.cpp \
|
preferences_base.cpp \
|
||||||
|
project.cpp \
|
||||||
resolution_resampler.cpp \
|
resolution_resampler.cpp \
|
||||||
scintilla_text_ctrl.cpp \
|
scintilla_text_ctrl.cpp \
|
||||||
search_replace_engine.cpp \
|
search_replace_engine.cpp \
|
||||||
|
@ -233,7 +235,6 @@ SRC += \
|
||||||
text_file_writer.cpp \
|
text_file_writer.cpp \
|
||||||
text_selection_controller.cpp \
|
text_selection_controller.cpp \
|
||||||
thesaurus.cpp \
|
thesaurus.cpp \
|
||||||
threaded_frame_source.cpp \
|
|
||||||
timeedit_ctrl.cpp \
|
timeedit_ctrl.cpp \
|
||||||
toggle_bitmap.cpp \
|
toggle_bitmap.cpp \
|
||||||
toolbar.cpp \
|
toolbar.cpp \
|
||||||
|
@ -243,7 +244,7 @@ SRC += \
|
||||||
vector2d.cpp \
|
vector2d.cpp \
|
||||||
version.cpp \
|
version.cpp \
|
||||||
video_box.cpp \
|
video_box.cpp \
|
||||||
video_context.cpp \
|
video_controller.cpp \
|
||||||
video_display.cpp \
|
video_display.cpp \
|
||||||
video_frame.cpp \
|
video_frame.cpp \
|
||||||
video_out_gl.cpp \
|
video_out_gl.cpp \
|
||||||
|
|
|
@ -38,8 +38,8 @@
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
|
#include "project.h"
|
||||||
#include "subtitle_format.h"
|
#include "subtitle_format.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
@ -51,7 +51,7 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
|
||||||
for (auto& filter : *AssExportFilterChain::GetFilterList()) {
|
for (auto& filter : *AssExportFilterChain::GetFilterList()) {
|
||||||
// Make sure to construct static box sizer first, so it won't overlap
|
// Make sure to construct static box sizer first, so it won't overlap
|
||||||
// the controls on wxMac.
|
// the controls on wxMac.
|
||||||
wxSizer *box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
|
auto box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
|
||||||
wxWindow *window = filter.GetConfigDialogWindow(parent, c);
|
wxWindow *window = filter.GetConfigDialogWindow(parent, c);
|
||||||
if (window) {
|
if (window) {
|
||||||
box->Add(window, 0, wxEXPAND, 0);
|
box->Add(window, 0, wxEXPAND, 0);
|
||||||
|
@ -92,7 +92,7 @@ void AssExporter::Export(agi::fs::path const& filename, std::string const& chars
|
||||||
if (!writer)
|
if (!writer)
|
||||||
throw "Unknown file type.";
|
throw "Unknown file type.";
|
||||||
|
|
||||||
writer->WriteFile(&subs, filename, c->videoController->FPS(), charset);
|
writer->WriteFile(&subs, filename, c->project->Timecodes(), charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {
|
wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {
|
||||||
|
|
|
@ -14,18 +14,12 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file threaded_frame_source.cpp
|
#include "async_video_provider.h"
|
||||||
/// @see threaded_frame_source.h
|
|
||||||
/// @ingroup video
|
|
||||||
///
|
|
||||||
|
|
||||||
#include "threaded_frame_source.h"
|
|
||||||
|
|
||||||
#include "ass_dialogue.h"
|
#include "ass_dialogue.h"
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "export_fixstyle.h"
|
#include "export_fixstyle.h"
|
||||||
#include "include/aegisub/subtitles_provider.h"
|
#include "include/aegisub/subtitles_provider.h"
|
||||||
#include "include/aegisub/video_provider.h"
|
|
||||||
#include "video_frame.h"
|
#include "video_frame.h"
|
||||||
#include "video_provider_manager.h"
|
#include "video_provider_manager.h"
|
||||||
|
|
||||||
|
@ -40,11 +34,11 @@ enum {
|
||||||
SUBS_FILE_ALREADY_LOADED = -2
|
SUBS_FILE_ALREADY_LOADED = -2
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
|
std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) {
|
||||||
std::shared_ptr<VideoFrame> frame;
|
std::shared_ptr<VideoFrame> frame;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
frame = video_provider->GetFrame(frame_number);
|
frame = source_provider->GetFrame(frame_number);
|
||||||
}
|
}
|
||||||
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
|
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
|
||||||
|
|
||||||
|
@ -89,20 +83,20 @@ static std::unique_ptr<SubtitlesProvider> get_subs_provider(wxEvtHandler *evt_ha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadedFrameSource::ThreadedFrameSource(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
|
AsyncVideoProvider::AsyncVideoProvider(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
|
||||||
: worker(agi::dispatch::Create())
|
: worker(agi::dispatch::Create())
|
||||||
, subs_provider(get_subs_provider(parent, br))
|
, subs_provider(get_subs_provider(parent, br))
|
||||||
, video_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
|
, source_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
|
||||||
, parent(parent)
|
, parent(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadedFrameSource::~ThreadedFrameSource() {
|
AsyncVideoProvider::~AsyncVideoProvider() {
|
||||||
// Block until all currently queued jobs are complete
|
// Block until all currently queued jobs are complete
|
||||||
worker->Sync([]{});
|
worker->Sync([]{});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
|
void AsyncVideoProvider::LoadSubtitles(const AssFile *new_subs) throw() {
|
||||||
uint_fast32_t req_version = ++version;
|
uint_fast32_t req_version = ++version;
|
||||||
|
|
||||||
auto copy = new AssFile(*new_subs);
|
auto copy = new AssFile(*new_subs);
|
||||||
|
@ -113,7 +107,7 @@ void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> const& changes) throw() {
|
void AsyncVideoProvider::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> const& changes) throw() {
|
||||||
uint_fast32_t req_version = ++version;
|
uint_fast32_t req_version = ++version;
|
||||||
|
|
||||||
// Copy just the lines which were changed, then replace the lines at the
|
// Copy just the lines which were changed, then replace the lines at the
|
||||||
|
@ -141,7 +135,7 @@ void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<cons
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
|
void AsyncVideoProvider::RequestFrame(int new_frame, double new_time) throw() {
|
||||||
uint_fast32_t req_version = ++version;
|
uint_fast32_t req_version = ++version;
|
||||||
|
|
||||||
worker->Async([=]{
|
worker->Async([=]{
|
||||||
|
@ -151,7 +145,7 @@ void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
|
void AsyncVideoProvider::ProcAsync(uint_fast32_t req_version) {
|
||||||
// Only actually produce the frame if there's no queued changes waiting
|
// Only actually produce the frame if there's no queued changes waiting
|
||||||
if (req_version < version || frame_number < 0) return;
|
if (req_version < version || frame_number < 0) return;
|
||||||
|
|
||||||
|
@ -166,14 +160,14 @@ void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
|
std::shared_ptr<VideoFrame> AsyncVideoProvider::GetFrame(int frame, double time, bool raw) {
|
||||||
std::shared_ptr<VideoFrame> ret;
|
std::shared_ptr<VideoFrame> ret;
|
||||||
worker->Sync([&]{ ret = ProcFrame(frame, time, raw); });
|
worker->Sync([&]{ ret = ProcFrame(frame, time, raw); });
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedFrameSource::SetColorSpace(std::string const& matrix) {
|
void AsyncVideoProvider::SetColorSpace(std::string const& matrix) {
|
||||||
worker->Async([=] { video_provider->SetColorSpace(matrix); });
|
worker->Async([=] { source_provider->SetColorSpace(matrix); });
|
||||||
}
|
}
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
|
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
|
|
@ -14,19 +14,14 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file threaded_frame_source.h
|
#include "include/aegisub/video_provider.h"
|
||||||
/// @see threaded_frame_source.cpp
|
|
||||||
/// @ingroup video
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
#include <libaegisub/fs_fwd.h>
|
#include <libaegisub/fs_fwd.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
|
|
||||||
class AssDialogue;
|
class AssDialogue;
|
||||||
|
@ -40,16 +35,15 @@ namespace agi {
|
||||||
namespace dispatch { class Queue; }
|
namespace dispatch { class Queue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @class ThreadedFrameSource
|
/// An asynchronous video decoding and subtitle rendering wrapper
|
||||||
/// @brief An asynchronous video decoding and subtitle rendering wrapper
|
class AsyncVideoProvider {
|
||||||
class ThreadedFrameSource {
|
|
||||||
/// Asynchronous work queue
|
/// Asynchronous work queue
|
||||||
std::unique_ptr<agi::dispatch::Queue> worker;
|
std::unique_ptr<agi::dispatch::Queue> worker;
|
||||||
|
|
||||||
/// Subtitles provider
|
/// Subtitles provider
|
||||||
std::unique_ptr<SubtitlesProvider> subs_provider;
|
std::unique_ptr<SubtitlesProvider> subs_provider;
|
||||||
/// Video provider
|
/// Video provider
|
||||||
std::unique_ptr<VideoProvider> video_provider;
|
std::unique_ptr<VideoProvider> source_provider;
|
||||||
/// Event handler to send FrameReady events to
|
/// Event handler to send FrameReady events to
|
||||||
wxEvtHandler *parent;
|
wxEvtHandler *parent;
|
||||||
|
|
||||||
|
@ -103,17 +97,26 @@ public:
|
||||||
/// @brief raw Get raw frame without subtitles
|
/// @brief raw Get raw frame without subtitles
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
|
std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
|
||||||
|
|
||||||
/// Get a reference to the video provider this is using
|
|
||||||
VideoProvider *GetVideoProvider() const { return video_provider.get(); }
|
|
||||||
|
|
||||||
/// Ask the video provider to change YCbCr matricies
|
/// Ask the video provider to change YCbCr matricies
|
||||||
void SetColorSpace(std::string const& matrix);
|
void SetColorSpace(std::string const& matrix);
|
||||||
|
|
||||||
|
int GetFrameCount() const { return source_provider->GetFrameCount(); }
|
||||||
|
int GetWidth() const { return source_provider->GetWidth(); }
|
||||||
|
int GetHeight() const { return source_provider->GetHeight(); }
|
||||||
|
double GetDAR() const { return source_provider->GetDAR(); }
|
||||||
|
agi::vfr::Framerate GetFPS() const { return source_provider->GetFPS(); }
|
||||||
|
std::vector<int> GetKeyFrames() const { return source_provider->GetKeyFrames(); }
|
||||||
|
std::string GetColorSpace() const { return source_provider->GetColorSpace(); }
|
||||||
|
std::string GetRealColorSpace() const { return source_provider->GetRealColorSpace(); }
|
||||||
|
std::string GetWarning() const { return source_provider->GetWarning(); }
|
||||||
|
std::string GetDecoderName() const { return source_provider->GetDecoderName(); }
|
||||||
|
bool ShouldSetVideoProperties() const { return source_provider->ShouldSetVideoProperties(); }
|
||||||
|
|
||||||
/// @brief Constructor
|
/// @brief Constructor
|
||||||
/// @param videoFileName File to open
|
/// @param videoFileName File to open
|
||||||
/// @param parent Event handler to send FrameReady events to
|
/// @param parent Event handler to send FrameReady events to
|
||||||
ThreadedFrameSource(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
|
AsyncVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
|
||||||
~ThreadedFrameSource();
|
~AsyncVideoProvider();
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Event which signals that a requested frame is ready
|
/// Event which signals that a requested frame is ready
|
|
@ -61,6 +61,7 @@
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "toggle_bitmap.h"
|
#include "toggle_bitmap.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
@ -75,7 +76,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
|
||||||
: wxSashWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxSW_3D | wxCLIP_CHILDREN)
|
: wxSashWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxSW_3D | wxCLIP_CHILDREN)
|
||||||
, controller(context->audioController.get())
|
, controller(context->audioController.get())
|
||||||
, context(context)
|
, context(context)
|
||||||
, audio_open_connection(controller->AddAudioOpenListener(&AudioBox::OnAudioOpen, this))
|
, audio_open_connection(context->project->AddAudioProviderListener(&AudioBox::OnAudioOpen, this))
|
||||||
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_RAISED))
|
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_RAISED))
|
||||||
, audioDisplay(new AudioDisplay(panel, context->audioController.get(), context))
|
, audioDisplay(new AudioDisplay(panel, context->audioController.get(), context))
|
||||||
, HorizontalZoom(new wxSlider(panel, Audio_Horizontal_Zoom, -OPT_GET("Audio/Zoom/Horizontal")->GetInt(), -50, 30, wxDefaultPosition, wxSize(-1, 20), wxSL_VERTICAL|wxSL_BOTH))
|
, HorizontalZoom(new wxSlider(panel, Audio_Horizontal_Zoom, -OPT_GET("Audio/Zoom/Horizontal")->GetInt(), -50, 30, wxDefaultPosition, wxSize(-1, 20), wxSL_VERTICAL|wxSL_BOTH))
|
||||||
|
|
|
@ -27,36 +27,23 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file audio_controller.cpp
|
|
||||||
/// @brief Manage open audio and abstract state away from display
|
|
||||||
/// @ingroup audio_ui
|
|
||||||
///
|
|
||||||
|
|
||||||
#include "audio_controller.h"
|
#include "audio_controller.h"
|
||||||
|
|
||||||
#include "ass_file.h"
|
|
||||||
#include "audio_timing.h"
|
#include "audio_timing.h"
|
||||||
#include "compat.h"
|
|
||||||
#include "dialog_progress.h"
|
|
||||||
#include "include/aegisub/audio_player.h"
|
#include "include/aegisub/audio_player.h"
|
||||||
#include "include/aegisub/audio_provider.h"
|
#include "include/aegisub/audio_provider.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "pen.h"
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/io.h>
|
|
||||||
#include <libaegisub/path.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
AudioController::AudioController(agi::Context *context)
|
AudioController::AudioController(agi::Context *context)
|
||||||
: context(context)
|
: context(context)
|
||||||
, subtitle_save_slot(context->subsController->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
|
|
||||||
, playback_timer(this)
|
, playback_timer(this)
|
||||||
|
, provider_connection(context->project->AddAudioProviderListener(&AudioController::OnAudioProvider, this))
|
||||||
{
|
{
|
||||||
Bind(wxEVT_TIMER, &AudioController::OnPlaybackTimer, this, playback_timer.GetId());
|
Bind(wxEVT_TIMER, &AudioController::OnPlaybackTimer, this, playback_timer.GetId());
|
||||||
|
|
||||||
|
@ -66,25 +53,18 @@ AudioController::AudioController(agi::Context *context)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
OPT_SUB("Audio/Player", &AudioController::OnAudioPlayerChanged, this);
|
OPT_SUB("Audio/Player", &AudioController::OnAudioPlayerChanged, this);
|
||||||
OPT_SUB("Audio/Provider", &AudioController::OnAudioProviderChanged, this);
|
|
||||||
OPT_SUB("Audio/Cache/Type", &AudioController::OnAudioProviderChanged, this);
|
|
||||||
|
|
||||||
#ifdef WITH_FFMS2
|
|
||||||
// As with the video ones, it'd be nice to figure out a decent way to move
|
|
||||||
// this to the provider itself
|
|
||||||
OPT_SUB("Provider/Audio/FFmpegSource/Decode Error Handling", &AudioController::OnAudioProviderChanged, this);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioController::~AudioController()
|
AudioController::~AudioController()
|
||||||
{
|
{
|
||||||
CloseAudio();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioController::OnPlaybackTimer(wxTimerEvent &)
|
void AudioController::OnPlaybackTimer(wxTimerEvent &)
|
||||||
{
|
{
|
||||||
int64_t pos = player->GetCurrentPosition();
|
if (!player) return;
|
||||||
|
|
||||||
|
int64_t pos = player->GetCurrentPosition();
|
||||||
if (!player->IsPlaying() ||
|
if (!player->IsPlaying() ||
|
||||||
(playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
|
(playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
|
||||||
{
|
{
|
||||||
|
@ -107,113 +87,40 @@ void AudioController::OnComputerSuspending(wxPowerEvent &)
|
||||||
|
|
||||||
void AudioController::OnComputerResuming(wxPowerEvent &)
|
void AudioController::OnComputerResuming(wxPowerEvent &)
|
||||||
{
|
{
|
||||||
if (provider)
|
OnAudioPlayerChanged();
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
CloseAudio();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void AudioController::OnAudioPlayerChanged()
|
void AudioController::OnAudioPlayerChanged()
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
if (!provider) return;
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
player.reset();
|
player.reset();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
|
player = AudioPlayerFactory::GetAudioPlayer(provider, context->parent);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
CloseAudio();
|
context->project->CloseAudio();
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioController::OnAudioProviderChanged()
|
void AudioController::OnAudioProvider(AudioProvider *new_provider)
|
||||||
{
|
|
||||||
if (IsAudioOpen())
|
|
||||||
// url is cloned because CloseAudio clears it and OpenAudio takes a const reference
|
|
||||||
OpenAudio(agi::fs::path(audio_url));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioController::OpenAudio(agi::fs::path const& url)
|
|
||||||
{
|
|
||||||
if (url.empty())
|
|
||||||
throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", nullptr);
|
|
||||||
|
|
||||||
std::unique_ptr<AudioProvider> new_provider;
|
|
||||||
try {
|
|
||||||
DialogProgress progress(context->parent);
|
|
||||||
new_provider = AudioProviderFactory::GetProvider(url, &progress);
|
|
||||||
config::path->SetToken("?audio", url);
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
config::mru->Remove("Audio", url);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseAudio();
|
|
||||||
|
|
||||||
player = AudioPlayerFactory::GetAudioPlayer(new_provider.get(), context->parent);
|
|
||||||
provider = std::move(new_provider);
|
|
||||||
|
|
||||||
audio_url = url;
|
|
||||||
|
|
||||||
config::mru->Add("Audio", url);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AnnounceAudioOpen(provider.get());
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
CloseAudio();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioController::CloseAudio()
|
|
||||||
{
|
{
|
||||||
|
provider = new_provider;
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
player.reset();
|
player.reset();
|
||||||
provider.reset();
|
OnAudioPlayerChanged();
|
||||||
player = nullptr;
|
|
||||||
provider = nullptr;
|
|
||||||
|
|
||||||
audio_url.clear();
|
|
||||||
|
|
||||||
config::path->SetToken("?audio", "");
|
|
||||||
|
|
||||||
AnnounceAudioClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioController::IsAudioOpen() const
|
|
||||||
{
|
|
||||||
return player && provider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioController::SetTimingController(std::unique_ptr<AudioTimingController> new_controller)
|
void AudioController::SetTimingController(std::unique_ptr<AudioTimingController> new_controller)
|
||||||
{
|
{
|
||||||
timing_controller = std::move(new_controller);
|
timing_controller = std::move(new_controller);
|
||||||
if (timing_controller)
|
if (timing_controller)
|
||||||
{
|
|
||||||
timing_controller->AddUpdatedPrimaryRangeListener(&AudioController::OnTimingControllerUpdatedPrimaryRange, this);
|
timing_controller->AddUpdatedPrimaryRangeListener(&AudioController::OnTimingControllerUpdatedPrimaryRange, this);
|
||||||
}
|
|
||||||
|
|
||||||
AnnounceTimingControllerChanged();
|
AnnounceTimingControllerChanged();
|
||||||
}
|
}
|
||||||
|
@ -224,17 +131,9 @@ void AudioController::OnTimingControllerUpdatedPrimaryRange()
|
||||||
player->SetEndPosition(SamplesFromMilliseconds(timing_controller->GetPrimaryPlaybackRange().end()));
|
player->SetEndPosition(SamplesFromMilliseconds(timing_controller->GetPrimaryPlaybackRange().end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioController::OnSubtitlesSave()
|
|
||||||
{
|
|
||||||
if (IsAudioOpen())
|
|
||||||
context->ass->SetScriptInfo("Audio URI", config::path->MakeRelative(audio_url, "?script").generic_string());
|
|
||||||
else
|
|
||||||
context->ass->SetScriptInfo("Audio URI", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioController::PlayRange(const TimeRange &range)
|
void AudioController::PlayRange(const TimeRange &range)
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
if (!player) return;
|
||||||
|
|
||||||
player->Play(SamplesFromMilliseconds(range.begin()), SamplesFromMilliseconds(range.length()));
|
player->Play(SamplesFromMilliseconds(range.begin()), SamplesFromMilliseconds(range.length()));
|
||||||
playback_mode = PM_Range;
|
playback_mode = PM_Range;
|
||||||
|
@ -252,8 +151,6 @@ void AudioController::PlayPrimaryRange()
|
||||||
|
|
||||||
void AudioController::PlayToEndOfPrimary(int start_ms)
|
void AudioController::PlayToEndOfPrimary(int start_ms)
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
|
||||||
|
|
||||||
PlayRange(TimeRange(start_ms, GetPrimaryPlaybackRange().end()));
|
PlayRange(TimeRange(start_ms, GetPrimaryPlaybackRange().end()));
|
||||||
if (playback_mode == PM_Range)
|
if (playback_mode == PM_Range)
|
||||||
playback_mode = PM_PrimaryRange;
|
playback_mode = PM_PrimaryRange;
|
||||||
|
@ -261,7 +158,7 @@ void AudioController::PlayToEndOfPrimary(int start_ms)
|
||||||
|
|
||||||
void AudioController::PlayToEnd(int start_ms)
|
void AudioController::PlayToEnd(int start_ms)
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
if (!player) return;
|
||||||
|
|
||||||
int64_t start_sample = SamplesFromMilliseconds(start_ms);
|
int64_t start_sample = SamplesFromMilliseconds(start_ms);
|
||||||
player->Play(start_sample, provider->GetNumSamples()-start_sample);
|
player->Play(start_sample, provider->GetNumSamples()-start_sample);
|
||||||
|
@ -273,7 +170,7 @@ void AudioController::PlayToEnd(int start_ms)
|
||||||
|
|
||||||
void AudioController::Stop()
|
void AudioController::Stop()
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
if (!player) return;
|
||||||
|
|
||||||
player->Stop();
|
player->Stop();
|
||||||
playback_mode = PM_NotPlaying;
|
playback_mode = PM_NotPlaying;
|
||||||
|
@ -284,7 +181,7 @@ void AudioController::Stop()
|
||||||
|
|
||||||
bool AudioController::IsPlaying()
|
bool AudioController::IsPlaying()
|
||||||
{
|
{
|
||||||
return IsAudioOpen() && playback_mode != PM_NotPlaying;
|
return player && playback_mode != PM_NotPlaying;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioController::GetPlaybackPosition()
|
int AudioController::GetPlaybackPosition()
|
||||||
|
@ -297,88 +194,31 @@ int AudioController::GetPlaybackPosition()
|
||||||
int AudioController::GetDuration() const
|
int AudioController::GetDuration() const
|
||||||
{
|
{
|
||||||
if (!provider) return 0;
|
if (!provider) return 0;
|
||||||
|
|
||||||
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
|
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeRange AudioController::GetPrimaryPlaybackRange() const
|
TimeRange AudioController::GetPrimaryPlaybackRange() const
|
||||||
{
|
{
|
||||||
if (timing_controller)
|
if (timing_controller)
|
||||||
{
|
|
||||||
return timing_controller->GetPrimaryPlaybackRange();
|
return timing_controller->GetPrimaryPlaybackRange();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
return TimeRange{0, 0};
|
||||||
return TimeRange(0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioController::SetVolume(double volume)
|
void AudioController::SetVolume(double volume)
|
||||||
{
|
{
|
||||||
if (!IsAudioOpen()) return;
|
if (!player) return;
|
||||||
player->SetVolume(volume);
|
player->SetVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t AudioController::SamplesFromMilliseconds(int64_t ms) const
|
int64_t AudioController::SamplesFromMilliseconds(int64_t ms) const
|
||||||
{
|
{
|
||||||
/// @todo There might be some subtle rounding errors here.
|
|
||||||
|
|
||||||
if (!provider) return 0;
|
if (!provider) return 0;
|
||||||
|
return (ms * provider->GetSampleRate() + 999) / 1000;
|
||||||
int64_t sr = provider->GetSampleRate();
|
|
||||||
|
|
||||||
int64_t millisamples = ms * sr;
|
|
||||||
|
|
||||||
return (millisamples + 999) / 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
|
int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
|
||||||
{
|
{
|
||||||
/// @todo There might be some subtle rounding errors here.
|
|
||||||
|
|
||||||
if (!provider) return 0;
|
if (!provider) return 0;
|
||||||
|
return samples * 1000 / provider->GetSampleRate();
|
||||||
int64_t sr = provider->GetSampleRate();
|
|
||||||
|
|
||||||
int64_t millisamples = samples * 1000;
|
|
||||||
|
|
||||||
return millisamples / sr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioController::SaveClip(agi::fs::path const& filename, TimeRange const& range) const
|
|
||||||
{
|
|
||||||
int64_t start_sample = SamplesFromMilliseconds(range.begin());
|
|
||||||
int64_t end_sample = SamplesFromMilliseconds(range.end());
|
|
||||||
if (filename.empty() || start_sample > provider->GetNumSamples() || range.length() == 0) return;
|
|
||||||
|
|
||||||
agi::io::Save outfile(filename, true);
|
|
||||||
std::ostream& out(outfile.Get());
|
|
||||||
|
|
||||||
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
|
|
||||||
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
|
|
||||||
|
|
||||||
int intval;
|
|
||||||
short shortval;
|
|
||||||
|
|
||||||
out << "RIFF";
|
|
||||||
out.write((char*)&(intval=bufsize+36),4);
|
|
||||||
out<< "WAVEfmt ";
|
|
||||||
out.write((char*)&(intval=16),4);
|
|
||||||
out.write((char*)&(shortval=1),2);
|
|
||||||
out.write((char*)&(shortval=provider->GetChannels()),2);
|
|
||||||
out.write((char*)&(intval=provider->GetSampleRate()),4);
|
|
||||||
out.write((char*)&(intval=provider->GetSampleRate()*provider->GetChannels()*provider->GetBytesPerSample()),4);
|
|
||||||
out.write((char*)&(intval=provider->GetChannels()*provider->GetBytesPerSample()),2);
|
|
||||||
out.write((char*)&(shortval=provider->GetBytesPerSample()<<3),2);
|
|
||||||
out << "data";
|
|
||||||
out.write((char*)&bufsize,4);
|
|
||||||
|
|
||||||
//samples per read
|
|
||||||
size_t spr = 65536 / bytes_per_sample;
|
|
||||||
std::vector<char> buf(bufsize);
|
|
||||||
for(int64_t i = start_sample; i < end_sample; i += spr) {
|
|
||||||
size_t len = std::min<size_t>(spr, end_sample - i);
|
|
||||||
provider->GetAudio(&buf[0], i, len);
|
|
||||||
out.write(&buf[0], len * bytes_per_sample);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,24 +27,15 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file audio_controller.h
|
|
||||||
/// @see audio_controller.cpp
|
|
||||||
/// @ingroup audio_ui
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <wx/event.h>
|
|
||||||
#include <wx/timer.h>
|
|
||||||
#include <wx/power.h>
|
|
||||||
|
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
#include <libaegisub/fs_fwd.h>
|
#include <libaegisub/fs_fwd.h>
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/power.h>
|
||||||
|
#include <wx/timer.h>
|
||||||
|
|
||||||
class AudioPlayer;
|
class AudioPlayer;
|
||||||
class AudioProvider;
|
class AudioProvider;
|
||||||
class AudioTimingController;
|
class AudioTimingController;
|
||||||
|
@ -52,17 +43,10 @@ namespace agi { struct Context; }
|
||||||
class TimeRange;
|
class TimeRange;
|
||||||
|
|
||||||
/// @class AudioController
|
/// @class AudioController
|
||||||
/// @brief Manage an open audio stream
|
/// @brief Manage playback of an open audio stream
|
||||||
///
|
///
|
||||||
/// Creates and destroys audio providers and players. This behaviour should at
|
/// AudioController owns an AudioPlayer and uses it to play audio from the
|
||||||
/// some point be moved to a separate class, as it adds too many
|
/// project's current audio provider.
|
||||||
/// responsibilities to this class, but at the time of writing, it would extend
|
|
||||||
/// the scope of reworking components too much.
|
|
||||||
///
|
|
||||||
/// There is not supposed to be a way to get direct access to the audio
|
|
||||||
/// providers or players owned by a controller. If some operation that isn't
|
|
||||||
/// possible in the existing design is needed, the controller should be
|
|
||||||
/// extended in some way to allow it.
|
|
||||||
class AudioController final : public wxEvtHandler {
|
class AudioController final : public wxEvtHandler {
|
||||||
/// Project context this controller belongs to
|
/// Project context this controller belongs to
|
||||||
agi::Context *context;
|
agi::Context *context;
|
||||||
|
@ -70,12 +54,6 @@ class AudioController final : public wxEvtHandler {
|
||||||
/// Slot for subtitles save signal
|
/// Slot for subtitles save signal
|
||||||
agi::signal::Connection subtitle_save_slot;
|
agi::signal::Connection subtitle_save_slot;
|
||||||
|
|
||||||
/// A new audio stream was opened (and any previously open was closed)
|
|
||||||
agi::signal::Signal<AudioProvider*> AnnounceAudioOpen;
|
|
||||||
|
|
||||||
/// The current audio stream was closed
|
|
||||||
agi::signal::Signal<> AnnounceAudioClose;
|
|
||||||
|
|
||||||
/// Playback is in progress and the current position was updated
|
/// Playback is in progress and the current position was updated
|
||||||
agi::signal::Signal<int> AnnouncePlaybackPosition;
|
agi::signal::Signal<int> AnnouncePlaybackPosition;
|
||||||
|
|
||||||
|
@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler {
|
||||||
/// The audio output object
|
/// The audio output object
|
||||||
std::unique_ptr<AudioPlayer> player;
|
std::unique_ptr<AudioPlayer> player;
|
||||||
|
|
||||||
/// The audio provider
|
|
||||||
std::unique_ptr<AudioProvider> provider;
|
|
||||||
|
|
||||||
/// The current timing mode, if any; owned by the audio controller
|
/// The current timing mode, if any; owned by the audio controller
|
||||||
std::unique_ptr<AudioTimingController> timing_controller;
|
std::unique_ptr<AudioTimingController> timing_controller;
|
||||||
|
|
||||||
/// The URL of the currently open audio, if any
|
|
||||||
agi::fs::path audio_url;
|
|
||||||
|
|
||||||
|
|
||||||
enum PlaybackMode {
|
enum PlaybackMode {
|
||||||
PM_NotPlaying,
|
PM_NotPlaying,
|
||||||
PM_Range,
|
PM_Range,
|
||||||
|
@ -110,6 +81,12 @@ class AudioController final : public wxEvtHandler {
|
||||||
/// Timer used for playback position updates
|
/// Timer used for playback position updates
|
||||||
wxTimer playback_timer;
|
wxTimer playback_timer;
|
||||||
|
|
||||||
|
/// The audio provider
|
||||||
|
AudioProvider *provider = nullptr;
|
||||||
|
agi::signal::Connection provider_connection;
|
||||||
|
|
||||||
|
void OnAudioProvider(AudioProvider *new_provider);
|
||||||
|
|
||||||
/// Event handler for the playback timer
|
/// Event handler for the playback timer
|
||||||
void OnPlaybackTimer(wxTimerEvent &event);
|
void OnPlaybackTimer(wxTimerEvent &event);
|
||||||
|
|
||||||
|
@ -119,15 +96,9 @@ class AudioController final : public wxEvtHandler {
|
||||||
/// @brief Timing controller signals that the rendering style ranges have changed
|
/// @brief Timing controller signals that the rendering style ranges have changed
|
||||||
void OnTimingControllerUpdatedStyleRanges();
|
void OnTimingControllerUpdatedStyleRanges();
|
||||||
|
|
||||||
/// Subtitles save slot which adds the audio uri to the subtitles
|
|
||||||
void OnSubtitlesSave();
|
|
||||||
|
|
||||||
/// Handler for the current audio player changing
|
/// Handler for the current audio player changing
|
||||||
void OnAudioPlayerChanged();
|
void OnAudioPlayerChanged();
|
||||||
|
|
||||||
/// Handler for the current audio provider changing
|
|
||||||
void OnAudioProviderChanged();
|
|
||||||
|
|
||||||
#ifdef wxHAS_POWER_EVENTS
|
#ifdef wxHAS_POWER_EVENTS
|
||||||
/// Handle computer going into suspend mode by stopping audio and closing device
|
/// Handle computer going into suspend mode by stopping audio and closing device
|
||||||
void OnComputerSuspending(wxPowerEvent &event);
|
void OnComputerSuspending(wxPowerEvent &event);
|
||||||
|
@ -146,31 +117,9 @@ class AudioController final : public wxEvtHandler {
|
||||||
int64_t SamplesFromMilliseconds(int64_t ms) const;
|
int64_t SamplesFromMilliseconds(int64_t ms) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Constructor
|
|
||||||
AudioController(agi::Context *context);
|
AudioController(agi::Context *context);
|
||||||
|
|
||||||
/// @brief Destructor
|
|
||||||
~AudioController();
|
~AudioController();
|
||||||
|
|
||||||
/// @brief Open an audio stream
|
|
||||||
/// @param url URL of the stream to open
|
|
||||||
void OpenAudio(agi::fs::path const& url);
|
|
||||||
|
|
||||||
/// @brief Closes the current audio stream
|
|
||||||
void CloseAudio();
|
|
||||||
|
|
||||||
/// @brief Determine whether audio is currently open
|
|
||||||
/// @return True if an audio stream is open and can be played back
|
|
||||||
bool IsAudioOpen() const;
|
|
||||||
|
|
||||||
/// @brief Get the URL for the current open audio stream
|
|
||||||
/// @return The URL for the audio stream
|
|
||||||
///
|
|
||||||
/// The returned URL can be passed into OpenAudio() later to open the same
|
|
||||||
/// stream again.
|
|
||||||
agi::fs::path GetAudioURL() const { return audio_url; }
|
|
||||||
|
|
||||||
|
|
||||||
/// @brief Start or restart audio playback, playing a range
|
/// @brief Start or restart audio playback, playing a range
|
||||||
/// @param range The range of audio to play back
|
/// @param range The range of audio to play back
|
||||||
///
|
///
|
||||||
|
@ -233,13 +182,6 @@ public:
|
||||||
/// @param new_mode The new timing controller or nullptr
|
/// @param new_mode The new timing controller or nullptr
|
||||||
void SetTimingController(std::unique_ptr<AudioTimingController> new_controller);
|
void SetTimingController(std::unique_ptr<AudioTimingController> new_controller);
|
||||||
|
|
||||||
/// @brief Save a portion of the decoded loaded audio to a wav file
|
|
||||||
/// @param filename File to save to
|
|
||||||
/// @param range Time range to save
|
|
||||||
void SaveClip(agi::fs::path const& filename, TimeRange const& range) const;
|
|
||||||
|
|
||||||
DEFINE_SIGNAL_ADDERS(AnnounceAudioOpen, AddAudioOpenListener)
|
|
||||||
DEFINE_SIGNAL_ADDERS(AnnounceAudioClose, AddAudioCloseListener)
|
|
||||||
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackPosition, AddPlaybackPositionListener)
|
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackPosition, AddPlaybackPositionListener)
|
||||||
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackStop, AddPlaybackStopListener)
|
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackStop, AddPlaybackStopListener)
|
||||||
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
|
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
|
||||||
|
|
|
@ -48,9 +48,10 @@
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "include/aegisub/hotkey.h"
|
#include "include/aegisub/hotkey.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
@ -547,7 +548,7 @@ public:
|
||||||
|
|
||||||
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
|
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
|
||||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
|
||||||
, audio_open_connection(controller->AddAudioOpenListener(&AudioDisplay::OnAudioOpen, this))
|
, audio_open_connection(context->project->AddAudioProviderListener(&AudioDisplay::OnAudioOpen, this))
|
||||||
, context(context)
|
, context(context)
|
||||||
, audio_renderer(agi::make_unique<AudioRenderer>())
|
, audio_renderer(agi::make_unique<AudioRenderer>())
|
||||||
, controller(controller)
|
, controller(controller)
|
||||||
|
@ -1193,16 +1194,16 @@ void AudioDisplay::OnAudioOpen(AudioProvider *provider)
|
||||||
{
|
{
|
||||||
if (connections.empty())
|
if (connections.empty())
|
||||||
{
|
{
|
||||||
connections.push_back(controller->AddAudioCloseListener([=] { OnAudioOpen(nullptr); }));
|
connections = agi::signal::make_vector({
|
||||||
connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
|
controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this),
|
||||||
connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
|
controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this),
|
||||||
connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
|
controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this),
|
||||||
connections.push_back(OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
|
OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
||||||
connections.push_back(OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this));
|
OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
|
OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this));
|
OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this),
|
||||||
connections.push_back(OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this));
|
OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this),
|
||||||
|
});
|
||||||
OnTimingController();
|
OnTimingController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
@ -103,7 +102,7 @@ public:
|
||||||
class AudioDisplay: public wxWindow {
|
class AudioDisplay: public wxWindow {
|
||||||
agi::signal::Connection audio_open_connection;
|
agi::signal::Connection audio_open_connection;
|
||||||
|
|
||||||
std::deque<agi::signal::Connection> connections;
|
std::vector<agi::signal::Connection> connections;
|
||||||
agi::Context *context;
|
agi::Context *context;
|
||||||
|
|
||||||
/// The audio renderer manager
|
/// The audio renderer manager
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
@ -40,7 +41,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/locale/boundary.hpp>
|
#include <boost/locale/boundary.hpp>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include <wx/bmpbuttn.h>
|
#include <wx/bmpbuttn.h>
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/dcclient.h>
|
#include <wx/dcclient.h>
|
||||||
|
@ -63,8 +63,7 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c)
|
||||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN)
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN)
|
||||||
, c(c)
|
, c(c)
|
||||||
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
|
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
|
||||||
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
|
, audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this))
|
||||||
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
|
|
||||||
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
||||||
, kara(agi::make_unique<AssKaraoke>())
|
, kara(agi::make_unique<AssKaraoke>())
|
||||||
{
|
{
|
||||||
|
@ -122,12 +121,11 @@ void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioKaraoke::OnAudioOpened() {
|
void AudioKaraoke::OnAudioOpened(AudioProvider *provider) {
|
||||||
SetEnabled(enabled);
|
if (provider)
|
||||||
}
|
SetEnabled(enabled);
|
||||||
|
else
|
||||||
void AudioKaraoke::OnAudioClosed() {
|
c->audioController->SetTimingController(nullptr);
|
||||||
c->audioController->SetTimingController(nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioKaraoke::SetEnabled(bool en) {
|
void AudioKaraoke::SetEnabled(bool en) {
|
||||||
|
|
|
@ -14,26 +14,20 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file audio_karaoke.h
|
|
||||||
/// @see audio_karaoke.cpp
|
|
||||||
/// @ingroup audio_ui
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wx/bitmap.h>
|
#include <wx/bitmap.h>
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
#include <wx/window.h>
|
#include <wx/window.h>
|
||||||
|
|
||||||
class AssDialogue;
|
class AssDialogue;
|
||||||
class AssKaraoke;
|
class AssKaraoke;
|
||||||
|
class AudioProvider;
|
||||||
class wxButton;
|
class wxButton;
|
||||||
|
|
||||||
namespace agi { struct Context; }
|
namespace agi { struct Context; }
|
||||||
|
|
||||||
/// @class AudioKaraoke
|
/// @class AudioKaraoke
|
||||||
|
@ -147,8 +141,7 @@ class AudioKaraoke final : public wxWindow {
|
||||||
void OnMouse(wxMouseEvent &event);
|
void OnMouse(wxMouseEvent &event);
|
||||||
void OnPaint(wxPaintEvent &event);
|
void OnPaint(wxPaintEvent &event);
|
||||||
void OnSize(wxSizeEvent &event);
|
void OnSize(wxSizeEvent &event);
|
||||||
void OnAudioOpened();
|
void OnAudioOpened(AudioProvider *provider);
|
||||||
void OnAudioClosed();
|
|
||||||
void OnScrollTimer(wxTimerEvent &event);
|
void OnScrollTimer(wxTimerEvent &event);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "pen.h"
|
#include "pen.h"
|
||||||
#include "video_context.h"
|
#include "project.h"
|
||||||
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
@ -42,9 +43,9 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
|
AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
|
||||||
: vc(c->videoController.get())
|
: p(c->project.get())
|
||||||
, keyframe_slot(vc->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
|
, keyframe_slot(p->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||||
, timecode_slot(vc->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
|
, timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||||
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
|
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
|
||||||
, enabled_opt(OPT_GET(opt_name))
|
, enabled_opt(OPT_GET(opt_name))
|
||||||
, style(agi::make_unique<Pen>("Colour/Audio Display/Keyframe"))
|
, style(agi::make_unique<Pen>("Colour/Audio Display/Keyframe"))
|
||||||
|
@ -55,8 +56,8 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons
|
||||||
AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }
|
AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }
|
||||||
|
|
||||||
void AudioMarkerProviderKeyframes::Update() {
|
void AudioMarkerProviderKeyframes::Update() {
|
||||||
std::vector<int> const& keyframes = vc->GetKeyFrames();
|
auto const& keyframes = p->Keyframes();
|
||||||
agi::vfr::Framerate const& timecodes = vc->FPS();
|
auto const& timecodes = p->Timecodes();
|
||||||
|
|
||||||
if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
|
if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
|
||||||
if (!markers.empty()) {
|
if (!markers.empty()) {
|
||||||
|
|
|
@ -14,11 +14,6 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file audio_marker.h
|
|
||||||
/// @see audio_marker.cpp
|
|
||||||
/// @ingroup audio_ui
|
|
||||||
///
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
|
@ -30,7 +25,8 @@
|
||||||
|
|
||||||
class AudioMarkerKeyframe;
|
class AudioMarkerKeyframe;
|
||||||
class Pen;
|
class Pen;
|
||||||
class VideoContext;
|
class Project;
|
||||||
|
class VideoController;
|
||||||
class TimeRange;
|
class TimeRange;
|
||||||
class VideoPositionMarker;
|
class VideoPositionMarker;
|
||||||
class wxPen;
|
class wxPen;
|
||||||
|
@ -113,8 +109,8 @@ public:
|
||||||
|
|
||||||
/// Marker provider for video keyframes
|
/// Marker provider for video keyframes
|
||||||
class AudioMarkerProviderKeyframes final : public AudioMarkerProvider {
|
class AudioMarkerProviderKeyframes final : public AudioMarkerProvider {
|
||||||
/// Video controller to get keyframes from
|
/// Project to get keyframes from
|
||||||
VideoContext *vc;
|
Project *p;
|
||||||
|
|
||||||
agi::signal::Connection keyframe_slot;
|
agi::signal::Connection keyframe_slot;
|
||||||
agi::signal::Connection timecode_slot;
|
agi::signal::Connection timecode_slot;
|
||||||
|
@ -146,7 +142,7 @@ public:
|
||||||
|
|
||||||
/// Marker provider for the current video playback position
|
/// Marker provider for the current video playback position
|
||||||
class VideoPositionMarkerProvider final : public AudioMarkerProvider {
|
class VideoPositionMarkerProvider final : public AudioMarkerProvider {
|
||||||
VideoContext *vc;
|
VideoController *vc;
|
||||||
|
|
||||||
std::unique_ptr<VideoPositionMarker> marker;
|
std::unique_ptr<VideoPositionMarker> marker;
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,6 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
|
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
|
||||||
this->filename = filename;
|
|
||||||
|
|
||||||
agi::acs::CheckFileRead(filename);
|
agi::acs::CheckFileRead(filename);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());
|
std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());
|
||||||
|
|
|
@ -158,8 +158,6 @@ public:
|
||||||
RiffWavPCMAudioProvider(agi::fs::path const& filename)
|
RiffWavPCMAudioProvider(agi::fs::path const& filename)
|
||||||
: PCMAudioProvider(filename)
|
: PCMAudioProvider(filename)
|
||||||
{
|
{
|
||||||
this->filename = filename;
|
|
||||||
|
|
||||||
// Read header
|
// Read header
|
||||||
auto const& header = Read<RIFFChunk>(0);
|
auto const& header = Read<RIFFChunk>(0);
|
||||||
|
|
||||||
|
@ -292,8 +290,6 @@ public:
|
||||||
Wave64AudioProvider(agi::fs::path const& filename)
|
Wave64AudioProvider(agi::fs::path const& filename)
|
||||||
: PCMAudioProvider(filename)
|
: PCMAudioProvider(filename)
|
||||||
{
|
{
|
||||||
this->filename = filename;
|
|
||||||
|
|
||||||
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
|
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
|
||||||
|
|
||||||
if (file->size() < smallest_possible_file)
|
if (file->size() < smallest_possible_file)
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
#include <boost/range/algorithm/copy.hpp>
|
#include <boost/range/algorithm/copy.hpp>
|
||||||
#include <boost/range/adaptor/filtered.hpp>
|
#include <boost/range/adaptor/filtered.hpp>
|
||||||
#include <boost/range/adaptor/sliced.hpp>
|
#include <boost/range/adaptor/sliced.hpp>
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
/// @class KaraokeMarker
|
/// @class KaraokeMarker
|
||||||
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
|
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
|
||||||
|
@ -55,6 +54,8 @@ public:
|
||||||
|
|
||||||
void Move(int new_pos) { position = new_pos; }
|
void Move(int new_pos) { position = new_pos; }
|
||||||
|
|
||||||
|
KaraokeMarker(int position) : position(position) { }
|
||||||
|
|
||||||
KaraokeMarker(int position, Pen *pen, FeetStyle style)
|
KaraokeMarker(int position, Pen *pen, FeetStyle style)
|
||||||
: position(position)
|
: position(position)
|
||||||
, pen(pen)
|
, pen(pen)
|
||||||
|
@ -62,11 +63,6 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
KaraokeMarker(int position)
|
|
||||||
: position(position)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
operator int() const { return position; }
|
operator int() const { return position; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,7 +75,7 @@ public:
|
||||||
/// This does not support \kt, as it inherently requires that the end time of
|
/// This does not support \kt, as it inherently requires that the end time of
|
||||||
/// one syllable be the same as the start time of the next one.
|
/// one syllable be the same as the start time of the next one.
|
||||||
class AudioTimingControllerKaraoke final : public AudioTimingController {
|
class AudioTimingControllerKaraoke final : public AudioTimingController {
|
||||||
std::deque<agi::signal::Connection> slots;
|
std::vector<agi::signal::Connection> connections;
|
||||||
agi::signal::Connection& file_changed_slot;
|
agi::signal::Connection& file_changed_slot;
|
||||||
|
|
||||||
agi::Context *c; ///< Project context
|
agi::Context *c; ///< Project context
|
||||||
|
@ -161,8 +157,8 @@ AudioTimingControllerKaraoke::AudioTimingControllerKaraoke(agi::Context *c, AssK
|
||||||
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Karaoke Mode")
|
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Karaoke Mode")
|
||||||
, video_position_provider(c)
|
, video_position_provider(c)
|
||||||
{
|
{
|
||||||
slots.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
|
connections.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
|
||||||
slots.push_back(OPT_SUB("Audio/Auto/Commit", [=](agi::OptionValue const& opt) { auto_commit = opt.GetBool(); }));
|
connections.push_back(OPT_SUB("Audio/Auto/Commit", [=](agi::OptionValue const& opt) { auto_commit = opt.GetBool(); }));
|
||||||
|
|
||||||
keyframes_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
keyframes_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
||||||
video_position_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
video_position_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
||||||
|
|
|
@ -373,9 +373,11 @@ namespace Automation4 {
|
||||||
|
|
||||||
LocalScriptManager::LocalScriptManager(agi::Context *c)
|
LocalScriptManager::LocalScriptManager(agi::Context *c)
|
||||||
: context(c)
|
: context(c)
|
||||||
|
, connections(agi::signal::make_vector({
|
||||||
|
c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this),
|
||||||
|
c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this),
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
slots.push_back(c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this));
|
|
||||||
slots.push_back(c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalScriptManager::Reload()
|
void LocalScriptManager::Reload()
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -215,8 +214,8 @@ namespace Automation4 {
|
||||||
|
|
||||||
/// Manager for scripts specified by a subtitle file
|
/// Manager for scripts specified by a subtitle file
|
||||||
class LocalScriptManager final : public ScriptManager {
|
class LocalScriptManager final : public ScriptManager {
|
||||||
std::deque<agi::signal::Connection> slots;
|
|
||||||
agi::Context *context;
|
agi::Context *context;
|
||||||
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
void OnSubtitlesSave();
|
void OnSubtitlesSave();
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -39,15 +39,18 @@
|
||||||
#include "ass_info.h"
|
#include "ass_info.h"
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "ass_style.h"
|
#include "ass_style.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "auto4_lua_factory.h"
|
#include "auto4_lua_factory.h"
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
|
#include "include/aegisub/video_provider.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <libaegisub/access.h>
|
#include <libaegisub/access.h>
|
||||||
|
@ -169,7 +172,7 @@ namespace {
|
||||||
const agi::Context *c = get_context(L);
|
const agi::Context *c = get_context(L);
|
||||||
int ms = lua_tointeger(L, -1);
|
int ms = lua_tointeger(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
if (c && c->videoController->TimecodesLoaded())
|
if (c && c->project->Timecodes().IsLoaded())
|
||||||
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
|
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
|
||||||
else
|
else
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
|
@ -182,7 +185,7 @@ namespace {
|
||||||
const agi::Context *c = get_context(L);
|
const agi::Context *c = get_context(L);
|
||||||
int frame = lua_tointeger(L, -1);
|
int frame = lua_tointeger(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
if (c && c->videoController->TimecodesLoaded())
|
if (c && c->project->Timecodes().IsLoaded())
|
||||||
push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
|
push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
|
||||||
else
|
else
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
|
@ -192,9 +195,10 @@ namespace {
|
||||||
int video_size(lua_State *L)
|
int video_size(lua_State *L)
|
||||||
{
|
{
|
||||||
const agi::Context *c = get_context(L);
|
const agi::Context *c = get_context(L);
|
||||||
if (c && c->videoController->IsLoaded()) {
|
if (c && c->project->VideoProvider()) {
|
||||||
push_value(L, c->videoController->GetWidth());
|
auto provider = c->project->VideoProvider();
|
||||||
push_value(L, c->videoController->GetHeight());
|
push_value(L, provider->GetWidth());
|
||||||
|
push_value(L, provider->GetHeight());
|
||||||
push_value(L, c->videoController->GetAspectRatioValue());
|
push_value(L, c->videoController->GetAspectRatioValue());
|
||||||
push_value(L, (int)c->videoController->GetAspectRatioType());
|
push_value(L, (int)c->videoController->GetAspectRatioType());
|
||||||
return 4;
|
return 4;
|
||||||
|
@ -209,7 +213,7 @@ namespace {
|
||||||
{
|
{
|
||||||
const agi::Context *c = get_context(L);
|
const agi::Context *c = get_context(L);
|
||||||
if (c)
|
if (c)
|
||||||
push_value(L, c->videoController->GetKeyFrames());
|
push_value(L, c->project->Keyframes());
|
||||||
else
|
else
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -40,10 +40,11 @@
|
||||||
#include "frame_main.h"
|
#include "frame_main.h"
|
||||||
#include "grid_column.h"
|
#include "grid_column.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/util.h>
|
#include <libaegisub/util.h>
|
||||||
|
|
||||||
|
@ -123,30 +124,32 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
||||||
UpdateStyle();
|
UpdateStyle();
|
||||||
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
|
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
|
||||||
|
|
||||||
connections.push_back(context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this));
|
connections = agi::signal::make_vector({
|
||||||
connections.push_back(context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this));
|
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this),
|
||||||
connections.push_back(context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this));
|
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this),
|
||||||
|
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this),
|
||||||
|
|
||||||
connections.push_back(context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this));
|
context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this),
|
||||||
connections.push_back(context->selectionController->AddSelectionListener([&]{ Refresh(false); }));
|
context->selectionController->AddSelectionListener([&]{ Refresh(false); }),
|
||||||
|
|
||||||
connections.push_back(OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this),
|
||||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this));
|
OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this),
|
||||||
|
|
||||||
connections.push_back(OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this));
|
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this),
|
||||||
connections.push_back(OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }));
|
OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }),
|
||||||
|
});
|
||||||
|
|
||||||
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
|
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
|
||||||
}
|
}
|
||||||
|
@ -634,10 +637,10 @@ AssDialogue *BaseGrid::GetDialogue(int n) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseGrid::IsDisplayed(const AssDialogue *line) const {
|
bool BaseGrid::IsDisplayed(const AssDialogue *line) const {
|
||||||
if (!context->videoController->IsLoaded()) return false;
|
if (!context->project->VideoProvider()) return false;
|
||||||
int frame = context->videoController->GetFrameN();
|
int frame = context->videoController->GetFrameN();
|
||||||
return context->videoController->FrameAtTime(line->Start,agi::vfr::START) <= frame
|
return context->project->Timecodes().FrameAtTime(line->Start, agi::vfr::START) <= frame
|
||||||
&& context->videoController->FrameAtTime(line->End,agi::vfr::END) >= frame;
|
&& context->project->Timecodes().FrameAtTime(line->End, agi::vfr::END) >= frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseGrid::OnCharHook(wxKeyEvent &event) {
|
void BaseGrid::OnCharHook(wxKeyEvent &event) {
|
||||||
|
|
|
@ -36,7 +36,6 @@
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/log.h>
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
#include "../audio_controller.h"
|
|
||||||
#include "../compat.h"
|
#include "../compat.h"
|
||||||
#include "../dialog_about.h"
|
#include "../dialog_about.h"
|
||||||
#include "../dialog_detached_video.h"
|
#include "../dialog_detached_video.h"
|
||||||
|
@ -48,9 +47,9 @@
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../preferences.h"
|
#include "../preferences.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using cmd::Command;
|
using cmd::Command;
|
||||||
|
@ -79,7 +78,7 @@ struct app_display_audio_subs final : public Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->audioController->IsAudioOpen();
|
return !!c->project->AudioProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsActive(const agi::Context *c) override {
|
bool IsActive(const agi::Context *c) override {
|
||||||
|
@ -99,7 +98,7 @@ struct app_display_full final : public Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->audioController->IsAudioOpen() && c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
|
return c->project->AudioProvider() && c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsActive(const agi::Context *c) override {
|
bool IsActive(const agi::Context *c) override {
|
||||||
|
@ -115,7 +114,7 @@ struct app_display_subs final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->frame->SetDisplayMode(0,0);
|
c->frame->SetDisplayMode(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsActive(const agi::Context *c) override {
|
bool IsActive(const agi::Context *c) override {
|
||||||
|
@ -131,11 +130,11 @@ struct app_display_video_subs final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->frame->SetDisplayMode(1,0);
|
c->frame->SetDisplayMode(1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
|
return c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsActive(const agi::Context *c) override {
|
bool IsActive(const agi::Context *c) override {
|
||||||
|
|
|
@ -37,15 +37,18 @@
|
||||||
#include "../audio_karaoke.h"
|
#include "../audio_karaoke.h"
|
||||||
#include "../audio_timing.h"
|
#include "../audio_timing.h"
|
||||||
#include "../compat.h"
|
#include "../compat.h"
|
||||||
|
#include "../include/aegisub/audio_provider.h"
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../selection_controller.h"
|
#include "../selection_controller.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
|
#include <libaegisub/io.h>
|
||||||
|
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
|
@ -55,7 +58,7 @@ namespace {
|
||||||
struct validate_audio_open : public Command {
|
struct validate_audio_open : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->audioController->IsAudioOpen();
|
return !!c->project->AudioProvider();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,35 +70,11 @@ struct audio_close final : public validate_audio_open {
|
||||||
STR_HELP("Close the currently open audio file")
|
STR_HELP("Close the currently open audio file")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->audioController->CloseAudio();
|
c->project->CloseAudio();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
struct audio_open final : public Command {
|
||||||
struct audio_open_from_file : public Command {
|
|
||||||
protected:
|
|
||||||
void do_open(agi::Context *c, agi::fs::path const& filename) {
|
|
||||||
try {
|
|
||||||
c->audioController->OpenAudio(filename);
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) {}
|
|
||||||
catch (agi::fs::FileNotFound const& e) {
|
|
||||||
wxMessageBox(_("The audio file was not found: ") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
catch (agi::AudioDataNotFoundError const& e) {
|
|
||||||
wxMessageBox(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
catch (agi::AudioProviderOpenError const& e) {
|
|
||||||
wxMessageBox(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
catch (agi::Exception const& e) {
|
|
||||||
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct audio_open final : public audio_open_from_file {
|
|
||||||
CMD_NAME("audio/open")
|
CMD_NAME("audio/open")
|
||||||
CMD_ICON(open_audio_menu)
|
CMD_ICON(open_audio_menu)
|
||||||
STR_MENU("&Open Audio File...")
|
STR_MENU("&Open Audio File...")
|
||||||
|
@ -107,9 +86,8 @@ struct audio_open final : public audio_open_from_file {
|
||||||
+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
|
+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
|
||||||
+ _("All Files") + " (*.*)|*.*";
|
+ _("All Files") + " (*.*)|*.*";
|
||||||
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);
|
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);
|
||||||
if (filename.empty()) return;
|
if (!filename.empty())
|
||||||
|
c->project->LoadAudio(filename);
|
||||||
do_open(c, filename);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,12 +98,7 @@ struct audio_open_blank final : public Command {
|
||||||
STR_HELP("Open a 150 minutes blank audio clip, for debugging")
|
STR_HELP("Open a 150 minutes blank audio clip, for debugging")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
try {
|
c->project->LoadAudio("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000");
|
||||||
c->audioController->OpenAudio("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000");
|
|
||||||
}
|
|
||||||
catch (agi::Exception const& e) {
|
|
||||||
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,16 +109,11 @@ struct audio_open_noise final : public Command {
|
||||||
STR_HELP("Open a 150 minutes noise-filled audio clip, for debugging")
|
STR_HELP("Open a 150 minutes noise-filled audio clip, for debugging")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
try {
|
c->project->LoadAudio("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000");
|
||||||
c->audioController->OpenAudio("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000");
|
|
||||||
}
|
|
||||||
catch (agi::Exception const& e) {
|
|
||||||
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct audio_open_video final : public audio_open_from_file {
|
struct audio_open_video final : public Command {
|
||||||
CMD_NAME("audio/open/video")
|
CMD_NAME("audio/open/video")
|
||||||
CMD_ICON(open_audio_from_video_menu)
|
CMD_ICON(open_audio_from_video_menu)
|
||||||
STR_MENU("Open Audio from &Video")
|
STR_MENU("Open Audio from &Video")
|
||||||
|
@ -154,11 +122,11 @@ struct audio_open_video final : public audio_open_from_file {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded();
|
return !c->project->VideoName().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
do_open(c, c->videoController->GetVideoName());
|
c->project->LoadAudio(c->project->VideoName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -194,6 +162,29 @@ struct audio_view_waveform final : public Command {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class writer {
|
||||||
|
agi::io::Save outfile;
|
||||||
|
std::ostream& out;
|
||||||
|
|
||||||
|
public:
|
||||||
|
writer(agi::fs::path const& filename) : outfile(filename, true), out(outfile.Get()) { }
|
||||||
|
|
||||||
|
template<int N>
|
||||||
|
void write(const char(&str)[N]) {
|
||||||
|
out.write(str, N - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(std::vector<char> const& data) {
|
||||||
|
out.write(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Dest, typename Src>
|
||||||
|
void write(Src v) {
|
||||||
|
auto converted = static_cast<Dest>(v);
|
||||||
|
out.write(reinterpret_cast<char *>(&converted), sizeof(Dest));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct audio_save_clip final : public Command {
|
struct audio_save_clip final : public Command {
|
||||||
CMD_NAME("audio/save/clip")
|
CMD_NAME("audio/save/clip")
|
||||||
STR_MENU("Create audio clip")
|
STR_MENU("Create audio clip")
|
||||||
|
@ -202,22 +193,55 @@ struct audio_save_clip final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->audioController->IsAudioOpen() && !c->selectionController->GetSelectedSet().empty();
|
return c->project->AudioProvider() && !c->selectionController->GetSelectedSet().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
auto const& sel = c->selectionController->GetSelectedSet();
|
auto const& sel = c->selectionController->GetSelectedSet();
|
||||||
if (sel.empty()) return;
|
if (sel.empty()) return;
|
||||||
|
|
||||||
|
auto filename = SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent);
|
||||||
|
if (filename.empty()) return;
|
||||||
|
|
||||||
AssTime start = INT_MAX, end = 0;
|
AssTime start = INT_MAX, end = 0;
|
||||||
for (auto line : sel) {
|
for (auto line : sel) {
|
||||||
start = std::min(start, line->Start);
|
start = std::min(start, line->Start);
|
||||||
end = std::max(end, line->End);
|
end = std::max(end, line->End);
|
||||||
}
|
}
|
||||||
|
|
||||||
c->audioController->SaveClip(
|
auto provider = c->project->AudioProvider();
|
||||||
SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent),
|
|
||||||
TimeRange(start, end));
|
auto start_sample = (start * provider->GetSampleRate() + 999) / 1000;
|
||||||
|
auto end_sample = (end * provider->GetSampleRate() + 999) / 1000;
|
||||||
|
if (start_sample >= provider->GetNumSamples() || start_sample <= end_sample) return;
|
||||||
|
|
||||||
|
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
|
||||||
|
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
|
||||||
|
|
||||||
|
writer out{filename};
|
||||||
|
out.write("RIFF");
|
||||||
|
out.write<int32_t>(bufsize + 36);
|
||||||
|
|
||||||
|
out.write("WAVEfmt ");
|
||||||
|
out.write<int32_t>(16); // Size of chunk
|
||||||
|
out.write<int16_t>(1); // compression format (PCM)
|
||||||
|
out.write<int16_t>(provider->GetChannels());
|
||||||
|
out.write<int32_t>(provider->GetSampleRate());
|
||||||
|
out.write<int32_t>(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample());
|
||||||
|
out.write<int16_t>(provider->GetChannels() * provider->GetBytesPerSample());
|
||||||
|
out.write<int16_t>(provider->GetBytesPerSample() * 8);
|
||||||
|
|
||||||
|
out.write("data");
|
||||||
|
out.write<int32_t>(bufsize);
|
||||||
|
|
||||||
|
// samples per read
|
||||||
|
size_t spr = 65536 / bytes_per_sample;
|
||||||
|
std::vector<char> buf(bufsize);
|
||||||
|
for (int64_t i = start_sample; i < end_sample; i += spr) {
|
||||||
|
buf.resize(std::min<size_t>(spr, end_sample - i));
|
||||||
|
provider->GetAudio(&buf[0], i, buf.size());
|
||||||
|
out.write(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,14 @@
|
||||||
#include "../initial_line_state.h"
|
#include "../initial_line_state.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../search_replace_engine.h"
|
#include "../search_replace_engine.h"
|
||||||
#include "../selection_controller.h"
|
#include "../selection_controller.h"
|
||||||
#include "../subs_controller.h"
|
#include "../subs_controller.h"
|
||||||
#include "../subs_edit_ctrl.h"
|
#include "../subs_edit_ctrl.h"
|
||||||
#include "../text_selection_controller.h"
|
#include "../text_selection_controller.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/address_of_adaptor.h>
|
#include <libaegisub/address_of_adaptor.h>
|
||||||
#include <libaegisub/of_type_adaptor.h>
|
#include <libaegisub/of_type_adaptor.h>
|
||||||
|
@ -83,7 +84,7 @@ struct validate_sel_nonempty : public Command {
|
||||||
struct validate_video_and_sel_nonempty : public Command {
|
struct validate_video_and_sel_nonempty : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded() && !c->selectionController->GetSelectedSet().empty();
|
return c->project->VideoProvider() && !c->selectionController->GetSelectedSet().empty();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,10 @@
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
|
||||||
|
|
||||||
|
#include <libaegisub/keyframe.h>
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -51,11 +52,11 @@ struct keyframe_close final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->OverKeyFramesLoaded();
|
return c->project->CanCloseKeyframes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->videoController->CloseKeyframes();
|
c->project->CloseKeyframes();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ struct keyframe_open final : public Command {
|
||||||
c->parent);
|
c->parent);
|
||||||
|
|
||||||
if (!filename.empty())
|
if (!filename.empty())
|
||||||
c->videoController->LoadKeyframes(filename);
|
c->project->LoadKeyframes(filename);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,13 +88,15 @@ struct keyframe_save final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->KeyFramesLoaded();
|
return !c->project->Keyframes().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
auto filename = SaveFileSelector(_("Save keyframes file"), "Path/Last/Keyframes", "", "*.key.txt", "Text files (*.txt)|*.txt", c->parent);
|
auto filename = SaveFileSelector(_("Save keyframes file"), "Path/Last/Keyframes", "", "*.key.txt", "Text files (*.txt)|*.txt", c->parent);
|
||||||
if (!filename.empty())
|
if (filename.empty()) return;
|
||||||
c->videoController->SaveKeyframes(filename);
|
|
||||||
|
agi::keyframe::Save(filename, c->project->Keyframes());
|
||||||
|
config::mru->Add("Keyframes", filename);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,19 +29,15 @@
|
||||||
|
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
|
|
||||||
#include "../audio_controller.h"
|
|
||||||
#include "../compat.h"
|
#include "../compat.h"
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../subs_controller.h"
|
#include "../subs_controller.h"
|
||||||
#include "../video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
#include <wx/event.h>
|
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using cmd::Command;
|
using cmd::Command;
|
||||||
|
|
||||||
|
@ -58,13 +54,7 @@ struct recent_audio_entry : public Command {
|
||||||
STR_HELP("Open recent audio")
|
STR_HELP("Open recent audio")
|
||||||
|
|
||||||
void operator()(agi::Context *c, int id) {
|
void operator()(agi::Context *c, int id) {
|
||||||
try {
|
c->project->LoadAudio(config::mru->GetEntry("Audio", id));
|
||||||
c->audioController->OpenAudio(config::mru->GetEntry("Audio", id));
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) { }
|
|
||||||
catch (agi::Exception const& e) {
|
|
||||||
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,7 +65,7 @@ struct recent_keyframes_entry : public Command {
|
||||||
STR_HELP("Open recent keyframes")
|
STR_HELP("Open recent keyframes")
|
||||||
|
|
||||||
void operator()(agi::Context *c, int id) {
|
void operator()(agi::Context *c, int id) {
|
||||||
c->videoController->LoadKeyframes(config::mru->GetEntry("Keyframes", id));
|
c->project->LoadKeyframes(config::mru->GetEntry("Keyframes", id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,7 +77,7 @@ struct recent_subtitle_entry : public Command {
|
||||||
|
|
||||||
void operator()(agi::Context *c, int id) {
|
void operator()(agi::Context *c, int id) {
|
||||||
if (c->subsController->TryToClose() == wxCANCEL) return;
|
if (c->subsController->TryToClose() == wxCANCEL) return;
|
||||||
c->subsController->Load(config::mru->GetEntry("Subtitle", id));
|
c->project->LoadSubtitles(config::mru->GetEntry("Subtitle", id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +88,7 @@ struct recent_timecodes_entry : public Command {
|
||||||
STR_HELP("Open recent timecodes")
|
STR_HELP("Open recent timecodes")
|
||||||
|
|
||||||
void operator()(agi::Context *c, int id) {
|
void operator()(agi::Context *c, int id) {
|
||||||
c->videoController->LoadTimecodes(config::mru->GetEntry("Timecodes", id));
|
c->project->LoadTimecodes(config::mru->GetEntry("Timecodes", id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,7 +99,7 @@ struct recent_video_entry : public Command {
|
||||||
STR_HELP("Open recent videos")
|
STR_HELP("Open recent videos")
|
||||||
|
|
||||||
void operator()(agi::Context *c, int id) {
|
void operator()(agi::Context *c, int id) {
|
||||||
c->videoController->SetVideo(config::mru->GetEntry("Video", id));
|
c->project->LoadVideo(config::mru->GetEntry("Video", id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -43,12 +43,13 @@
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../search_replace_engine.h"
|
#include "../search_replace_engine.h"
|
||||||
#include "../selection_controller.h"
|
#include "../selection_controller.h"
|
||||||
#include "../subs_controller.h"
|
#include "../subs_controller.h"
|
||||||
#include "../subtitle_format.h"
|
#include "../subtitle_format.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/address_of_adaptor.h>
|
#include <libaegisub/address_of_adaptor.h>
|
||||||
#include <libaegisub/charset_conv.h>
|
#include <libaegisub/charset_conv.h>
|
||||||
|
@ -71,7 +72,7 @@ struct validate_nonempty_selection : public Command {
|
||||||
struct validate_nonempty_selection_video_loaded : public Command {
|
struct validate_nonempty_selection_video_loaded : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded() && !c->selectionController->GetSelectedSet().empty();
|
return c->project->VideoProvider() && !c->selectionController->GetSelectedSet().empty();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -227,7 +228,7 @@ struct subtitle_new final : public Command {
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (c->subsController->TryToClose() != wxCANCEL)
|
if (c->subsController->TryToClose() != wxCANCEL)
|
||||||
c->subsController->Close();
|
c->project->CloseSubtitles();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -242,7 +243,7 @@ struct subtitle_open final : public Command {
|
||||||
if (c->subsController->TryToClose() == wxCANCEL) return;
|
if (c->subsController->TryToClose() == wxCANCEL) return;
|
||||||
auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "","", SubtitleFormat::GetWildcards(0), c->parent);
|
auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "","", SubtitleFormat::GetWildcards(0), c->parent);
|
||||||
if (!filename.empty())
|
if (!filename.empty())
|
||||||
c->subsController->Load(filename);
|
c->project->LoadSubtitles(filename);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -256,7 +257,7 @@ struct subtitle_open_autosave final : public Command {
|
||||||
if (c->subsController->TryToClose() == wxCANCEL) return;
|
if (c->subsController->TryToClose() == wxCANCEL) return;
|
||||||
DialogAutosave dialog(c->parent);
|
DialogAutosave dialog(c->parent);
|
||||||
if (dialog.ShowModal() == wxID_OK)
|
if (dialog.ShowModal() == wxID_OK)
|
||||||
c->subsController->Load(dialog.ChosenFile());
|
c->project->LoadSubtitles(dialog.ChosenFile());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -276,7 +277,7 @@ struct subtitle_open_charset final : public Command {
|
||||||
wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
|
wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
|
||||||
if (charset.empty()) return;
|
if (charset.empty()) return;
|
||||||
|
|
||||||
c->subsController->Load(filename, from_wx(charset));
|
c->project->LoadSubtitles(filename, from_wx(charset));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -289,11 +290,11 @@ struct subtitle_open_video final : public Command {
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (c->subsController->TryToClose() == wxCANCEL) return;
|
if (c->subsController->TryToClose() == wxCANCEL) return;
|
||||||
c->subsController->Load(c->videoController->GetVideoName(), "binary");
|
c->subsController->Load(c->project->VideoName(), "binary");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded() && c->videoController->HasSubtitles();
|
return c->project->CanLoadSubtitlesFromVideo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -384,7 +385,6 @@ struct subtitle_select_visible final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (!c->videoController->IsLoaded()) return;
|
|
||||||
c->videoController->Stop();
|
c->videoController->Stop();
|
||||||
|
|
||||||
Selection new_selection;
|
Selection new_selection;
|
||||||
|
@ -404,7 +404,7 @@ struct subtitle_select_visible final : public Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded();
|
return !!c->project->VideoProvider();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "../ass_dialogue.h"
|
#include "../ass_dialogue.h"
|
||||||
#include "../ass_file.h"
|
#include "../ass_file.h"
|
||||||
|
#include "../async_video_provider.h"
|
||||||
#include "../audio_controller.h"
|
#include "../audio_controller.h"
|
||||||
#include "../audio_timing.h"
|
#include "../audio_timing.h"
|
||||||
#include "../dialog_manager.h"
|
#include "../dialog_manager.h"
|
||||||
|
@ -40,38 +41,39 @@
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../selection_controller.h"
|
#include "../selection_controller.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using cmd::Command;
|
using cmd::Command;
|
||||||
|
|
||||||
struct validate_video_loaded : public Command {
|
struct validate_video_loaded : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded();
|
return !!c->project->VideoProvider();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct validate_adjoinable : public Command {
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
auto sel = c->selectionController->GetSortedSelection();
|
||||||
|
if (sel.empty()) return false;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < sel.size(); ++i) {
|
||||||
|
if (sel[i]->Row != sel[i - 1]->Row + 1)
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct validate_adjoinable : public Command {
|
void adjoin_lines(agi::Context *c, bool set_start) {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
|
||||||
bool Validate(const agi::Context *c) override {
|
|
||||||
auto sel = c->selectionController->GetSortedSelection();
|
|
||||||
if (sel.empty()) return false;
|
|
||||||
|
|
||||||
for (size_t i = 1; i < sel.size(); ++i) {
|
|
||||||
if (sel[i]->Row != sel[i - 1]->Row + 1)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void adjoin_lines(agi::Context *c, bool set_start) {
|
|
||||||
auto const& sel = c->selectionController->GetSelectedSet();
|
auto const& sel = c->selectionController->GetSelectedSet();
|
||||||
AssDialogue *prev = nullptr;
|
AssDialogue *prev = nullptr;
|
||||||
size_t seen = 0;
|
size_t seen = 0;
|
||||||
|
@ -129,8 +131,6 @@ struct time_frame_current final : public validate_video_loaded {
|
||||||
STR_HELP("Shift selection so that the active line starts at current frame")
|
STR_HELP("Shift selection so that the active line starts at current frame")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (!c->videoController->IsLoaded()) return;
|
|
||||||
|
|
||||||
auto const& sel = c->selectionController->GetSelectedSet();
|
auto const& sel = c->selectionController->GetSelectedSet();
|
||||||
const auto active_line = c->selectionController->GetActiveLine();
|
const auto active_line = c->selectionController->GetActiveLine();
|
||||||
|
|
||||||
|
@ -162,8 +162,7 @@ struct time_shift final : public Command {
|
||||||
|
|
||||||
static void snap_subs_video(agi::Context *c, bool set_start) {
|
static void snap_subs_video(agi::Context *c, bool set_start) {
|
||||||
auto const& sel = c->selectionController->GetSelectedSet();
|
auto const& sel = c->selectionController->GetSelectedSet();
|
||||||
|
if (sel.empty()) return;
|
||||||
if (!c->videoController->IsLoaded() || sel.empty()) return;
|
|
||||||
|
|
||||||
int start = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::START);
|
int start = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::START);
|
||||||
int end = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::END);
|
int end = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::END);
|
||||||
|
@ -198,19 +197,19 @@ struct time_snap_scene final : public validate_video_loaded {
|
||||||
STR_HELP("Set start and end of subtitles to the keyframes around current video frame")
|
STR_HELP("Set start and end of subtitles to the keyframes around current video frame")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
VideoContext *con = c->videoController.get();
|
auto const& keyframes = c->project->Keyframes();
|
||||||
if (!con->IsLoaded() || !con->KeyFramesLoaded()) return;
|
if (keyframes.empty()) return;
|
||||||
|
|
||||||
|
VideoController *con = c->videoController.get();
|
||||||
int curFrame = con->GetFrameN();
|
int curFrame = con->GetFrameN();
|
||||||
int prev = 0;
|
int prev = 0;
|
||||||
int next = 0;
|
int next = 0;
|
||||||
|
|
||||||
auto const& keyframes = con->GetKeyFrames();
|
|
||||||
if (curFrame < keyframes.front())
|
if (curFrame < keyframes.front())
|
||||||
next = keyframes.front();
|
next = keyframes.front();
|
||||||
else if (curFrame >= keyframes.back()) {
|
else if (curFrame >= keyframes.back()) {
|
||||||
prev = keyframes.back();
|
prev = keyframes.back();
|
||||||
next = con->GetLength();
|
next = c->project->VideoProvider()->GetFrameCount();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto kf = std::lower_bound(keyframes.begin(), keyframes.end(), curFrame);
|
auto kf = std::lower_bound(keyframes.begin(), keyframes.end(), curFrame);
|
||||||
|
|
|
@ -31,14 +31,18 @@
|
||||||
|
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
|
|
||||||
|
#include "../async_video_provider.h"
|
||||||
|
#include "../compat.h"
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using cmd::Command;
|
using cmd::Command;
|
||||||
|
|
||||||
|
@ -51,11 +55,11 @@ struct timecode_close final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->OverTimecodesLoaded();
|
return c->project->CanCloseTimecodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->videoController->CloseTimecodes();
|
c->project->CloseTimecodes();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,7 +74,7 @@ struct timecode_open final : public Command {
|
||||||
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
|
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
|
||||||
auto filename = OpenFileSelector(_("Open Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
auto filename = OpenFileSelector(_("Open Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
||||||
if (!filename.empty())
|
if (!filename.empty())
|
||||||
c->videoController->LoadTimecodes(filename);
|
c->project->LoadTimecodes(filename);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,14 +87,22 @@ struct timecode_save final : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->TimecodesLoaded();
|
return c->project->Timecodes().IsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
|
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
|
||||||
auto filename = SaveFileSelector(_("Save Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
auto filename = SaveFileSelector(_("Save Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
||||||
if (!filename.empty())
|
if (filename.empty()) return;
|
||||||
c->videoController->SaveTimecodes(filename);
|
|
||||||
|
try {
|
||||||
|
auto provider = c->project->VideoProvider();
|
||||||
|
c->project->Timecodes().Save(filename, provider ? provider->GetFrameCount() : -1);
|
||||||
|
config::mru->Add("Timecodes", filename);
|
||||||
|
}
|
||||||
|
catch (agi::Exception const& err) {
|
||||||
|
wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, c->parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
#include "../resolution_resampler.h"
|
#include "../resolution_resampler.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/path.h>
|
#include <libaegisub/path.h>
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "../ass_dialogue.h"
|
#include "../ass_dialogue.h"
|
||||||
#include "../ass_time.h"
|
#include "../ass_time.h"
|
||||||
|
#include "../async_video_provider.h"
|
||||||
#include "../compat.h"
|
#include "../compat.h"
|
||||||
#include "../dialog_detached_video.h"
|
#include "../dialog_detached_video.h"
|
||||||
#include "../dialog_dummy_video.h"
|
#include "../dialog_dummy_video.h"
|
||||||
|
@ -44,10 +45,11 @@
|
||||||
#include "../include/aegisub/subtitles_provider.h"
|
#include "../include/aegisub/subtitles_provider.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../selection_controller.h"
|
#include "../selection_controller.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../video_box.h"
|
#include "../video_box.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
#include "../video_display.h"
|
#include "../video_display.h"
|
||||||
#include "../video_frame.h"
|
#include "../video_frame.h"
|
||||||
#include "../video_slider.h"
|
#include "../video_slider.h"
|
||||||
|
@ -61,7 +63,6 @@
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/algorithm/string/split.hpp>
|
#include <boost/algorithm/string/split.hpp>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
#include <wx/clipbrd.h>
|
#include <wx/clipbrd.h>
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <wx/textdlg.h>
|
#include <wx/textdlg.h>
|
||||||
|
@ -72,14 +73,14 @@ namespace {
|
||||||
struct validator_video_loaded : public Command {
|
struct validator_video_loaded : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded();
|
return !!c->project->VideoProvider();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct validator_video_attached : public Command {
|
struct validator_video_attached : public Command {
|
||||||
CMD_TYPE(COMMAND_VALIDATE)
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
|
return !!c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -206,7 +207,7 @@ struct video_close final : public validator_video_loaded {
|
||||||
STR_HELP("Close the currently open video file")
|
STR_HELP("Close the currently open video file")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->videoController->SetVideo("");
|
c->project->CloseVideo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -291,6 +292,10 @@ struct video_focus_seek final : public validator_video_loaded {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wxImage get_image(agi::Context *c, bool raw) {
|
||||||
|
return GetImage(*c->project->VideoProvider()->GetFrame(c->videoController->GetFrameN(), raw));
|
||||||
|
}
|
||||||
|
|
||||||
struct video_frame_copy final : public validator_video_loaded {
|
struct video_frame_copy final : public validator_video_loaded {
|
||||||
CMD_NAME("video/frame/copy")
|
CMD_NAME("video/frame/copy")
|
||||||
STR_MENU("Copy image to Clipboard")
|
STR_MENU("Copy image to Clipboard")
|
||||||
|
@ -298,7 +303,7 @@ struct video_frame_copy final : public validator_video_loaded {
|
||||||
STR_HELP("Copy the currently displayed frame to the clipboard")
|
STR_HELP("Copy the currently displayed frame to the clipboard")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN())), 24));
|
SetClipboard(wxBitmap(get_image(c, false), 24));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -309,7 +314,7 @@ struct video_frame_copy_raw final : public validator_video_loaded {
|
||||||
STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles")
|
STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), true)), 24));
|
SetClipboard(wxBitmap(get_image(c, true), 24));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -360,10 +365,10 @@ struct video_frame_next_keyframe final : public validator_video_loaded {
|
||||||
STR_HELP("Seek to the next keyframe")
|
STR_HELP("Seek to the next keyframe")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
auto const& kf = c->videoController->GetKeyFrames();
|
auto const& kf = c->project->Keyframes();
|
||||||
auto pos = lower_bound(kf.begin(), kf.end(), c->videoController->GetFrameN() + 1);
|
auto pos = lower_bound(kf.begin(), kf.end(), c->videoController->GetFrameN() + 1);
|
||||||
|
|
||||||
c->videoController->JumpToFrame(pos == kf.end() ? c->videoController->GetLength() - 1 : *pos);
|
c->videoController->JumpToFrame(pos == kf.end() ? c->project->VideoProvider()->GetFrameCount() - 1 : *pos);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -427,7 +432,7 @@ struct video_frame_prev_keyframe final : public validator_video_loaded {
|
||||||
STR_HELP("Seek to the previous keyframe")
|
STR_HELP("Seek to the previous keyframe")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
auto const& kf = c->videoController->GetKeyFrames();
|
auto const& kf = c->project->Keyframes();
|
||||||
if (kf.empty()) {
|
if (kf.empty()) {
|
||||||
c->videoController->JumpToFrame(0);
|
c->videoController->JumpToFrame(0);
|
||||||
return;
|
return;
|
||||||
|
@ -459,7 +464,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
|
||||||
auto option = OPT_GET("Path/Screenshot")->GetString();
|
auto option = OPT_GET("Path/Screenshot")->GetString();
|
||||||
agi::fs::path basepath;
|
agi::fs::path basepath;
|
||||||
|
|
||||||
auto videoname = c->videoController->GetVideoName();
|
auto videoname = c->project->VideoName();
|
||||||
bool is_dummy = boost::starts_with(videoname.string(), "?dummy");
|
bool is_dummy = boost::starts_with(videoname.string(), "?dummy");
|
||||||
|
|
||||||
// Is it a path specifier and not an actual fixed path?
|
// Is it a path specifier and not an actual fixed path?
|
||||||
|
@ -490,7 +495,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
|
||||||
path = str(boost::format("%s_%03d_%d.png") % basepath.string() % session_shot_count++ % c->videoController->GetFrameN());
|
path = str(boost::format("%s_%03d_%d.png") % basepath.string() % session_shot_count++ % c->videoController->GetFrameN());
|
||||||
} while (agi::fs::FileExists(path));
|
} while (agi::fs::FileExists(path));
|
||||||
|
|
||||||
GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), raw)).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
|
get_image(c, raw).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct video_frame_save final : public validator_video_loaded {
|
struct video_frame_save final : public validator_video_loaded {
|
||||||
|
@ -524,10 +529,8 @@ struct video_jump final : public validator_video_loaded {
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
c->videoController->Stop();
|
c->videoController->Stop();
|
||||||
if (c->videoController->IsLoaded()) {
|
DialogJumpTo(c).ShowModal();
|
||||||
DialogJumpTo(c).ShowModal();
|
c->videoSlider->SetFocus();
|
||||||
c->videoSlider->SetFocus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -539,9 +542,8 @@ struct video_jump_end final : public validator_video_loaded {
|
||||||
STR_HELP("Jump the video to the end frame of current subtitle")
|
STR_HELP("Jump the video to the end frame of current subtitle")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (AssDialogue *active_line = c->selectionController->GetActiveLine()) {
|
if (auto active_line = c->selectionController->GetActiveLine())
|
||||||
c->videoController->JumpToTime(active_line->End, agi::vfr::END);
|
c->videoController->JumpToTime(active_line->End, agi::vfr::END);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -553,7 +555,7 @@ struct video_jump_start final : public validator_video_loaded {
|
||||||
STR_HELP("Jump the video to the start frame of current subtitle")
|
STR_HELP("Jump the video to the start frame of current subtitle")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
if (AssDialogue *active_line = c->selectionController->GetActiveLine())
|
if (auto active_line = c->selectionController->GetActiveLine())
|
||||||
c->videoController->JumpToTime(active_line->Start);
|
c->videoController->JumpToTime(active_line->Start);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -570,7 +572,7 @@ struct video_open final : public Command {
|
||||||
+ _("All Files") + " (*.*)|*.*";
|
+ _("All Files") + " (*.*)|*.*";
|
||||||
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
|
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
|
||||||
if (!filename.empty())
|
if (!filename.empty())
|
||||||
c->videoController->SetVideo(filename);
|
c->project->LoadVideo(filename);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -584,7 +586,7 @@ struct video_open_dummy final : public Command {
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
std::string fn = DialogDummyVideo::CreateDummyVideo(c->parent);
|
std::string fn = DialogDummyVideo::CreateDummyVideo(c->parent);
|
||||||
if (!fn.empty())
|
if (!fn.empty())
|
||||||
c->videoController->SetVideo(fn);
|
c->project->LoadVideo(fn);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
|
#include "../project.h"
|
||||||
#include "../video_box.h"
|
#include "../video_box.h"
|
||||||
#include "../video_context.h"
|
#include "../video_controller.h"
|
||||||
#include "../video_display.h"
|
#include "../video_display.h"
|
||||||
#include "../visual_tool_clip.h"
|
#include "../visual_tool_clip.h"
|
||||||
#include "../visual_tool_cross.h"
|
#include "../visual_tool_cross.h"
|
||||||
|
@ -39,7 +40,7 @@ namespace {
|
||||||
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoController->IsLoaded();
|
return !!c->project->VideoProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsActive(const agi::Context *c) override {
|
bool IsActive(const agi::Context *c) override {
|
||||||
|
|
|
@ -21,11 +21,12 @@
|
||||||
#include "auto4_base.h"
|
#include "auto4_base.h"
|
||||||
#include "dialog_manager.h"
|
#include "dialog_manager.h"
|
||||||
#include "initial_line_state.h"
|
#include "initial_line_state.h"
|
||||||
|
#include "project.h"
|
||||||
#include "search_replace_engine.h"
|
#include "search_replace_engine.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "text_selection_controller.h"
|
#include "text_selection_controller.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
@ -34,10 +35,11 @@ Context::Context()
|
||||||
: ass(make_unique<AssFile>())
|
: ass(make_unique<AssFile>())
|
||||||
, textSelectionController(make_unique<TextSelectionController>())
|
, textSelectionController(make_unique<TextSelectionController>())
|
||||||
, subsController(make_unique<SubsController>(this))
|
, subsController(make_unique<SubsController>(this))
|
||||||
|
, project(make_unique<Project>(this))
|
||||||
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
|
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
|
||||||
, videoController(make_unique<VideoContext>(this))
|
|
||||||
, audioController(make_unique<AudioController>(this))
|
|
||||||
, selectionController(make_unique<SelectionController>(this))
|
, selectionController(make_unique<SelectionController>(this))
|
||||||
|
, videoController(make_unique<VideoController>(this))
|
||||||
|
, audioController(make_unique<AudioController>(this))
|
||||||
, initialLineState(make_unique<InitialLineState>(this))
|
, initialLineState(make_unique<InitialLineState>(this))
|
||||||
, search(make_unique<SearchReplaceEngine>(this))
|
, search(make_unique<SearchReplaceEngine>(this))
|
||||||
, dialog(make_unique<DialogManager>())
|
, dialog(make_unique<DialogManager>())
|
||||||
|
|
|
@ -38,9 +38,10 @@
|
||||||
#include "include/aegisub/hotkey.h"
|
#include "include/aegisub/hotkey.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "persist_location.h"
|
#include "persist_location.h"
|
||||||
|
#include "project.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_box.h"
|
#include "video_box.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
@ -55,12 +56,12 @@ DialogDetachedVideo::DialogDetachedVideo(agi::Context *context)
|
||||||
, context(context)
|
, context(context)
|
||||||
, old_display(context->videoDisplay)
|
, old_display(context->videoDisplay)
|
||||||
, old_slider(context->videoSlider)
|
, old_slider(context->videoSlider)
|
||||||
, video_open(context->videoController->AddVideoOpenListener(&DialogDetachedVideo::OnVideoOpen, this))
|
, video_open(context->project->AddVideoProviderListener(&DialogDetachedVideo::OnVideoOpen, this))
|
||||||
{
|
{
|
||||||
// Set obscure stuff
|
// Set obscure stuff
|
||||||
SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES);
|
SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES);
|
||||||
|
|
||||||
SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
|
SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring()));
|
||||||
|
|
||||||
old_display->Unload();
|
old_display->Unload();
|
||||||
|
|
||||||
|
@ -108,8 +109,7 @@ void DialogDetachedVideo::OnClose(wxCloseEvent &evt) {
|
||||||
|
|
||||||
OPT_SET("Video/Detached/Enabled")->SetBool(false);
|
OPT_SET("Video/Detached/Enabled")->SetBool(false);
|
||||||
|
|
||||||
if (context->videoController->IsLoaded())
|
context->videoController->JumpToFrame(context->videoController->GetFrameN());
|
||||||
context->videoController->JumpToFrame(context->videoController->GetFrameN());
|
|
||||||
|
|
||||||
evt.Skip();
|
evt.Skip();
|
||||||
}
|
}
|
||||||
|
@ -128,8 +128,8 @@ void DialogDetachedVideo::OnKeyDown(wxKeyEvent &evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogDetachedVideo::OnVideoOpen() {
|
void DialogDetachedVideo::OnVideoOpen() {
|
||||||
if (context->videoController->IsLoaded())
|
if (context->project->VideoProvider())
|
||||||
SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
|
SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring()));
|
||||||
else {
|
else {
|
||||||
Close();
|
Close();
|
||||||
OPT_SET("Video/Detached/Enabled")->SetBool(true);
|
OPT_SET("Video/Detached/Enabled")->SetBool(true);
|
||||||
|
|
|
@ -34,12 +34,14 @@
|
||||||
|
|
||||||
#include "dialog_jumpto.h"
|
#include "dialog_jumpto.h"
|
||||||
|
|
||||||
#include "include/aegisub/context.h"
|
|
||||||
#include "ass_time.h"
|
#include "ass_time.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
|
#include "include/aegisub/context.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
|
#include "project.h"
|
||||||
#include "timeedit_ctrl.h"
|
#include "timeedit_ctrl.h"
|
||||||
#include "validators.h"
|
#include "validators.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
@ -57,7 +59,7 @@ DialogJumpTo::DialogJumpTo(agi::Context *c)
|
||||||
auto LabelTime = new wxStaticText(this, -1, _("Time: "));
|
auto LabelTime = new wxStaticText(this, -1, _("Time: "));
|
||||||
|
|
||||||
JumpFrame = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(-1,-1),wxTE_PROCESS_ENTER, IntValidator((int)jumpframe));
|
JumpFrame = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(-1,-1),wxTE_PROCESS_ENTER, IntValidator((int)jumpframe));
|
||||||
JumpFrame->SetMaxLength(std::to_string(c->videoController->GetLength() - 1).size());
|
JumpFrame->SetMaxLength(std::to_string(c->project->VideoProvider()->GetFrameCount() - 1).size());
|
||||||
JumpTime = new TimeEdit(this, -1, c, AssTime(c->videoController->TimeAtFrame(jumpframe)).GetAssFormated(), wxSize(-1,-1));
|
JumpTime = new TimeEdit(this, -1, c, AssTime(c->videoController->TimeAtFrame(jumpframe)).GetAssFormated(), wxSize(-1,-1));
|
||||||
|
|
||||||
auto TimesSizer = new wxGridSizer(2, 5, 5);
|
auto TimesSizer = new wxGridSizer(2, 5, 5);
|
||||||
|
@ -95,7 +97,7 @@ void DialogJumpTo::OnInitDialog(wxInitDialogEvent&) {
|
||||||
|
|
||||||
void DialogJumpTo::OnOK(wxCommandEvent &) {
|
void DialogJumpTo::OnOK(wxCommandEvent &) {
|
||||||
EndModal(0);
|
EndModal(0);
|
||||||
c->videoController->JumpToFrame(std::min<int>(jumpframe, c->videoController->GetLength() - 1));
|
c->videoController->JumpToFrame(jumpframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogJumpTo::OnEditTime (wxCommandEvent &) {
|
void DialogJumpTo::OnEditTime (wxCommandEvent &) {
|
||||||
|
|
|
@ -35,13 +35,14 @@
|
||||||
#include "dialog_properties.h"
|
#include "dialog_properties.h"
|
||||||
|
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
|
#include "project.h"
|
||||||
#include "resolution_resampler.h"
|
#include "resolution_resampler.h"
|
||||||
#include "validators.h"
|
#include "validators.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
@ -80,7 +81,7 @@ DialogProperties::DialogProperties(agi::Context *c)
|
||||||
ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,IntValidator(c->ass->GetScriptInfoAsInt("PlayResY")));
|
ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,IntValidator(c->ass->GetScriptInfoAsInt("PlayResY")));
|
||||||
|
|
||||||
wxButton *FromVideo = new wxButton(this,-1,_("From &video"));
|
wxButton *FromVideo = new wxButton(this,-1,_("From &video"));
|
||||||
if (!c->videoController->IsLoaded())
|
if (!c->project->VideoProvider())
|
||||||
FromVideo->Enable(false);
|
FromVideo->Enable(false);
|
||||||
else
|
else
|
||||||
FromVideo->Bind(wxEVT_BUTTON, &DialogProperties::OnSetFromVideo, this);
|
FromVideo->Bind(wxEVT_BUTTON, &DialogProperties::OnSetFromVideo, this);
|
||||||
|
@ -172,6 +173,6 @@ int DialogProperties::SetInfoIfDifferent(std::string const& key, std::string con
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogProperties::OnSetFromVideo(wxCommandEvent &) {
|
void DialogProperties::OnSetFromVideo(wxCommandEvent &) {
|
||||||
ResX->SetValue(std::to_wstring(c->videoController->GetWidth()));
|
ResX->SetValue(std::to_wstring(c->project->VideoProvider()->GetWidth()));
|
||||||
ResY->SetValue(std::to_wstring(c->videoController->GetHeight()));
|
ResY->SetValue(std::to_wstring(c->project->VideoProvider()->GetHeight()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,14 @@
|
||||||
#include "dialog_resample.h"
|
#include "dialog_resample.h"
|
||||||
|
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "include/aegisub/video_provider.h"
|
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
|
#include "project.h"
|
||||||
#include "resolution_resampler.h"
|
#include "resolution_resampler.h"
|
||||||
#include "validators.h"
|
#include "validators.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <boost/range/size.hpp>
|
#include <boost/range/size.hpp>
|
||||||
#include <wx/checkbox.h>
|
#include <wx/checkbox.h>
|
||||||
|
@ -57,10 +57,10 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
|
||||||
settings.source_y = script_h;
|
settings.source_y = script_h;
|
||||||
settings.source_matrix = script_mat = MatrixFromString(c->ass->GetScriptInfo("YCbCr Matrix"));
|
settings.source_matrix = script_mat = MatrixFromString(c->ass->GetScriptInfo("YCbCr Matrix"));
|
||||||
|
|
||||||
if (c->videoController->IsLoaded()) {
|
if (auto provider = c->project->VideoProvider()) {
|
||||||
settings.dest_x = video_w = c->videoController->GetWidth();
|
settings.dest_x = video_w = provider->GetWidth();
|
||||||
settings.dest_y = video_h = c->videoController->GetHeight();
|
settings.dest_y = video_h = provider->GetHeight();
|
||||||
settings.dest_matrix = video_mat = MatrixFromString(c->videoController->GetProvider()->GetRealColorSpace());
|
settings.dest_matrix = video_mat = MatrixFromString(provider->GetRealColorSpace());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
settings.dest_x = script_w;
|
settings.dest_x = script_w;
|
||||||
|
@ -186,7 +186,7 @@ void DialogResample::SetSourceFromScript(wxCommandEvent&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogResample::UpdateButtons() {
|
void DialogResample::UpdateButtons() {
|
||||||
from_video->Enable(c->videoController->IsLoaded() &&
|
from_video->Enable(c->project->VideoProvider() &&
|
||||||
(dest_x->GetValue() != video_w || dest_y->GetValue() != video_h));
|
(dest_x->GetValue() != video_w || dest_y->GetValue() != video_h));
|
||||||
from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h);
|
from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h);
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "timeedit_ctrl.h"
|
#include "timeedit_ctrl.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/io.h>
|
#include <libaegisub/io.h>
|
||||||
|
@ -100,7 +100,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
|
||||||
, context(context)
|
, context(context)
|
||||||
, history_filename(config::path->Decode("?user/shift_history.json"))
|
, history_filename(config::path->Decode("?user/shift_history.json"))
|
||||||
, history(agi::make_unique<json::Array>())
|
, history(agi::make_unique<json::Array>())
|
||||||
, timecodes_loaded_slot(context->videoController->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this))
|
, timecodes_loaded_slot(context->project->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this))
|
||||||
, selected_set_changed_slot(context->selectionController->AddSelectionListener(&DialogShiftTimes::OnSelectedSetChanged, this))
|
, selected_set_changed_slot(context->selectionController->AddSelectionListener(&DialogShiftTimes::OnSelectedSetChanged, this))
|
||||||
{
|
{
|
||||||
SetIcon(GETICON(shift_times_toolbutton_16));
|
SetIcon(GETICON(shift_times_toolbutton_16));
|
||||||
|
@ -138,7 +138,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
|
||||||
clear_button->Bind(wxEVT_BUTTON, &DialogShiftTimes::OnClear, this);
|
clear_button->Bind(wxEVT_BUTTON, &DialogShiftTimes::OnClear, this);
|
||||||
|
|
||||||
// Set initial control states
|
// Set initial control states
|
||||||
OnTimecodesLoaded(context->videoController->FPS());
|
OnTimecodesLoaded(context->project->Timecodes());
|
||||||
OnSelectedSetChanged();
|
OnSelectedSetChanged();
|
||||||
LoadHistory();
|
LoadHistory();
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,9 @@
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "persist_location.h"
|
#include "persist_location.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
@ -111,11 +112,11 @@ DialogStyling::DialogStyling(agi::Context *context)
|
||||||
actions_box->AddStretchSpacer(1);
|
actions_box->AddStretchSpacer(1);
|
||||||
|
|
||||||
play_audio = new wxButton(this, -1, _("Play &Audio"));
|
play_audio = new wxButton(this, -1, _("Play &Audio"));
|
||||||
play_audio->Enable(c->audioController->IsAudioOpen());
|
play_audio->Enable(!!c->project->AudioProvider());
|
||||||
actions_box->Add(play_audio, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
actions_box->Add(play_audio, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||||
|
|
||||||
play_video = new wxButton(this, -1, _("Play &Video"));
|
play_video = new wxButton(this, -1, _("Play &Video"));
|
||||||
play_video->Enable(c->videoController->IsLoaded());
|
play_video->Enable(!!c->project->VideoProvider());
|
||||||
actions_box->Add(play_video, 0, wxBOTTOM | wxRIGHT, 5);
|
actions_box->Add(play_video, 0, wxBOTTOM | wxRIGHT, 5);
|
||||||
|
|
||||||
actions_box->AddStretchSpacer(1);
|
actions_box->AddStretchSpacer(1);
|
||||||
|
@ -180,8 +181,8 @@ void DialogStyling::Commit(bool next) {
|
||||||
void DialogStyling::OnActivate(wxActivateEvent &) {
|
void DialogStyling::OnActivate(wxActivateEvent &) {
|
||||||
if (!IsActive()) return;
|
if (!IsActive()) return;
|
||||||
|
|
||||||
play_video->Enable(c->videoController->IsLoaded());
|
play_video->Enable(!!c->project->VideoProvider());
|
||||||
play_audio->Enable(c->audioController->IsAudioOpen());
|
play_audio->Enable(!!c->project->AudioProvider());
|
||||||
|
|
||||||
style_list->Set(to_wx(c->ass->GetStyles()));
|
style_list->Set(to_wx(c->ass->GetStyles()));
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,15 @@
|
||||||
#include "ass_dialogue.h"
|
#include "ass_dialogue.h"
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "ass_time.h"
|
#include "ass_time.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/address_of_adaptor.h>
|
#include <libaegisub/address_of_adaptor.h>
|
||||||
|
|
||||||
|
@ -118,24 +119,24 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
adjOverlap = OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent Overlap")->GetInt();
|
adjOverlap = OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent Overlap")->GetInt();
|
||||||
|
|
||||||
// Styles box
|
// Styles box
|
||||||
wxSizer *LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles"));
|
auto LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles"));
|
||||||
StyleList = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(150,150), to_wx(c->ass->GetStyles()));
|
StyleList = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(150,150), to_wx(c->ass->GetStyles()));
|
||||||
StyleList->SetToolTip(_("Select styles to process. Unchecked ones will be ignored."));
|
StyleList->SetToolTip(_("Select styles to process. Unchecked ones will be ignored."));
|
||||||
|
|
||||||
wxButton *all = new wxButton(this,-1,_("&All"));
|
auto all = new wxButton(this,-1,_("&All"));
|
||||||
all->SetToolTip(_("Select all styles"));
|
all->SetToolTip(_("Select all styles"));
|
||||||
|
|
||||||
wxButton *none = new wxButton(this,-1,_("&None"));
|
auto none = new wxButton(this,-1,_("&None"));
|
||||||
none->SetToolTip(_("Deselect all styles"));
|
none->SetToolTip(_("Deselect all styles"));
|
||||||
|
|
||||||
// Options box
|
// Options box
|
||||||
wxStaticBoxSizer *optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options"));
|
auto optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options"));
|
||||||
onlySelection = new wxCheckBox(this,-1,_("Affect &selection only"));
|
onlySelection = new wxCheckBox(this,-1,_("Affect &selection only"));
|
||||||
onlySelection->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool());
|
onlySelection->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool());
|
||||||
optionsSizer->Add(onlySelection,1,wxALL,0);
|
optionsSizer->Add(onlySelection,1,wxALL,0);
|
||||||
|
|
||||||
// Lead-in/out box
|
// Lead-in/out box
|
||||||
wxStaticBoxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Lead-in/Lead-out"));
|
auto LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Lead-in/Lead-out"));
|
||||||
|
|
||||||
hasLeadIn = make_check(LeadSizer, _("Add lead &in:"),
|
hasLeadIn = make_check(LeadSizer, _("Add lead &in:"),
|
||||||
"Tool/Timing Post Processor/Enable/Lead/IN",
|
"Tool/Timing Post Processor/Enable/Lead/IN",
|
||||||
|
@ -150,12 +151,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
LeadSizer->AddStretchSpacer(1);
|
LeadSizer->AddStretchSpacer(1);
|
||||||
|
|
||||||
// Adjacent subs sizer
|
// Adjacent subs sizer
|
||||||
wxStaticBoxSizer *AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Make adjacent subtitles continuous"));
|
auto AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Make adjacent subtitles continuous"));
|
||||||
adjsEnable = make_check(AdjacentSizer, _("&Enable"),
|
adjsEnable = make_check(AdjacentSizer, _("&Enable"),
|
||||||
"Tool/Timing Post Processor/Enable/Adjacent",
|
"Tool/Timing Post Processor/Enable/Adjacent",
|
||||||
_("Enable snapping of subtitles together if they are within a certain distance of each other"));
|
_("Enable snapping of subtitles together if they are within a certain distance of each other"));
|
||||||
|
|
||||||
wxSizer *adjBoxes = new wxBoxSizer(wxHORIZONTAL);
|
auto adjBoxes = new wxBoxSizer(wxHORIZONTAL);
|
||||||
make_ctrl(this, adjBoxes, _("Max gap:"), &adjGap, adjsEnable,
|
make_ctrl(this, adjBoxes, _("Max gap:"), &adjGap, adjsEnable,
|
||||||
_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds"));
|
_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds"));
|
||||||
make_ctrl(this, adjBoxes, _("Max overlap:"), &adjOverlap, adjsEnable,
|
make_ctrl(this, adjBoxes, _("Max overlap:"), &adjOverlap, adjsEnable,
|
||||||
|
@ -164,19 +165,19 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
adjacentBias = new wxSlider(this, -1, mid<int>(0, OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble() * 100, 100), 0, 100, wxDefaultPosition, wxSize(-1,20));
|
adjacentBias = new wxSlider(this, -1, mid<int>(0, OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble() * 100, 100), 0, 100, wxDefaultPosition, wxSize(-1,20));
|
||||||
adjacentBias->SetToolTip(_("Sets how to set the adjoining of lines. If set totally to left, it will extend or shrink start time of the second line; if totally to right, it will extend or shrink the end time of the first line."));
|
adjacentBias->SetToolTip(_("Sets how to set the adjoining of lines. If set totally to left, it will extend or shrink start time of the second line; if totally to right, it will extend or shrink the end time of the first line."));
|
||||||
|
|
||||||
wxSizer *adjSliderSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto adjSliderSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
adjSliderSizer->Add(new wxStaticText(this, -1, _("Bias: Start <- ")), wxSizerFlags().Center());
|
adjSliderSizer->Add(new wxStaticText(this, -1, _("Bias: Start <- ")), wxSizerFlags().Center());
|
||||||
adjSliderSizer->Add(adjacentBias, wxSizerFlags(1).Center());
|
adjSliderSizer->Add(adjacentBias, wxSizerFlags(1).Center());
|
||||||
adjSliderSizer->Add(new wxStaticText(this, -1, _(" -> End")), wxSizerFlags().Center());
|
adjSliderSizer->Add(new wxStaticText(this, -1, _(" -> End")), wxSizerFlags().Center());
|
||||||
|
|
||||||
wxSizer *adjRightSizer = new wxBoxSizer(wxVERTICAL);
|
auto adjRightSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
adjRightSizer->Add(adjBoxes, wxSizerFlags().Expand());
|
adjRightSizer->Add(adjBoxes, wxSizerFlags().Expand());
|
||||||
adjRightSizer->Add(adjSliderSizer, wxSizerFlags().Expand().Border(wxTOP));
|
adjRightSizer->Add(adjSliderSizer, wxSizerFlags().Expand().Border(wxTOP));
|
||||||
AdjacentSizer->Add(adjRightSizer);
|
AdjacentSizer->Add(adjRightSizer);
|
||||||
|
|
||||||
// Keyframes sizer
|
// Keyframes sizer
|
||||||
wxStaticBoxSizer *KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
|
auto KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
|
||||||
wxSizer *KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
|
auto KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
|
||||||
|
|
||||||
keysEnable = new wxCheckBox(this, -1, _("E&nable"));
|
keysEnable = new wxCheckBox(this, -1, _("E&nable"));
|
||||||
keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold"));
|
keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold"));
|
||||||
|
@ -184,7 +185,7 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
KeyframesFlexSizer->Add(keysEnable,0,wxRIGHT|wxEXPAND,10);
|
KeyframesFlexSizer->Add(keysEnable,0,wxRIGHT|wxEXPAND,10);
|
||||||
|
|
||||||
// Keyframes are only available if timecodes are loaded
|
// Keyframes are only available if timecodes are loaded
|
||||||
bool keysAvailable = c->videoController->KeyFramesLoaded() && c->videoController->TimecodesLoaded();
|
bool keysAvailable = !c->project->Keyframes().empty() && c->project->Timecodes().IsLoaded();
|
||||||
if (!keysAvailable) {
|
if (!keysAvailable) {
|
||||||
keysEnable->SetValue(false);
|
keysEnable->SetValue(false);
|
||||||
keysEnable->Enable(false);
|
keysEnable->Enable(false);
|
||||||
|
@ -208,12 +209,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
KeyframesSizer->AddStretchSpacer(1);
|
KeyframesSizer->AddStretchSpacer(1);
|
||||||
|
|
||||||
// Button sizer
|
// Button sizer
|
||||||
wxStdDialogButtonSizer *ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
|
auto ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
|
||||||
ApplyButton = ButtonSizer->GetAffirmativeButton();
|
ApplyButton = ButtonSizer->GetAffirmativeButton();
|
||||||
ButtonSizer->GetHelpButton()->Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Timing Processor"));
|
ButtonSizer->GetHelpButton()->Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Timing Processor"));
|
||||||
|
|
||||||
// Right Sizer
|
// Right Sizer
|
||||||
wxSizer *RightSizer = new wxBoxSizer(wxVERTICAL);
|
auto RightSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
RightSizer->Add(optionsSizer,0,wxBOTTOM|wxEXPAND,5);
|
RightSizer->Add(optionsSizer,0,wxBOTTOM|wxEXPAND,5);
|
||||||
RightSizer->Add(LeadSizer,0,wxBOTTOM|wxEXPAND,5);
|
RightSizer->Add(LeadSizer,0,wxBOTTOM|wxEXPAND,5);
|
||||||
RightSizer->Add(AdjacentSizer,0,wxBOTTOM|wxEXPAND,5);
|
RightSizer->Add(AdjacentSizer,0,wxBOTTOM|wxEXPAND,5);
|
||||||
|
@ -222,7 +223,7 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
RightSizer->Add(ButtonSizer,0,wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND,0);
|
RightSizer->Add(ButtonSizer,0,wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND,0);
|
||||||
|
|
||||||
// Style buttons sizer
|
// Style buttons sizer
|
||||||
wxSizer *StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
StyleButtonsSizer->Add(all,1,0,0);
|
StyleButtonsSizer->Add(all,1,0,0);
|
||||||
StyleButtonsSizer->Add(none,1,0,0);
|
StyleButtonsSizer->Add(none,1,0,0);
|
||||||
|
|
||||||
|
@ -231,12 +232,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
||||||
LeftSizer->Add(StyleButtonsSizer, wxSizerFlags().Expand());
|
LeftSizer->Add(StyleButtonsSizer, wxSizerFlags().Expand());
|
||||||
|
|
||||||
// Top Sizer
|
// Top Sizer
|
||||||
wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto TopSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
TopSizer->Add(LeftSizer,0,wxRIGHT|wxEXPAND,5);
|
TopSizer->Add(LeftSizer,0,wxRIGHT|wxEXPAND,5);
|
||||||
TopSizer->Add(RightSizer,1,wxALL|wxEXPAND,0);
|
TopSizer->Add(RightSizer,1,wxALL|wxEXPAND,0);
|
||||||
|
|
||||||
// Main Sizer
|
// Main Sizer
|
||||||
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
|
auto MainSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5);
|
MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5);
|
||||||
SetSizerAndFit(MainSizer);
|
SetSizerAndFit(MainSizer);
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
|
@ -289,10 +290,10 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
|
std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
|
||||||
std::set<std::string> styles;
|
std::set<boost::flyweight<std::string>> styles;
|
||||||
for (size_t i = 0; i < StyleList->GetCount(); ++i) {
|
for (size_t i = 0; i < StyleList->GetCount(); ++i) {
|
||||||
if (StyleList->IsChecked(i))
|
if (StyleList->IsChecked(i))
|
||||||
styles.insert(from_wx(StyleList->GetString(i)));
|
styles.insert(boost::flyweight<std::string>(from_wx(StyleList->GetString(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<AssDialogue*> sorted;
|
std::vector<AssDialogue*> sorted;
|
||||||
|
@ -382,28 +383,27 @@ void DialogTimingProcessor::Process() {
|
||||||
|
|
||||||
// Keyframe snapping
|
// Keyframe snapping
|
||||||
if (keysEnable->IsChecked()) {
|
if (keysEnable->IsChecked()) {
|
||||||
std::vector<int> kf = c->videoController->GetKeyFrames();
|
std::vector<int> kf = c->project->Keyframes();
|
||||||
if (c->videoController->IsLoaded())
|
auto fps = c->project->Timecodes();
|
||||||
kf.push_back(c->videoController->GetLength() - 1);
|
if (auto provider = c->project->VideoProvider())
|
||||||
|
kf.push_back(provider->GetFrameCount() - 1);
|
||||||
|
|
||||||
for (AssDialogue *cur : sorted) {
|
for (AssDialogue *cur : sorted) {
|
||||||
// Get start/end frames
|
// Get start/end frames
|
||||||
int startF = c->videoController->FrameAtTime(cur->Start, agi::vfr::START);
|
int startF = fps.FrameAtTime(cur->Start, agi::vfr::START);
|
||||||
int endF = c->videoController->FrameAtTime(cur->End, agi::vfr::END);
|
int endF = fps.FrameAtTime(cur->End, agi::vfr::END);
|
||||||
|
|
||||||
// Get closest for start
|
// Get closest for start
|
||||||
int closest = get_closest_kf(kf, startF);
|
int closest = get_closest_kf(kf, startF);
|
||||||
int time = c->videoController->TimeAtFrame(closest, agi::vfr::START);
|
int time = fps.TimeAtFrame(closest, agi::vfr::START);
|
||||||
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart)) {
|
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart))
|
||||||
cur->Start = time;
|
cur->Start = time;
|
||||||
}
|
|
||||||
|
|
||||||
// Get closest for end
|
// Get closest for end
|
||||||
closest = get_closest_kf(kf, endF) - 1;
|
closest = get_closest_kf(kf, endF) - 1;
|
||||||
time = c->videoController->TimeAtFrame(closest, agi::vfr::END);
|
time = fps.TimeAtFrame(closest, agi::vfr::END);
|
||||||
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd)) {
|
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd))
|
||||||
cur->End = time;
|
cur->End = time;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,11 @@
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "persist_location.h"
|
#include "persist_location.h"
|
||||||
|
#include "project.h"
|
||||||
#include "subs_edit_ctrl.h"
|
#include "subs_edit_ctrl.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
@ -127,12 +128,12 @@ DialogTranslation::DialogTranslation(agi::Context *c)
|
||||||
wxStaticBoxSizer *actions_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Actions"));
|
wxStaticBoxSizer *actions_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Actions"));
|
||||||
|
|
||||||
wxButton *play_audio = new wxButton(this, -1, _("Play &Audio"));
|
wxButton *play_audio = new wxButton(this, -1, _("Play &Audio"));
|
||||||
play_audio->Enable(c->audioController->IsAudioOpen());
|
play_audio->Enable(!!c->project->AudioProvider());
|
||||||
play_audio->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayAudioButton, this);
|
play_audio->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayAudioButton, this);
|
||||||
actions_box->Add(play_audio, 0, wxALL, 5);
|
actions_box->Add(play_audio, 0, wxALL, 5);
|
||||||
|
|
||||||
wxButton *play_video = new wxButton(this, -1, _("Play &Video"));
|
wxButton *play_video = new wxButton(this, -1, _("Play &Video"));
|
||||||
play_video->Enable(c->videoController->IsLoaded());
|
play_video->Enable(!!c->project->VideoProvider());
|
||||||
play_video->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayVideoButton, this);
|
play_video->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayVideoButton, this);
|
||||||
actions_box->Add(play_video, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
actions_box->Add(play_video, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||||
|
|
||||||
|
|
|
@ -35,13 +35,12 @@
|
||||||
#include "dialog_video_details.h"
|
#include "dialog_video_details.h"
|
||||||
|
|
||||||
#include "ass_time.h"
|
#include "ass_time.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "include/aegisub/video_provider.h"
|
#include "project.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <boost/rational.hpp>
|
#include <boost/rational.hpp>
|
||||||
|
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
#include <wx/textctrl.h>
|
#include <wx/textctrl.h>
|
||||||
|
@ -49,10 +48,11 @@
|
||||||
DialogVideoDetails::DialogVideoDetails(agi::Context *c)
|
DialogVideoDetails::DialogVideoDetails(agi::Context *c)
|
||||||
: wxDialog(c->parent , -1, _("Video Details"))
|
: wxDialog(c->parent , -1, _("Video Details"))
|
||||||
{
|
{
|
||||||
auto width = c->videoController->GetWidth();
|
auto provider = c->project->VideoProvider();
|
||||||
auto height = c->videoController->GetHeight();
|
auto width = provider->GetWidth();
|
||||||
auto framecount = c->videoController->GetLength();
|
auto height = provider->GetHeight();
|
||||||
auto fps = c->videoController->FPS();
|
auto framecount = provider->GetFrameCount();
|
||||||
|
auto fps = provider->GetFPS();
|
||||||
boost::rational<int> ar(width, height);
|
boost::rational<int> ar(width, height);
|
||||||
|
|
||||||
auto fg = new wxFlexGridSizer(2, 5, 10);
|
auto fg = new wxFlexGridSizer(2, 5, 10);
|
||||||
|
@ -60,11 +60,11 @@ DialogVideoDetails::DialogVideoDetails(agi::Context *c)
|
||||||
fg->Add(new wxStaticText(this, -1, name), 0, wxALIGN_CENTRE_VERTICAL);
|
fg->Add(new wxStaticText(this, -1, name), 0, wxALIGN_CENTRE_VERTICAL);
|
||||||
fg->Add(new wxTextCtrl(this, -1, value, wxDefaultPosition, wxSize(300,-1), wxTE_READONLY), 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
fg->Add(new wxTextCtrl(this, -1, value, wxDefaultPosition, wxSize(300,-1), wxTE_READONLY), 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||||
};
|
};
|
||||||
make_field(_("File name:"), c->videoController->GetVideoName().wstring());
|
make_field(_("File name:"), c->project->VideoName().wstring());
|
||||||
make_field(_("FPS:"), wxString::Format("%.3f", fps.FPS()));
|
make_field(_("FPS:"), wxString::Format("%.3f", fps.FPS()));
|
||||||
make_field(_("Resolution:"), wxString::Format("%dx%d (%d:%d)", width, height, ar.numerator(), ar.denominator()));
|
make_field(_("Resolution:"), wxString::Format("%dx%d (%d:%d)", width, height, ar.numerator(), ar.denominator()));
|
||||||
make_field(_("Length:"), wxString::Format(_("%d frames (%s)"), framecount, to_wx(AssTime(fps.TimeAtFrame(framecount - 1)).GetAssFormated(true))));
|
make_field(_("Length:"), wxString::Format(_("%d frames (%s)"), framecount, to_wx(AssTime(fps.TimeAtFrame(framecount - 1)).GetAssFormated(true))));
|
||||||
make_field(_("Decoder:"), to_wx(c->videoController->GetProvider()->GetDecoderName()));
|
make_field(_("Decoder:"), to_wx(provider->GetDecoderName()));
|
||||||
|
|
||||||
wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Video"));
|
wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Video"));
|
||||||
video_sizer->Add(fg);
|
video_sizer->Add(fg);
|
||||||
|
|
|
@ -17,11 +17,12 @@
|
||||||
#include "dialog_video_properties.h"
|
#include "dialog_video_properties.h"
|
||||||
|
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "include/aegisub/video_provider.h"
|
#include "async_video_provider.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "resolution_resampler.h"
|
#include "resolution_resampler.h"
|
||||||
|
|
||||||
#include <wx/dialog.h>
|
#include <wx/dialog.h>
|
||||||
|
#include <wx/intl.h>
|
||||||
#include <wx/radiobox.h>
|
#include <wx/radiobox.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
|
@ -79,9 +80,7 @@ public:
|
||||||
Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { EndModal(0); }, wxID_CANCEL);
|
Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { EndModal(0); }, wxID_CANCEL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
bool update_video_properties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent) {
|
||||||
|
|
||||||
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent) {
|
|
||||||
bool commit_subs = false;
|
bool commit_subs = false;
|
||||||
|
|
||||||
// When opening dummy video only want to set the script properties if
|
// When opening dummy video only want to set the script properties if
|
||||||
|
@ -156,3 +155,9 @@ bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxW
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent) {
|
||||||
|
if (update_video_properties(file, new_provider, parent))
|
||||||
|
file->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
|
||||||
|
}
|
||||||
|
|
|
@ -15,9 +15,8 @@
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
class AssFile;
|
class AssFile;
|
||||||
class VideoProvider;
|
class AsyncVideoProvider;
|
||||||
class wxWindow;
|
class wxWindow;
|
||||||
|
|
||||||
/// Update the video properties for a newly opened video, possibly prompting the user about what to do
|
/// Update the video properties for a newly opened video, possibly prompting the user about what to do
|
||||||
/// @return Does the file need to be committed?
|
void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent);
|
||||||
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent);
|
|
|
@ -27,24 +27,19 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file export_framerate.cpp
|
|
||||||
/// @brief Transform Framerate export filter
|
|
||||||
/// @ingroup export
|
|
||||||
///
|
|
||||||
|
|
||||||
#include "export_framerate.h"
|
#include "export_framerate.h"
|
||||||
|
|
||||||
#include "ass_dialogue.h"
|
#include "ass_dialogue.h"
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
|
#include "project.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/of_type_adaptor.h>
|
#include <libaegisub/of_type_adaptor.h>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/checkbox.h>
|
#include <wx/checkbox.h>
|
||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
|
@ -65,18 +60,18 @@ void AssTransformFramerateFilter::ProcessSubs(AssFile *subs, wxWindow *) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
|
wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
|
||||||
wxWindow *base = new wxPanel(parent, -1);
|
|
||||||
|
|
||||||
LoadSettings(true, c);
|
LoadSettings(true, c);
|
||||||
|
|
||||||
|
wxWindow *base = new wxPanel(parent, -1);
|
||||||
|
|
||||||
// Input sizer
|
// Input sizer
|
||||||
wxSizer *InputSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto InputSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
wxString initialInput;
|
wxString initialInput;
|
||||||
wxButton *FromVideo = new wxButton(base,-1,_("From &video"));
|
auto FromVideo = new wxButton(base,-1,_("From &video"));
|
||||||
if (Input->IsLoaded()) {
|
if (Input.IsLoaded()) {
|
||||||
initialInput = wxString::Format("%2.3f",Input->FPS());
|
initialInput = wxString::Format("%2.3f", Input.FPS());
|
||||||
FromVideo->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) {
|
FromVideo->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) {
|
||||||
InputFramerate->SetValue(wxString::Format("%g", c->videoController->FPS().FPS()));
|
InputFramerate->SetValue(wxString::Format("%g", c->project->Timecodes().FPS()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -89,9 +84,9 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
||||||
InputSizer->AddStretchSpacer(1);
|
InputSizer->AddStretchSpacer(1);
|
||||||
|
|
||||||
// Output sizers
|
// Output sizers
|
||||||
wxSizer *OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
|
auto OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
|
||||||
wxSizer *OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
|
auto OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
|
||||||
wxSizer *OutputSizer = new wxBoxSizer(wxVERTICAL);
|
auto OutputSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
// Output top line
|
// Output top line
|
||||||
RadioOutputVFR = new wxRadioButton(base,-1,_("V&ariable"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
|
RadioOutputVFR = new wxRadioButton(base,-1,_("V&ariable"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
|
||||||
|
@ -100,7 +95,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
||||||
// Output bottom line
|
// Output bottom line
|
||||||
RadioOutputCFR = new wxRadioButton(base,-1,_("&Constant: "));
|
RadioOutputCFR = new wxRadioButton(base,-1,_("&Constant: "));
|
||||||
wxString initialOutput = initialInput;
|
wxString initialOutput = initialInput;
|
||||||
if (!Output->IsVFR()) {
|
if (!Output.IsVFR()) {
|
||||||
RadioOutputVFR->Enable(false);
|
RadioOutputVFR->Enable(false);
|
||||||
RadioOutputCFR->SetValue(true);
|
RadioOutputCFR->SetValue(true);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +112,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
||||||
OutputSizer->Add(OutputSizerBottom,0,wxLEFT,5);
|
OutputSizer->Add(OutputSizerBottom,0,wxLEFT,5);
|
||||||
|
|
||||||
// Main window
|
// Main window
|
||||||
wxSizer *MainSizer = new wxFlexGridSizer(3,2,5,10);
|
auto MainSizer = new wxFlexGridSizer(3,2,5,10);
|
||||||
MainSizer->Add(new wxStaticText(base,-1,_("Input framerate: ")),0,wxEXPAND | wxALIGN_CENTER_VERTICAL,0);
|
MainSizer->Add(new wxStaticText(base,-1,_("Input framerate: ")),0,wxEXPAND | wxALIGN_CENTER_VERTICAL,0);
|
||||||
MainSizer->Add(InputSizer,0,wxEXPAND,0);
|
MainSizer->Add(InputSizer,0,wxEXPAND,0);
|
||||||
MainSizer->Add(new wxStaticText(base,-1,_("Output: ")),0,wxALIGN_CENTER_VERTICAL,0);
|
MainSizer->Add(new wxStaticText(base,-1,_("Output: ")),0,wxALIGN_CENTER_VERTICAL,0);
|
||||||
|
@ -133,29 +128,27 @@ void AssTransformFramerateFilter::LoadSettings(bool is_default, agi::Context *c)
|
||||||
this->c = c;
|
this->c = c;
|
||||||
|
|
||||||
if (is_default) {
|
if (is_default) {
|
||||||
Input = &c->videoController->VideoFPS();
|
auto provider = c->project->VideoProvider();
|
||||||
Output = &c->videoController->FPS();
|
Output = c->project->Timecodes();
|
||||||
|
Input = provider ? provider->GetFPS() : Output;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
double temp;
|
double temp;
|
||||||
InputFramerate->GetValue().ToDouble(&temp);
|
InputFramerate->GetValue().ToDouble(&temp);
|
||||||
t1 = temp;
|
Input = temp;
|
||||||
Input = &t1;
|
|
||||||
if (RadioOutputCFR->GetValue()) {
|
if (RadioOutputCFR->GetValue()) {
|
||||||
OutputFramerate->GetValue().ToDouble(&temp);
|
OutputFramerate->GetValue().ToDouble(&temp);
|
||||||
t2 = temp;
|
Output = temp;
|
||||||
Output = &t2;
|
|
||||||
}
|
}
|
||||||
else Output = &c->videoController->FPS();
|
else Output = c->project->Timecodes();
|
||||||
|
|
||||||
if (Reverse->IsChecked()) {
|
if (Reverse->IsChecked())
|
||||||
std::swap(Input, Output);
|
std::swap(Input, Output);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate a time to centisecond precision
|
/// Truncate a time to centisecond precision
|
||||||
int FORCEINLINE trunc_cs(int time) {
|
static int trunc_cs(int time) {
|
||||||
return (time / 10) * 10;
|
return (time / 10) * 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +191,7 @@ void AssTransformFramerateFilter::TransformTimeTags(std::string const& name, Ass
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
|
void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
|
||||||
if (!Input->IsLoaded() || !Output->IsLoaded()) return;
|
if (!Input.IsLoaded() || !Output.IsLoaded()) return;
|
||||||
for (auto& curDialogue : subs->Events) {
|
for (auto& curDialogue : subs->Events) {
|
||||||
line = &curDialogue;
|
line = &curDialogue;
|
||||||
newK = 0;
|
newK = 0;
|
||||||
|
@ -217,14 +210,14 @@ void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int AssTransformFramerateFilter::ConvertTime(int time) {
|
int AssTransformFramerateFilter::ConvertTime(int time) {
|
||||||
int frame = Output->FrameAtTime(time);
|
int frame = Output.FrameAtTime(time);
|
||||||
int frameStart = Output->TimeAtFrame(frame);
|
int frameStart = Output.TimeAtFrame(frame);
|
||||||
int frameEnd = Output->TimeAtFrame(frame + 1);
|
int frameEnd = Output.TimeAtFrame(frame + 1);
|
||||||
int frameDur = frameEnd - frameStart;
|
int frameDur = frameEnd - frameStart;
|
||||||
double dist = double(time - frameStart) / frameDur;
|
double dist = double(time - frameStart) / frameDur;
|
||||||
|
|
||||||
int newStart = Input->TimeAtFrame(frame);
|
int newStart = Input.TimeAtFrame(frame);
|
||||||
int newEnd = Input->TimeAtFrame(frame + 1);
|
int newEnd = Input.TimeAtFrame(frame + 1);
|
||||||
int newDur = newEnd - newStart;
|
int newDur = newEnd - newStart;
|
||||||
|
|
||||||
return newStart + newDur * dist;
|
return newStart + newDur * dist;
|
||||||
|
|
|
@ -27,11 +27,6 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file export_framerate.h
|
|
||||||
/// @see export_framerate.cpp
|
|
||||||
/// @ingroup export
|
|
||||||
///
|
|
||||||
|
|
||||||
#include "ass_export_filter.h"
|
#include "ass_export_filter.h"
|
||||||
|
|
||||||
#include <libaegisub/vfr.h>
|
#include <libaegisub/vfr.h>
|
||||||
|
@ -53,10 +48,8 @@ class AssTransformFramerateFilter final : public AssExportFilter {
|
||||||
int oldK = 0;
|
int oldK = 0;
|
||||||
|
|
||||||
// Yes, these are backwards. It sort of makes sense if you think about what it's doing.
|
// Yes, these are backwards. It sort of makes sense if you think about what it's doing.
|
||||||
const agi::vfr::Framerate *Input = nullptr; ///< Destination frame rate
|
agi::vfr::Framerate Input; ///< Destination frame rate
|
||||||
const agi::vfr::Framerate *Output = nullptr; ///< Source frame rate
|
agi::vfr::Framerate Output; ///< Source frame rate
|
||||||
|
|
||||||
agi::vfr::Framerate t1,t2;
|
|
||||||
|
|
||||||
wxTextCtrl *InputFramerate; ///< Input frame rate text box
|
wxTextCtrl *InputFramerate; ///< Input frame rate text box
|
||||||
wxTextCtrl *OutputFramerate; ///< Output frame rate text box
|
wxTextCtrl *OutputFramerate; ///< Output frame rate text box
|
||||||
|
@ -85,7 +78,6 @@ class AssTransformFramerateFilter final : public AssExportFilter {
|
||||||
/// is in and the beginning of the next frame
|
/// is in and the beginning of the next frame
|
||||||
int ConvertTime(int time);
|
int ConvertTime(int time);
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
|
||||||
AssTransformFramerateFilter();
|
AssTransformFramerateFilter();
|
||||||
void ProcessSubs(AssFile *subs, wxWindow *) override;
|
void ProcessSubs(AssFile *subs, wxWindow *) override;
|
||||||
wxWindow *GetConfigDialogWindow(wxWindow *parent, agi::Context *c) override;
|
wxWindow *GetConfigDialogWindow(wxWindow *parent, agi::Context *c) override;
|
||||||
|
|
|
@ -37,9 +37,9 @@
|
||||||
#include "include/aegisub/menu.h"
|
#include "include/aegisub/menu.h"
|
||||||
#include "include/aegisub/toolbar.h"
|
#include "include/aegisub/toolbar.h"
|
||||||
#include "include/aegisub/hotkey.h"
|
#include "include/aegisub/hotkey.h"
|
||||||
#include "include/aegisub/video_provider.h"
|
|
||||||
|
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "audio_controller.h"
|
#include "audio_controller.h"
|
||||||
#include "audio_box.h"
|
#include "audio_box.h"
|
||||||
#include "base_grid.h"
|
#include "base_grid.h"
|
||||||
|
@ -51,13 +51,14 @@
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "subs_edit_box.h"
|
#include "subs_edit_box.h"
|
||||||
#include "subs_edit_ctrl.h"
|
#include "subs_edit_ctrl.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "video_box.h"
|
#include "video_box.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
#include "video_slider.h"
|
#include "video_slider.h"
|
||||||
|
|
||||||
|
@ -86,91 +87,16 @@ enum {
|
||||||
#define StartupLog(a) LOG_I("frame_main/init") << a
|
#define StartupLog(a) LOG_I("frame_main/init") << a
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
wxDEFINE_EVENT(FILE_LIST_DROPPED, wxThreadEvent);
|
|
||||||
|
|
||||||
static void get_files_to_load(wxArrayString const& list, std::string &subs, std::string &audio, std::string &video) {
|
|
||||||
// Keep these lists sorted
|
|
||||||
|
|
||||||
// Video formats
|
|
||||||
const wxString videoList[] = {
|
|
||||||
"asf",
|
|
||||||
"avi",
|
|
||||||
"avs",
|
|
||||||
"d2v",
|
|
||||||
"m2ts",
|
|
||||||
"m4v",
|
|
||||||
"mkv",
|
|
||||||
"mov",
|
|
||||||
"mp4",
|
|
||||||
"mpeg",
|
|
||||||
"mpg",
|
|
||||||
"ogm",
|
|
||||||
"rm",
|
|
||||||
"rmvb",
|
|
||||||
"ts",
|
|
||||||
"webm"
|
|
||||||
"wmv",
|
|
||||||
"y4m",
|
|
||||||
"yuv"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subtitle formats
|
|
||||||
const wxString subsList[] = {
|
|
||||||
"ass",
|
|
||||||
"srt",
|
|
||||||
"ssa",
|
|
||||||
"sub",
|
|
||||||
"ttxt",
|
|
||||||
"txt"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Audio formats
|
|
||||||
const wxString audioList[] = {
|
|
||||||
"aac",
|
|
||||||
"ac3",
|
|
||||||
"ape",
|
|
||||||
"dts",
|
|
||||||
"flac",
|
|
||||||
"m4a",
|
|
||||||
"mka",
|
|
||||||
"mp3",
|
|
||||||
"ogg",
|
|
||||||
"w64",
|
|
||||||
"wav",
|
|
||||||
"wma"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Scan list
|
|
||||||
for (wxFileName file : list) {
|
|
||||||
if (file.IsRelative()) file.MakeAbsolute();
|
|
||||||
if (!file.FileExists()) continue;
|
|
||||||
|
|
||||||
wxString ext = file.GetExt().Lower();
|
|
||||||
|
|
||||||
if (subs.empty() && std::binary_search(std::begin(subsList), std::end(subsList), ext))
|
|
||||||
subs = from_wx(file.GetFullPath());
|
|
||||||
if (video.empty() && std::binary_search(std::begin(videoList), std::end(videoList), ext))
|
|
||||||
video = from_wx(file.GetFullPath());
|
|
||||||
if (audio.empty() && std::binary_search(std::begin(audioList), std::end(audioList), ext))
|
|
||||||
audio = from_wx(file.GetFullPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle files drag and dropped onto Aegisub
|
/// Handle files drag and dropped onto Aegisub
|
||||||
class AegisubFileDropTarget final : public wxFileDropTarget {
|
class AegisubFileDropTarget final : public wxFileDropTarget {
|
||||||
FrameMain *parent;
|
agi::Context *context;
|
||||||
public:
|
public:
|
||||||
AegisubFileDropTarget(FrameMain *parent) : parent(parent) { }
|
AegisubFileDropTarget(agi::Context *context) : context(context) { }
|
||||||
bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) override {
|
bool OnDropFiles(wxCoord, wxCoord, wxArrayString const& filenames) override {
|
||||||
std::string subs, audio, video;
|
std::vector<agi::fs::path> files;
|
||||||
get_files_to_load(filenames, subs, audio, video);
|
for (wxString const& fn : filenames)
|
||||||
|
files.push_back(from_wx(fn));
|
||||||
if (subs.empty() && audio.empty() && video.empty())
|
context->project->LoadList(files);
|
||||||
return false;
|
|
||||||
|
|
||||||
auto evt = new wxThreadEvent(FILE_LIST_DROPPED);
|
|
||||||
evt->SetPayload(filenames);
|
|
||||||
parent->QueueEvent(evt);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -194,9 +120,8 @@ FrameMain::FrameMain()
|
||||||
context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
|
context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
|
||||||
context->subsController->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
|
context->subsController->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
|
||||||
context->subsController->AddFileSaveListener(&FrameMain::UpdateTitle, this);
|
context->subsController->AddFileSaveListener(&FrameMain::UpdateTitle, this);
|
||||||
context->audioController->AddAudioOpenListener(&FrameMain::OnAudioOpen, this);
|
context->project->AddAudioProviderListener(&FrameMain::OnAudioOpen, this);
|
||||||
context->audioController->AddAudioCloseListener(&FrameMain::OnAudioClose, this);
|
context->project->AddVideoProviderListener(&FrameMain::OnVideoOpen, this);
|
||||||
context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);
|
|
||||||
|
|
||||||
StartupLog("Initializing context frames");
|
StartupLog("Initializing context frames");
|
||||||
context->parent = this;
|
context->parent = this;
|
||||||
|
@ -206,7 +131,9 @@ FrameMain::FrameMain()
|
||||||
if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
|
if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
|
||||||
|
|
||||||
StartupLog("Initialize toolbar");
|
StartupLog("Initialize toolbar");
|
||||||
InitToolbar();
|
wxSystemOptions::SetOption("msw.remap", 0);
|
||||||
|
OPT_SUB("App/Show Toolbar", &FrameMain::EnableToolBar, this);
|
||||||
|
EnableToolBar(*OPT_GET("App/Show Toolbar"));
|
||||||
|
|
||||||
StartupLog("Initialize menu bar");
|
StartupLog("Initialize menu bar");
|
||||||
menu::GetMenuBar("main", this, context.get());
|
menu::GetMenuBar("main", this, context.get());
|
||||||
|
@ -228,10 +155,7 @@ FrameMain::FrameMain()
|
||||||
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this);
|
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this);
|
||||||
|
|
||||||
StartupLog("Set up drag/drop target");
|
StartupLog("Set up drag/drop target");
|
||||||
SetDropTarget(new AegisubFileDropTarget(this));
|
SetDropTarget(new AegisubFileDropTarget(context.get()));
|
||||||
Bind(FILE_LIST_DROPPED, [=](wxThreadEvent &evt) {
|
|
||||||
LoadList(evt.GetPayload<wxArrayString>());
|
|
||||||
});
|
|
||||||
|
|
||||||
StartupLog("Load default file");
|
StartupLog("Load default file");
|
||||||
context->subsController->Close();
|
context->subsController->Close();
|
||||||
|
@ -247,18 +171,12 @@ FrameMain::FrameMain()
|
||||||
FrameMain::~FrameMain () {
|
FrameMain::~FrameMain () {
|
||||||
wxGetApp().frame = nullptr;
|
wxGetApp().frame = nullptr;
|
||||||
|
|
||||||
context->videoController->SetVideo("");
|
context->project->CloseAudio();
|
||||||
context->audioController->CloseAudio();
|
context->project->CloseVideo();
|
||||||
|
|
||||||
DestroyChildren();
|
DestroyChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameMain::InitToolbar() {
|
|
||||||
wxSystemOptions::SetOption("msw.remap", 0);
|
|
||||||
OPT_SUB("App/Show Toolbar", &FrameMain::EnableToolBar, this);
|
|
||||||
EnableToolBar(*OPT_GET("App/Show Toolbar"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
|
void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
|
||||||
if (opt.GetBool()) {
|
if (opt.GetBool()) {
|
||||||
if (!GetToolBar()) {
|
if (!GetToolBar()) {
|
||||||
|
@ -275,7 +193,7 @@ void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
|
||||||
|
|
||||||
void FrameMain::InitContents() {
|
void FrameMain::InitContents() {
|
||||||
StartupLog("Create background panel");
|
StartupLog("Create background panel");
|
||||||
wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
|
auto Panel = new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
|
||||||
|
|
||||||
StartupLog("Create subtitles grid");
|
StartupLog("Create subtitles grid");
|
||||||
context->subsGrid = new BaseGrid(Panel, context.get());
|
context->subsGrid = new BaseGrid(Panel, context.get());
|
||||||
|
@ -313,10 +231,10 @@ void FrameMain::SetDisplayMode(int video, int audio) {
|
||||||
bool sv = false, sa = false;
|
bool sv = false, sa = false;
|
||||||
|
|
||||||
if (video == -1) sv = showVideo;
|
if (video == -1) sv = showVideo;
|
||||||
else if (video) sv = context->videoController->IsLoaded() && !context->dialog->Get<DialogDetachedVideo>();
|
else if (video) sv = context->project->VideoProvider() && !context->dialog->Get<DialogDetachedVideo>();
|
||||||
|
|
||||||
if (audio == -1) sa = showAudio;
|
if (audio == -1) sa = showAudio;
|
||||||
else if (audio) sa = context->audioController->IsAudioOpen();
|
else if (audio) sa = !!context->project->AudioProvider();
|
||||||
|
|
||||||
// See if anything changed
|
// See if anything changed
|
||||||
if (sv == showVideo && sa == showAudio) return;
|
if (sv == showVideo && sa == showAudio) return;
|
||||||
|
@ -357,15 +275,14 @@ void FrameMain::UpdateTitle() {
|
||||||
if (GetTitle() != newTitle) SetTitle(newTitle);
|
if (GetTitle() != newTitle) SetTitle(newTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameMain::OnVideoOpen() {
|
void FrameMain::OnVideoOpen(AsyncVideoProvider *provider) {
|
||||||
if (!context->videoController->IsLoaded()) {
|
if (!provider) {
|
||||||
SetDisplayMode(0, -1);
|
SetDisplayMode(0, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Freeze();
|
Freeze();
|
||||||
int vidx = context->videoController->GetWidth(),
|
int vidx = provider->GetWidth(), vidy = provider->GetHeight();
|
||||||
vidy = context->videoController->GetHeight();
|
|
||||||
|
|
||||||
// Set zoom level based on video resolution and window size
|
// Set zoom level based on video resolution and window size
|
||||||
double zoom = context->videoDisplay->GetZoom();
|
double zoom = context->videoDisplay->GetZoom();
|
||||||
|
@ -380,30 +297,12 @@ void FrameMain::OnVideoOpen() {
|
||||||
if (OPT_GET("Video/Detached/Enabled")->GetBool() && !context->dialog->Get<DialogDetachedVideo>())
|
if (OPT_GET("Video/Detached/Enabled")->GetBool() && !context->dialog->Get<DialogDetachedVideo>())
|
||||||
cmd::call("video/detach", context.get());
|
cmd::call("video/detach", context.get());
|
||||||
Thaw();
|
Thaw();
|
||||||
|
|
||||||
if (!blockAudioLoad && OPT_GET("Video/Open Audio")->GetBool() && context->audioController->GetAudioURL() != context->videoController->GetVideoName()) {
|
|
||||||
try {
|
|
||||||
context->audioController->OpenAudio(context->videoController->GetVideoName());
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) { }
|
|
||||||
// Opening a video with no audio data isn't an error, so just log
|
|
||||||
// and move on
|
|
||||||
catch (agi::fs::FileSystemError const&) {
|
|
||||||
LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " found by video provider but not audio provider";
|
|
||||||
}
|
|
||||||
catch (agi::AudioDataNotFoundError const& e) {
|
|
||||||
LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " has no audio data: " << e.GetChainedMessage();
|
|
||||||
}
|
|
||||||
catch (agi::AudioOpenError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error loading audio", wxOK | wxICON_ERROR | wxCENTER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameMain::OnVideoDetach(agi::OptionValue const& opt) {
|
void FrameMain::OnVideoDetach(agi::OptionValue const& opt) {
|
||||||
if (opt.GetBool())
|
if (opt.GetBool())
|
||||||
SetDisplayMode(0, -1);
|
SetDisplayMode(0, -1);
|
||||||
else if (context->videoController->IsLoaded())
|
else if (context->project->VideoProvider())
|
||||||
SetDisplayMode(1, -1);
|
SetDisplayMode(1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,41 +312,9 @@ void FrameMain::StatusTimeout(wxString text,int ms) {
|
||||||
StatusClear.Start(ms,true);
|
StatusClear.Start(ms,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FrameMain::LoadList(wxArrayString list) {
|
|
||||||
std::string audio, video, subs;
|
|
||||||
get_files_to_load(list, subs, audio, video);
|
|
||||||
|
|
||||||
blockVideoLoad = !video.empty();
|
|
||||||
blockAudioLoad = !audio.empty();
|
|
||||||
|
|
||||||
// Load files
|
|
||||||
if (subs.size())
|
|
||||||
context->subsController->Load(subs);
|
|
||||||
|
|
||||||
if (blockVideoLoad) {
|
|
||||||
blockVideoLoad = false;
|
|
||||||
context->videoController->SetVideo(video);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockAudioLoad) {
|
|
||||||
blockAudioLoad = false;
|
|
||||||
try {
|
|
||||||
context->audioController->OpenAudio(audio);
|
|
||||||
} catch (agi::UserCancelException const&) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loaded_any = subs.size() || audio.size() || video.size();
|
|
||||||
if (loaded_any)
|
|
||||||
Refresh(false);
|
|
||||||
|
|
||||||
return loaded_any;
|
|
||||||
}
|
|
||||||
|
|
||||||
BEGIN_EVENT_TABLE(FrameMain, wxFrame)
|
BEGIN_EVENT_TABLE(FrameMain, wxFrame)
|
||||||
EVT_TIMER(ID_APP_TIMER_STATUSCLEAR, FrameMain::OnStatusClear)
|
EVT_TIMER(ID_APP_TIMER_STATUSCLEAR, FrameMain::OnStatusClear)
|
||||||
|
|
||||||
EVT_CLOSE(FrameMain::OnCloseWindow)
|
EVT_CLOSE(FrameMain::OnCloseWindow)
|
||||||
|
|
||||||
EVT_CHAR_HOOK(FrameMain::OnKeyDown)
|
EVT_CHAR_HOOK(FrameMain::OnKeyDown)
|
||||||
EVT_MOUSEWHEEL(FrameMain::OnMouseWheel)
|
EVT_MOUSEWHEEL(FrameMain::OnMouseWheel)
|
||||||
END_EVENT_TABLE()
|
END_EVENT_TABLE()
|
||||||
|
@ -476,95 +343,15 @@ void FrameMain::OnStatusClear(wxTimerEvent &) {
|
||||||
SetStatusText("",1);
|
SetStatusText("",1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameMain::OnAudioOpen(AudioProvider *) {
|
void FrameMain::OnAudioOpen(AudioProvider *provider) {
|
||||||
SetDisplayMode(-1, 1);
|
if (provider)
|
||||||
}
|
SetDisplayMode(-1, 1);
|
||||||
|
else
|
||||||
void FrameMain::OnAudioClose() {
|
SetDisplayMode(-1, 0);
|
||||||
SetDisplayMode(-1, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameMain::OnSubtitlesOpen() {
|
void FrameMain::OnSubtitlesOpen() {
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
auto vc = context->videoController.get();
|
|
||||||
|
|
||||||
/// @todo figure out how to move this to the relevant controllers without
|
|
||||||
/// prompting for each file loaded/unloaded
|
|
||||||
|
|
||||||
// Load stuff from the new script
|
|
||||||
auto video = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
|
|
||||||
auto vfr = 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->GetScriptInfo("Audio URI"), "?script");
|
|
||||||
|
|
||||||
bool videoChanged = !blockVideoLoad && video != vc->GetVideoName();
|
|
||||||
bool timecodesChanged = vfr != vc->GetTimecodesName();
|
|
||||||
bool keyframesChanged = keyframes != vc->GetKeyFramesName();
|
|
||||||
bool audioChanged = !blockAudioLoad && audio != context->audioController->GetAudioURL();
|
|
||||||
|
|
||||||
// Check if there is anything to change
|
|
||||||
int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
|
|
||||||
if (autoLoadMode == 0 || (!videoChanged && !timecodesChanged && !keyframesChanged && !audioChanged)) {
|
|
||||||
SetDisplayMode(1, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoLoadMode == 2) {
|
|
||||||
if (wxMessageBox(_("Do you want to load/unload the associated files?"), _("(Un)Load files?"), wxYES_NO | wxCENTRE, this) != wxYES) {
|
|
||||||
SetDisplayMode(1, 1);
|
|
||||||
if (vc->IsLoaded() && vc->GetProvider()->GetColorSpace() != context->ass->GetScriptInfo("YCbCr Matrix"))
|
|
||||||
vc->Reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioChanged)
|
|
||||||
blockAudioLoad = true;
|
|
||||||
|
|
||||||
// Video
|
|
||||||
if (videoChanged) {
|
|
||||||
vc->SetVideo(video);
|
|
||||||
if (vc->IsLoaded()) {
|
|
||||||
vc->JumpToFrame(context->ass->GetUIStateAsInt("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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (vc->IsLoaded() && vc->GetProvider()->GetColorSpace() != context->ass->GetScriptInfo("YCbCr Matrix"))
|
|
||||||
vc->Reload();
|
|
||||||
|
|
||||||
vc->LoadTimecodes(vfr);
|
|
||||||
vc->LoadKeyframes(keyframes);
|
|
||||||
|
|
||||||
// Audio
|
|
||||||
if (audioChanged) {
|
|
||||||
blockAudioLoad = false;
|
|
||||||
try {
|
|
||||||
if (audio.empty())
|
|
||||||
context->audioController->CloseAudio();
|
|
||||||
else
|
|
||||||
context->audioController->OpenAudio(audio);
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) { }
|
|
||||||
catch (agi::fs::FileSystemError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error opening audio", wxOK | wxICON_ERROR | wxCENTER, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDisplayMode(1, 1);
|
SetDisplayMode(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,31 +27,23 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file frame_main.h
|
|
||||||
/// @see frame_main.cpp
|
|
||||||
/// @ingroup main_ui
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <libaegisub/fs_fwd.h>
|
#include <libaegisub/fs_fwd.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wx/frame.h>
|
#include <wx/frame.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
|
|
||||||
class AegisubApp;
|
class AegisubApp;
|
||||||
class AegisubFileDropTarget;
|
class AsyncVideoProvider;
|
||||||
class AudioBox;
|
class AudioBox;
|
||||||
class AudioProvider;
|
class AudioProvider;
|
||||||
class VideoBox;
|
class VideoBox;
|
||||||
|
|
||||||
namespace agi { struct Context; class OptionValue; }
|
namespace agi { struct Context; class OptionValue; }
|
||||||
|
|
||||||
class FrameMain : public wxFrame {
|
class FrameMain : public wxFrame {
|
||||||
friend class AegisubApp;
|
friend class AegisubApp;
|
||||||
friend class AegisubFileDropTarget;
|
|
||||||
|
|
||||||
std::unique_ptr<agi::Context> context;
|
std::unique_ptr<agi::Context> context;
|
||||||
|
|
||||||
|
@ -64,13 +56,7 @@ class FrameMain : public wxFrame {
|
||||||
bool showVideo = true; ///< Is the video display shown?
|
bool showVideo = true; ///< Is the video display shown?
|
||||||
bool showAudio = true; ///< Is the audio display shown?
|
bool showAudio = true; ///< Is the audio display shown?
|
||||||
wxTimer StatusClear; ///< Status bar timeout timer
|
wxTimer StatusClear; ///< Status bar timeout timer
|
||||||
/// Block video loading; used when both video and subtitles are opened at
|
|
||||||
/// the same time, so that the video associated with the subtitles (if any)
|
|
||||||
/// isn't loaded
|
|
||||||
bool blockVideoLoad = false;
|
|
||||||
bool blockAudioLoad = false;
|
|
||||||
|
|
||||||
void InitToolbar();
|
|
||||||
void InitContents();
|
void InitContents();
|
||||||
|
|
||||||
void UpdateTitle();
|
void UpdateTitle();
|
||||||
|
@ -81,13 +67,9 @@ class FrameMain : public wxFrame {
|
||||||
void OnStatusClear(wxTimerEvent &event);
|
void OnStatusClear(wxTimerEvent &event);
|
||||||
void OnCloseWindow (wxCloseEvent &event);
|
void OnCloseWindow (wxCloseEvent &event);
|
||||||
|
|
||||||
// AudioControllerAudioEventListener implementation
|
|
||||||
void OnAudioOpen(AudioProvider *provider);
|
void OnAudioOpen(AudioProvider *provider);
|
||||||
void OnAudioClose();
|
void OnVideoOpen(AsyncVideoProvider *provider);
|
||||||
|
|
||||||
void OnVideoOpen();
|
|
||||||
void OnVideoDetach(agi::OptionValue const& opt);
|
void OnVideoDetach(agi::OptionValue const& opt);
|
||||||
|
|
||||||
void OnSubtitlesOpen();
|
void OnSubtitlesOpen();
|
||||||
|
|
||||||
void EnableToolBar(agi::OptionValue const& opt);
|
void EnableToolBar(agi::OptionValue const& opt);
|
||||||
|
@ -116,7 +98,5 @@ public:
|
||||||
bool IsVideoShown() const { return showVideo; }
|
bool IsVideoShown() const { return showVideo; }
|
||||||
bool IsAudioShown() const { return showAudio; }
|
bool IsAudioShown() const { return showAudio; }
|
||||||
|
|
||||||
bool LoadList(wxArrayString list);
|
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/character_count.h>
|
#include <libaegisub/character_count.h>
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,6 @@ protected:
|
||||||
int sample_rate;
|
int sample_rate;
|
||||||
int bytes_per_sample;
|
int bytes_per_sample;
|
||||||
bool float_samples;
|
bool float_samples;
|
||||||
agi::fs::path filename;
|
|
||||||
|
|
||||||
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
|
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
|
||||||
|
|
||||||
|
@ -62,7 +61,6 @@ public:
|
||||||
void GetAudio(void *buf, int64_t start, int64_t count) const;
|
void GetAudio(void *buf, int64_t start, int64_t count) const;
|
||||||
void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
|
void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
|
||||||
|
|
||||||
agi::fs::path GetFilename() const { return filename; }
|
|
||||||
int64_t GetNumSamples() const { return num_samples; }
|
int64_t GetNumSamples() const { return num_samples; }
|
||||||
int64_t GetDecodedSamples() const { return decoded_samples; }
|
int64_t GetDecodedSamples() const { return decoded_samples; }
|
||||||
int GetSampleRate() const { return sample_rate; }
|
int GetSampleRate() const { return sample_rate; }
|
||||||
|
@ -88,7 +86,6 @@ public:
|
||||||
sample_rate = source->GetSampleRate();
|
sample_rate = source->GetSampleRate();
|
||||||
bytes_per_sample = source->GetBytesPerSample();
|
bytes_per_sample = source->GetBytesPerSample();
|
||||||
float_samples = source->AreSamplesFloat();
|
float_samples = source->AreSamplesFloat();
|
||||||
filename = source->GetFilename();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,14 @@ class AssDialogue;
|
||||||
class AudioKaraoke;
|
class AudioKaraoke;
|
||||||
class DialogManager;
|
class DialogManager;
|
||||||
class FrameMain;
|
class FrameMain;
|
||||||
|
class Project;
|
||||||
class SearchReplaceEngine;
|
class SearchReplaceEngine;
|
||||||
class InitialLineState;
|
class InitialLineState;
|
||||||
class SelectionController;
|
class SelectionController;
|
||||||
class SubsController;
|
class SubsController;
|
||||||
class BaseGrid;
|
class BaseGrid;
|
||||||
class TextSelectionController;
|
class TextSelectionController;
|
||||||
class VideoContext;
|
class VideoController;
|
||||||
class VideoDisplay;
|
class VideoDisplay;
|
||||||
class wxWindow;
|
class wxWindow;
|
||||||
namespace Automation4 { class ScriptManager; }
|
namespace Automation4 { class ScriptManager; }
|
||||||
|
@ -42,10 +43,11 @@ struct Context {
|
||||||
std::unique_ptr<AssFile> ass;
|
std::unique_ptr<AssFile> ass;
|
||||||
std::unique_ptr<TextSelectionController> textSelectionController;
|
std::unique_ptr<TextSelectionController> textSelectionController;
|
||||||
std::unique_ptr<SubsController> subsController;
|
std::unique_ptr<SubsController> subsController;
|
||||||
|
std::unique_ptr<Project> project;
|
||||||
std::unique_ptr<Automation4::ScriptManager> local_scripts;
|
std::unique_ptr<Automation4::ScriptManager> local_scripts;
|
||||||
std::unique_ptr<VideoContext> videoController;
|
|
||||||
std::unique_ptr<AudioController> audioController;
|
|
||||||
std::unique_ptr<SelectionController> selectionController;
|
std::unique_ptr<SelectionController> selectionController;
|
||||||
|
std::unique_ptr<VideoController> videoController;
|
||||||
|
std::unique_ptr<AudioController> audioController;
|
||||||
std::unique_ptr<InitialLineState> initialLineState;
|
std::unique_ptr<InitialLineState> initialLineState;
|
||||||
std::unique_ptr<SearchReplaceEngine> search;
|
std::unique_ptr<SearchReplaceEngine> search;
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ struct VideoFrame;
|
||||||
|
|
||||||
class VideoProvider {
|
class VideoProvider {
|
||||||
public:
|
public:
|
||||||
virtual ~VideoProvider() {}
|
virtual ~VideoProvider() = default;
|
||||||
|
|
||||||
/// Override this method to actually get frames
|
/// Override this method to actually get frames
|
||||||
virtual std::shared_ptr<VideoFrame> GetFrame(int n)=0;
|
virtual std::shared_ptr<VideoFrame> GetFrame(int n)=0;
|
||||||
|
|
19
src/main.cpp
19
src/main.cpp
|
@ -49,10 +49,9 @@
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "subs_controller.h"
|
#include "project.h"
|
||||||
#include "subtitle_format.h"
|
#include "subtitle_format.h"
|
||||||
#include "subtitles_provider_libass.h"
|
#include "subtitles_provider_libass.h"
|
||||||
#include "video_context.h"
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
@ -317,15 +316,14 @@ bool AegisubApp::OnInit() {
|
||||||
|
|
||||||
// Get parameter subs
|
// Get parameter subs
|
||||||
StartupLog("Parse command line");
|
StartupLog("Parse command line");
|
||||||
wxArrayString subs;
|
std::vector<agi::fs::path> files;
|
||||||
for (int i = 1; i < argc; ++i)
|
for (int i = 1; i < argc; ++i)
|
||||||
subs.push_back(argv[i]);
|
files.push_back(from_wx(argv[i]));
|
||||||
if (!subs.empty())
|
if (!files.empty())
|
||||||
frame->LoadList(subs);
|
frame->context->project->LoadList(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (const char *err) {
|
catch (const char *err) {
|
||||||
wxMessageBox(err,"Fatal error while initializing");
|
wxMessageBox(err, "Fatal error while initializing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (wxString const& err) {
|
catch (wxString const& err) {
|
||||||
|
@ -333,10 +331,9 @@ bool AegisubApp::OnInit() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (agi::Exception const& e) {
|
catch (agi::Exception const& e) {
|
||||||
wxMessageBox(to_wx(e.GetMessage()),"Fatal error while initializing");
|
wxMessageBox(to_wx(e.GetMessage()), "Fatal error while initializing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _DEBUG
|
#ifndef _DEBUG
|
||||||
catch (...) {
|
catch (...) {
|
||||||
wxMessageBox("Unhandled exception","Fatal error while initializing");
|
wxMessageBox("Unhandled exception","Fatal error while initializing");
|
||||||
|
@ -470,5 +467,5 @@ int AegisubApp::OnRun() {
|
||||||
|
|
||||||
void AegisubApp::MacOpenFile(const wxString &filename) {
|
void AegisubApp::MacOpenFile(const wxString &filename) {
|
||||||
if (frame && !filename.empty())
|
if (frame && !filename.empty())
|
||||||
frame->context->subsController->Load(agi::fs::path(filename));
|
frame->context->project->LoadSubtitles(agi::fs::path(filename));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,443 @@
|
||||||
|
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
#include "project.h"
|
||||||
|
|
||||||
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
|
#include "audio_controller.h"
|
||||||
|
#include "charset_detect.h"
|
||||||
|
#include "compat.h"
|
||||||
|
#include "dialog_progress.h"
|
||||||
|
#include "dialog_video_properties.h"
|
||||||
|
#include "include/aegisub/audio_provider.h"
|
||||||
|
#include "include/aegisub/context.h"
|
||||||
|
#include "include/aegisub/video_provider.h"
|
||||||
|
#include "mkv_wrap.h"
|
||||||
|
#include "options.h"
|
||||||
|
#include "subs_controller.h"
|
||||||
|
#include "video_controller.h"
|
||||||
|
#include "video_display.h"
|
||||||
|
|
||||||
|
#include <libaegisub/fs.h>
|
||||||
|
#include <libaegisub/keyframe.h>
|
||||||
|
#include <libaegisub/log.h>
|
||||||
|
#include <libaegisub/make_unique.h>
|
||||||
|
#include <libaegisub/path.h>
|
||||||
|
#include <libaegisub/util.h>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
|
Project::Project(agi::Context *c) : context(c) {
|
||||||
|
OPT_SUB("Audio/Cache/Type", &Project::ReloadAudio, this);
|
||||||
|
OPT_SUB("Audio/Provider", &Project::ReloadAudio, this);
|
||||||
|
OPT_SUB("Provider/Audio/FFmpegSource/Decode Error Handling", &Project::ReloadAudio, this);
|
||||||
|
OPT_SUB("Provider/Avisynth/Allow Ancient", &Project::ReloadVideo, this);
|
||||||
|
OPT_SUB("Provider/Avisynth/Memory Max", &Project::ReloadVideo, this);
|
||||||
|
OPT_SUB("Provider/Video/FFmpegSource/Decoding Threads", &Project::ReloadVideo, this);
|
||||||
|
OPT_SUB("Provider/Video/FFmpegSource/Unsafe Seeking", &Project::ReloadVideo, this);
|
||||||
|
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::ReloadAudio() {
|
||||||
|
if (audio_provider)
|
||||||
|
LoadAudio(audio_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::ReloadVideo() {
|
||||||
|
if (video_provider)
|
||||||
|
LoadAudio(video_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::ShowError(wxString const& message) {
|
||||||
|
wxMessageBox(message, "Error loading file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::ShowError(std::string const& message) {
|
||||||
|
ShowError(to_wx(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::DoLoadSubtitles(agi::fs::path const& path, std::string encoding) {
|
||||||
|
try {
|
||||||
|
if (encoding.empty())
|
||||||
|
encoding = CharSetDetect::GetEncoding(path);
|
||||||
|
}
|
||||||
|
catch (agi::UserCancelException const&) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoding != "binary") {
|
||||||
|
// Try loading as timecodes and keyframes first since we can't
|
||||||
|
// distinguish them based on filename alone, and just ignore failures
|
||||||
|
// rather than trying to differentiate between malformed timecodes
|
||||||
|
// files and things that aren't timecodes files at all
|
||||||
|
try { return DoLoadTimecodes(path); } catch (...) { }
|
||||||
|
try { return DoLoadKeyframes(path); } catch (...) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context->subsController->Load(path, encoding);
|
||||||
|
}
|
||||||
|
catch (agi::UserCancelException const&) { return; }
|
||||||
|
catch (agi::fs::FileNotFound const&) {
|
||||||
|
config::mru->Remove("Subtitle", path);
|
||||||
|
return ShowError(path.string() + " not found.");
|
||||||
|
}
|
||||||
|
catch (agi::Exception const& e) {
|
||||||
|
return ShowError(e.GetChainedMessage());
|
||||||
|
}
|
||||||
|
catch (std::exception const& e) {
|
||||||
|
return ShowError(std::string(e.what()));
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
return ShowError(wxString("Unknown error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadSubtitles(agi::fs::path const& path, std::string encoding) {
|
||||||
|
DoLoadSubtitles(path, encoding);
|
||||||
|
LoadUnloadFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::CloseSubtitles() {
|
||||||
|
context->subsController->Close();
|
||||||
|
config::path->SetToken("?script", "");
|
||||||
|
LoadUnloadFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
if (video == video_file && audio == audio_file && keyframes == keyframes_file && timecodes == timecodes_file)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (load_linked == 2) {
|
||||||
|
if (wxMessageBox(_("Do you want to load/unload the associated files?"), _("(Un)Load files?"), wxYES_NO | wxCENTRE, context->parent) != wxYES)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loaded_video = false;
|
||||||
|
if (video != video_file) {
|
||||||
|
if (video.empty())
|
||||||
|
CloseVideo();
|
||||||
|
else if ((loaded_video = DoLoadVideo(video))) {
|
||||||
|
auto vc = context->videoController.get();
|
||||||
|
vc->JumpToFrame(context->ass->GetUIStateAsInt("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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timecodes.empty()) LoadTimecodes(timecodes);
|
||||||
|
if (!keyframes.empty()) LoadKeyframes(keyframes);
|
||||||
|
|
||||||
|
if (audio != audio_file) {
|
||||||
|
if (audio.empty())
|
||||||
|
CloseAudio();
|
||||||
|
else
|
||||||
|
DoLoadAudio(audio, false);
|
||||||
|
}
|
||||||
|
else if (loaded_video && OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
|
||||||
|
DoLoadAudio(video, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
|
||||||
|
if (!progress)
|
||||||
|
progress = new DialogProgress(context->parent);
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
audio_provider = AudioProviderFactory::GetProvider(path, progress);
|
||||||
|
}
|
||||||
|
catch (agi::UserCancelException const&) { return; }
|
||||||
|
catch (...) {
|
||||||
|
config::mru->Remove("Audio", path);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (agi::fs::FileNotFound const& e) {
|
||||||
|
return ShowError(_("The audio file was not found: ") + to_wx(e.GetChainedMessage()));
|
||||||
|
}
|
||||||
|
catch (agi::AudioDataNotFoundError const& e) {
|
||||||
|
if (quiet) {
|
||||||
|
LOG_D("video/open/audio") << "File " << video_file << " has no audio data: " << e.GetChainedMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return ShowError(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()));
|
||||||
|
}
|
||||||
|
catch (agi::AudioProviderOpenError const& e) {
|
||||||
|
return ShowError(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()));
|
||||||
|
}
|
||||||
|
catch (agi::Exception const& e) {
|
||||||
|
return ShowError(e.GetChainedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_file = path;
|
||||||
|
config::path->SetToken("?audio", path);
|
||||||
|
config::mru->Add("Audio", path);
|
||||||
|
AnnounceAudioProviderModified(audio_provider.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadAudio(agi::fs::path const& path) {
|
||||||
|
DoLoadAudio(path, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::CloseAudio() {
|
||||||
|
AnnounceAudioProviderModified(nullptr);
|
||||||
|
audio_provider.reset();
|
||||||
|
audio_file.clear();
|
||||||
|
config::path->SetToken("?audio", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Project::DoLoadVideo(agi::fs::path const& path) {
|
||||||
|
if (!progress)
|
||||||
|
progress = new DialogProgress(context->parent);
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto old_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
|
||||||
|
video_provider = agi::make_unique<AsyncVideoProvider>(path, old_matrix, context->videoController.get(), progress);
|
||||||
|
}
|
||||||
|
catch (agi::UserCancelException const&) { return false; }
|
||||||
|
catch (agi::fs::FileSystemError const& err) {
|
||||||
|
config::mru->Remove("Video", path);
|
||||||
|
ShowError(to_wx(err.GetMessage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (VideoProviderError const& err) {
|
||||||
|
ShowError(to_wx(err.GetMessage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateVideoProperties(context->ass.get(), video_provider.get(), context->parent);
|
||||||
|
video_provider->LoadSubtitles(context->ass.get());
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
std::string warning = video_provider->GetWarning();
|
||||||
|
if (!warning.empty())
|
||||||
|
wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
|
||||||
|
|
||||||
|
video_has_subtitles = false;
|
||||||
|
if (agi::fs::HasExtension(path, "mkv"))
|
||||||
|
video_has_subtitles = MatroskaWrapper::HasSubtitles(path);
|
||||||
|
|
||||||
|
AnnounceVideoProviderModified(video_provider.get());
|
||||||
|
AnnounceKeyframesModified(keyframes);
|
||||||
|
AnnounceTimecodesModified(timecodes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadVideo(agi::fs::path const& path) {
|
||||||
|
if (!DoLoadVideo(path)) return;
|
||||||
|
if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
|
||||||
|
DoLoadAudio(video_file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::CloseVideo() {
|
||||||
|
AnnounceVideoProviderModified(nullptr);
|
||||||
|
video_provider.reset();
|
||||||
|
video_file.clear();
|
||||||
|
config::path->SetToken("?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);
|
||||||
|
AnnounceTimecodesModified(timecodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadTimecodes(agi::fs::path const& path) {
|
||||||
|
try {
|
||||||
|
DoLoadTimecodes(path);
|
||||||
|
}
|
||||||
|
catch (agi::fs::FileSystemError const& e) {
|
||||||
|
ShowError(e.GetChainedMessage());
|
||||||
|
config::mru->Remove("Timecodes", path);
|
||||||
|
}
|
||||||
|
catch (agi::vfr::Error const& e) {
|
||||||
|
ShowError("Failed to parse timecodes file: " + e.GetChainedMessage());
|
||||||
|
config::mru->Remove("Timecodes", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::CloseTimecodes() {
|
||||||
|
timecodes = video_provider ? video_provider->GetFPS() : agi::vfr::Framerate{};
|
||||||
|
timecodes_file.clear();
|
||||||
|
AnnounceTimecodesModified(timecodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::DoLoadKeyframes(agi::fs::path const& path) {
|
||||||
|
keyframes = agi::keyframe::Load(path);
|
||||||
|
keyframes_file = path;
|
||||||
|
config::mru->Add("Keyframes", path);
|
||||||
|
AnnounceKeyframesModified(keyframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadKeyframes(agi::fs::path const& path) {
|
||||||
|
try {
|
||||||
|
DoLoadKeyframes(path);
|
||||||
|
}
|
||||||
|
catch (agi::fs::FileSystemError const& e) {
|
||||||
|
ShowError(e.GetChainedMessage());
|
||||||
|
config::mru->Remove("Keyframes", path);
|
||||||
|
}
|
||||||
|
catch (agi::keyframe::Error const& e) {
|
||||||
|
ShowError("Failed to parse keyframes file: " + e.GetChainedMessage());
|
||||||
|
config::mru->Remove("Keyframes", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::CloseKeyframes() {
|
||||||
|
keyframes = video_provider ? video_provider->GetKeyFrames() : std::vector<int>{};
|
||||||
|
keyframes_file.clear();
|
||||||
|
AnnounceKeyframesModified(keyframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Project::LoadList(std::vector<agi::fs::path> const& files) {
|
||||||
|
// Keep these lists sorted
|
||||||
|
|
||||||
|
// Video formats
|
||||||
|
const char *videoList[] = {
|
||||||
|
".asf",
|
||||||
|
".avi",
|
||||||
|
".avs",
|
||||||
|
".d2v",
|
||||||
|
".m2ts",
|
||||||
|
".m4v",
|
||||||
|
".mkv",
|
||||||
|
".mov",
|
||||||
|
".mp4",
|
||||||
|
".mpeg",
|
||||||
|
".mpg",
|
||||||
|
".ogm",
|
||||||
|
".rm",
|
||||||
|
".rmvb",
|
||||||
|
".ts",
|
||||||
|
".webm"
|
||||||
|
".wmv",
|
||||||
|
".y4m",
|
||||||
|
".yuv"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subtitle formats
|
||||||
|
const char *subsList[] = {
|
||||||
|
".ass",
|
||||||
|
".srt",
|
||||||
|
".ssa",
|
||||||
|
".sub",
|
||||||
|
".ttxt",
|
||||||
|
".txt"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Audio formats
|
||||||
|
const char *audioList[] = {
|
||||||
|
".aac",
|
||||||
|
".ac3",
|
||||||
|
".ape",
|
||||||
|
".dts",
|
||||||
|
".flac",
|
||||||
|
".m4a",
|
||||||
|
".mka",
|
||||||
|
".mp3",
|
||||||
|
".ogg",
|
||||||
|
".w64",
|
||||||
|
".wav",
|
||||||
|
".wma"
|
||||||
|
};
|
||||||
|
|
||||||
|
auto search = [](const char **begin, const char **end, std::string const& str) {
|
||||||
|
return std::binary_search(begin, end, str.c_str(), [](const char *a, const char *b) {
|
||||||
|
return strcmp(a, b) < 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
agi::fs::path audio, video, subs;
|
||||||
|
for (auto file : files) {
|
||||||
|
if (file.is_relative()) file = absolute(file);
|
||||||
|
if (!agi::fs::FileExists(file)) continue;
|
||||||
|
|
||||||
|
auto ext = file.extension().string();
|
||||||
|
boost::to_lower(ext);
|
||||||
|
|
||||||
|
if (subs.empty() && search(std::begin(subsList), std::end(subsList), ext))
|
||||||
|
subs = file;
|
||||||
|
if (video.empty() && search(std::begin(videoList), std::end(videoList), ext))
|
||||||
|
video = file;
|
||||||
|
if (audio.empty() && search(std::begin(audioList), std::end(audioList), ext))
|
||||||
|
audio = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subs.empty())
|
||||||
|
DoLoadSubtitles(subs);
|
||||||
|
if (!video.empty())
|
||||||
|
DoLoadVideo(video);
|
||||||
|
if (!audio.empty())
|
||||||
|
DoLoadAudio(audio, false);
|
||||||
|
else if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
|
||||||
|
DoLoadAudio(video_file, true);
|
||||||
|
|
||||||
|
if (!subs.empty())
|
||||||
|
LoadUnloadFiles();
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
#include <libaegisub/fs_fwd.h>
|
||||||
|
#include <libaegisub/signal.h>
|
||||||
|
#include <libaegisub/vfr.h>
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class AsyncVideoProvider;
|
||||||
|
class AudioProvider;
|
||||||
|
class DialogProgress;
|
||||||
|
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;
|
||||||
|
std::vector<int> keyframes;
|
||||||
|
|
||||||
|
agi::fs::path audio_file;
|
||||||
|
agi::fs::path video_file;
|
||||||
|
agi::fs::path timecodes_file;
|
||||||
|
agi::fs::path keyframes_file;
|
||||||
|
|
||||||
|
agi::signal::Signal<AudioProvider *> AnnounceAudioProviderModified;
|
||||||
|
agi::signal::Signal<AsyncVideoProvider *> AnnounceVideoProviderModified;
|
||||||
|
agi::signal::Signal<agi::vfr::Framerate const&> AnnounceTimecodesModified;
|
||||||
|
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);
|
||||||
|
void ShowError(std::string const& message);
|
||||||
|
|
||||||
|
void DoLoadSubtitles(agi::fs::path const& path, std::string encoding="");
|
||||||
|
void DoLoadAudio(agi::fs::path const& path, bool quiet);
|
||||||
|
bool DoLoadVideo(agi::fs::path const& path);
|
||||||
|
void DoLoadTimecodes(agi::fs::path const& path);
|
||||||
|
void DoLoadKeyframes(agi::fs::path const& path);
|
||||||
|
|
||||||
|
void LoadUnloadFiles();
|
||||||
|
|
||||||
|
void OnSubtitlesSave();
|
||||||
|
void ReloadAudio();
|
||||||
|
void ReloadVideo();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Project(agi::Context *context);
|
||||||
|
~Project();
|
||||||
|
|
||||||
|
void LoadSubtitles(agi::fs::path const& path, std::string encoding="");
|
||||||
|
void CloseSubtitles();
|
||||||
|
bool CanLoadSubtitlesFromVideo() const { return video_has_subtitles; }
|
||||||
|
|
||||||
|
void LoadAudio(agi::fs::path const& path);
|
||||||
|
void CloseAudio();
|
||||||
|
AudioProvider *AudioProvider() const { return audio_provider.get(); }
|
||||||
|
agi::fs::path const& AudioName() const { return audio_file; }
|
||||||
|
|
||||||
|
void LoadVideo(agi::fs::path const& path);
|
||||||
|
void CloseVideo();
|
||||||
|
AsyncVideoProvider *VideoProvider() const { return video_provider.get(); }
|
||||||
|
agi::fs::path const& VideoName() const { return video_file; }
|
||||||
|
|
||||||
|
void LoadTimecodes(agi::fs::path const& path);
|
||||||
|
void CloseTimecodes();
|
||||||
|
bool CanCloseTimecodes() const { return !timecodes_file.empty(); }
|
||||||
|
agi::vfr::Framerate const& Timecodes() const { return timecodes; }
|
||||||
|
|
||||||
|
void LoadKeyframes(agi::fs::path const& path);
|
||||||
|
void CloseKeyframes();
|
||||||
|
bool CanCloseKeyframes() const { return !keyframes_file.empty(); }
|
||||||
|
std::vector<int> const& Keyframes() const { return keyframes; }
|
||||||
|
|
||||||
|
void LoadList(std::vector<agi::fs::path> const& files);
|
||||||
|
|
||||||
|
DEFINE_SIGNAL_ADDERS(AnnounceAudioProviderModified, AddAudioProviderListener)
|
||||||
|
DEFINE_SIGNAL_ADDERS(AnnounceVideoProviderModified, AddVideoProviderListener)
|
||||||
|
DEFINE_SIGNAL_ADDERS(AnnounceTimecodesModified, AddTimecodesListener)
|
||||||
|
DEFINE_SIGNAL_ADDERS(AnnounceKeyframesModified, AddKeyframesListener)
|
||||||
|
};
|
|
@ -22,18 +22,16 @@
|
||||||
#include "ass_info.h"
|
#include "ass_info.h"
|
||||||
#include "ass_style.h"
|
#include "ass_style.h"
|
||||||
#include "ass_style_storage.h"
|
#include "ass_style_storage.h"
|
||||||
#include "charset_detect.h"
|
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "frame_main.h"
|
#include "frame_main.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subtitle_format.h"
|
#include "subtitle_format.h"
|
||||||
#include "text_file_reader.h"
|
|
||||||
#include "text_selection_controller.h"
|
#include "text_selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/path.h>
|
#include <libaegisub/path.h>
|
||||||
|
@ -175,61 +173,15 @@ void SubsController::SetSelectionController(SelectionController *selection_contr
|
||||||
void SubsController::Load(agi::fs::path const& filename, std::string charset) {
|
void SubsController::Load(agi::fs::path const& filename, std::string charset) {
|
||||||
AssFile temp;
|
AssFile temp;
|
||||||
|
|
||||||
try {
|
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->project->Timecodes(), charset);
|
||||||
try {
|
|
||||||
if (charset.empty())
|
|
||||||
charset = CharSetDetect::GetEncoding(filename);
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that file isn't actually a timecode file
|
// Make sure the file has at least one style and one dialogue line
|
||||||
if (charset != "binary") {
|
if (temp.Styles.empty())
|
||||||
try {
|
temp.Styles.push_back(*new AssStyle);
|
||||||
TextFileReader testSubs(filename, charset);
|
if (temp.Events.empty())
|
||||||
std::string cur = testSubs.ReadLineFromFile();
|
temp.Events.push_back(*new AssDialogue);
|
||||||
if (boost::starts_with(cur, "# timecode")) {
|
|
||||||
context->videoController->LoadTimecodes(filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
// if trying to load the file as timecodes fails it's fairly
|
|
||||||
// safe to assume that it is in fact not a timecode file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->videoController->FPS(), charset);
|
context->ass->swap(temp);
|
||||||
|
|
||||||
// Make sure the file has at least one style and one dialogue line
|
|
||||||
if (temp.Styles.empty())
|
|
||||||
temp.Styles.push_back(*new AssStyle);
|
|
||||||
if (temp.Events.empty())
|
|
||||||
temp.Events.push_back(*new AssDialogue);
|
|
||||||
|
|
||||||
context->ass->swap(temp);
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (agi::fs::FileNotFound const&) {
|
|
||||||
wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
config::mru->Remove("Subtitle", filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (agi::Exception const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (std::exception const& err) {
|
|
||||||
wxMessageBox(to_wx(err.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetFileName(filename);
|
SetFileName(filename);
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,8 @@ public:
|
||||||
|
|
||||||
/// @brief Load from a file
|
/// @brief Load from a file
|
||||||
/// @param file File name
|
/// @param file File name
|
||||||
/// @param charset Character set of file or empty to autodetect
|
/// @param charset Character set of file
|
||||||
void Load(agi::fs::path const& file, std::string charset="");
|
void Load(agi::fs::path const& file, std::string charset);
|
||||||
|
|
||||||
/// @brief Save to a file
|
/// @brief Save to a file
|
||||||
/// @param file Path to save to
|
/// @param file Path to save to
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "initial_line_state.h"
|
#include "initial_line_state.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "placeholder_ctrl.h"
|
#include "placeholder_ctrl.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_edit_ctrl.h"
|
#include "subs_edit_ctrl.h"
|
||||||
|
@ -52,7 +53,6 @@
|
||||||
#include "timeedit_ctrl.h"
|
#include "timeedit_ctrl.h"
|
||||||
#include "tooltip_manager.h"
|
#include "tooltip_manager.h"
|
||||||
#include "validators.h"
|
#include "validators.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <libaegisub/character_count.h>
|
#include <libaegisub/character_count.h>
|
||||||
#include <libaegisub/util.h>
|
#include <libaegisub/util.h>
|
||||||
|
@ -228,10 +228,12 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
|
||||||
OnSize(evt);
|
OnSize(evt);
|
||||||
|
|
||||||
file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this);
|
file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this);
|
||||||
connections.push_back(context->videoController->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this));
|
connections = agi::signal::make_vector({
|
||||||
connections.push_back(context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this));
|
context->project->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this),
|
||||||
connections.push_back(context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this));
|
context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this),
|
||||||
connections.push_back(context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this));
|
context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this),
|
||||||
|
context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this),
|
||||||
|
});
|
||||||
|
|
||||||
context->textSelectionController->SetControl(edit_ctrl);
|
context->textSelectionController->SetControl(edit_ctrl);
|
||||||
edit_ctrl->SetFocus();
|
edit_ctrl->SetFocus();
|
||||||
|
@ -394,14 +396,6 @@ void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) {
|
||||||
commit_id = -1;
|
commit_id = -1;
|
||||||
|
|
||||||
UpdateFields(AssFile::COMMIT_DIAG_FULL, false);
|
UpdateFields(AssFile::COMMIT_DIAG_FULL, false);
|
||||||
|
|
||||||
/// @todo VideoContext should be doing this
|
|
||||||
if (c->videoController->IsLoaded()) {
|
|
||||||
if (OPT_GET("Video/Subtitle Sync")->GetBool()) {
|
|
||||||
c->videoController->Stop();
|
|
||||||
c->videoController->JumpToTime(line->Start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SubsEditBox::OnSelectedSetChanged() {
|
void SubsEditBox::OnSelectedSetChanged() {
|
||||||
|
@ -488,8 +482,10 @@ void SubsEditBox::CommitTimes(TimeField field) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TIME_DURATION:
|
case TIME_DURATION:
|
||||||
if (by_frame->GetValue())
|
if (by_frame->GetValue()) {
|
||||||
d->End = c->videoController->TimeAtFrame(c->videoController->FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
|
auto const& fps = c->project->Timecodes();
|
||||||
|
d->End = fps.TimeAtFrame(fps.FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
d->End = d->Start + duration->GetTime();
|
d->End = d->Start + duration->GetTime();
|
||||||
initial_times[d].second = d->End;
|
initial_times[d].second = d->End;
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
///
|
///
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
|
||||||
#include <boost/container/map.hpp>
|
#include <boost/container/map.hpp>
|
||||||
#include <boost/flyweight/flyweight_fwd.hpp>
|
#include <boost/flyweight/flyweight_fwd.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -74,7 +73,7 @@ class SubsEditBox final : public wxPanel {
|
||||||
TIME_DURATION
|
TIME_DURATION
|
||||||
};
|
};
|
||||||
|
|
||||||
std::deque<agi::signal::Connection> connections;
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
/// Currently active dialogue line
|
/// Currently active dialogue line
|
||||||
AssDialogue *line = nullptr;
|
AssDialogue *line = nullptr;
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
#include "subtitle_format_transtation.h"
|
#include "subtitle_format_transtation.h"
|
||||||
#include "subtitle_format_ttxt.h"
|
#include "subtitle_format_ttxt.h"
|
||||||
#include "subtitle_format_txt.h"
|
#include "subtitle_format_txt.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "text_file_reader.h"
|
#include "text_file_reader.h"
|
||||||
#include "text_file_writer.h"
|
#include "text_file_writer.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/util.h>
|
#include <libaegisub/util.h>
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include <wx/clipbrd.h>
|
#include <wx/clipbrd.h>
|
||||||
#include <wx/dataobj.h>
|
#include <wx/dataobj.h>
|
||||||
|
@ -88,17 +88,17 @@ void TimeEdit::SetTime(AssTime new_time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int TimeEdit::GetFrame() const {
|
int TimeEdit::GetFrame() const {
|
||||||
return c->videoController->FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START);
|
return c->project->Timecodes().FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimeEdit::SetFrame(int fn) {
|
void TimeEdit::SetFrame(int fn) {
|
||||||
SetTime(c->videoController->TimeAtFrame(fn, isEnd ? agi::vfr::END : agi::vfr::START));
|
SetTime(c->project->Timecodes().TimeAtFrame(fn, isEnd ? agi::vfr::END : agi::vfr::START));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimeEdit::SetByFrame(bool enableByFrame) {
|
void TimeEdit::SetByFrame(bool enableByFrame) {
|
||||||
if (enableByFrame == byFrame) return;
|
if (enableByFrame == byFrame) return;
|
||||||
|
|
||||||
byFrame = enableByFrame && c->videoController->TimecodesLoaded();
|
byFrame = enableByFrame && c->project->Timecodes().IsLoaded();
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
|
||||||
if (byFrame) {
|
if (byFrame) {
|
||||||
long temp = 0;
|
long temp = 0;
|
||||||
GetValue().ToLong(&temp);
|
GetValue().ToLong(&temp);
|
||||||
time = c->videoController->TimeAtFrame(temp, isEnd ? agi::vfr::END : agi::vfr::START);
|
time = c->project->Timecodes().TimeAtFrame(temp, isEnd ? agi::vfr::END : agi::vfr::START);
|
||||||
}
|
}
|
||||||
else if (insert)
|
else if (insert)
|
||||||
time = from_wx(GetValue());
|
time = from_wx(GetValue());
|
||||||
|
@ -115,7 +115,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
|
||||||
|
|
||||||
void TimeEdit::UpdateText() {
|
void TimeEdit::UpdateText() {
|
||||||
if (byFrame)
|
if (byFrame)
|
||||||
ChangeValue(std::to_wstring(c->videoController->FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START)));
|
ChangeValue(std::to_wstring(c->project->Timecodes().FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START)));
|
||||||
else
|
else
|
||||||
ChangeValue(to_wx(time.GetAssFormated()));
|
ChangeValue(to_wx(time.GetAssFormated()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,15 +97,6 @@ std::string GetClipboard();
|
||||||
void SetClipboard(std::string const& new_value);
|
void SetClipboard(std::string const& new_value);
|
||||||
void SetClipboard(wxBitmap const& new_value);
|
void SetClipboard(wxBitmap const& new_value);
|
||||||
|
|
||||||
#ifndef FORCEINLINE
|
|
||||||
#ifdef __VISUALC__
|
|
||||||
#define FORCEINLINE __forceinline
|
|
||||||
#else
|
|
||||||
#define FORCEINLINE inline
|
|
||||||
// __attribute__((always_inline)) gives me errors on g++ ~amz
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define countof(array) (sizeof(array) / sizeof(array[0]))
|
#define countof(array) (sizeof(array) / sizeof(array[0]))
|
||||||
|
|
||||||
wxString FontFace(std::string opt_prefix);
|
wxString FontFace(std::string opt_prefix);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <wx/combobox.h>
|
||||||
#include <wx/radiobox.h>
|
#include <wx/radiobox.h>
|
||||||
#include <wx/validate.h>
|
#include <wx/validate.h>
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,9 @@
|
||||||
#include "include/aegisub/toolbar.h"
|
#include "include/aegisub/toolbar.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
#include "video_slider.h"
|
#include "video_slider.h"
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
||||||
auto videoSlider = new VideoSlider(this, context);
|
auto videoSlider = new VideoSlider(this, context);
|
||||||
videoSlider->SetToolTip(_("Seek video"));
|
videoSlider->SetToolTip(_("Seek video"));
|
||||||
|
|
||||||
wxToolBar *mainToolbar = toolbar::GetToolbar(this, "video", context, "Video", false);
|
auto mainToolbar = toolbar::GetToolbar(this, "video", context, "Video", false);
|
||||||
|
|
||||||
VideoPosition = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(110, 20), wxTE_READONLY);
|
VideoPosition = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(110, 20), wxTE_READONLY);
|
||||||
VideoPosition->SetToolTip(_("Current frame time and number"));
|
VideoPosition->SetToolTip(_("Current frame time and number"));
|
||||||
|
@ -67,29 +68,29 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
||||||
wxArrayString choices;
|
wxArrayString choices;
|
||||||
for (int i = 1; i <= 24; ++i)
|
for (int i = 1; i <= 24; ++i)
|
||||||
choices.Add(wxString::Format("%g%%", i * 12.5));
|
choices.Add(wxString::Format("%g%%", i * 12.5));
|
||||||
wxComboBox *zoomBox = new wxComboBox(this, -1, "75%", wxDefaultPosition, wxDefaultSize, choices, wxCB_DROPDOWN | wxTE_PROCESS_ENTER);
|
auto zoomBox = new wxComboBox(this, -1, "75%", wxDefaultPosition, wxDefaultSize, choices, wxCB_DROPDOWN | wxTE_PROCESS_ENTER);
|
||||||
|
|
||||||
wxToolBar *visualToolBar = toolbar::GetToolbar(this, "visual_tools", context, "Video", true);
|
auto visualToolBar = toolbar::GetToolbar(this, "visual_tools", context, "Video", true);
|
||||||
wxToolBar *visualSubToolBar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_VERTICAL | wxTB_BOTTOM | wxTB_FLAT);
|
auto visualSubToolBar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_VERTICAL | wxTB_BOTTOM | wxTB_FLAT);
|
||||||
|
|
||||||
auto videoDisplay = new VideoDisplay(visualSubToolBar, isDetached, zoomBox, this, context);
|
auto videoDisplay = new VideoDisplay(visualSubToolBar, isDetached, zoomBox, this, context);
|
||||||
videoDisplay->MoveBeforeInTabOrder(videoSlider);
|
videoDisplay->MoveBeforeInTabOrder(videoSlider);
|
||||||
|
|
||||||
wxSizer *toolbarSizer = new wxBoxSizer(wxVERTICAL);
|
auto toolbarSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
toolbarSizer->Add(visualToolBar, wxSizerFlags(1));
|
toolbarSizer->Add(visualToolBar, wxSizerFlags(1));
|
||||||
toolbarSizer->Add(visualSubToolBar, wxSizerFlags());
|
toolbarSizer->Add(visualSubToolBar, wxSizerFlags());
|
||||||
|
|
||||||
wxSizer *topSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto topSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
topSizer->Add(toolbarSizer, 0, wxEXPAND);
|
topSizer->Add(toolbarSizer, 0, wxEXPAND);
|
||||||
topSizer->Add(videoDisplay, isDetached, isDetached ? wxEXPAND : 0);
|
topSizer->Add(videoDisplay, isDetached, isDetached ? wxEXPAND : 0);
|
||||||
|
|
||||||
wxSizer *videoBottomSizer = new wxBoxSizer(wxHORIZONTAL);
|
auto videoBottomSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
videoBottomSizer->Add(mainToolbar, wxSizerFlags(0).Center());
|
videoBottomSizer->Add(mainToolbar, wxSizerFlags(0).Center());
|
||||||
videoBottomSizer->Add(VideoPosition, wxSizerFlags(1).Center().Border(wxLEFT));
|
videoBottomSizer->Add(VideoPosition, wxSizerFlags(1).Center().Border(wxLEFT));
|
||||||
videoBottomSizer->Add(VideoSubsPos, wxSizerFlags(1).Center().Border(wxLEFT));
|
videoBottomSizer->Add(VideoSubsPos, wxSizerFlags(1).Center().Border(wxLEFT));
|
||||||
videoBottomSizer->Add(zoomBox, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT));
|
videoBottomSizer->Add(zoomBox, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT));
|
||||||
|
|
||||||
wxSizer *VideoSizer = new wxBoxSizer(wxVERTICAL);
|
auto VideoSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
VideoSizer->Add(topSizer, 1, wxEXPAND, 0);
|
VideoSizer->Add(topSizer, 1, wxEXPAND, 0);
|
||||||
VideoSizer->Add(new wxStaticLine(this), 0, wxEXPAND, 0);
|
VideoSizer->Add(new wxStaticLine(this), 0, wxEXPAND, 0);
|
||||||
VideoSizer->Add(videoSlider, 0, wxEXPAND, 0);
|
VideoSizer->Add(videoSlider, 0, wxEXPAND, 0);
|
||||||
|
@ -98,23 +99,25 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
||||||
|
|
||||||
UpdateTimeBoxes();
|
UpdateTimeBoxes();
|
||||||
|
|
||||||
slots.push_back(context->videoController->AddSeekListener(&VideoBox::UpdateTimeBoxes, this));
|
connections = agi::signal::make_vector({
|
||||||
slots.push_back(context->videoController->AddKeyframesListener(&VideoBox::UpdateTimeBoxes, this));
|
context->ass->AddCommitListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
slots.push_back(context->videoController->AddTimecodesListener(&VideoBox::UpdateTimeBoxes, this));
|
context->project->AddKeyframesListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
slots.push_back(context->videoController->AddVideoOpenListener(&VideoBox::UpdateTimeBoxes, this));
|
context->project->AddTimecodesListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
slots.push_back(context->ass->AddCommitListener(&VideoBox::UpdateTimeBoxes, this));
|
context->project->AddVideoProviderListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
slots.push_back(context->selectionController->AddSelectionListener(&VideoBox::UpdateTimeBoxes, this));
|
context->selectionController->AddSelectionListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
|
context->videoController->AddSeekListener(&VideoBox::UpdateTimeBoxes, this),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoBox::UpdateTimeBoxes() {
|
void VideoBox::UpdateTimeBoxes() {
|
||||||
if (!context->videoController->IsLoaded()) return;
|
if (!context->project->VideoProvider()) return;
|
||||||
|
|
||||||
int frame = context->videoController->GetFrameN();
|
int frame = context->videoController->GetFrameN();
|
||||||
int time = context->videoController->TimeAtFrame(frame, agi::vfr::EXACT);
|
int time = context->videoController->TimeAtFrame(frame, agi::vfr::EXACT);
|
||||||
|
|
||||||
// Set the text box for frame number and time
|
// Set the text box for frame number and time
|
||||||
VideoPosition->SetValue(wxString::Format("%s - %d", AssTime(time).GetAssFormated(true), frame));
|
VideoPosition->SetValue(wxString::Format("%s - %d", AssTime(time).GetAssFormated(true), frame));
|
||||||
if (boost::binary_search(context->videoController->GetKeyFrames(), frame)) {
|
if (boost::binary_search(context->project->Keyframes(), frame)) {
|
||||||
// Set the background color to indicate this is a keyframe
|
// Set the background color to indicate this is a keyframe
|
||||||
VideoPosition->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColor()));
|
VideoPosition->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColor()));
|
||||||
VideoPosition->SetForegroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Selection")->GetColor()));
|
VideoPosition->SetForegroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Selection")->GetColor()));
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
|
|
||||||
#include <deque>
|
#include <vector>
|
||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
|
|
||||||
namespace agi { struct Context; }
|
namespace agi { struct Context; }
|
||||||
|
@ -38,7 +38,7 @@ class wxTextCtrl;
|
||||||
/// @class VideoBox
|
/// @class VideoBox
|
||||||
/// @brief The box containing the video display and associated controls
|
/// @brief The box containing the video display and associated controls
|
||||||
class VideoBox final : public wxPanel {
|
class VideoBox final : public wxPanel {
|
||||||
std::deque<agi::signal::Connection> slots;
|
std::vector<agi::signal::Connection> connections;
|
||||||
agi::Context *context; ///< Project context
|
agi::Context *context; ///< Project context
|
||||||
wxTextCtrl *VideoPosition; ///< Current frame/time
|
wxTextCtrl *VideoPosition; ///< Current frame/time
|
||||||
wxTextCtrl *VideoSubsPos; ///< Time relative to the active subtitle line
|
wxTextCtrl *VideoSubsPos; ///< Time relative to the active subtitle line
|
||||||
|
|
|
@ -1,445 +0,0 @@
|
||||||
// Copyright (c) 2005-2007, 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 video_context.cpp
|
|
||||||
/// @brief Keep track of loaded video
|
|
||||||
/// @ingroup video
|
|
||||||
///
|
|
||||||
|
|
||||||
#include "video_context.h"
|
|
||||||
|
|
||||||
#include "ass_dialogue.h"
|
|
||||||
#include "ass_file.h"
|
|
||||||
#include "ass_time.h"
|
|
||||||
#include "audio_controller.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "dialog_progress.h"
|
|
||||||
#include "dialog_video_properties.h"
|
|
||||||
#include "include/aegisub/context.h"
|
|
||||||
#include "include/aegisub/video_provider.h"
|
|
||||||
#include "mkv_wrap.h"
|
|
||||||
#include "options.h"
|
|
||||||
#include "selection_controller.h"
|
|
||||||
#include "subs_controller.h"
|
|
||||||
#include "time_range.h"
|
|
||||||
#include "threaded_frame_source.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "video_frame.h"
|
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
|
||||||
#include <libaegisub/keyframe.h>
|
|
||||||
#include <libaegisub/path.h>
|
|
||||||
#include <libaegisub/make_unique.h>
|
|
||||||
|
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
|
|
||||||
VideoContext::VideoContext(agi::Context *c)
|
|
||||||
: context(c)
|
|
||||||
, playback(this)
|
|
||||||
, playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
|
|
||||||
{
|
|
||||||
context->ass->AddCommitListener(&VideoContext::OnSubtitlesCommit, this);
|
|
||||||
context->subsController->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
|
|
||||||
|
|
||||||
Bind(EVT_VIDEO_ERROR, &VideoContext::OnVideoError, this);
|
|
||||||
Bind(EVT_SUBTITLES_ERROR, &VideoContext::OnSubtitlesError, this);
|
|
||||||
Bind(wxEVT_TIMER, &VideoContext::OnPlayTimer, this);
|
|
||||||
|
|
||||||
OPT_SUB("Subtitle/Provider", &VideoContext::Reload, this);
|
|
||||||
OPT_SUB("Video/Provider", &VideoContext::Reload, this);
|
|
||||||
|
|
||||||
// It would be nice to find a way to move these to the individual providers
|
|
||||||
OPT_SUB("Provider/Avisynth/Allow Ancient", &VideoContext::Reload, this);
|
|
||||||
OPT_SUB("Provider/Avisynth/Memory Max", &VideoContext::Reload, this);
|
|
||||||
|
|
||||||
OPT_SUB("Provider/Video/FFmpegSource/Decoding Threads", &VideoContext::Reload, this);
|
|
||||||
OPT_SUB("Provider/Video/FFmpegSource/Unsafe Seeking", &VideoContext::Reload, this);
|
|
||||||
OPT_SUB("Video/Force BT.601", &VideoContext::Reload, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoContext::~VideoContext () { }
|
|
||||||
|
|
||||||
void VideoContext::Reset() {
|
|
||||||
config::path->SetToken("?video", "");
|
|
||||||
|
|
||||||
// Remove video data
|
|
||||||
Stop();
|
|
||||||
frame_n = 0;
|
|
||||||
|
|
||||||
// Clean up video data
|
|
||||||
video_filename.clear();
|
|
||||||
color_matrix.clear();
|
|
||||||
|
|
||||||
// Remove provider
|
|
||||||
provider.reset();
|
|
||||||
video_provider = nullptr;
|
|
||||||
|
|
||||||
keyframes.clear();
|
|
||||||
keyframes_filename.clear();
|
|
||||||
video_fps = agi::vfr::Framerate();
|
|
||||||
KeyframesOpen(keyframes);
|
|
||||||
if (!ovr_fps.IsLoaded()) TimecodesOpen(video_fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::SetVideo(const agi::fs::path &filename) {
|
|
||||||
Reset();
|
|
||||||
if (filename.empty()) {
|
|
||||||
VideoOpen();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool commit_subs = false;
|
|
||||||
try {
|
|
||||||
if (!progress)
|
|
||||||
progress = new DialogProgress(context->parent);
|
|
||||||
auto old_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
|
|
||||||
provider = agi::make_unique<ThreadedFrameSource>(filename, old_matrix, this, progress);
|
|
||||||
video_provider = provider->GetVideoProvider();
|
|
||||||
video_filename = filename;
|
|
||||||
color_matrix = video_provider->GetColorSpace();
|
|
||||||
keyframes = video_provider->GetKeyFrames();
|
|
||||||
video_fps = video_provider->GetFPS();
|
|
||||||
|
|
||||||
commit_subs = UpdateVideoProperties(context->ass.get(), video_provider, context->parent);
|
|
||||||
|
|
||||||
// Set frame rate
|
|
||||||
if (ovr_fps.IsLoaded()) {
|
|
||||||
int ovr = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"),
|
|
||||||
_("Replace timecodes?"), wxYES_NO | wxICON_QUESTION);
|
|
||||||
if (ovr == wxYES) {
|
|
||||||
ovr_fps = agi::vfr::Framerate();
|
|
||||||
timecodes_filename.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set aspect ratio
|
|
||||||
double dar = video_provider->GetDAR();
|
|
||||||
if (dar > 0)
|
|
||||||
SetAspectRatio(dar);
|
|
||||||
|
|
||||||
// Set filename
|
|
||||||
config::mru->Add("Video", filename);
|
|
||||||
config::path->SetToken("?video", filename);
|
|
||||||
|
|
||||||
// Show warning
|
|
||||||
std::string warning = video_provider->GetWarning();
|
|
||||||
if (!warning.empty())
|
|
||||||
wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
|
|
||||||
|
|
||||||
has_subtitles = false;
|
|
||||||
if (agi::fs::HasExtension(filename, "mkv"))
|
|
||||||
has_subtitles = MatroskaWrapper::HasSubtitles(filename);
|
|
||||||
|
|
||||||
provider->LoadSubtitles(context->ass.get());
|
|
||||||
VideoOpen();
|
|
||||||
KeyframesOpen(keyframes);
|
|
||||||
TimecodesOpen(FPS());
|
|
||||||
}
|
|
||||||
catch (agi::UserCancelException const&) { }
|
|
||||||
catch (agi::fs::FileSystemError const& err) {
|
|
||||||
config::mru->Remove("Video", filename);
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
|
|
||||||
}
|
|
||||||
catch (VideoProviderError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commit_subs)
|
|
||||||
context->ass->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
|
|
||||||
else
|
|
||||||
JumpToFrame(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::Reload() {
|
|
||||||
if (IsLoaded()) {
|
|
||||||
int frame = frame_n;
|
|
||||||
SetVideo(agi::fs::path(video_filename)); // explicitly copy videoFile since it's cleared in SetVideo
|
|
||||||
JumpToFrame(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed) {
|
|
||||||
if (!IsLoaded()) return;
|
|
||||||
|
|
||||||
if ((type & AssFile::COMMIT_SCRIPTINFO) || type == AssFile::COMMIT_NEW) {
|
|
||||||
auto new_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
|
|
||||||
if (!new_matrix.empty() && new_matrix != color_matrix) {
|
|
||||||
color_matrix = new_matrix;
|
|
||||||
provider->SetColorSpace(new_matrix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed.empty() || no_amend)
|
|
||||||
provider->LoadSubtitles(context->ass.get());
|
|
||||||
else
|
|
||||||
provider->UpdateSubtitles(context->ass.get(), changed);
|
|
||||||
if (!IsPlaying())
|
|
||||||
GetFrameAsync(frame_n);
|
|
||||||
|
|
||||||
no_amend = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::OnSubtitlesSave() {
|
|
||||||
no_amend = true;
|
|
||||||
|
|
||||||
context->ass->SetScriptInfo("VFR File", config::path->MakeRelative(GetTimecodesName(), "?script").generic_string());
|
|
||||||
context->ass->SetScriptInfo("Keyframes File", config::path->MakeRelative(GetKeyFramesName(), "?script").generic_string());
|
|
||||||
|
|
||||||
if (!IsLoaded()) {
|
|
||||||
context->ass->SetScriptInfo("Video File", "");
|
|
||||||
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->SetScriptInfo("Video File", config::path->MakeRelative(video_filename, "?script").generic_string());
|
|
||||||
context->ass->SaveUIState("Video Aspect Ratio", ar);
|
|
||||||
context->ass->SaveUIState("Video Position", std::to_string(frame_n));
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::JumpToFrame(int n) {
|
|
||||||
if (!IsLoaded()) return;
|
|
||||||
|
|
||||||
bool was_playing = IsPlaying();
|
|
||||||
if (was_playing)
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
frame_n = mid(0, n, GetLength() - 1);
|
|
||||||
|
|
||||||
GetFrameAsync(frame_n);
|
|
||||||
Seek(frame_n);
|
|
||||||
|
|
||||||
if (was_playing)
|
|
||||||
Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::JumpToTime(int ms, agi::vfr::Time end) {
|
|
||||||
JumpToFrame(FrameAtTime(ms, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::GetFrameAsync(int n) {
|
|
||||||
provider->RequestFrame(n, TimeAtFrame(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> VideoContext::GetFrame(int n, bool raw) {
|
|
||||||
return provider->GetFrame(n, TimeAtFrame(n), raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
int VideoContext::GetWidth() const { return video_provider->GetWidth(); }
|
|
||||||
int VideoContext::GetHeight() const { return video_provider->GetHeight(); }
|
|
||||||
int VideoContext::GetLength() const { return video_provider->GetFrameCount(); }
|
|
||||||
|
|
||||||
void VideoContext::NextFrame() {
|
|
||||||
if (!video_provider || IsPlaying() || frame_n == video_provider->GetFrameCount())
|
|
||||||
return;
|
|
||||||
|
|
||||||
JumpToFrame(frame_n + 1);
|
|
||||||
if (playAudioOnStep->GetBool())
|
|
||||||
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::PrevFrame() {
|
|
||||||
if (!video_provider || IsPlaying() || frame_n == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
JumpToFrame(frame_n - 1);
|
|
||||||
if (playAudioOnStep->GetBool())
|
|
||||||
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::Play() {
|
|
||||||
if (IsPlaying()) {
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsLoaded()) return;
|
|
||||||
|
|
||||||
start_ms = TimeAtFrame(frame_n);
|
|
||||||
end_frame = GetLength() - 1;
|
|
||||||
|
|
||||||
context->audioController->PlayToEnd(start_ms);
|
|
||||||
|
|
||||||
playback_start_time = std::chrono::steady_clock::now();
|
|
||||||
playback.Start(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::PlayLine() {
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
AssDialogue *curline = context->selectionController->GetActiveLine();
|
|
||||||
if (!curline) return;
|
|
||||||
|
|
||||||
context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
|
|
||||||
|
|
||||||
// Round-trip conversion to convert start to exact
|
|
||||||
int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
|
|
||||||
start_ms = TimeAtFrame(startFrame);
|
|
||||||
end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
|
|
||||||
|
|
||||||
JumpToFrame(startFrame);
|
|
||||||
|
|
||||||
playback_start_time = std::chrono::steady_clock::now();
|
|
||||||
playback.Start(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::Stop() {
|
|
||||||
if (IsPlaying()) {
|
|
||||||
playback.Stop();
|
|
||||||
context->audioController->Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::OnPlayTimer(wxTimerEvent &) {
|
|
||||||
using namespace std::chrono;
|
|
||||||
int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
|
|
||||||
if (next_frame == frame_n) return;
|
|
||||||
|
|
||||||
if (next_frame >= end_frame)
|
|
||||||
Stop();
|
|
||||||
else {
|
|
||||||
frame_n = next_frame;
|
|
||||||
GetFrameAsync(frame_n);
|
|
||||||
Seek(frame_n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double VideoContext::GetARFromType(AspectRatio type) const {
|
|
||||||
switch (type) {
|
|
||||||
case AspectRatio::Default: return (double)GetWidth()/(double)GetHeight();
|
|
||||||
case AspectRatio::Fullscreen: return 4.0/3.0;
|
|
||||||
case AspectRatio::Widescreen: return 16.0/9.0;
|
|
||||||
case AspectRatio::Cinematic: return 2.35;
|
|
||||||
}
|
|
||||||
throw agi::InternalError("Bad AR type", nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::SetAspectRatio(double value) {
|
|
||||||
ar_type = AspectRatio::Custom;
|
|
||||||
ar_value = mid(.5, value, 5.);
|
|
||||||
ARChange(ar_type, ar_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::SetAspectRatio(AspectRatio type) {
|
|
||||||
ar_value = mid(.5, GetARFromType(type), 5.);
|
|
||||||
ar_type = type;
|
|
||||||
ARChange(ar_type, ar_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::LoadKeyframes(agi::fs::path const& filename) {
|
|
||||||
if (filename == keyframes_filename || filename.empty()) return;
|
|
||||||
try {
|
|
||||||
keyframes = agi::keyframe::Load(filename);
|
|
||||||
keyframes_filename = filename;
|
|
||||||
KeyframesOpen(keyframes);
|
|
||||||
config::mru->Add("Keyframes", filename);
|
|
||||||
}
|
|
||||||
catch (agi::keyframe::Error const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
config::mru->Remove("Keyframes", filename);
|
|
||||||
}
|
|
||||||
catch (agi::fs::FileSystemError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
config::mru->Remove("Keyframes", filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::SaveKeyframes(agi::fs::path const& filename) {
|
|
||||||
agi::keyframe::Save(filename, GetKeyFrames());
|
|
||||||
config::mru->Add("Keyframes", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::CloseKeyframes() {
|
|
||||||
keyframes_filename.clear();
|
|
||||||
if (video_provider)
|
|
||||||
keyframes = video_provider->GetKeyFrames();
|
|
||||||
else
|
|
||||||
keyframes.clear();
|
|
||||||
KeyframesOpen(keyframes);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
|
|
||||||
if (filename == timecodes_filename || filename.empty()) return;
|
|
||||||
try {
|
|
||||||
ovr_fps = agi::vfr::Framerate(filename);
|
|
||||||
timecodes_filename = filename;
|
|
||||||
config::mru->Add("Timecodes", filename);
|
|
||||||
OnSubtitlesCommit(0, std::set<const AssDialogue*>());
|
|
||||||
TimecodesOpen(ovr_fps);
|
|
||||||
}
|
|
||||||
catch (agi::fs::FileSystemError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error opening timecodes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
config::mru->Remove("Timecodes", filename);
|
|
||||||
}
|
|
||||||
catch (const agi::vfr::Error& e) {
|
|
||||||
wxLogError("Timecode file parse error: %s", to_wx(e.GetMessage()));
|
|
||||||
config::mru->Remove("Timecodes", filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void VideoContext::SaveTimecodes(agi::fs::path const& filename) {
|
|
||||||
try {
|
|
||||||
FPS().Save(filename, IsLoaded() ? GetLength() : -1);
|
|
||||||
config::mru->Add("Timecodes", filename);
|
|
||||||
}
|
|
||||||
catch (agi::fs::FileSystemError const& err) {
|
|
||||||
wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void VideoContext::CloseTimecodes() {
|
|
||||||
ovr_fps = agi::vfr::Framerate();
|
|
||||||
timecodes_filename.clear();
|
|
||||||
OnSubtitlesCommit(0, std::set<const AssDialogue*>());
|
|
||||||
TimecodesOpen(video_fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
int VideoContext::TimeAtFrame(int frame, agi::vfr::Time type) const {
|
|
||||||
return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).TimeAtFrame(frame, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
int VideoContext::FrameAtTime(int time, agi::vfr::Time type) const {
|
|
||||||
return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).FrameAtTime(time, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoContext::OnVideoError(VideoProviderErrorEvent const& err) {
|
|
||||||
wxLogError(
|
|
||||||
"Failed seeking video. The video file may be corrupt or incomplete.\n"
|
|
||||||
"Error message reported: %s",
|
|
||||||
to_wx(err.GetMessage()));
|
|
||||||
}
|
|
||||||
void VideoContext::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
|
|
||||||
wxLogError(
|
|
||||||
"Failed rendering subtitles. Error message reported: %s",
|
|
||||||
to_wx(err.GetMessage()));
|
|
||||||
}
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
// Copyright (c) 2005-2007, 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/
|
||||||
|
|
||||||
|
#include "video_controller.h"
|
||||||
|
|
||||||
|
#include "ass_dialogue.h"
|
||||||
|
#include "ass_file.h"
|
||||||
|
#include "ass_time.h"
|
||||||
|
#include "audio_controller.h"
|
||||||
|
#include "compat.h"
|
||||||
|
#include "dialog_progress.h"
|
||||||
|
#include "dialog_video_properties.h"
|
||||||
|
#include "include/aegisub/context.h"
|
||||||
|
#include "include/aegisub/video_provider.h"
|
||||||
|
#include "mkv_wrap.h"
|
||||||
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
|
#include "selection_controller.h"
|
||||||
|
#include "subs_controller.h"
|
||||||
|
#include "time_range.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "video_frame.h"
|
||||||
|
|
||||||
|
#include <libaegisub/fs.h>
|
||||||
|
#include <libaegisub/path.h>
|
||||||
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
|
VideoController::VideoController(agi::Context *c)
|
||||||
|
: context(c)
|
||||||
|
, playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
|
||||||
|
, connections(agi::signal::make_vector({
|
||||||
|
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);
|
||||||
|
Bind(EVT_SUBTITLES_ERROR, &VideoController::OnSubtitlesError, this);
|
||||||
|
playback.Bind(wxEVT_TIMER, &VideoController::OnPlayTimer, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::OnNewVideoProvider(AsyncVideoProvider *new_provider) {
|
||||||
|
Stop();
|
||||||
|
frame_n = 0;
|
||||||
|
|
||||||
|
provider = new_provider;
|
||||||
|
if (!provider) {
|
||||||
|
color_matrix.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_matrix = provider->GetColorSpace();
|
||||||
|
double dar = provider->GetDAR();
|
||||||
|
if (dar > 0)
|
||||||
|
SetAspectRatio(dar);
|
||||||
|
|
||||||
|
JumpToFrame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed) {
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
if ((type & AssFile::COMMIT_SCRIPTINFO) || type == AssFile::COMMIT_NEW) {
|
||||||
|
auto new_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
|
||||||
|
if (!new_matrix.empty() && new_matrix != color_matrix) {
|
||||||
|
color_matrix = new_matrix;
|
||||||
|
provider->SetColorSpace(new_matrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed.empty() || no_amend)
|
||||||
|
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) {
|
||||||
|
if (line && provider && OPT_GET("Video/Subtitle Sync")->GetBool())
|
||||||
|
JumpToTime(line->Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::RequestFrame() {
|
||||||
|
provider->RequestFrame(frame_n, TimeAtFrame(frame_n));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::JumpToFrame(int n) {
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
bool was_playing = IsPlaying();
|
||||||
|
if (was_playing)
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
frame_n = mid(0, n, provider->GetFrameCount() - 1);
|
||||||
|
RequestFrame();
|
||||||
|
Seek(frame_n);
|
||||||
|
|
||||||
|
if (was_playing)
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::JumpToTime(int ms, agi::vfr::Time end) {
|
||||||
|
JumpToFrame(FrameAtTime(ms, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::NextFrame() {
|
||||||
|
if (!provider || IsPlaying() || frame_n == provider->GetFrameCount())
|
||||||
|
return;
|
||||||
|
|
||||||
|
JumpToFrame(frame_n + 1);
|
||||||
|
if (playAudioOnStep->GetBool())
|
||||||
|
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::PrevFrame() {
|
||||||
|
if (!provider || IsPlaying() || frame_n == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
JumpToFrame(frame_n - 1);
|
||||||
|
if (playAudioOnStep->GetBool())
|
||||||
|
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::Play() {
|
||||||
|
if (IsPlaying()) {
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
start_ms = TimeAtFrame(frame_n);
|
||||||
|
end_frame = provider->GetFrameCount() - 1;
|
||||||
|
|
||||||
|
context->audioController->PlayToEnd(start_ms);
|
||||||
|
|
||||||
|
playback_start_time = std::chrono::steady_clock::now();
|
||||||
|
playback.Start(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::PlayLine() {
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
AssDialogue *curline = context->selectionController->GetActiveLine();
|
||||||
|
if (!curline) return;
|
||||||
|
|
||||||
|
context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
|
||||||
|
|
||||||
|
// Round-trip conversion to convert start to exact
|
||||||
|
int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
|
||||||
|
start_ms = TimeAtFrame(startFrame);
|
||||||
|
end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
|
||||||
|
|
||||||
|
JumpToFrame(startFrame);
|
||||||
|
|
||||||
|
playback_start_time = std::chrono::steady_clock::now();
|
||||||
|
playback.Start(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::Stop() {
|
||||||
|
if (IsPlaying()) {
|
||||||
|
playback.Stop();
|
||||||
|
context->audioController->Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::OnPlayTimer(wxTimerEvent &) {
|
||||||
|
using namespace std::chrono;
|
||||||
|
int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
|
||||||
|
if (next_frame == frame_n) return;
|
||||||
|
|
||||||
|
if (next_frame >= end_frame)
|
||||||
|
Stop();
|
||||||
|
else {
|
||||||
|
frame_n = next_frame;
|
||||||
|
RequestFrame();
|
||||||
|
Seek(frame_n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double VideoController::GetARFromType(AspectRatio type) const {
|
||||||
|
switch (type) {
|
||||||
|
case AspectRatio::Default: return (double)provider->GetWidth()/provider->GetHeight();
|
||||||
|
case AspectRatio::Fullscreen: return 4.0/3.0;
|
||||||
|
case AspectRatio::Widescreen: return 16.0/9.0;
|
||||||
|
case AspectRatio::Cinematic: return 2.35;
|
||||||
|
}
|
||||||
|
throw agi::InternalError("Bad AR type", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::SetAspectRatio(double value) {
|
||||||
|
ar_type = AspectRatio::Custom;
|
||||||
|
ar_value = mid(.5, value, 5.);
|
||||||
|
ARChange(ar_type, ar_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::SetAspectRatio(AspectRatio type) {
|
||||||
|
ar_value = mid(.5, GetARFromType(type), 5.);
|
||||||
|
ar_type = type;
|
||||||
|
ARChange(ar_type, ar_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoController::TimeAtFrame(int frame, agi::vfr::Time type) const {
|
||||||
|
return context->project->Timecodes().TimeAtFrame(frame, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoController::FrameAtTime(int time, agi::vfr::Time type) const {
|
||||||
|
return context->project->Timecodes().FrameAtTime(time, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::OnVideoError(VideoProviderErrorEvent const& err) {
|
||||||
|
wxLogError(
|
||||||
|
"Failed seeking video. The video file may be corrupt or incomplete.\n"
|
||||||
|
"Error message reported: %s",
|
||||||
|
to_wx(err.GetMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoController::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
|
||||||
|
wxLogError(
|
||||||
|
"Failed rendering subtitles. Error message reported: %s",
|
||||||
|
to_wx(err.GetMessage()));
|
||||||
|
}
|
|
@ -27,28 +27,17 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file video_context.h
|
|
||||||
/// @see video_context.cpp
|
|
||||||
/// @ingroup video
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <libaegisub/fs_fwd.h>
|
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
#include <libaegisub/vfr.h>
|
#include <libaegisub/vfr.h>
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
|
|
||||||
class AssDialogue;
|
class AssDialogue;
|
||||||
class DialogProgress;
|
class AsyncVideoProvider;
|
||||||
class ThreadedFrameSource;
|
|
||||||
class VideoProvider;
|
|
||||||
struct SubtitlesProviderErrorEvent;
|
struct SubtitlesProviderErrorEvent;
|
||||||
struct VideoFrame;
|
struct VideoFrame;
|
||||||
struct VideoProviderErrorEvent;
|
struct VideoProviderErrorEvent;
|
||||||
|
@ -66,47 +55,22 @@ enum class AspectRatio {
|
||||||
Custom
|
Custom
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @class VideoContext
|
/// Manage stuff related to video playback
|
||||||
/// @brief Manage a bunch of things vaguely related to video playback
|
class VideoController final : public wxEvtHandler {
|
||||||
///
|
|
||||||
/// VideoContext's core responsibility is opening and playing videos. Along
|
|
||||||
/// with that, it also manages video timecodes and keyframes, and some
|
|
||||||
/// video-related UI properties
|
|
||||||
class VideoContext final : public wxEvtHandler {
|
|
||||||
/// Current frame number changed (new frame number)
|
/// Current frame number changed (new frame number)
|
||||||
agi::signal::Signal<int> Seek;
|
agi::signal::Signal<int> Seek;
|
||||||
/// A new video was opened
|
|
||||||
agi::signal::Signal<> VideoOpen;
|
|
||||||
/// New keyframes opened (new keyframe data)
|
|
||||||
agi::signal::Signal<std::vector<int> const&> KeyframesOpen;
|
|
||||||
/// New timecodes opened (new timecode data)
|
|
||||||
agi::signal::Signal<agi::vfr::Framerate const&> TimecodesOpen;
|
|
||||||
/// Aspect ratio was changed (type, value)
|
/// Aspect ratio was changed (type, value)
|
||||||
agi::signal::Signal<AspectRatio, double> ARChange;
|
agi::signal::Signal<AspectRatio, double> ARChange;
|
||||||
|
|
||||||
agi::Context *context;
|
agi::Context *context;
|
||||||
|
|
||||||
DialogProgress *progress = nullptr;
|
|
||||||
|
|
||||||
/// The video provider owned by the threaded frame source, or nullptr if no
|
/// The video provider owned by the threaded frame source, or nullptr if no
|
||||||
/// video is open
|
/// video is open
|
||||||
VideoProvider *video_provider = nullptr;
|
AsyncVideoProvider *provider = nullptr;
|
||||||
|
|
||||||
/// Asynchronous provider of video frames
|
|
||||||
std::unique_ptr<ThreadedFrameSource> provider;
|
|
||||||
|
|
||||||
/// Filename of currently open video
|
|
||||||
agi::fs::path video_filename;
|
|
||||||
|
|
||||||
/// Last seen script color matrix
|
/// Last seen script color matrix
|
||||||
std::string color_matrix;
|
std::string color_matrix;
|
||||||
|
|
||||||
/// List of frame numbers which are keyframes
|
|
||||||
std::vector<int> keyframes;
|
|
||||||
|
|
||||||
/// File name of the currently open keyframes or empty if keyframes are not overridden
|
|
||||||
agi::fs::path keyframes_filename;
|
|
||||||
|
|
||||||
/// Playback timer used to periodically check if we should go to the next
|
/// Playback timer used to periodically check if we should go to the next
|
||||||
/// frame while playing video
|
/// frame while playing video
|
||||||
wxTimer playback;
|
wxTimer playback;
|
||||||
|
@ -132,16 +96,11 @@ class VideoContext final : public wxEvtHandler {
|
||||||
/// The current AR type
|
/// The current AR type
|
||||||
AspectRatio ar_type = AspectRatio::Default;
|
AspectRatio ar_type = AspectRatio::Default;
|
||||||
|
|
||||||
/// Does the currently loaded video file have subtitles muxed into it?
|
|
||||||
bool has_subtitles = false;
|
|
||||||
|
|
||||||
/// Filename of the currently loaded timecodes file, or empty if timecodes
|
|
||||||
/// have not been overridden
|
|
||||||
agi::fs::path timecodes_filename;
|
|
||||||
|
|
||||||
/// Cached option for audio playing when frame stepping
|
/// Cached option for audio playing when frame stepping
|
||||||
const agi::OptionValue* playAudioOnStep;
|
const agi::OptionValue* playAudioOnStep;
|
||||||
|
|
||||||
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
/// Amending the frame source's copy of the subtitle file requires that it
|
/// 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
|
/// 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
|
/// without a commit, breaking this sync, so force a non-amend after each
|
||||||
|
@ -150,58 +109,22 @@ class VideoContext final : public wxEvtHandler {
|
||||||
|
|
||||||
void OnPlayTimer(wxTimerEvent &event);
|
void OnPlayTimer(wxTimerEvent &event);
|
||||||
|
|
||||||
/// The timecodes from the video file
|
|
||||||
agi::vfr::Framerate video_fps;
|
|
||||||
/// External timecode which have been loaded, if any
|
|
||||||
agi::vfr::Framerate ovr_fps;
|
|
||||||
|
|
||||||
void OnVideoError(VideoProviderErrorEvent const& err);
|
void OnVideoError(VideoProviderErrorEvent const& err);
|
||||||
void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
|
void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
|
||||||
|
|
||||||
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
|
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
|
||||||
void OnSubtitlesSave();
|
void OnSubtitlesSave();
|
||||||
|
void OnNewVideoProvider(AsyncVideoProvider *provider);
|
||||||
|
void OnActiveLineChanged(AssDialogue *line);
|
||||||
|
|
||||||
/// Close the video, keyframes and timecodes
|
void RequestFrame();
|
||||||
void Reset();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VideoContext(agi::Context *context);
|
VideoController(agi::Context *context);
|
||||||
~VideoContext();
|
|
||||||
|
|
||||||
/// @brief Get the video provider used for the currently open video
|
|
||||||
VideoProvider *GetProvider() const { return video_provider; }
|
|
||||||
|
|
||||||
/// Synchronously get a video frame
|
|
||||||
/// @param n Frame number to get
|
|
||||||
/// @param raw If true, subtitles are not rendered on the frame
|
|
||||||
/// @return The requested frame
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n, bool raw = false);
|
|
||||||
|
|
||||||
/// Asynchronously get a video frame, triggering a EVT_FRAME_READY event when it's ready
|
|
||||||
/// @param n Frame number to get
|
|
||||||
void GetFrameAsync(int n);
|
|
||||||
|
|
||||||
/// Is there a video loaded?
|
|
||||||
bool IsLoaded() const { return !!video_provider; }
|
|
||||||
|
|
||||||
/// Get the file name of the currently open video, if any
|
|
||||||
agi::fs::path GetVideoName() const { return video_filename; }
|
|
||||||
|
|
||||||
/// Is the video currently playing?
|
/// Is the video currently playing?
|
||||||
bool IsPlaying() const { return playback.IsRunning(); }
|
bool IsPlaying() const { return playback.IsRunning(); }
|
||||||
|
|
||||||
/// Does the video file loaded have muxed subtitles that we can load?
|
|
||||||
bool HasSubtitles() const { return has_subtitles; }
|
|
||||||
|
|
||||||
/// Get the width of the currently open video
|
|
||||||
int GetWidth() const;
|
|
||||||
|
|
||||||
/// Get the height of the currently open video
|
|
||||||
int GetHeight() const;
|
|
||||||
|
|
||||||
/// Get the length in frames of the currently open video
|
|
||||||
int GetLength() const;
|
|
||||||
|
|
||||||
/// Get the current frame number
|
/// Get the current frame number
|
||||||
int GetFrameN() const { return frame_n; }
|
int GetFrameN() const { return frame_n; }
|
||||||
|
|
||||||
|
@ -221,12 +144,6 @@ public:
|
||||||
/// Get the current aspect ratio of the video
|
/// Get the current aspect ratio of the video
|
||||||
double GetAspectRatioValue() const { return ar_value; }
|
double GetAspectRatioValue() const { return ar_value; }
|
||||||
|
|
||||||
/// @brief Open a new video
|
|
||||||
/// @param filename Video to open, or empty to close the current video
|
|
||||||
void SetVideo(const agi::fs::path &filename);
|
|
||||||
/// @brief Close and reopen the current video
|
|
||||||
void Reload();
|
|
||||||
|
|
||||||
/// @brief Jump to the beginning of a frame
|
/// @brief Jump to the beginning of a frame
|
||||||
/// @param n Frame number to jump to
|
/// @param n Frame number to jump to
|
||||||
void JumpToFrame(int n);
|
void JumpToFrame(int n);
|
||||||
|
@ -247,29 +164,8 @@ public:
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
DEFINE_SIGNAL_ADDERS(Seek, AddSeekListener)
|
DEFINE_SIGNAL_ADDERS(Seek, AddSeekListener)
|
||||||
DEFINE_SIGNAL_ADDERS(VideoOpen, AddVideoOpenListener)
|
|
||||||
DEFINE_SIGNAL_ADDERS(KeyframesOpen, AddKeyframesListener)
|
|
||||||
DEFINE_SIGNAL_ADDERS(TimecodesOpen, AddTimecodesListener)
|
|
||||||
DEFINE_SIGNAL_ADDERS(ARChange, AddARChangeListener)
|
DEFINE_SIGNAL_ADDERS(ARChange, AddARChangeListener)
|
||||||
|
|
||||||
const std::vector<int>& GetKeyFrames() const { return keyframes; };
|
|
||||||
agi::fs::path GetKeyFramesName() const { return keyframes_filename; }
|
|
||||||
void LoadKeyframes(agi::fs::path const& filename);
|
|
||||||
void SaveKeyframes(agi::fs::path const& filename);
|
|
||||||
void CloseKeyframes();
|
|
||||||
bool OverKeyFramesLoaded() const { return !keyframes_filename.empty(); }
|
|
||||||
bool KeyFramesLoaded() const { return !keyframes.empty(); }
|
|
||||||
|
|
||||||
agi::fs::path GetTimecodesName() const { return timecodes_filename; }
|
|
||||||
void LoadTimecodes(agi::fs::path const& filename);
|
|
||||||
void SaveTimecodes(agi::fs::path const& filename);
|
|
||||||
void CloseTimecodes();
|
|
||||||
bool OverTimecodesLoaded() const { return ovr_fps.IsLoaded(); }
|
|
||||||
bool TimecodesLoaded() const { return video_fps.IsLoaded() || ovr_fps.IsLoaded(); };
|
|
||||||
|
|
||||||
const agi::vfr::Framerate& FPS() const { return ovr_fps.IsLoaded() ? ovr_fps : video_fps; }
|
|
||||||
const agi::vfr::Framerate& VideoFPS() const { return video_fps; }
|
|
||||||
|
|
||||||
int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
|
int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
|
||||||
int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
|
int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
|
||||||
};
|
};
|
|
@ -35,26 +35,26 @@
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
|
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "include/aegisub/hotkey.h"
|
#include "include/aegisub/hotkey.h"
|
||||||
#include "include/aegisub/menu.h"
|
#include "include/aegisub/menu.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "retina_helper.h"
|
#include "retina_helper.h"
|
||||||
#include "spline_curve.h"
|
#include "spline_curve.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "threaded_frame_source.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_out_gl.h"
|
#include "video_out_gl.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_frame.h"
|
#include "video_frame.h"
|
||||||
#include "visual_tool.h"
|
#include "visual_tool.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <wx/combobox.h>
|
#include <wx/combobox.h>
|
||||||
#include <wx/dataobj.h>
|
#include <wx/dataobj.h>
|
||||||
#include <wx/dcclient.h>
|
#include <wx/dcclient.h>
|
||||||
|
@ -85,17 +85,12 @@ public:
|
||||||
|
|
||||||
#define E(cmd) cmd; if (GLenum err = glGetError()) throw OpenGlException(#cmd, err)
|
#define E(cmd) cmd; if (GLenum err = glGetError()) throw OpenGlException(#cmd, err)
|
||||||
|
|
||||||
VideoDisplay::VideoDisplay(
|
VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBox, wxWindow *parent, agi::Context *c)
|
||||||
wxToolBar *visualSubToolBar,
|
|
||||||
bool freeSize,
|
|
||||||
wxComboBox *zoomBox,
|
|
||||||
wxWindow* parent,
|
|
||||||
agi::Context *c)
|
|
||||||
: wxGLCanvas(parent, -1, attribList)
|
: wxGLCanvas(parent, -1, attribList)
|
||||||
, autohideTools(OPT_GET("Tool/Visual/Autohide"))
|
, autohideTools(OPT_GET("Tool/Visual/Autohide"))
|
||||||
, con(c)
|
, con(c)
|
||||||
, zoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125)
|
, zoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125)
|
||||||
, toolBar(visualSubToolBar)
|
, toolBar(toolbar)
|
||||||
, zoomBox(zoomBox)
|
, zoomBox(zoomBox)
|
||||||
, freeSize(freeSize)
|
, freeSize(freeSize)
|
||||||
, retina_helper(agi::make_unique<RetinaHelper>(this))
|
, retina_helper(agi::make_unique<RetinaHelper>(this))
|
||||||
|
@ -111,10 +106,11 @@ VideoDisplay::VideoDisplay(
|
||||||
zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this);
|
zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this);
|
||||||
|
|
||||||
con->videoController->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
|
con->videoController->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
|
||||||
slots.push_back(con->videoController->AddVideoOpenListener(&VideoDisplay::UpdateSize, this));
|
connections = agi::signal::make_vector({
|
||||||
slots.push_back(con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this));
|
con->project->AddVideoProviderListener(&VideoDisplay::UpdateSize, this),
|
||||||
|
con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this),
|
||||||
slots.push_back(con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this));
|
con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this),
|
||||||
|
});
|
||||||
|
|
||||||
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
|
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
|
||||||
Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);
|
Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);
|
||||||
|
@ -132,8 +128,7 @@ VideoDisplay::VideoDisplay(
|
||||||
|
|
||||||
c->videoDisplay = this;
|
c->videoDisplay = this;
|
||||||
|
|
||||||
if (con->videoController->IsLoaded())
|
con->videoController->JumpToFrame(con->videoController->GetFrameN());
|
||||||
con->videoController->JumpToFrame(con->videoController->GetFrameN());
|
|
||||||
|
|
||||||
SetLayoutDirection(wxLayout_LeftToRight);
|
SetLayoutDirection(wxLayout_LeftToRight);
|
||||||
}
|
}
|
||||||
|
@ -164,7 +159,7 @@ void VideoDisplay::UploadFrameData(FrameReadyEvent &evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDisplay::Render() try {
|
void VideoDisplay::Render() try {
|
||||||
if (!con->videoController->IsLoaded() || !InitContext() || (!videoOut && !pending_frame))
|
if (!con->project->VideoProvider() || !InitContext() || (!videoOut && !pending_frame))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!videoOut)
|
if (!videoOut)
|
||||||
|
@ -185,7 +180,7 @@ void VideoDisplay::Render() try {
|
||||||
"programs and updating your video card drivers may fix this.\n"
|
"programs and updating your video card drivers may fix this.\n"
|
||||||
"Error message reported: %s",
|
"Error message reported: %s",
|
||||||
err.GetMessage());
|
err.GetMessage());
|
||||||
con->videoController->SetVideo("");
|
con->project->CloseVideo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (const VideoOutRenderException& err) {
|
catch (const VideoOutRenderException& err) {
|
||||||
|
@ -235,7 +230,7 @@ catch (const agi::Exception &err) {
|
||||||
"An error occurred trying to render the video frame on the screen.\n"
|
"An error occurred trying to render the video frame on the screen.\n"
|
||||||
"Error message reported: %s",
|
"Error message reported: %s",
|
||||||
err.GetChainedMessage());
|
err.GetChainedMessage());
|
||||||
con->videoController->SetVideo("");
|
con->project->CloseVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_percent) const {
|
void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_percent) const {
|
||||||
|
@ -277,7 +272,8 @@ void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_per
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDisplay::PositionVideo() {
|
void VideoDisplay::PositionVideo() {
|
||||||
if (!con->videoController->IsLoaded() || !IsShownOnScreen()) return;
|
auto provider = con->project->VideoProvider();
|
||||||
|
if (!provider || !IsShownOnScreen()) return;
|
||||||
|
|
||||||
viewport_left = 0;
|
viewport_left = 0;
|
||||||
viewport_bottom = GetClientSize().GetHeight() * scale_factor - videoSize.GetHeight();
|
viewport_bottom = GetClientSize().GetHeight() * scale_factor - videoSize.GetHeight();
|
||||||
|
@ -286,8 +282,8 @@ void VideoDisplay::PositionVideo() {
|
||||||
viewport_height = videoSize.GetHeight();
|
viewport_height = videoSize.GetHeight();
|
||||||
|
|
||||||
if (freeSize) {
|
if (freeSize) {
|
||||||
int vidW = con->videoController->GetWidth();
|
int vidW = provider->GetWidth();
|
||||||
int vidH = con->videoController->GetHeight();
|
int vidH = provider->GetHeight();
|
||||||
|
|
||||||
AspectRatio arType = con->videoController->GetAspectRatioType();
|
AspectRatio arType = con->videoController->GetAspectRatioType();
|
||||||
double displayAr = double(viewport_width) / viewport_height;
|
double displayAr = double(viewport_width) / viewport_height;
|
||||||
|
@ -315,9 +311,10 @@ void VideoDisplay::PositionVideo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDisplay::UpdateSize() {
|
void VideoDisplay::UpdateSize() {
|
||||||
if (!con->videoController->IsLoaded() || !IsShownOnScreen()) return;
|
auto provider = con->project->VideoProvider();
|
||||||
|
if (!provider || !IsShownOnScreen()) return;
|
||||||
|
|
||||||
videoSize.Set(con->videoController->GetWidth(), con->videoController->GetHeight());
|
videoSize.Set(provider->GetWidth(), provider->GetHeight());
|
||||||
videoSize *= zoomValue;
|
videoSize *= zoomValue;
|
||||||
if (con->videoController->GetAspectRatioType() != AspectRatio::Default)
|
if (con->videoController->GetAspectRatioType() != AspectRatio::Default)
|
||||||
videoSize.SetWidth(videoSize.GetHeight() * con->videoController->GetAspectRatioValue());
|
videoSize.SetWidth(videoSize.GetHeight() * con->videoController->GetAspectRatioValue());
|
||||||
|
@ -346,7 +343,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
|
||||||
if (freeSize) {
|
if (freeSize) {
|
||||||
videoSize = GetClientSize() * scale_factor;
|
videoSize = GetClientSize() * scale_factor;
|
||||||
PositionVideo();
|
PositionVideo();
|
||||||
zoomValue = double(viewport_height) / con->videoController->GetHeight();
|
zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight();
|
||||||
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
|
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -36,14 +36,14 @@
|
||||||
|
|
||||||
#include "vector2d.h"
|
#include "vector2d.h"
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
|
#include <vector>
|
||||||
#include <wx/glcanvas.h>
|
#include <wx/glcanvas.h>
|
||||||
|
|
||||||
// Prototypes
|
// Prototypes
|
||||||
class RetinaHelper;
|
class RetinaHelper;
|
||||||
class VideoContext;
|
class VideoController;
|
||||||
class VideoOutGL;
|
class VideoOutGL;
|
||||||
class VisualToolBase;
|
class VisualToolBase;
|
||||||
class wxComboBox;
|
class wxComboBox;
|
||||||
|
@ -59,7 +59,7 @@ namespace agi {
|
||||||
|
|
||||||
class VideoDisplay final : public wxGLCanvas {
|
class VideoDisplay final : public wxGLCanvas {
|
||||||
/// Signals the display is connected to
|
/// Signals the display is connected to
|
||||||
std::deque<agi::signal::Connection> slots;
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
const agi::OptionValue* autohideTools;
|
const agi::OptionValue* autohideTools;
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_frame.h"
|
#include "video_frame.h"
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
|
|
|
@ -34,13 +34,15 @@
|
||||||
|
|
||||||
#include "video_slider.h"
|
#include "video_slider.h"
|
||||||
|
|
||||||
|
#include "async_video_provider.h"
|
||||||
#include "base_grid.h"
|
#include "base_grid.h"
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "include/aegisub/hotkey.h"
|
#include "include/aegisub/hotkey.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "project.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
|
|
||||||
#include <wx/dcbuffer.h>
|
#include <wx/dcbuffer.h>
|
||||||
#include <wx/settings.h>
|
#include <wx/settings.h>
|
||||||
|
@ -48,19 +50,19 @@
|
||||||
VideoSlider::VideoSlider (wxWindow* parent, agi::Context *c)
|
VideoSlider::VideoSlider (wxWindow* parent, agi::Context *c)
|
||||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE)
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE)
|
||||||
, c(c)
|
, c(c)
|
||||||
|
, connections(agi::signal::make_vector({
|
||||||
|
OPT_SUB("Video/Slider/Show Keyframes", [=] { Refresh(false); }),
|
||||||
|
c->videoController->AddSeekListener(&VideoSlider::SetValue, this),
|
||||||
|
c->project->AddVideoProviderListener(&VideoSlider::VideoOpened, this),
|
||||||
|
c->project->AddKeyframesListener(&VideoSlider::KeyframesChanged, this),
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
SetClientSize(20,25);
|
SetClientSize(20,25);
|
||||||
SetMinSize(wxSize(20, 25));
|
SetMinSize(wxSize(20, 25));
|
||||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||||
|
|
||||||
slots.push_back(OPT_SUB("Video/Slider/Show Keyframes", [=] { Refresh(false); }));
|
|
||||||
slots.push_back(c->videoController->AddSeekListener(&VideoSlider::SetValue, this));
|
|
||||||
slots.push_back(c->videoController->AddVideoOpenListener(&VideoSlider::VideoOpened, this));
|
|
||||||
slots.push_back(c->videoController->AddKeyframesListener(&VideoSlider::KeyframesChanged, this));
|
|
||||||
|
|
||||||
c->videoSlider = this;
|
c->videoSlider = this;
|
||||||
|
VideoOpened(c->project->VideoProvider());
|
||||||
VideoOpened();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoSlider::SetValue(int value) {
|
void VideoSlider::SetValue(int value) {
|
||||||
|
@ -69,10 +71,9 @@ void VideoSlider::SetValue(int value) {
|
||||||
Refresh(false);
|
Refresh(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoSlider::VideoOpened() {
|
void VideoSlider::VideoOpened(AsyncVideoProvider *provider) {
|
||||||
if (c->videoController->IsLoaded()) {
|
if (provider) {
|
||||||
max = c->videoController->GetLength() - 1;
|
max = provider->GetFrameCount() - 1;
|
||||||
keyframes = c->videoController->GetKeyFrames();
|
|
||||||
Refresh(false);
|
Refresh(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,27 +27,22 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
/// @file video_slider.h
|
#include <libaegisub/signal.h>
|
||||||
/// @see video_slider.cpp
|
|
||||||
/// @ingroup custom_control
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wx/window.h>
|
#include <wx/window.h>
|
||||||
|
|
||||||
#include <libaegisub/signal.h>
|
|
||||||
|
|
||||||
namespace agi { struct Context; }
|
namespace agi { struct Context; }
|
||||||
|
|
||||||
class VideoContext;
|
class VideoController;
|
||||||
|
class AsyncVideoProvider;
|
||||||
|
|
||||||
/// @class VideoSlider
|
/// @class VideoSlider
|
||||||
/// @brief Slider for displaying and adjusting the video position
|
/// @brief Slider for displaying and adjusting the video position
|
||||||
class VideoSlider: public wxWindow {
|
class VideoSlider: public wxWindow {
|
||||||
agi::Context *c; ///< Associated project context
|
agi::Context *c; ///< Associated project context
|
||||||
std::vector<int> keyframes; ///< Currently loaded keyframes
|
std::vector<int> keyframes; ///< Currently loaded keyframes
|
||||||
std::vector<agi::signal::Connection> slots;
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
int val = 0; ///< Current frame number
|
int val = 0; ///< Current frame number
|
||||||
int max = 1; ///< Last frame number
|
int max = 1; ///< Last frame number
|
||||||
|
@ -60,7 +55,7 @@ class VideoSlider: public wxWindow {
|
||||||
void SetValue(int value);
|
void SetValue(int value);
|
||||||
|
|
||||||
/// Video open event handler
|
/// Video open event handler
|
||||||
void VideoOpened();
|
void VideoOpened(AsyncVideoProvider *new_provider);
|
||||||
/// Keyframe open even handler
|
/// Keyframe open even handler
|
||||||
void KeyframesChanged(std::vector<int> const& newKeyframes);
|
void KeyframesChanged(std::vector<int> const& newKeyframes);
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
#include "visual_feature.h"
|
#include "visual_feature.h"
|
||||||
#include "visual_tool_clip.h"
|
#include "visual_tool_clip.h"
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#include <libaegisub/owning_intrusive_list.h>
|
#include <libaegisub/owning_intrusive_list.h>
|
||||||
#include <libaegisub/signal.h>
|
#include <libaegisub/signal.h>
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
|
|
||||||
|
@ -79,7 +78,7 @@ class VisualToolBase {
|
||||||
virtual void DoRefresh() { }
|
virtual void DoRefresh() { }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::deque<agi::signal::Connection> connections;
|
std::vector<agi::signal::Connection> connections;
|
||||||
|
|
||||||
OpenGLWrapper gl;
|
OpenGLWrapper gl;
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_controller.h"
|
||||||
#include "video_display.h"
|
#include "video_display.h"
|
||||||
|
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
|
@ -79,7 +79,7 @@ void VisualToolDrag::UpdateToggleButtons() {
|
||||||
|
|
||||||
void VisualToolDrag::OnSubTool(wxCommandEvent &) {
|
void VisualToolDrag::OnSubTool(wxCommandEvent &) {
|
||||||
// Toggle \move <-> \pos
|
// Toggle \move <-> \pos
|
||||||
VideoContext *vc = c->videoController.get();
|
VideoController *vc = c->videoController.get();
|
||||||
for (auto line : selection) {
|
for (auto line : selection) {
|
||||||
Vector2D p1, p2;
|
Vector2D p1, p2;
|
||||||
int t1, t2;
|
int t1, t2;
|
||||||
|
|
Loading…
Reference in New Issue