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)preferences.h" />
|
||||
<ClInclude Include="$(SrcDir)preferences_base.h" />
|
||||
<ClInclude Include="$(SrcDir)project.h" />
|
||||
<ClInclude Include="$(SrcDir)resolution_resampler.h" />
|
||||
<ClInclude Include="$(SrcDir)scintilla_text_ctrl.h" />
|
||||
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
|
||||
|
@ -234,7 +235,7 @@
|
|||
<ClInclude Include="$(SrcDir)text_file_writer.h" />
|
||||
<ClInclude Include="$(SrcDir)text_selection_controller.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)timeedit_ctrl.h" />
|
||||
<ClInclude Include="$(SrcDir)toggle_bitmap.h" />
|
||||
|
@ -244,7 +245,7 @@
|
|||
<ClInclude Include="$(SrcDir)vector2d.h" />
|
||||
<ClInclude Include="$(SrcDir)version.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_frame.h" />
|
||||
<ClInclude Include="$(SrcDir)video_out_gl.h" />
|
||||
|
@ -282,6 +283,7 @@
|
|||
<ClCompile Include="$(SrcDir)ass_style.cpp" />
|
||||
<ClCompile Include="$(SrcDir)ass_style_storage.cpp" />
|
||||
<ClCompile Include="$(SrcDir)ass_time.cpp" />
|
||||
<ClCompile Include="$(SrcDir)async_video_provider.cpp" />
|
||||
<ClCompile Include="$(SrcDir)audio_box.cpp" />
|
||||
<ClCompile Include="$(SrcDir)audio_colorscheme.cpp" />
|
||||
<ClCompile Include="$(SrcDir)audio_controller.cpp" />
|
||||
|
@ -395,6 +397,7 @@
|
|||
<ClCompile Include="$(SrcDir)persist_location.cpp" />
|
||||
<ClCompile Include="$(SrcDir)preferences.cpp" />
|
||||
<ClCompile Include="$(SrcDir)preferences_base.cpp" />
|
||||
<ClCompile Include="$(SrcDir)project.cpp" />
|
||||
<ClCompile Include="$(SrcDir)resolution_resampler.cpp" />
|
||||
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
|
||||
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
|
||||
|
@ -426,7 +429,6 @@
|
|||
<ClCompile Include="$(SrcDir)text_file_writer.cpp" />
|
||||
<ClCompile Include="$(SrcDir)text_selection_controller.cpp" />
|
||||
<ClCompile Include="$(SrcDir)thesaurus.cpp" />
|
||||
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp" />
|
||||
<ClCompile Include="$(SrcDir)timeedit_ctrl.cpp" />
|
||||
<ClCompile Include="$(SrcDir)toggle_bitmap.cpp" />
|
||||
<ClCompile Include="$(SrcDir)toolbar.cpp" />
|
||||
|
@ -436,7 +438,7 @@
|
|||
<ClCompile Include="$(SrcDir)vector2d.cpp" />
|
||||
<ClCompile Include="$(SrcDir)version.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_frame.cpp" />
|
||||
<ClCompile Include="$(SrcDir)video_out_gl.cpp" />
|
||||
|
|
|
@ -348,10 +348,7 @@
|
|||
<ClInclude Include="$(SrcDir)dialog_version_check.h">
|
||||
<Filter>Features\Update checker</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)threaded_frame_source.h">
|
||||
<Filter>Video\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)video_context.h">
|
||||
<ClInclude Include="$(SrcDir)video_controller.h">
|
||||
<Filter>Video</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)video_frame.h">
|
||||
|
@ -624,6 +621,12 @@
|
|||
<ClInclude Include="$(SrcDir)dialog_video_properties.h">
|
||||
<Filter>Features\Resolution resampler</Filter>
|
||||
</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>
|
||||
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
|
||||
|
@ -950,10 +953,7 @@
|
|||
<ClCompile Include="$(SrcDir)dialog_version_check.cpp">
|
||||
<Filter>Features\Update checker</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp">
|
||||
<Filter>Video\Providers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(SrcDir)video_context.cpp">
|
||||
<ClCompile Include="$(SrcDir)video_controller.cpp">
|
||||
<Filter>Video</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(SrcDir)video_slider.cpp">
|
||||
|
@ -1181,9 +1181,15 @@
|
|||
<ClCompile Include="$(SrcDir)dialog_video_properties.cpp">
|
||||
<Filter>Features\Resolution resampler</Filter>
|
||||
</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>
|
||||
<ResourceCompile Include="$(SrcDir)res\res.rc" />
|
||||
<ResourceCompile Include="$(SrcDir)res\strings.rc" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
/// @file signal.h
|
||||
/// @brief
|
||||
/// @ingroup libaegisub
|
||||
|
||||
#pragma once
|
||||
|
||||
#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
|
||||
class Connection {
|
||||
std::unique_ptr<detail::ConnectionToken> token;
|
||||
public:
|
||||
Connection() = default;
|
||||
Connection(UnscopedConnection src) BOOST_NOEXCEPT : token(src.token) { token->claimed = true; }
|
||||
Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { }
|
||||
Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; }
|
||||
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; }
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
/// 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
|
||||
|
|
|
@ -122,6 +122,7 @@ SRC += \
|
|||
ass_style.cpp \
|
||||
ass_style_storage.cpp \
|
||||
ass_time.cpp \
|
||||
async_video_provider.cpp \
|
||||
audio_box.cpp \
|
||||
audio_colorscheme.cpp \
|
||||
audio_controller.cpp \
|
||||
|
@ -205,6 +206,7 @@ SRC += \
|
|||
persist_location.cpp \
|
||||
preferences.cpp \
|
||||
preferences_base.cpp \
|
||||
project.cpp \
|
||||
resolution_resampler.cpp \
|
||||
scintilla_text_ctrl.cpp \
|
||||
search_replace_engine.cpp \
|
||||
|
@ -233,7 +235,6 @@ SRC += \
|
|||
text_file_writer.cpp \
|
||||
text_selection_controller.cpp \
|
||||
thesaurus.cpp \
|
||||
threaded_frame_source.cpp \
|
||||
timeedit_ctrl.cpp \
|
||||
toggle_bitmap.cpp \
|
||||
toolbar.cpp \
|
||||
|
@ -243,7 +244,7 @@ SRC += \
|
|||
vector2d.cpp \
|
||||
version.cpp \
|
||||
video_box.cpp \
|
||||
video_context.cpp \
|
||||
video_controller.cpp \
|
||||
video_display.cpp \
|
||||
video_frame.cpp \
|
||||
video_out_gl.cpp \
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
#include "ass_file.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "project.h"
|
||||
#include "subtitle_format.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <memory>
|
||||
#include <wx/sizer.h>
|
||||
|
@ -51,7 +51,7 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
|
|||
for (auto& filter : *AssExportFilterChain::GetFilterList()) {
|
||||
// Make sure to construct static box sizer first, so it won't overlap
|
||||
// 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);
|
||||
if (window) {
|
||||
box->Add(window, 0, wxEXPAND, 0);
|
||||
|
@ -92,7 +92,7 @@ void AssExporter::Export(agi::fs::path const& filename, std::string const& chars
|
|||
if (!writer)
|
||||
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) {
|
||||
|
|
|
@ -14,18 +14,12 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file threaded_frame_source.cpp
|
||||
/// @see threaded_frame_source.h
|
||||
/// @ingroup video
|
||||
///
|
||||
|
||||
#include "threaded_frame_source.h"
|
||||
#include "async_video_provider.h"
|
||||
|
||||
#include "ass_dialogue.h"
|
||||
#include "ass_file.h"
|
||||
#include "export_fixstyle.h"
|
||||
#include "include/aegisub/subtitles_provider.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "video_frame.h"
|
||||
#include "video_provider_manager.h"
|
||||
|
||||
|
@ -40,11 +34,11 @@ enum {
|
|||
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;
|
||||
|
||||
try {
|
||||
frame = video_provider->GetFrame(frame_number);
|
||||
frame = source_provider->GetFrame(frame_number);
|
||||
}
|
||||
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())
|
||||
, 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)
|
||||
{
|
||||
}
|
||||
|
||||
ThreadedFrameSource::~ThreadedFrameSource() {
|
||||
AsyncVideoProvider::~AsyncVideoProvider() {
|
||||
// Block until all currently queued jobs are complete
|
||||
worker->Sync([]{});
|
||||
}
|
||||
|
||||
void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
|
||||
void AsyncVideoProvider::LoadSubtitles(const AssFile *new_subs) throw() {
|
||||
uint_fast32_t req_version = ++version;
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
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;
|
||||
worker->Sync([&]{ ret = ProcFrame(frame, time, raw); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ThreadedFrameSource::SetColorSpace(std::string const& matrix) {
|
||||
worker->Async([=] { video_provider->SetColorSpace(matrix); });
|
||||
void AsyncVideoProvider::SetColorSpace(std::string const& matrix) {
|
||||
worker->Async([=] { source_provider->SetColorSpace(matrix); });
|
||||
}
|
||||
|
||||
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
|
|
@ -14,19 +14,14 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file threaded_frame_source.h
|
||||
/// @see threaded_frame_source.cpp
|
||||
/// @ingroup video
|
||||
///
|
||||
#include "include/aegisub/video_provider.h"
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
class AssDialogue;
|
||||
|
@ -40,16 +35,15 @@ namespace agi {
|
|||
namespace dispatch { class Queue; }
|
||||
}
|
||||
|
||||
/// @class ThreadedFrameSource
|
||||
/// @brief An asynchronous video decoding and subtitle rendering wrapper
|
||||
class ThreadedFrameSource {
|
||||
/// An asynchronous video decoding and subtitle rendering wrapper
|
||||
class AsyncVideoProvider {
|
||||
/// Asynchronous work queue
|
||||
std::unique_ptr<agi::dispatch::Queue> worker;
|
||||
|
||||
/// Subtitles provider
|
||||
std::unique_ptr<SubtitlesProvider> subs_provider;
|
||||
/// Video provider
|
||||
std::unique_ptr<VideoProvider> video_provider;
|
||||
std::unique_ptr<VideoProvider> source_provider;
|
||||
/// Event handler to send FrameReady events to
|
||||
wxEvtHandler *parent;
|
||||
|
||||
|
@ -103,17 +97,26 @@ public:
|
|||
/// @brief raw Get raw frame without subtitles
|
||||
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
|
||||
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
|
||||
/// @param videoFileName File to open
|
||||
/// @param parent Event handler to send FrameReady events to
|
||||
ThreadedFrameSource(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
|
||||
~ThreadedFrameSource();
|
||||
AsyncVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
|
||||
~AsyncVideoProvider();
|
||||
};
|
||||
|
||||
/// Event which signals that a requested frame is ready
|
|
@ -61,6 +61,7 @@
|
|||
#include "command/command.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "toggle_bitmap.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
|
@ -75,7 +76,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
|
|||
: wxSashWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxSW_3D | wxCLIP_CHILDREN)
|
||||
, controller(context->audioController.get())
|
||||
, 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))
|
||||
, 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))
|
||||
|
|
|
@ -27,36 +27,23 @@
|
|||
//
|
||||
// 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 "ass_file.h"
|
||||
#include "audio_timing.h"
|
||||
#include "compat.h"
|
||||
#include "dialog_progress.h"
|
||||
#include "include/aegisub/audio_player.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "pen.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <libaegisub/io.h>
|
||||
#include <libaegisub/path.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
AudioController::AudioController(agi::Context *context)
|
||||
: context(context)
|
||||
, subtitle_save_slot(context->subsController->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
|
||||
, playback_timer(this)
|
||||
, provider_connection(context->project->AddAudioProviderListener(&AudioController::OnAudioProvider, this))
|
||||
{
|
||||
Bind(wxEVT_TIMER, &AudioController::OnPlaybackTimer, this, playback_timer.GetId());
|
||||
|
||||
|
@ -66,25 +53,18 @@ AudioController::AudioController(agi::Context *context)
|
|||
#endif
|
||||
|
||||
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()
|
||||
{
|
||||
CloseAudio();
|
||||
Stop();
|
||||
}
|
||||
|
||||
void AudioController::OnPlaybackTimer(wxTimerEvent &)
|
||||
{
|
||||
int64_t pos = player->GetCurrentPosition();
|
||||
if (!player) return;
|
||||
|
||||
int64_t pos = player->GetCurrentPosition();
|
||||
if (!player->IsPlaying() ||
|
||||
(playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
|
||||
{
|
||||
|
@ -107,113 +87,40 @@ void AudioController::OnComputerSuspending(wxPowerEvent &)
|
|||
|
||||
void AudioController::OnComputerResuming(wxPowerEvent &)
|
||||
{
|
||||
if (provider)
|
||||
{
|
||||
try
|
||||
{
|
||||
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
CloseAudio();
|
||||
}
|
||||
}
|
||||
OnAudioPlayerChanged();
|
||||
}
|
||||
#endif
|
||||
|
||||
void AudioController::OnAudioPlayerChanged()
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
if (!provider) return;
|
||||
|
||||
Stop();
|
||||
|
||||
player.reset();
|
||||
|
||||
try
|
||||
{
|
||||
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
|
||||
player = AudioPlayerFactory::GetAudioPlayer(provider, context->parent);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
CloseAudio();
|
||||
throw;
|
||||
context->project->CloseAudio();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioController::OnAudioProviderChanged()
|
||||
{
|
||||
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()
|
||||
void AudioController::OnAudioProvider(AudioProvider *new_provider)
|
||||
{
|
||||
provider = new_provider;
|
||||
Stop();
|
||||
|
||||
player.reset();
|
||||
provider.reset();
|
||||
player = nullptr;
|
||||
provider = nullptr;
|
||||
|
||||
audio_url.clear();
|
||||
|
||||
config::path->SetToken("?audio", "");
|
||||
|
||||
AnnounceAudioClose();
|
||||
}
|
||||
|
||||
bool AudioController::IsAudioOpen() const
|
||||
{
|
||||
return player && provider;
|
||||
OnAudioPlayerChanged();
|
||||
}
|
||||
|
||||
void AudioController::SetTimingController(std::unique_ptr<AudioTimingController> new_controller)
|
||||
{
|
||||
timing_controller = std::move(new_controller);
|
||||
if (timing_controller)
|
||||
{
|
||||
timing_controller->AddUpdatedPrimaryRangeListener(&AudioController::OnTimingControllerUpdatedPrimaryRange, this);
|
||||
}
|
||||
|
||||
AnnounceTimingControllerChanged();
|
||||
}
|
||||
|
@ -224,17 +131,9 @@ void AudioController::OnTimingControllerUpdatedPrimaryRange()
|
|||
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)
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
if (!player) return;
|
||||
|
||||
player->Play(SamplesFromMilliseconds(range.begin()), SamplesFromMilliseconds(range.length()));
|
||||
playback_mode = PM_Range;
|
||||
|
@ -252,8 +151,6 @@ void AudioController::PlayPrimaryRange()
|
|||
|
||||
void AudioController::PlayToEndOfPrimary(int start_ms)
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
|
||||
PlayRange(TimeRange(start_ms, GetPrimaryPlaybackRange().end()));
|
||||
if (playback_mode == PM_Range)
|
||||
playback_mode = PM_PrimaryRange;
|
||||
|
@ -261,7 +158,7 @@ void AudioController::PlayToEndOfPrimary(int start_ms)
|
|||
|
||||
void AudioController::PlayToEnd(int start_ms)
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
if (!player) return;
|
||||
|
||||
int64_t start_sample = SamplesFromMilliseconds(start_ms);
|
||||
player->Play(start_sample, provider->GetNumSamples()-start_sample);
|
||||
|
@ -273,7 +170,7 @@ void AudioController::PlayToEnd(int start_ms)
|
|||
|
||||
void AudioController::Stop()
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
if (!player) return;
|
||||
|
||||
player->Stop();
|
||||
playback_mode = PM_NotPlaying;
|
||||
|
@ -284,7 +181,7 @@ void AudioController::Stop()
|
|||
|
||||
bool AudioController::IsPlaying()
|
||||
{
|
||||
return IsAudioOpen() && playback_mode != PM_NotPlaying;
|
||||
return player && playback_mode != PM_NotPlaying;
|
||||
}
|
||||
|
||||
int AudioController::GetPlaybackPosition()
|
||||
|
@ -297,88 +194,31 @@ int AudioController::GetPlaybackPosition()
|
|||
int AudioController::GetDuration() const
|
||||
{
|
||||
if (!provider) return 0;
|
||||
|
||||
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
|
||||
}
|
||||
|
||||
TimeRange AudioController::GetPrimaryPlaybackRange() const
|
||||
{
|
||||
if (timing_controller)
|
||||
{
|
||||
return timing_controller->GetPrimaryPlaybackRange();
|
||||
}
|
||||
else
|
||||
{
|
||||
return TimeRange(0, 0);
|
||||
}
|
||||
return TimeRange{0, 0};
|
||||
}
|
||||
|
||||
void AudioController::SetVolume(double volume)
|
||||
{
|
||||
if (!IsAudioOpen()) return;
|
||||
if (!player) return;
|
||||
player->SetVolume(volume);
|
||||
}
|
||||
|
||||
int64_t AudioController::SamplesFromMilliseconds(int64_t ms) const
|
||||
{
|
||||
/// @todo There might be some subtle rounding errors here.
|
||||
|
||||
if (!provider) return 0;
|
||||
|
||||
int64_t sr = provider->GetSampleRate();
|
||||
|
||||
int64_t millisamples = ms * sr;
|
||||
|
||||
return (millisamples + 999) / 1000;
|
||||
return (ms * provider->GetSampleRate() + 999) / 1000;
|
||||
}
|
||||
|
||||
int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
|
||||
{
|
||||
/// @todo There might be some subtle rounding errors here.
|
||||
|
||||
if (!provider) return 0;
|
||||
|
||||
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);
|
||||
}
|
||||
return samples * 1000 / provider->GetSampleRate();
|
||||
}
|
||||
|
|
|
@ -27,24 +27,15 @@
|
|||
//
|
||||
// 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/fs_fwd.h>
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <wx/event.h>
|
||||
#include <wx/power.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
class AudioPlayer;
|
||||
class AudioProvider;
|
||||
class AudioTimingController;
|
||||
|
@ -52,17 +43,10 @@ namespace agi { struct Context; }
|
|||
class TimeRange;
|
||||
|
||||
/// @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
|
||||
/// some point be moved to a separate class, as it adds too many
|
||||
/// 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.
|
||||
/// AudioController owns an AudioPlayer and uses it to play audio from the
|
||||
/// project's current audio provider.
|
||||
class AudioController final : public wxEvtHandler {
|
||||
/// Project context this controller belongs to
|
||||
agi::Context *context;
|
||||
|
@ -70,12 +54,6 @@ class AudioController final : public wxEvtHandler {
|
|||
/// Slot for subtitles save signal
|
||||
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
|
||||
agi::signal::Signal<int> AnnouncePlaybackPosition;
|
||||
|
||||
|
@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler {
|
|||
/// The audio output object
|
||||
std::unique_ptr<AudioPlayer> player;
|
||||
|
||||
/// The audio provider
|
||||
std::unique_ptr<AudioProvider> provider;
|
||||
|
||||
/// The current timing mode, if any; owned by the audio controller
|
||||
std::unique_ptr<AudioTimingController> timing_controller;
|
||||
|
||||
/// The URL of the currently open audio, if any
|
||||
agi::fs::path audio_url;
|
||||
|
||||
|
||||
enum PlaybackMode {
|
||||
PM_NotPlaying,
|
||||
PM_Range,
|
||||
|
@ -110,6 +81,12 @@ class AudioController final : public wxEvtHandler {
|
|||
/// Timer used for playback position updates
|
||||
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
|
||||
void OnPlaybackTimer(wxTimerEvent &event);
|
||||
|
||||
|
@ -119,15 +96,9 @@ class AudioController final : public wxEvtHandler {
|
|||
/// @brief Timing controller signals that the rendering style ranges have changed
|
||||
void OnTimingControllerUpdatedStyleRanges();
|
||||
|
||||
/// Subtitles save slot which adds the audio uri to the subtitles
|
||||
void OnSubtitlesSave();
|
||||
|
||||
/// Handler for the current audio player changing
|
||||
void OnAudioPlayerChanged();
|
||||
|
||||
/// Handler for the current audio provider changing
|
||||
void OnAudioProviderChanged();
|
||||
|
||||
#ifdef wxHAS_POWER_EVENTS
|
||||
/// Handle computer going into suspend mode by stopping audio and closing device
|
||||
void OnComputerSuspending(wxPowerEvent &event);
|
||||
|
@ -146,31 +117,9 @@ class AudioController final : public wxEvtHandler {
|
|||
int64_t SamplesFromMilliseconds(int64_t ms) const;
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
AudioController(agi::Context *context);
|
||||
|
||||
/// @brief Destructor
|
||||
~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
|
||||
/// @param range The range of audio to play back
|
||||
///
|
||||
|
@ -233,13 +182,6 @@ public:
|
|||
/// @param new_mode The new timing controller or nullptr
|
||||
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(AnnouncePlaybackStop, AddPlaybackStopListener)
|
||||
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
|
||||
|
|
|
@ -48,9 +48,10 @@
|
|||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -547,7 +548,7 @@ public:
|
|||
|
||||
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
|
||||
: 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)
|
||||
, audio_renderer(agi::make_unique<AudioRenderer>())
|
||||
, controller(controller)
|
||||
|
@ -1193,16 +1194,16 @@ void AudioDisplay::OnAudioOpen(AudioProvider *provider)
|
|||
{
|
||||
if (connections.empty())
|
||||
{
|
||||
connections.push_back(controller->AddAudioCloseListener([=] { OnAudioOpen(nullptr); }));
|
||||
connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
|
||||
connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
|
||||
connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
|
||||
connections.push_back(OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
|
||||
connections.push_back(OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this));
|
||||
connections.push_back(OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
|
||||
connections.push_back(OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this));
|
||||
connections.push_back(OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this));
|
||||
|
||||
connections = agi::signal::make_vector({
|
||||
controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this),
|
||||
controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this),
|
||||
controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this),
|
||||
OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
||||
OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this),
|
||||
OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
||||
OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this),
|
||||
OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this),
|
||||
});
|
||||
OnTimingController();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
|
@ -103,7 +102,7 @@ public:
|
|||
class AudioDisplay: public wxWindow {
|
||||
agi::signal::Connection audio_open_connection;
|
||||
|
||||
std::deque<agi::signal::Connection> connections;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
agi::Context *context;
|
||||
|
||||
/// The audio renderer manager
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "compat.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
@ -40,7 +41,6 @@
|
|||
#include <algorithm>
|
||||
#include <boost/locale/boundary.hpp>
|
||||
#include <numeric>
|
||||
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/button.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)
|
||||
, c(c)
|
||||
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
|
||||
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
|
||||
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
|
||||
, audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this))
|
||||
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
||||
, kara(agi::make_unique<AssKaraoke>())
|
||||
{
|
||||
|
@ -122,12 +121,11 @@ void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const&
|
|||
}
|
||||
}
|
||||
|
||||
void AudioKaraoke::OnAudioOpened() {
|
||||
SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void AudioKaraoke::OnAudioClosed() {
|
||||
c->audioController->SetTimingController(nullptr);
|
||||
void AudioKaraoke::OnAudioOpened(AudioProvider *provider) {
|
||||
if (provider)
|
||||
SetEnabled(enabled);
|
||||
else
|
||||
c->audioController->SetTimingController(nullptr);
|
||||
}
|
||||
|
||||
void AudioKaraoke::SetEnabled(bool en) {
|
||||
|
|
|
@ -14,26 +14,20 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file audio_karaoke.h
|
||||
/// @see audio_karaoke.cpp
|
||||
/// @ingroup audio_ui
|
||||
///
|
||||
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
class AssDialogue;
|
||||
class AssKaraoke;
|
||||
class AudioProvider;
|
||||
class wxButton;
|
||||
|
||||
namespace agi { struct Context; }
|
||||
|
||||
/// @class AudioKaraoke
|
||||
|
@ -147,8 +141,7 @@ class AudioKaraoke final : public wxWindow {
|
|||
void OnMouse(wxMouseEvent &event);
|
||||
void OnPaint(wxPaintEvent &event);
|
||||
void OnSize(wxSizeEvent &event);
|
||||
void OnAudioOpened();
|
||||
void OnAudioClosed();
|
||||
void OnAudioOpened(AudioProvider *provider);
|
||||
void OnScrollTimer(wxTimerEvent &event);
|
||||
|
||||
public:
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "pen.h"
|
||||
#include "video_context.h"
|
||||
#include "project.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -42,9 +43,9 @@ public:
|
|||
};
|
||||
|
||||
AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
|
||||
: vc(c->videoController.get())
|
||||
, keyframe_slot(vc->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||
, timecode_slot(vc->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||
: p(c->project.get())
|
||||
, keyframe_slot(p->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||
, timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
|
||||
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
|
||||
, enabled_opt(OPT_GET(opt_name))
|
||||
, style(agi::make_unique<Pen>("Colour/Audio Display/Keyframe"))
|
||||
|
@ -55,8 +56,8 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons
|
|||
AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }
|
||||
|
||||
void AudioMarkerProviderKeyframes::Update() {
|
||||
std::vector<int> const& keyframes = vc->GetKeyFrames();
|
||||
agi::vfr::Framerate const& timecodes = vc->FPS();
|
||||
auto const& keyframes = p->Keyframes();
|
||||
auto const& timecodes = p->Timecodes();
|
||||
|
||||
if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
|
||||
if (!markers.empty()) {
|
||||
|
|
|
@ -14,11 +14,6 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file audio_marker.h
|
||||
/// @see audio_marker.cpp
|
||||
/// @ingroup audio_ui
|
||||
///
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libaegisub/signal.h>
|
||||
|
@ -30,7 +25,8 @@
|
|||
|
||||
class AudioMarkerKeyframe;
|
||||
class Pen;
|
||||
class VideoContext;
|
||||
class Project;
|
||||
class VideoController;
|
||||
class TimeRange;
|
||||
class VideoPositionMarker;
|
||||
class wxPen;
|
||||
|
@ -113,8 +109,8 @@ public:
|
|||
|
||||
/// Marker provider for video keyframes
|
||||
class AudioMarkerProviderKeyframes final : public AudioMarkerProvider {
|
||||
/// Video controller to get keyframes from
|
||||
VideoContext *vc;
|
||||
/// Project to get keyframes from
|
||||
Project *p;
|
||||
|
||||
agi::signal::Connection keyframe_slot;
|
||||
agi::signal::Connection timecode_slot;
|
||||
|
@ -146,7 +142,7 @@ public:
|
|||
|
||||
/// Marker provider for the current video playback position
|
||||
class VideoPositionMarkerProvider final : public AudioMarkerProvider {
|
||||
VideoContext *vc;
|
||||
VideoController *vc;
|
||||
|
||||
std::unique_ptr<VideoPositionMarker> marker;
|
||||
|
||||
|
|
|
@ -64,8 +64,6 @@ public:
|
|||
};
|
||||
|
||||
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
|
||||
this->filename = filename;
|
||||
|
||||
agi::acs::CheckFileRead(filename);
|
||||
|
||||
std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());
|
||||
|
|
|
@ -158,8 +158,6 @@ public:
|
|||
RiffWavPCMAudioProvider(agi::fs::path const& filename)
|
||||
: PCMAudioProvider(filename)
|
||||
{
|
||||
this->filename = filename;
|
||||
|
||||
// Read header
|
||||
auto const& header = Read<RIFFChunk>(0);
|
||||
|
||||
|
@ -292,8 +290,6 @@ public:
|
|||
Wave64AudioProvider(agi::fs::path const& filename)
|
||||
: PCMAudioProvider(filename)
|
||||
{
|
||||
this->filename = filename;
|
||||
|
||||
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
|
||||
|
||||
if (file->size() < smallest_possible_file)
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
#include <boost/range/algorithm/copy.hpp>
|
||||
#include <boost/range/adaptor/filtered.hpp>
|
||||
#include <boost/range/adaptor/sliced.hpp>
|
||||
#include <deque>
|
||||
|
||||
/// @class KaraokeMarker
|
||||
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
|
||||
|
@ -55,6 +54,8 @@ public:
|
|||
|
||||
void Move(int new_pos) { position = new_pos; }
|
||||
|
||||
KaraokeMarker(int position) : position(position) { }
|
||||
|
||||
KaraokeMarker(int position, Pen *pen, FeetStyle style)
|
||||
: position(position)
|
||||
, pen(pen)
|
||||
|
@ -62,11 +63,6 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
KaraokeMarker(int position)
|
||||
: position(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
|
||||
/// one syllable be the same as the start time of the next one.
|
||||
class AudioTimingControllerKaraoke final : public AudioTimingController {
|
||||
std::deque<agi::signal::Connection> slots;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
agi::signal::Connection& file_changed_slot;
|
||||
|
||||
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")
|
||||
, video_position_provider(c)
|
||||
{
|
||||
slots.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(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
|
||||
connections.push_back(OPT_SUB("Audio/Auto/Commit", [=](agi::OptionValue const& opt) { auto_commit = opt.GetBool(); }));
|
||||
|
||||
keyframes_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
||||
video_position_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
|
||||
|
|
|
@ -373,9 +373,11 @@ namespace Automation4 {
|
|||
|
||||
LocalScriptManager::LocalScriptManager(agi::Context *c)
|
||||
: context(c)
|
||||
, connections(agi::signal::make_vector({
|
||||
c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this),
|
||||
c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this),
|
||||
}))
|
||||
{
|
||||
slots.push_back(c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this));
|
||||
slots.push_back(c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this));
|
||||
}
|
||||
|
||||
void LocalScriptManager::Reload()
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
#include "compat.h"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -215,8 +214,8 @@ namespace Automation4 {
|
|||
|
||||
/// Manager for scripts specified by a subtitle file
|
||||
class LocalScriptManager final : public ScriptManager {
|
||||
std::deque<agi::signal::Connection> slots;
|
||||
agi::Context *context;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
void OnSubtitlesSave();
|
||||
public:
|
||||
|
|
|
@ -39,15 +39,18 @@
|
|||
#include "ass_info.h"
|
||||
#include "ass_file.h"
|
||||
#include "ass_style.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "auto4_lua_factory.h"
|
||||
#include "command/command.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "main.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_controller.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/access.h>
|
||||
|
@ -169,7 +172,7 @@ namespace {
|
|||
const agi::Context *c = get_context(L);
|
||||
int ms = lua_tointeger(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));
|
||||
else
|
||||
lua_pushnil(L);
|
||||
|
@ -182,7 +185,7 @@ namespace {
|
|||
const agi::Context *c = get_context(L);
|
||||
int frame = lua_tointeger(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));
|
||||
else
|
||||
lua_pushnil(L);
|
||||
|
@ -192,9 +195,10 @@ namespace {
|
|||
int video_size(lua_State *L)
|
||||
{
|
||||
const agi::Context *c = get_context(L);
|
||||
if (c && c->videoController->IsLoaded()) {
|
||||
push_value(L, c->videoController->GetWidth());
|
||||
push_value(L, c->videoController->GetHeight());
|
||||
if (c && c->project->VideoProvider()) {
|
||||
auto provider = c->project->VideoProvider();
|
||||
push_value(L, provider->GetWidth());
|
||||
push_value(L, provider->GetHeight());
|
||||
push_value(L, c->videoController->GetAspectRatioValue());
|
||||
push_value(L, (int)c->videoController->GetAspectRatioType());
|
||||
return 4;
|
||||
|
@ -209,7 +213,7 @@ namespace {
|
|||
{
|
||||
const agi::Context *c = get_context(L);
|
||||
if (c)
|
||||
push_value(L, c->videoController->GetKeyFrames());
|
||||
push_value(L, c->project->Keyframes());
|
||||
else
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
|
|
|
@ -40,10 +40,11 @@
|
|||
#include "frame_main.h"
|
||||
#include "grid_column.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "utils.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_controller.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
|
@ -123,30 +124,32 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
|||
UpdateStyle();
|
||||
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
|
||||
|
||||
connections.push_back(context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this));
|
||||
connections.push_back(context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this));
|
||||
connections.push_back(context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this));
|
||||
connections = agi::signal::make_vector({
|
||||
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this),
|
||||
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this),
|
||||
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this),
|
||||
|
||||
connections.push_back(context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this));
|
||||
connections.push_back(context->selectionController->AddSelectionListener([&]{ Refresh(false); }));
|
||||
context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this),
|
||||
context->selectionController->AddSelectionListener([&]{ Refresh(false); }),
|
||||
|
||||
connections.push_back(OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this));
|
||||
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this));
|
||||
OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this),
|
||||
OPT_SUB("Colour/Subtitle Grid/Selection", &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));
|
||||
connections.push_back(OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }));
|
||||
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this),
|
||||
OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }),
|
||||
});
|
||||
|
||||
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
|
||||
}
|
||||
|
@ -634,10 +637,10 @@ AssDialogue *BaseGrid::GetDialogue(int n) 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();
|
||||
return context->videoController->FrameAtTime(line->Start,agi::vfr::START) <= frame
|
||||
&& context->videoController->FrameAtTime(line->End,agi::vfr::END) >= frame;
|
||||
return context->project->Timecodes().FrameAtTime(line->Start, agi::vfr::START) <= frame
|
||||
&& context->project->Timecodes().FrameAtTime(line->End, agi::vfr::END) >= frame;
|
||||
}
|
||||
|
||||
void BaseGrid::OnCharHook(wxKeyEvent &event) {
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include "../audio_controller.h"
|
||||
#include "../compat.h"
|
||||
#include "../dialog_about.h"
|
||||
#include "../dialog_detached_video.h"
|
||||
|
@ -48,9 +47,9 @@
|
|||
#include "../libresrc/libresrc.h"
|
||||
#include "../main.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../preferences.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
|
||||
namespace {
|
||||
using cmd::Command;
|
||||
|
@ -79,7 +78,7 @@ struct app_display_audio_subs final : public Command {
|
|||
}
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->audioController->IsAudioOpen();
|
||||
return !!c->project->AudioProvider();
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
@ -115,7 +114,7 @@ struct app_display_subs final : public Command {
|
|||
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
c->frame->SetDisplayMode(0,0);
|
||||
c->frame->SetDisplayMode(0, 0);
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
c->frame->SetDisplayMode(1,0);
|
||||
c->frame->SetDisplayMode(1, 0);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -37,15 +37,18 @@
|
|||
#include "../audio_karaoke.h"
|
||||
#include "../audio_timing.h"
|
||||
#include "../compat.h"
|
||||
#include "../include/aegisub/audio_provider.h"
|
||||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../selection_controller.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/io.h>
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
|
@ -55,7 +58,7 @@ namespace {
|
|||
struct validate_audio_open : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
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")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
c->audioController->CloseAudio();
|
||||
c->project->CloseAudio();
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
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 {
|
||||
struct audio_open final : public Command {
|
||||
CMD_NAME("audio/open")
|
||||
CMD_ICON(open_audio_menu)
|
||||
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|"
|
||||
+ _("All Files") + " (*.*)|*.*";
|
||||
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);
|
||||
if (filename.empty()) return;
|
||||
|
||||
do_open(c, filename);
|
||||
if (!filename.empty())
|
||||
c->project->LoadAudio(filename);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -120,12 +98,7 @@ struct audio_open_blank final : public Command {
|
|||
STR_HELP("Open a 150 minutes blank audio clip, for debugging")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
c->project->LoadAudio("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -136,16 +109,11 @@ struct audio_open_noise final : public Command {
|
|||
STR_HELP("Open a 150 minutes noise-filled audio clip, for debugging")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
c->project->LoadAudio("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000");
|
||||
}
|
||||
};
|
||||
|
||||
struct audio_open_video final : public audio_open_from_file {
|
||||
struct audio_open_video final : public Command {
|
||||
CMD_NAME("audio/open/video")
|
||||
CMD_ICON(open_audio_from_video_menu)
|
||||
STR_MENU("Open Audio from &Video")
|
||||
|
@ -154,11 +122,11 @@ struct audio_open_video final : public audio_open_from_file {
|
|||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->IsLoaded();
|
||||
return !c->project->VideoName().empty();
|
||||
}
|
||||
|
||||
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 {
|
||||
CMD_NAME("audio/save/clip")
|
||||
STR_MENU("Create audio clip")
|
||||
|
@ -202,22 +193,55 @@ struct audio_save_clip final : public Command {
|
|||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
|
||||
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 {
|
||||
auto const& sel = c->selectionController->GetSelectedSet();
|
||||
if (sel.empty()) return;
|
||||
|
||||
auto filename = SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent);
|
||||
if (filename.empty()) return;
|
||||
|
||||
AssTime start = INT_MAX, end = 0;
|
||||
for (auto line : sel) {
|
||||
start = std::min(start, line->Start);
|
||||
end = std::max(end, line->End);
|
||||
}
|
||||
|
||||
c->audioController->SaveClip(
|
||||
SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent),
|
||||
TimeRange(start, end));
|
||||
auto provider = c->project->AudioProvider();
|
||||
|
||||
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 "../options.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
|
|
@ -43,13 +43,14 @@
|
|||
#include "../initial_line_state.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../search_replace_engine.h"
|
||||
#include "../selection_controller.h"
|
||||
#include "../subs_controller.h"
|
||||
#include "../subs_edit_ctrl.h"
|
||||
#include "../text_selection_controller.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/address_of_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 {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
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 "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
|
||||
#include <libaegisub/keyframe.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
namespace {
|
||||
|
@ -51,11 +52,11 @@ struct keyframe_close final : public Command {
|
|||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->OverKeyFramesLoaded();
|
||||
return c->project->CanCloseKeyframes();
|
||||
}
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
c->videoController->CloseKeyframes();
|
||||
c->project->CloseKeyframes();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -74,7 +75,7 @@ struct keyframe_open final : public Command {
|
|||
c->parent);
|
||||
|
||||
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)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->KeyFramesLoaded();
|
||||
return !c->project->Keyframes().empty();
|
||||
}
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto filename = SaveFileSelector(_("Save keyframes file"), "Path/Last/Keyframes", "", "*.key.txt", "Text files (*.txt)|*.txt", c->parent);
|
||||
if (!filename.empty())
|
||||
c->videoController->SaveKeyframes(filename);
|
||||
if (filename.empty()) return;
|
||||
|
||||
agi::keyframe::Save(filename, c->project->Keyframes());
|
||||
config::mru->Add("Keyframes", filename);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,19 +29,15 @@
|
|||
|
||||
#include "command.h"
|
||||
|
||||
#include "../audio_controller.h"
|
||||
#include "../compat.h"
|
||||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../subs_controller.h"
|
||||
#include "../video_context.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace {
|
||||
using cmd::Command;
|
||||
|
||||
|
@ -58,13 +54,7 @@ struct recent_audio_entry : public Command {
|
|||
STR_HELP("Open recent audio")
|
||||
|
||||
void operator()(agi::Context *c, int id) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
c->project->LoadAudio(config::mru->GetEntry("Audio", id));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -75,7 +65,7 @@ struct recent_keyframes_entry : public Command {
|
|||
STR_HELP("Open recent keyframes")
|
||||
|
||||
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) {
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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 "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../search_replace_engine.h"
|
||||
#include "../selection_controller.h"
|
||||
#include "../subs_controller.h"
|
||||
#include "../subtitle_format.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/address_of_adaptor.h>
|
||||
#include <libaegisub/charset_conv.h>
|
||||
|
@ -71,7 +72,7 @@ struct validate_nonempty_selection : public Command {
|
|||
struct validate_nonempty_selection_video_loaded : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
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 {
|
||||
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;
|
||||
auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "","", SubtitleFormat::GetWildcards(0), c->parent);
|
||||
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;
|
||||
DialogAutosave dialog(c->parent);
|
||||
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);
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
if (!c->videoController->IsLoaded()) return;
|
||||
c->videoController->Stop();
|
||||
|
||||
Selection new_selection;
|
||||
|
@ -404,7 +404,7 @@ struct subtitle_select_visible final : public Command {
|
|||
}
|
||||
|
||||
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_file.h"
|
||||
#include "../async_video_provider.h"
|
||||
#include "../audio_controller.h"
|
||||
#include "../audio_timing.h"
|
||||
#include "../dialog_manager.h"
|
||||
|
@ -40,38 +41,39 @@
|
|||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../selection_controller.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
using cmd::Command;
|
||||
using cmd::Command;
|
||||
|
||||
struct validate_video_loaded : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->IsLoaded();
|
||||
struct validate_video_loaded : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
bool Validate(const agi::Context *c) override {
|
||||
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 {
|
||||
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) {
|
||||
void adjoin_lines(agi::Context *c, bool set_start) {
|
||||
auto const& sel = c->selectionController->GetSelectedSet();
|
||||
AssDialogue *prev = nullptr;
|
||||
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")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
if (!c->videoController->IsLoaded()) return;
|
||||
|
||||
auto const& sel = c->selectionController->GetSelectedSet();
|
||||
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) {
|
||||
auto const& sel = c->selectionController->GetSelectedSet();
|
||||
|
||||
if (!c->videoController->IsLoaded() || sel.empty()) return;
|
||||
if (sel.empty()) return;
|
||||
|
||||
int start = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::START);
|
||||
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")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
VideoContext *con = c->videoController.get();
|
||||
if (!con->IsLoaded() || !con->KeyFramesLoaded()) return;
|
||||
auto const& keyframes = c->project->Keyframes();
|
||||
if (keyframes.empty()) return;
|
||||
|
||||
VideoController *con = c->videoController.get();
|
||||
int curFrame = con->GetFrameN();
|
||||
int prev = 0;
|
||||
int next = 0;
|
||||
|
||||
auto const& keyframes = con->GetKeyFrames();
|
||||
if (curFrame < keyframes.front())
|
||||
next = keyframes.front();
|
||||
else if (curFrame >= keyframes.back()) {
|
||||
prev = keyframes.back();
|
||||
next = con->GetLength();
|
||||
next = c->project->VideoProvider()->GetFrameCount();
|
||||
}
|
||||
else {
|
||||
auto kf = std::lower_bound(keyframes.begin(), keyframes.end(), curFrame);
|
||||
|
|
|
@ -31,14 +31,18 @@
|
|||
|
||||
#include "command.h"
|
||||
|
||||
#include "../async_video_provider.h"
|
||||
#include "../compat.h"
|
||||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_context.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace {
|
||||
using cmd::Command;
|
||||
|
||||
|
@ -51,11 +55,11 @@ struct timecode_close final : public Command {
|
|||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->OverTimecodesLoaded();
|
||||
return c->project->CanCloseTimecodes();
|
||||
}
|
||||
|
||||
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 filename = OpenFileSelector(_("Open Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
||||
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)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->TimecodesLoaded();
|
||||
return c->project->Timecodes().IsLoaded();
|
||||
}
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
|
||||
auto filename = SaveFileSelector(_("Save Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
|
||||
if (!filename.empty())
|
||||
c->videoController->SaveTimecodes(filename);
|
||||
if (filename.empty()) return;
|
||||
|
||||
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 "../options.h"
|
||||
#include "../resolution_resampler.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/path.h>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include "../ass_dialogue.h"
|
||||
#include "../ass_time.h"
|
||||
#include "../async_video_provider.h"
|
||||
#include "../compat.h"
|
||||
#include "../dialog_detached_video.h"
|
||||
#include "../dialog_dummy_video.h"
|
||||
|
@ -44,10 +45,11 @@
|
|||
#include "../include/aegisub/subtitles_provider.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
#include "../project.h"
|
||||
#include "../selection_controller.h"
|
||||
#include "../utils.h"
|
||||
#include "../video_box.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
#include "../video_display.h"
|
||||
#include "../video_frame.h"
|
||||
#include "../video_slider.h"
|
||||
|
@ -61,7 +63,6 @@
|
|||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/textdlg.h>
|
||||
|
@ -72,14 +73,14 @@ namespace {
|
|||
struct validator_video_loaded : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->IsLoaded();
|
||||
return !!c->project->VideoProvider();
|
||||
}
|
||||
};
|
||||
|
||||
struct validator_video_attached : public Command {
|
||||
CMD_TYPE(COMMAND_VALIDATE)
|
||||
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")
|
||||
|
||||
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 {
|
||||
CMD_NAME("video/frame/copy")
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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);
|
||||
|
||||
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")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto const& kf = c->videoController->GetKeyFrames();
|
||||
auto const& kf = c->project->Keyframes();
|
||||
if (kf.empty()) {
|
||||
c->videoController->JumpToFrame(0);
|
||||
return;
|
||||
|
@ -459,7 +464,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
|
|||
auto option = OPT_GET("Path/Screenshot")->GetString();
|
||||
agi::fs::path basepath;
|
||||
|
||||
auto videoname = c->videoController->GetVideoName();
|
||||
auto videoname = c->project->VideoName();
|
||||
bool is_dummy = boost::starts_with(videoname.string(), "?dummy");
|
||||
|
||||
// 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());
|
||||
} 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 {
|
||||
|
@ -524,10 +529,8 @@ struct video_jump final : public validator_video_loaded {
|
|||
|
||||
void operator()(agi::Context *c) override {
|
||||
c->videoController->Stop();
|
||||
if (c->videoController->IsLoaded()) {
|
||||
DialogJumpTo(c).ShowModal();
|
||||
c->videoSlider->SetFocus();
|
||||
}
|
||||
DialogJumpTo(c).ShowModal();
|
||||
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")
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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")
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -570,7 +572,7 @@ struct video_open final : public Command {
|
|||
+ _("All Files") + " (*.*)|*.*";
|
||||
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
|
||||
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 {
|
||||
std::string fn = DialogDummyVideo::CreateDummyVideo(c->parent);
|
||||
if (!fn.empty())
|
||||
c->videoController->SetVideo(fn);
|
||||
c->project->LoadVideo(fn);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../project.h"
|
||||
#include "../video_box.h"
|
||||
#include "../video_context.h"
|
||||
#include "../video_controller.h"
|
||||
#include "../video_display.h"
|
||||
#include "../visual_tool_clip.h"
|
||||
#include "../visual_tool_cross.h"
|
||||
|
@ -39,7 +40,7 @@ namespace {
|
|||
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
|
||||
|
||||
bool Validate(const agi::Context *c) override {
|
||||
return c->videoController->IsLoaded();
|
||||
return !!c->project->VideoProvider();
|
||||
}
|
||||
|
||||
bool IsActive(const agi::Context *c) override {
|
||||
|
|
|
@ -21,11 +21,12 @@
|
|||
#include "auto4_base.h"
|
||||
#include "dialog_manager.h"
|
||||
#include "initial_line_state.h"
|
||||
#include "project.h"
|
||||
#include "search_replace_engine.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_controller.h"
|
||||
#include "text_selection_controller.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -34,10 +35,11 @@ Context::Context()
|
|||
: ass(make_unique<AssFile>())
|
||||
, textSelectionController(make_unique<TextSelectionController>())
|
||||
, subsController(make_unique<SubsController>(this))
|
||||
, project(make_unique<Project>(this))
|
||||
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
|
||||
, videoController(make_unique<VideoContext>(this))
|
||||
, audioController(make_unique<AudioController>(this))
|
||||
, selectionController(make_unique<SelectionController>(this))
|
||||
, videoController(make_unique<VideoController>(this))
|
||||
, audioController(make_unique<AudioController>(this))
|
||||
, initialLineState(make_unique<InitialLineState>(this))
|
||||
, search(make_unique<SearchReplaceEngine>(this))
|
||||
, dialog(make_unique<DialogManager>())
|
||||
|
|
|
@ -38,9 +38,10 @@
|
|||
#include "include/aegisub/hotkey.h"
|
||||
#include "options.h"
|
||||
#include "persist_location.h"
|
||||
#include "project.h"
|
||||
#include "utils.h"
|
||||
#include "video_box.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
@ -55,12 +56,12 @@ DialogDetachedVideo::DialogDetachedVideo(agi::Context *context)
|
|||
, context(context)
|
||||
, old_display(context->videoDisplay)
|
||||
, old_slider(context->videoSlider)
|
||||
, video_open(context->videoController->AddVideoOpenListener(&DialogDetachedVideo::OnVideoOpen, this))
|
||||
, video_open(context->project->AddVideoProviderListener(&DialogDetachedVideo::OnVideoOpen, this))
|
||||
{
|
||||
// Set obscure stuff
|
||||
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();
|
||||
|
||||
|
@ -108,8 +109,7 @@ void DialogDetachedVideo::OnClose(wxCloseEvent &evt) {
|
|||
|
||||
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();
|
||||
}
|
||||
|
@ -128,8 +128,8 @@ void DialogDetachedVideo::OnKeyDown(wxKeyEvent &evt) {
|
|||
}
|
||||
|
||||
void DialogDetachedVideo::OnVideoOpen() {
|
||||
if (context->videoController->IsLoaded())
|
||||
SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
|
||||
if (context->project->VideoProvider())
|
||||
SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring()));
|
||||
else {
|
||||
Close();
|
||||
OPT_SET("Video/Detached/Enabled")->SetBool(true);
|
||||
|
|
|
@ -34,12 +34,14 @@
|
|||
|
||||
#include "dialog_jumpto.h"
|
||||
|
||||
#include "include/aegisub/context.h"
|
||||
#include "ass_time.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "project.h"
|
||||
#include "timeedit_ctrl.h"
|
||||
#include "validators.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/sizer.h>
|
||||
|
@ -57,7 +59,7 @@ DialogJumpTo::DialogJumpTo(agi::Context *c)
|
|||
auto LabelTime = new wxStaticText(this, -1, _("Time: "));
|
||||
|
||||
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));
|
||||
|
||||
auto TimesSizer = new wxGridSizer(2, 5, 5);
|
||||
|
@ -95,7 +97,7 @@ void DialogJumpTo::OnInitDialog(wxInitDialogEvent&) {
|
|||
|
||||
void DialogJumpTo::OnOK(wxCommandEvent &) {
|
||||
EndModal(0);
|
||||
c->videoController->JumpToFrame(std::min<int>(jumpframe, c->videoController->GetLength() - 1));
|
||||
c->videoController->JumpToFrame(jumpframe);
|
||||
}
|
||||
|
||||
void DialogJumpTo::OnEditTime (wxCommandEvent &) {
|
||||
|
|
|
@ -35,13 +35,14 @@
|
|||
#include "dialog_properties.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "compat.h"
|
||||
#include "help_button.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "project.h"
|
||||
#include "resolution_resampler.h"
|
||||
#include "validators.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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")));
|
||||
|
||||
wxButton *FromVideo = new wxButton(this,-1,_("From &video"));
|
||||
if (!c->videoController->IsLoaded())
|
||||
if (!c->project->VideoProvider())
|
||||
FromVideo->Enable(false);
|
||||
else
|
||||
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 &) {
|
||||
ResX->SetValue(std::to_wstring(c->videoController->GetWidth()));
|
||||
ResY->SetValue(std::to_wstring(c->videoController->GetHeight()));
|
||||
ResX->SetValue(std::to_wstring(c->project->VideoProvider()->GetWidth()));
|
||||
ResY->SetValue(std::to_wstring(c->project->VideoProvider()->GetHeight()));
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
#include "dialog_resample.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "compat.h"
|
||||
#include "help_button.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "project.h"
|
||||
#include "resolution_resampler.h"
|
||||
#include "validators.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <boost/range/size.hpp>
|
||||
#include <wx/checkbox.h>
|
||||
|
@ -57,10 +57,10 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
|
|||
settings.source_y = script_h;
|
||||
settings.source_matrix = script_mat = MatrixFromString(c->ass->GetScriptInfo("YCbCr Matrix"));
|
||||
|
||||
if (c->videoController->IsLoaded()) {
|
||||
settings.dest_x = video_w = c->videoController->GetWidth();
|
||||
settings.dest_y = video_h = c->videoController->GetHeight();
|
||||
settings.dest_matrix = video_mat = MatrixFromString(c->videoController->GetProvider()->GetRealColorSpace());
|
||||
if (auto provider = c->project->VideoProvider()) {
|
||||
settings.dest_x = video_w = provider->GetWidth();
|
||||
settings.dest_y = video_h = provider->GetHeight();
|
||||
settings.dest_matrix = video_mat = MatrixFromString(provider->GetRealColorSpace());
|
||||
}
|
||||
else {
|
||||
settings.dest_x = script_w;
|
||||
|
@ -186,7 +186,7 @@ void DialogResample::SetSourceFromScript(wxCommandEvent&) {
|
|||
}
|
||||
|
||||
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));
|
||||
from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h);
|
||||
|
||||
|
|
|
@ -29,10 +29,10 @@
|
|||
#include "help_button.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_controller.h"
|
||||
#include "timeedit_ctrl.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/io.h>
|
||||
|
@ -100,7 +100,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
|
|||
, context(context)
|
||||
, history_filename(config::path->Decode("?user/shift_history.json"))
|
||||
, 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))
|
||||
{
|
||||
SetIcon(GETICON(shift_times_toolbutton_16));
|
||||
|
@ -138,7 +138,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
|
|||
clear_button->Bind(wxEVT_BUTTON, &DialogShiftTimes::OnClear, this);
|
||||
|
||||
// Set initial control states
|
||||
OnTimecodesLoaded(context->videoController->FPS());
|
||||
OnTimecodesLoaded(context->project->Timecodes());
|
||||
OnSelectedSetChanged();
|
||||
LoadHistory();
|
||||
|
||||
|
|
|
@ -33,8 +33,9 @@
|
|||
#include "help_button.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "persist_location.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -111,11 +112,11 @@ DialogStyling::DialogStyling(agi::Context *context)
|
|||
actions_box->AddStretchSpacer(1);
|
||||
|
||||
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);
|
||||
|
||||
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->AddStretchSpacer(1);
|
||||
|
@ -180,8 +181,8 @@ void DialogStyling::Commit(bool next) {
|
|||
void DialogStyling::OnActivate(wxActivateEvent &) {
|
||||
if (!IsActive()) return;
|
||||
|
||||
play_video->Enable(c->videoController->IsLoaded());
|
||||
play_audio->Enable(c->audioController->IsAudioOpen());
|
||||
play_video->Enable(!!c->project->VideoProvider());
|
||||
play_audio->Enable(!!c->project->AudioProvider());
|
||||
|
||||
style_list->Set(to_wx(c->ass->GetStyles()));
|
||||
|
||||
|
|
|
@ -37,14 +37,15 @@
|
|||
#include "ass_dialogue.h"
|
||||
#include "ass_file.h"
|
||||
#include "ass_time.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "compat.h"
|
||||
#include "help_button.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.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();
|
||||
|
||||
// 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->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"));
|
||||
|
||||
wxButton *none = new wxButton(this,-1,_("&None"));
|
||||
auto none = new wxButton(this,-1,_("&None"));
|
||||
none->SetToolTip(_("Deselect all styles"));
|
||||
|
||||
// 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->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool());
|
||||
optionsSizer->Add(onlySelection,1,wxALL,0);
|
||||
|
||||
// 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:"),
|
||||
"Tool/Timing Post Processor/Enable/Lead/IN",
|
||||
|
@ -150,12 +151,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
|||
LeadSizer->AddStretchSpacer(1);
|
||||
|
||||
// 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"),
|
||||
"Tool/Timing Post Processor/Enable/Adjacent",
|
||||
_("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,
|
||||
_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds"));
|
||||
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->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(adjacentBias, wxSizerFlags(1).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(adjSliderSizer, wxSizerFlags().Expand().Border(wxTOP));
|
||||
AdjacentSizer->Add(adjRightSizer);
|
||||
|
||||
// Keyframes sizer
|
||||
wxStaticBoxSizer *KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
|
||||
wxSizer *KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
|
||||
auto KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
|
||||
auto KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
|
||||
|
||||
keysEnable = new wxCheckBox(this, -1, _("E&nable"));
|
||||
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);
|
||||
|
||||
// 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) {
|
||||
keysEnable->SetValue(false);
|
||||
keysEnable->Enable(false);
|
||||
|
@ -208,12 +209,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
|||
KeyframesSizer->AddStretchSpacer(1);
|
||||
|
||||
// Button sizer
|
||||
wxStdDialogButtonSizer *ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
|
||||
auto ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
|
||||
ApplyButton = ButtonSizer->GetAffirmativeButton();
|
||||
ButtonSizer->GetHelpButton()->Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Timing Processor"));
|
||||
|
||||
// Right Sizer
|
||||
wxSizer *RightSizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto RightSizer = new wxBoxSizer(wxVERTICAL);
|
||||
RightSizer->Add(optionsSizer,0,wxBOTTOM|wxEXPAND,5);
|
||||
RightSizer->Add(LeadSizer,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);
|
||||
|
||||
// Style buttons sizer
|
||||
wxSizer *StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
StyleButtonsSizer->Add(all,1,0,0);
|
||||
StyleButtonsSizer->Add(none,1,0,0);
|
||||
|
||||
|
@ -231,12 +232,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
|
|||
LeftSizer->Add(StyleButtonsSizer, wxSizerFlags().Expand());
|
||||
|
||||
// Top Sizer
|
||||
wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto TopSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
TopSizer->Add(LeftSizer,0,wxRIGHT|wxEXPAND,5);
|
||||
TopSizer->Add(RightSizer,1,wxALL|wxEXPAND,0);
|
||||
|
||||
// Main Sizer
|
||||
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto MainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5);
|
||||
SetSizerAndFit(MainSizer);
|
||||
CenterOnParent();
|
||||
|
@ -289,10 +290,10 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) {
|
|||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
@ -382,28 +383,27 @@ void DialogTimingProcessor::Process() {
|
|||
|
||||
// Keyframe snapping
|
||||
if (keysEnable->IsChecked()) {
|
||||
std::vector<int> kf = c->videoController->GetKeyFrames();
|
||||
if (c->videoController->IsLoaded())
|
||||
kf.push_back(c->videoController->GetLength() - 1);
|
||||
std::vector<int> kf = c->project->Keyframes();
|
||||
auto fps = c->project->Timecodes();
|
||||
if (auto provider = c->project->VideoProvider())
|
||||
kf.push_back(provider->GetFrameCount() - 1);
|
||||
|
||||
for (AssDialogue *cur : sorted) {
|
||||
// Get start/end frames
|
||||
int startF = c->videoController->FrameAtTime(cur->Start, agi::vfr::START);
|
||||
int endF = c->videoController->FrameAtTime(cur->End, agi::vfr::END);
|
||||
int startF = fps.FrameAtTime(cur->Start, agi::vfr::START);
|
||||
int endF = fps.FrameAtTime(cur->End, agi::vfr::END);
|
||||
|
||||
// Get closest for start
|
||||
int closest = get_closest_kf(kf, startF);
|
||||
int time = c->videoController->TimeAtFrame(closest, agi::vfr::START);
|
||||
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart)) {
|
||||
int time = fps.TimeAtFrame(closest, agi::vfr::START);
|
||||
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart))
|
||||
cur->Start = time;
|
||||
}
|
||||
|
||||
// Get closest for end
|
||||
closest = get_closest_kf(kf, endF) - 1;
|
||||
time = c->videoController->TimeAtFrame(closest, agi::vfr::END);
|
||||
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd)) {
|
||||
time = fps.TimeAtFrame(closest, agi::vfr::END);
|
||||
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd))
|
||||
cur->End = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,11 @@
|
|||
#include "help_button.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "persist_location.h"
|
||||
#include "project.h"
|
||||
#include "subs_edit_ctrl.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -127,12 +128,12 @@ DialogTranslation::DialogTranslation(agi::Context *c)
|
|||
wxStaticBoxSizer *actions_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Actions"));
|
||||
|
||||
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);
|
||||
actions_box->Add(play_audio, 0, wxALL, 5);
|
||||
|
||||
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);
|
||||
actions_box->Add(play_video, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||
|
||||
|
|
|
@ -35,13 +35,12 @@
|
|||
#include "dialog_video_details.h"
|
||||
|
||||
#include "ass_time.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "video_context.h"
|
||||
#include "project.h"
|
||||
|
||||
#include <boost/rational.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
@ -49,10 +48,11 @@
|
|||
DialogVideoDetails::DialogVideoDetails(agi::Context *c)
|
||||
: wxDialog(c->parent , -1, _("Video Details"))
|
||||
{
|
||||
auto width = c->videoController->GetWidth();
|
||||
auto height = c->videoController->GetHeight();
|
||||
auto framecount = c->videoController->GetLength();
|
||||
auto fps = c->videoController->FPS();
|
||||
auto provider = c->project->VideoProvider();
|
||||
auto width = provider->GetWidth();
|
||||
auto height = provider->GetHeight();
|
||||
auto framecount = provider->GetFrameCount();
|
||||
auto fps = provider->GetFPS();
|
||||
boost::rational<int> ar(width, height);
|
||||
|
||||
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 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(_("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(_("Decoder:"), to_wx(c->videoController->GetProvider()->GetDecoderName()));
|
||||
make_field(_("Decoder:"), to_wx(provider->GetDecoderName()));
|
||||
|
||||
wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Video"));
|
||||
video_sizer->Add(fg);
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
#include "dialog_video_properties.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "options.h"
|
||||
#include "resolution_resampler.h"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/radiobox.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
|
@ -79,9 +80,7 @@ public:
|
|||
Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { EndModal(0); }, wxID_CANCEL);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent) {
|
||||
bool update_video_properties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent) {
|
||||
bool commit_subs = false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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/
|
||||
|
||||
class AssFile;
|
||||
class VideoProvider;
|
||||
class AsyncVideoProvider;
|
||||
class wxWindow;
|
||||
|
||||
/// 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?
|
||||
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent);
|
||||
void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent);
|
|
@ -27,24 +27,19 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file export_framerate.cpp
|
||||
/// @brief Transform Framerate export filter
|
||||
/// @ingroup export
|
||||
///
|
||||
|
||||
#include "export_framerate.h"
|
||||
|
||||
#include "ass_dialogue.h"
|
||||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "project.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <libaegisub/of_type_adaptor.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/panel.h>
|
||||
|
@ -65,18 +60,18 @@ void AssTransformFramerateFilter::ProcessSubs(AssFile *subs, wxWindow *) {
|
|||
}
|
||||
|
||||
wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
|
||||
wxWindow *base = new wxPanel(parent, -1);
|
||||
|
||||
LoadSettings(true, c);
|
||||
|
||||
wxWindow *base = new wxPanel(parent, -1);
|
||||
|
||||
// Input sizer
|
||||
wxSizer *InputSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto InputSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxString initialInput;
|
||||
wxButton *FromVideo = new wxButton(base,-1,_("From &video"));
|
||||
if (Input->IsLoaded()) {
|
||||
initialInput = wxString::Format("%2.3f",Input->FPS());
|
||||
auto FromVideo = new wxButton(base,-1,_("From &video"));
|
||||
if (Input.IsLoaded()) {
|
||||
initialInput = wxString::Format("%2.3f", Input.FPS());
|
||||
FromVideo->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) {
|
||||
InputFramerate->SetValue(wxString::Format("%g", c->videoController->FPS().FPS()));
|
||||
InputFramerate->SetValue(wxString::Format("%g", c->project->Timecodes().FPS()));
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
@ -89,9 +84,9 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
|||
InputSizer->AddStretchSpacer(1);
|
||||
|
||||
// Output sizers
|
||||
wxSizer *OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxSizer *OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxSizer *OutputSizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto OutputSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Output top line
|
||||
RadioOutputVFR = new wxRadioButton(base,-1,_("V&ariable"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
|
||||
|
@ -100,7 +95,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
|||
// Output bottom line
|
||||
RadioOutputCFR = new wxRadioButton(base,-1,_("&Constant: "));
|
||||
wxString initialOutput = initialInput;
|
||||
if (!Output->IsVFR()) {
|
||||
if (!Output.IsVFR()) {
|
||||
RadioOutputVFR->Enable(false);
|
||||
RadioOutputCFR->SetValue(true);
|
||||
}
|
||||
|
@ -117,7 +112,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
|
|||
OutputSizer->Add(OutputSizerBottom,0,wxLEFT,5);
|
||||
|
||||
// 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(InputSizer,0,wxEXPAND,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;
|
||||
|
||||
if (is_default) {
|
||||
Input = &c->videoController->VideoFPS();
|
||||
Output = &c->videoController->FPS();
|
||||
auto provider = c->project->VideoProvider();
|
||||
Output = c->project->Timecodes();
|
||||
Input = provider ? provider->GetFPS() : Output;
|
||||
}
|
||||
else {
|
||||
double temp;
|
||||
InputFramerate->GetValue().ToDouble(&temp);
|
||||
t1 = temp;
|
||||
Input = &t1;
|
||||
Input = temp;
|
||||
if (RadioOutputCFR->GetValue()) {
|
||||
OutputFramerate->GetValue().ToDouble(&temp);
|
||||
t2 = temp;
|
||||
Output = &t2;
|
||||
Output = temp;
|
||||
}
|
||||
else Output = &c->videoController->FPS();
|
||||
else Output = c->project->Timecodes();
|
||||
|
||||
if (Reverse->IsChecked()) {
|
||||
if (Reverse->IsChecked())
|
||||
std::swap(Input, Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate a time to centisecond precision
|
||||
int FORCEINLINE trunc_cs(int time) {
|
||||
static int trunc_cs(int time) {
|
||||
return (time / 10) * 10;
|
||||
}
|
||||
|
||||
|
@ -198,7 +191,7 @@ void AssTransformFramerateFilter::TransformTimeTags(std::string const& name, Ass
|
|||
}
|
||||
|
||||
void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
|
||||
if (!Input->IsLoaded() || !Output->IsLoaded()) return;
|
||||
if (!Input.IsLoaded() || !Output.IsLoaded()) return;
|
||||
for (auto& curDialogue : subs->Events) {
|
||||
line = &curDialogue;
|
||||
newK = 0;
|
||||
|
@ -217,14 +210,14 @@ void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
|
|||
}
|
||||
|
||||
int AssTransformFramerateFilter::ConvertTime(int time) {
|
||||
int frame = Output->FrameAtTime(time);
|
||||
int frameStart = Output->TimeAtFrame(frame);
|
||||
int frameEnd = Output->TimeAtFrame(frame + 1);
|
||||
int frame = Output.FrameAtTime(time);
|
||||
int frameStart = Output.TimeAtFrame(frame);
|
||||
int frameEnd = Output.TimeAtFrame(frame + 1);
|
||||
int frameDur = frameEnd - frameStart;
|
||||
double dist = double(time - frameStart) / frameDur;
|
||||
|
||||
int newStart = Input->TimeAtFrame(frame);
|
||||
int newEnd = Input->TimeAtFrame(frame + 1);
|
||||
int newStart = Input.TimeAtFrame(frame);
|
||||
int newEnd = Input.TimeAtFrame(frame + 1);
|
||||
int newDur = newEnd - newStart;
|
||||
|
||||
return newStart + newDur * dist;
|
||||
|
|
|
@ -27,11 +27,6 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file export_framerate.h
|
||||
/// @see export_framerate.cpp
|
||||
/// @ingroup export
|
||||
///
|
||||
|
||||
#include "ass_export_filter.h"
|
||||
|
||||
#include <libaegisub/vfr.h>
|
||||
|
@ -53,10 +48,8 @@ class AssTransformFramerateFilter final : public AssExportFilter {
|
|||
int oldK = 0;
|
||||
|
||||
// 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
|
||||
const agi::vfr::Framerate *Output = nullptr; ///< Source frame rate
|
||||
|
||||
agi::vfr::Framerate t1,t2;
|
||||
agi::vfr::Framerate Input; ///< Destination frame rate
|
||||
agi::vfr::Framerate Output; ///< Source frame rate
|
||||
|
||||
wxTextCtrl *InputFramerate; ///< Input 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
|
||||
int ConvertTime(int time);
|
||||
public:
|
||||
/// Constructor
|
||||
AssTransformFramerateFilter();
|
||||
void ProcessSubs(AssFile *subs, wxWindow *) override;
|
||||
wxWindow *GetConfigDialogWindow(wxWindow *parent, agi::Context *c) override;
|
||||
|
|
|
@ -37,9 +37,9 @@
|
|||
#include "include/aegisub/menu.h"
|
||||
#include "include/aegisub/toolbar.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "audio_controller.h"
|
||||
#include "audio_box.h"
|
||||
#include "base_grid.h"
|
||||
|
@ -51,13 +51,14 @@
|
|||
#include "libresrc/libresrc.h"
|
||||
#include "main.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "subs_controller.h"
|
||||
#include "subs_edit_box.h"
|
||||
#include "subs_edit_ctrl.h"
|
||||
#include "utils.h"
|
||||
#include "version.h"
|
||||
#include "video_box.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
#include "video_slider.h"
|
||||
|
||||
|
@ -86,91 +87,16 @@ enum {
|
|||
#define StartupLog(a) LOG_I("frame_main/init") << a
|
||||
#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
|
||||
class AegisubFileDropTarget final : public wxFileDropTarget {
|
||||
FrameMain *parent;
|
||||
agi::Context *context;
|
||||
public:
|
||||
AegisubFileDropTarget(FrameMain *parent) : parent(parent) { }
|
||||
bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) override {
|
||||
std::string subs, audio, video;
|
||||
get_files_to_load(filenames, subs, audio, video);
|
||||
|
||||
if (subs.empty() && audio.empty() && video.empty())
|
||||
return false;
|
||||
|
||||
auto evt = new wxThreadEvent(FILE_LIST_DROPPED);
|
||||
evt->SetPayload(filenames);
|
||||
parent->QueueEvent(evt);
|
||||
AegisubFileDropTarget(agi::Context *context) : context(context) { }
|
||||
bool OnDropFiles(wxCoord, wxCoord, wxArrayString const& filenames) override {
|
||||
std::vector<agi::fs::path> files;
|
||||
for (wxString const& fn : filenames)
|
||||
files.push_back(from_wx(fn));
|
||||
context->project->LoadList(files);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
@ -194,9 +120,8 @@ FrameMain::FrameMain()
|
|||
context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
|
||||
context->subsController->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
|
||||
context->subsController->AddFileSaveListener(&FrameMain::UpdateTitle, this);
|
||||
context->audioController->AddAudioOpenListener(&FrameMain::OnAudioOpen, this);
|
||||
context->audioController->AddAudioCloseListener(&FrameMain::OnAudioClose, this);
|
||||
context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);
|
||||
context->project->AddAudioProviderListener(&FrameMain::OnAudioOpen, this);
|
||||
context->project->AddVideoProviderListener(&FrameMain::OnVideoOpen, this);
|
||||
|
||||
StartupLog("Initializing context frames");
|
||||
context->parent = this;
|
||||
|
@ -206,7 +131,9 @@ FrameMain::FrameMain()
|
|||
if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
|
||||
|
||||
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");
|
||||
menu::GetMenuBar("main", this, context.get());
|
||||
|
@ -228,10 +155,7 @@ FrameMain::FrameMain()
|
|||
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this);
|
||||
|
||||
StartupLog("Set up drag/drop target");
|
||||
SetDropTarget(new AegisubFileDropTarget(this));
|
||||
Bind(FILE_LIST_DROPPED, [=](wxThreadEvent &evt) {
|
||||
LoadList(evt.GetPayload<wxArrayString>());
|
||||
});
|
||||
SetDropTarget(new AegisubFileDropTarget(context.get()));
|
||||
|
||||
StartupLog("Load default file");
|
||||
context->subsController->Close();
|
||||
|
@ -247,18 +171,12 @@ FrameMain::FrameMain()
|
|||
FrameMain::~FrameMain () {
|
||||
wxGetApp().frame = nullptr;
|
||||
|
||||
context->videoController->SetVideo("");
|
||||
context->audioController->CloseAudio();
|
||||
context->project->CloseAudio();
|
||||
context->project->CloseVideo();
|
||||
|
||||
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) {
|
||||
if (opt.GetBool()) {
|
||||
if (!GetToolBar()) {
|
||||
|
@ -275,7 +193,7 @@ void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
|
|||
|
||||
void FrameMain::InitContents() {
|
||||
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");
|
||||
context->subsGrid = new BaseGrid(Panel, context.get());
|
||||
|
@ -313,10 +231,10 @@ void FrameMain::SetDisplayMode(int video, int audio) {
|
|||
bool sv = false, sa = false;
|
||||
|
||||
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;
|
||||
else if (audio) sa = context->audioController->IsAudioOpen();
|
||||
else if (audio) sa = !!context->project->AudioProvider();
|
||||
|
||||
// See if anything changed
|
||||
if (sv == showVideo && sa == showAudio) return;
|
||||
|
@ -357,15 +275,14 @@ void FrameMain::UpdateTitle() {
|
|||
if (GetTitle() != newTitle) SetTitle(newTitle);
|
||||
}
|
||||
|
||||
void FrameMain::OnVideoOpen() {
|
||||
if (!context->videoController->IsLoaded()) {
|
||||
void FrameMain::OnVideoOpen(AsyncVideoProvider *provider) {
|
||||
if (!provider) {
|
||||
SetDisplayMode(0, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
Freeze();
|
||||
int vidx = context->videoController->GetWidth(),
|
||||
vidy = context->videoController->GetHeight();
|
||||
int vidx = provider->GetWidth(), vidy = provider->GetHeight();
|
||||
|
||||
// Set zoom level based on video resolution and window size
|
||||
double zoom = context->videoDisplay->GetZoom();
|
||||
|
@ -380,30 +297,12 @@ void FrameMain::OnVideoOpen() {
|
|||
if (OPT_GET("Video/Detached/Enabled")->GetBool() && !context->dialog->Get<DialogDetachedVideo>())
|
||||
cmd::call("video/detach", context.get());
|
||||
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) {
|
||||
if (opt.GetBool())
|
||||
SetDisplayMode(0, -1);
|
||||
else if (context->videoController->IsLoaded())
|
||||
else if (context->project->VideoProvider())
|
||||
SetDisplayMode(1, -1);
|
||||
}
|
||||
|
||||
|
@ -413,41 +312,9 @@ void FrameMain::StatusTimeout(wxString text,int ms) {
|
|||
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)
|
||||
EVT_TIMER(ID_APP_TIMER_STATUSCLEAR, FrameMain::OnStatusClear)
|
||||
|
||||
EVT_CLOSE(FrameMain::OnCloseWindow)
|
||||
|
||||
EVT_CHAR_HOOK(FrameMain::OnKeyDown)
|
||||
EVT_MOUSEWHEEL(FrameMain::OnMouseWheel)
|
||||
END_EVENT_TABLE()
|
||||
|
@ -476,95 +343,15 @@ void FrameMain::OnStatusClear(wxTimerEvent &) {
|
|||
SetStatusText("",1);
|
||||
}
|
||||
|
||||
void FrameMain::OnAudioOpen(AudioProvider *) {
|
||||
SetDisplayMode(-1, 1);
|
||||
}
|
||||
|
||||
void FrameMain::OnAudioClose() {
|
||||
SetDisplayMode(-1, 0);
|
||||
void FrameMain::OnAudioOpen(AudioProvider *provider) {
|
||||
if (provider)
|
||||
SetDisplayMode(-1, 1);
|
||||
else
|
||||
SetDisplayMode(-1, 0);
|
||||
}
|
||||
|
||||
void FrameMain::OnSubtitlesOpen() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,31 +27,23 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file frame_main.h
|
||||
/// @see frame_main.cpp
|
||||
/// @ingroup main_ui
|
||||
///
|
||||
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
class AegisubApp;
|
||||
class AegisubFileDropTarget;
|
||||
class AsyncVideoProvider;
|
||||
class AudioBox;
|
||||
class AudioProvider;
|
||||
class VideoBox;
|
||||
|
||||
namespace agi { struct Context; class OptionValue; }
|
||||
|
||||
class FrameMain : public wxFrame {
|
||||
friend class AegisubApp;
|
||||
friend class AegisubFileDropTarget;
|
||||
|
||||
std::unique_ptr<agi::Context> context;
|
||||
|
||||
|
@ -64,13 +56,7 @@ class FrameMain : public wxFrame {
|
|||
bool showVideo = true; ///< Is the video display shown?
|
||||
bool showAudio = true; ///< Is the audio display shown?
|
||||
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 UpdateTitle();
|
||||
|
@ -81,13 +67,9 @@ class FrameMain : public wxFrame {
|
|||
void OnStatusClear(wxTimerEvent &event);
|
||||
void OnCloseWindow (wxCloseEvent &event);
|
||||
|
||||
// AudioControllerAudioEventListener implementation
|
||||
void OnAudioOpen(AudioProvider *provider);
|
||||
void OnAudioClose();
|
||||
|
||||
void OnVideoOpen();
|
||||
void OnVideoOpen(AsyncVideoProvider *provider);
|
||||
void OnVideoDetach(agi::OptionValue const& opt);
|
||||
|
||||
void OnSubtitlesOpen();
|
||||
|
||||
void EnableToolBar(agi::OptionValue const& opt);
|
||||
|
@ -116,7 +98,5 @@ public:
|
|||
bool IsVideoShown() const { return showVideo; }
|
||||
bool IsAudioShown() const { return showAudio; }
|
||||
|
||||
bool LoadList(wxArrayString list);
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/character_count.h>
|
||||
|
||||
|
|
|
@ -50,7 +50,6 @@ protected:
|
|||
int sample_rate;
|
||||
int bytes_per_sample;
|
||||
bool float_samples;
|
||||
agi::fs::path filename;
|
||||
|
||||
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 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 GetDecodedSamples() const { return decoded_samples; }
|
||||
int GetSampleRate() const { return sample_rate; }
|
||||
|
@ -88,7 +86,6 @@ public:
|
|||
sample_rate = source->GetSampleRate();
|
||||
bytes_per_sample = source->GetBytesPerSample();
|
||||
float_samples = source->AreSamplesFloat();
|
||||
filename = source->GetFilename();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -23,13 +23,14 @@ class AssDialogue;
|
|||
class AudioKaraoke;
|
||||
class DialogManager;
|
||||
class FrameMain;
|
||||
class Project;
|
||||
class SearchReplaceEngine;
|
||||
class InitialLineState;
|
||||
class SelectionController;
|
||||
class SubsController;
|
||||
class BaseGrid;
|
||||
class TextSelectionController;
|
||||
class VideoContext;
|
||||
class VideoController;
|
||||
class VideoDisplay;
|
||||
class wxWindow;
|
||||
namespace Automation4 { class ScriptManager; }
|
||||
|
@ -42,10 +43,11 @@ struct Context {
|
|||
std::unique_ptr<AssFile> ass;
|
||||
std::unique_ptr<TextSelectionController> textSelectionController;
|
||||
std::unique_ptr<SubsController> subsController;
|
||||
std::unique_ptr<Project> project;
|
||||
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<VideoController> videoController;
|
||||
std::unique_ptr<AudioController> audioController;
|
||||
std::unique_ptr<InitialLineState> initialLineState;
|
||||
std::unique_ptr<SearchReplaceEngine> search;
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ struct VideoFrame;
|
|||
|
||||
class VideoProvider {
|
||||
public:
|
||||
virtual ~VideoProvider() {}
|
||||
virtual ~VideoProvider() = default;
|
||||
|
||||
/// Override this method to actually get frames
|
||||
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 "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "subs_controller.h"
|
||||
#include "project.h"
|
||||
#include "subtitle_format.h"
|
||||
#include "subtitles_provider_libass.h"
|
||||
#include "video_context.h"
|
||||
#include "version.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
@ -317,15 +316,14 @@ bool AegisubApp::OnInit() {
|
|||
|
||||
// Get parameter subs
|
||||
StartupLog("Parse command line");
|
||||
wxArrayString subs;
|
||||
std::vector<agi::fs::path> files;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
subs.push_back(argv[i]);
|
||||
if (!subs.empty())
|
||||
frame->LoadList(subs);
|
||||
files.push_back(from_wx(argv[i]));
|
||||
if (!files.empty())
|
||||
frame->context->project->LoadList(files);
|
||||
}
|
||||
|
||||
catch (const char *err) {
|
||||
wxMessageBox(err,"Fatal error while initializing");
|
||||
wxMessageBox(err, "Fatal error while initializing");
|
||||
return false;
|
||||
}
|
||||
catch (wxString const& err) {
|
||||
|
@ -333,10 +331,9 @@ bool AegisubApp::OnInit() {
|
|||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
#ifndef _DEBUG
|
||||
catch (...) {
|
||||
wxMessageBox("Unhandled exception","Fatal error while initializing");
|
||||
|
@ -470,5 +467,5 @@ int AegisubApp::OnRun() {
|
|||
|
||||
void AegisubApp::MacOpenFile(const wxString &filename) {
|
||||
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_style.h"
|
||||
#include "ass_style_storage.h"
|
||||
#include "charset_detect.h"
|
||||
#include "compat.h"
|
||||
#include "command/command.h"
|
||||
#include "frame_main.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subtitle_format.h"
|
||||
#include "text_file_reader.h"
|
||||
#include "text_selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <libaegisub/fs.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) {
|
||||
AssFile temp;
|
||||
|
||||
try {
|
||||
try {
|
||||
if (charset.empty())
|
||||
charset = CharSetDetect::GetEncoding(filename);
|
||||
}
|
||||
catch (agi::UserCancelException const&) {
|
||||
return;
|
||||
}
|
||||
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->project->Timecodes(), charset);
|
||||
|
||||
// Make sure that file isn't actually a timecode file
|
||||
if (charset != "binary") {
|
||||
try {
|
||||
TextFileReader testSubs(filename, charset);
|
||||
std::string cur = testSubs.ReadLineFromFile();
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
|
||||
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->videoController->FPS(), charset);
|
||||
|
||||
// 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;
|
||||
}
|
||||
context->ass->swap(temp);
|
||||
|
||||
SetFileName(filename);
|
||||
|
||||
|
|
|
@ -87,8 +87,8 @@ public:
|
|||
|
||||
/// @brief Load from a file
|
||||
/// @param file File name
|
||||
/// @param charset Character set of file or empty to autodetect
|
||||
void Load(agi::fs::path const& file, std::string charset="");
|
||||
/// @param charset Character set of file
|
||||
void Load(agi::fs::path const& file, std::string charset);
|
||||
|
||||
/// @brief Save to a file
|
||||
/// @param file Path to save to
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "initial_line_state.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "placeholder_ctrl.h"
|
||||
#include "selection_controller.h"
|
||||
#include "subs_edit_ctrl.h"
|
||||
|
@ -52,7 +53,6 @@
|
|||
#include "timeedit_ctrl.h"
|
||||
#include "tooltip_manager.h"
|
||||
#include "validators.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <libaegisub/character_count.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
@ -228,10 +228,12 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
|
|||
OnSize(evt);
|
||||
|
||||
file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this);
|
||||
connections.push_back(context->videoController->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this));
|
||||
connections.push_back(context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this));
|
||||
connections.push_back(context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this));
|
||||
connections.push_back(context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this));
|
||||
connections = agi::signal::make_vector({
|
||||
context->project->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this),
|
||||
context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this),
|
||||
context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this),
|
||||
context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this),
|
||||
});
|
||||
|
||||
context->textSelectionController->SetControl(edit_ctrl);
|
||||
edit_ctrl->SetFocus();
|
||||
|
@ -394,14 +396,6 @@ void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) {
|
|||
commit_id = -1;
|
||||
|
||||
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() {
|
||||
|
@ -488,8 +482,10 @@ void SubsEditBox::CommitTimes(TimeField field) {
|
|||
break;
|
||||
|
||||
case TIME_DURATION:
|
||||
if (by_frame->GetValue())
|
||||
d->End = c->videoController->TimeAtFrame(c->videoController->FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
|
||||
if (by_frame->GetValue()) {
|
||||
auto const& fps = c->project->Timecodes();
|
||||
d->End = fps.TimeAtFrame(fps.FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
|
||||
}
|
||||
else
|
||||
d->End = d->Start + duration->GetTime();
|
||||
initial_times[d].second = d->End;
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
///
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/container/map.hpp>
|
||||
#include <boost/flyweight/flyweight_fwd.hpp>
|
||||
#include <memory>
|
||||
|
@ -74,7 +73,7 @@ class SubsEditBox final : public wxPanel {
|
|||
TIME_DURATION
|
||||
};
|
||||
|
||||
std::deque<agi::signal::Connection> connections;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
/// Currently active dialogue line
|
||||
AssDialogue *line = nullptr;
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
#include "subtitle_format_transtation.h"
|
||||
#include "subtitle_format_ttxt.h"
|
||||
#include "subtitle_format_txt.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
#include "options.h"
|
||||
#include "text_file_reader.h"
|
||||
#include "text_file_writer.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
|
|
@ -40,8 +40,8 @@
|
|||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/dataobj.h>
|
||||
|
@ -88,17 +88,17 @@ void TimeEdit::SetTime(AssTime new_time) {
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (enableByFrame == byFrame) return;
|
||||
|
||||
byFrame = enableByFrame && c->videoController->TimecodesLoaded();
|
||||
byFrame = enableByFrame && c->project->Timecodes().IsLoaded();
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
|
|||
if (byFrame) {
|
||||
long temp = 0;
|
||||
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)
|
||||
time = from_wx(GetValue());
|
||||
|
@ -115,7 +115,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
|
|||
|
||||
void TimeEdit::UpdateText() {
|
||||
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
|
||||
ChangeValue(to_wx(time.GetAssFormated()));
|
||||
}
|
||||
|
|
|
@ -97,15 +97,6 @@ std::string GetClipboard();
|
|||
void SetClipboard(std::string 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]))
|
||||
|
||||
wxString FontFace(std::string opt_prefix);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <libaegisub/exception.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/radiobox.h>
|
||||
#include <wx/validate.h>
|
||||
|
||||
|
|
|
@ -37,8 +37,9 @@
|
|||
#include "include/aegisub/toolbar.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
#include "video_slider.h"
|
||||
|
||||
|
@ -56,7 +57,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
|||
auto videoSlider = new VideoSlider(this, context);
|
||||
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->SetToolTip(_("Current frame time and number"));
|
||||
|
@ -67,29 +68,29 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
|||
wxArrayString choices;
|
||||
for (int i = 1; i <= 24; ++i)
|
||||
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);
|
||||
wxToolBar *visualSubToolBar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_VERTICAL | wxTB_BOTTOM | wxTB_FLAT);
|
||||
auto visualToolBar = toolbar::GetToolbar(this, "visual_tools", context, "Video", true);
|
||||
auto visualSubToolBar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_VERTICAL | wxTB_BOTTOM | wxTB_FLAT);
|
||||
|
||||
auto videoDisplay = new VideoDisplay(visualSubToolBar, isDetached, zoomBox, this, context);
|
||||
videoDisplay->MoveBeforeInTabOrder(videoSlider);
|
||||
|
||||
wxSizer *toolbarSizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto toolbarSizer = new wxBoxSizer(wxVERTICAL);
|
||||
toolbarSizer->Add(visualToolBar, wxSizerFlags(1));
|
||||
toolbarSizer->Add(visualSubToolBar, wxSizerFlags());
|
||||
|
||||
wxSizer *topSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto topSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
topSizer->Add(toolbarSizer, 0, wxEXPAND);
|
||||
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(VideoPosition, wxSizerFlags(1).Center().Border(wxLEFT));
|
||||
videoBottomSizer->Add(VideoSubsPos, wxSizerFlags(1).Center().Border(wxLEFT));
|
||||
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(new wxStaticLine(this), 0, wxEXPAND, 0);
|
||||
VideoSizer->Add(videoSlider, 0, wxEXPAND, 0);
|
||||
|
@ -98,23 +99,25 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context)
|
|||
|
||||
UpdateTimeBoxes();
|
||||
|
||||
slots.push_back(context->videoController->AddSeekListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
slots.push_back(context->videoController->AddKeyframesListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
slots.push_back(context->videoController->AddTimecodesListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
slots.push_back(context->videoController->AddVideoOpenListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
slots.push_back(context->ass->AddCommitListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
slots.push_back(context->selectionController->AddSelectionListener(&VideoBox::UpdateTimeBoxes, this));
|
||||
connections = agi::signal::make_vector({
|
||||
context->ass->AddCommitListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
context->project->AddKeyframesListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
context->project->AddTimecodesListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
context->project->AddVideoProviderListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
context->selectionController->AddSelectionListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
context->videoController->AddSeekListener(&VideoBox::UpdateTimeBoxes, this),
|
||||
});
|
||||
}
|
||||
|
||||
void VideoBox::UpdateTimeBoxes() {
|
||||
if (!context->videoController->IsLoaded()) return;
|
||||
if (!context->project->VideoProvider()) return;
|
||||
|
||||
int frame = context->videoController->GetFrameN();
|
||||
int time = context->videoController->TimeAtFrame(frame, agi::vfr::EXACT);
|
||||
|
||||
// Set the text box for frame number and time
|
||||
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
|
||||
VideoPosition->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColor()));
|
||||
VideoPosition->SetForegroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Selection")->GetColor()));
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <wx/panel.h>
|
||||
|
||||
namespace agi { struct Context; }
|
||||
|
@ -38,7 +38,7 @@ class wxTextCtrl;
|
|||
/// @class VideoBox
|
||||
/// @brief The box containing the video display and associated controls
|
||||
class VideoBox final : public wxPanel {
|
||||
std::deque<agi::signal::Connection> slots;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
agi::Context *context; ///< Project context
|
||||
wxTextCtrl *VideoPosition; ///< Current frame/time
|
||||
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/
|
||||
|
||||
/// @file video_context.h
|
||||
/// @see video_context.cpp
|
||||
/// @ingroup video
|
||||
///
|
||||
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
#include <libaegisub/signal.h>
|
||||
#include <libaegisub/vfr.h>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <wx/timer.h>
|
||||
|
||||
class AssDialogue;
|
||||
class DialogProgress;
|
||||
class ThreadedFrameSource;
|
||||
class VideoProvider;
|
||||
class AsyncVideoProvider;
|
||||
struct SubtitlesProviderErrorEvent;
|
||||
struct VideoFrame;
|
||||
struct VideoProviderErrorEvent;
|
||||
|
@ -66,47 +55,22 @@ enum class AspectRatio {
|
|||
Custom
|
||||
};
|
||||
|
||||
/// @class VideoContext
|
||||
/// @brief Manage a bunch of things vaguely related to video playback
|
||||
///
|
||||
/// 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 {
|
||||
/// Manage stuff related to video playback
|
||||
class VideoController final : public wxEvtHandler {
|
||||
/// Current frame number changed (new frame number)
|
||||
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)
|
||||
agi::signal::Signal<AspectRatio, double> ARChange;
|
||||
|
||||
agi::Context *context;
|
||||
|
||||
DialogProgress *progress = nullptr;
|
||||
|
||||
/// The video provider owned by the threaded frame source, or nullptr if no
|
||||
/// video is open
|
||||
VideoProvider *video_provider = nullptr;
|
||||
|
||||
/// Asynchronous provider of video frames
|
||||
std::unique_ptr<ThreadedFrameSource> provider;
|
||||
|
||||
/// Filename of currently open video
|
||||
agi::fs::path video_filename;
|
||||
AsyncVideoProvider *provider = nullptr;
|
||||
|
||||
/// Last seen script 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
|
||||
/// frame while playing video
|
||||
wxTimer playback;
|
||||
|
@ -132,16 +96,11 @@ class VideoContext final : public wxEvtHandler {
|
|||
/// The current AR type
|
||||
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
|
||||
const agi::OptionValue* playAudioOnStep;
|
||||
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
/// Amending the frame source's copy of the subtitle file requires that it
|
||||
/// be kept in perfect sync. Saving the file can add lines to the file
|
||||
/// without a commit, breaking this sync, so force a non-amend after each
|
||||
|
@ -150,58 +109,22 @@ class VideoContext final : public wxEvtHandler {
|
|||
|
||||
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 OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
|
||||
|
||||
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
|
||||
void OnSubtitlesSave();
|
||||
void OnNewVideoProvider(AsyncVideoProvider *provider);
|
||||
void OnActiveLineChanged(AssDialogue *line);
|
||||
|
||||
/// Close the video, keyframes and timecodes
|
||||
void Reset();
|
||||
void RequestFrame();
|
||||
|
||||
public:
|
||||
VideoContext(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; }
|
||||
VideoController(agi::Context *context);
|
||||
|
||||
/// Is the video currently playing?
|
||||
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
|
||||
int GetFrameN() const { return frame_n; }
|
||||
|
||||
|
@ -221,12 +144,6 @@ public:
|
|||
/// Get the current aspect ratio of the video
|
||||
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
|
||||
/// @param n Frame number to jump to
|
||||
void JumpToFrame(int n);
|
||||
|
@ -247,29 +164,8 @@ public:
|
|||
void Stop();
|
||||
|
||||
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)
|
||||
|
||||
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 FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
|
||||
};
|
|
@ -35,26 +35,26 @@
|
|||
#include "video_display.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "command/command.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "include/aegisub/menu.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "retina_helper.h"
|
||||
#include "spline_curve.h"
|
||||
#include "subs_controller.h"
|
||||
#include "threaded_frame_source.h"
|
||||
#include "utils.h"
|
||||
#include "video_out_gl.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_frame.h"
|
||||
#include "visual_tool.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/dataobj.h>
|
||||
#include <wx/dcclient.h>
|
||||
|
@ -85,17 +85,12 @@ public:
|
|||
|
||||
#define E(cmd) cmd; if (GLenum err = glGetError()) throw OpenGlException(#cmd, err)
|
||||
|
||||
VideoDisplay::VideoDisplay(
|
||||
wxToolBar *visualSubToolBar,
|
||||
bool freeSize,
|
||||
wxComboBox *zoomBox,
|
||||
wxWindow* parent,
|
||||
agi::Context *c)
|
||||
VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBox, wxWindow *parent, agi::Context *c)
|
||||
: wxGLCanvas(parent, -1, attribList)
|
||||
, autohideTools(OPT_GET("Tool/Visual/Autohide"))
|
||||
, con(c)
|
||||
, zoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125)
|
||||
, toolBar(visualSubToolBar)
|
||||
, toolBar(toolbar)
|
||||
, zoomBox(zoomBox)
|
||||
, freeSize(freeSize)
|
||||
, retina_helper(agi::make_unique<RetinaHelper>(this))
|
||||
|
@ -111,10 +106,11 @@ VideoDisplay::VideoDisplay(
|
|||
zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this);
|
||||
|
||||
con->videoController->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
|
||||
slots.push_back(con->videoController->AddVideoOpenListener(&VideoDisplay::UpdateSize, this));
|
||||
slots.push_back(con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this));
|
||||
|
||||
slots.push_back(con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this));
|
||||
connections = agi::signal::make_vector({
|
||||
con->project->AddVideoProviderListener(&VideoDisplay::UpdateSize, this),
|
||||
con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this),
|
||||
con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this),
|
||||
});
|
||||
|
||||
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
|
||||
Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);
|
||||
|
@ -132,8 +128,7 @@ VideoDisplay::VideoDisplay(
|
|||
|
||||
c->videoDisplay = this;
|
||||
|
||||
if (con->videoController->IsLoaded())
|
||||
con->videoController->JumpToFrame(con->videoController->GetFrameN());
|
||||
con->videoController->JumpToFrame(con->videoController->GetFrameN());
|
||||
|
||||
SetLayoutDirection(wxLayout_LeftToRight);
|
||||
}
|
||||
|
@ -164,7 +159,7 @@ void VideoDisplay::UploadFrameData(FrameReadyEvent &evt) {
|
|||
}
|
||||
|
||||
void VideoDisplay::Render() try {
|
||||
if (!con->videoController->IsLoaded() || !InitContext() || (!videoOut && !pending_frame))
|
||||
if (!con->project->VideoProvider() || !InitContext() || (!videoOut && !pending_frame))
|
||||
return;
|
||||
|
||||
if (!videoOut)
|
||||
|
@ -185,7 +180,7 @@ void VideoDisplay::Render() try {
|
|||
"programs and updating your video card drivers may fix this.\n"
|
||||
"Error message reported: %s",
|
||||
err.GetMessage());
|
||||
con->videoController->SetVideo("");
|
||||
con->project->CloseVideo();
|
||||
return;
|
||||
}
|
||||
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"
|
||||
"Error message reported: %s",
|
||||
err.GetChainedMessage());
|
||||
con->videoController->SetVideo("");
|
||||
con->project->CloseVideo();
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!con->videoController->IsLoaded() || !IsShownOnScreen()) return;
|
||||
auto provider = con->project->VideoProvider();
|
||||
if (!provider || !IsShownOnScreen()) return;
|
||||
|
||||
viewport_left = 0;
|
||||
viewport_bottom = GetClientSize().GetHeight() * scale_factor - videoSize.GetHeight();
|
||||
|
@ -286,8 +282,8 @@ void VideoDisplay::PositionVideo() {
|
|||
viewport_height = videoSize.GetHeight();
|
||||
|
||||
if (freeSize) {
|
||||
int vidW = con->videoController->GetWidth();
|
||||
int vidH = con->videoController->GetHeight();
|
||||
int vidW = provider->GetWidth();
|
||||
int vidH = provider->GetHeight();
|
||||
|
||||
AspectRatio arType = con->videoController->GetAspectRatioType();
|
||||
double displayAr = double(viewport_width) / viewport_height;
|
||||
|
@ -315,9 +311,10 @@ void VideoDisplay::PositionVideo() {
|
|||
}
|
||||
|
||||
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;
|
||||
if (con->videoController->GetAspectRatioType() != AspectRatio::Default)
|
||||
videoSize.SetWidth(videoSize.GetHeight() * con->videoController->GetAspectRatioValue());
|
||||
|
@ -346,7 +343,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
|
|||
if (freeSize) {
|
||||
videoSize = GetClientSize() * scale_factor;
|
||||
PositionVideo();
|
||||
zoomValue = double(viewport_height) / con->videoController->GetHeight();
|
||||
zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight();
|
||||
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -36,14 +36,14 @@
|
|||
|
||||
#include "vector2d.h"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
// Prototypes
|
||||
class RetinaHelper;
|
||||
class VideoContext;
|
||||
class VideoController;
|
||||
class VideoOutGL;
|
||||
class VisualToolBase;
|
||||
class wxComboBox;
|
||||
|
@ -59,7 +59,7 @@ namespace agi {
|
|||
|
||||
class VideoDisplay final : public wxGLCanvas {
|
||||
/// Signals the display is connected to
|
||||
std::deque<agi::signal::Connection> slots;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
const agi::OptionValue* autohideTools;
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
#include "compat.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_frame.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
|
|
|
@ -34,13 +34,15 @@
|
|||
|
||||
#include "video_slider.h"
|
||||
|
||||
#include "async_video_provider.h"
|
||||
#include "base_grid.h"
|
||||
#include "command/command.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/settings.h>
|
||||
|
@ -48,19 +50,19 @@
|
|||
VideoSlider::VideoSlider (wxWindow* parent, agi::Context *c)
|
||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE)
|
||||
, 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);
|
||||
SetMinSize(wxSize(20, 25));
|
||||
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;
|
||||
|
||||
VideoOpened();
|
||||
VideoOpened(c->project->VideoProvider());
|
||||
}
|
||||
|
||||
void VideoSlider::SetValue(int value) {
|
||||
|
@ -69,10 +71,9 @@ void VideoSlider::SetValue(int value) {
|
|||
Refresh(false);
|
||||
}
|
||||
|
||||
void VideoSlider::VideoOpened() {
|
||||
if (c->videoController->IsLoaded()) {
|
||||
max = c->videoController->GetLength() - 1;
|
||||
keyframes = c->videoController->GetKeyFrames();
|
||||
void VideoSlider::VideoOpened(AsyncVideoProvider *provider) {
|
||||
if (provider) {
|
||||
max = provider->GetFrameCount() - 1;
|
||||
Refresh(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,27 +27,22 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file video_slider.h
|
||||
/// @see video_slider.cpp
|
||||
/// @ingroup custom_control
|
||||
///
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
namespace agi { struct Context; }
|
||||
|
||||
class VideoContext;
|
||||
class VideoController;
|
||||
class AsyncVideoProvider;
|
||||
|
||||
/// @class VideoSlider
|
||||
/// @brief Slider for displaying and adjusting the video position
|
||||
class VideoSlider: public wxWindow {
|
||||
agi::Context *c; ///< Associated project context
|
||||
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 max = 1; ///< Last frame number
|
||||
|
@ -60,7 +55,7 @@ class VideoSlider: public wxWindow {
|
|||
void SetValue(int value);
|
||||
|
||||
/// Video open event handler
|
||||
void VideoOpened();
|
||||
void VideoOpened(AsyncVideoProvider *new_provider);
|
||||
/// Keyframe open even handler
|
||||
void KeyframesChanged(std::vector<int> const& newKeyframes);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include "options.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
#include "visual_feature.h"
|
||||
#include "visual_tool_clip.h"
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include <libaegisub/owning_intrusive_list.h>
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <wx/event.h>
|
||||
|
||||
|
@ -79,7 +78,7 @@ class VisualToolBase {
|
|||
virtual void DoRefresh() { }
|
||||
|
||||
protected:
|
||||
std::deque<agi::signal::Connection> connections;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
OpenGLWrapper gl;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include "options.h"
|
||||
#include "selection_controller.h"
|
||||
#include "utils.h"
|
||||
#include "video_context.h"
|
||||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
@ -79,7 +79,7 @@ void VisualToolDrag::UpdateToggleButtons() {
|
|||
|
||||
void VisualToolDrag::OnSubTool(wxCommandEvent &) {
|
||||
// Toggle \move <-> \pos
|
||||
VideoContext *vc = c->videoController.get();
|
||||
VideoController *vc = c->videoController.get();
|
||||
for (auto line : selection) {
|
||||
Vector2D p1, p2;
|
||||
int t1, t2;
|
||||
|
|
Loading…
Reference in New Issue