From 19e8f19e521234d0da6dd0906a1e07bd9726f747 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 21 May 2014 16:23:28 -0700 Subject: [PATCH] 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. --- build/Aegisub/Aegisub.vcxproj | 10 +- build/Aegisub/Aegisub.vcxproj.filters | 24 +- libaegisub/include/libaegisub/signal.h | 48 +- src/Makefile | 5 +- src/ass_exporter.cpp | 6 +- ...me_source.cpp => async_video_provider.cpp} | 32 +- ..._frame_source.h => async_video_provider.h} | 33 +- src/audio_box.cpp | 3 +- src/audio_controller.cpp | 200 +------- src/audio_controller.h | 86 +--- src/audio_display.cpp | 25 +- src/audio_display.h | 3 +- src/audio_karaoke.cpp | 16 +- src/audio_karaoke.h | 11 +- src/audio_marker.cpp | 13 +- src/audio_marker.h | 14 +- src/audio_provider_avs.cpp | 2 - src/audio_provider_pcm.cpp | 4 - src/audio_timing_karaoke.cpp | 14 +- src/auto4_base.cpp | 6 +- src/auto4_base.h | 3 +- src/auto4_lua.cpp | 18 +- src/base_grid.cpp | 53 ++- src/command/app.cpp | 13 +- src/command/audio.cpp | 124 +++-- src/command/automation.cpp | 2 +- src/command/edit.cpp | 5 +- src/command/keyframe.cpp | 17 +- src/command/recent.cpp | 22 +- src/command/subtitle.cpp | 20 +- src/command/time.cpp | 59 ++- src/command/timecode.cpp | 26 +- src/command/tool.cpp | 2 +- src/command/video.cpp | 44 +- src/command/vis_tool.cpp | 5 +- src/context.cpp | 8 +- src/dialog_detached_video.cpp | 14 +- src/dialog_jumpto.cpp | 10 +- src/dialog_properties.cpp | 9 +- src/dialog_resample.cpp | 14 +- src/dialog_shift_times.cpp | 6 +- src/dialog_styling_assistant.cpp | 11 +- src/dialog_timing_processor.cpp | 62 +-- src/dialog_translation.cpp | 7 +- src/dialog_video_details.cpp | 18 +- src/dialog_video_properties.cpp | 13 +- src/dialog_video_properties.h | 5 +- src/export_framerate.cpp | 63 ++- src/export_framerate.h | 12 +- src/frame_main.cpp | 273 ++--------- src/frame_main.h | 24 +- src/grid_column.cpp | 2 +- src/include/aegisub/audio_provider.h | 3 - src/include/aegisub/context.h | 8 +- src/include/aegisub/video_provider.h | 2 +- src/main.cpp | 19 +- src/project.cpp | 443 +++++++++++++++++ src/project.h | 104 ++++ src/subs_controller.cpp | 64 +-- src/subs_controller.h | 4 +- src/subs_edit_box.cpp | 26 +- src/subs_edit_box.h | 3 +- src/subtitle_format.cpp | 2 +- src/subtitle_format_microdvd.cpp | 2 +- src/timeedit_ctrl.cpp | 12 +- src/utils.h | 9 - src/validators.h | 2 +- src/video_box.cpp | 37 +- src/video_box.h | 4 +- src/video_context.cpp | 445 ------------------ src/video_controller.cpp | 274 +++++++++++ src/{video_context.h => video_controller.h} | 124 +---- src/video_display.cpp | 47 +- src/video_display.h | 6 +- src/video_provider_ffmpegsource.cpp | 2 +- src/video_slider.cpp | 25 +- src/video_slider.h | 15 +- src/visual_tool.cpp | 2 +- src/visual_tool.h | 3 +- src/visual_tool_drag.cpp | 4 +- 80 files changed, 1493 insertions(+), 1717 deletions(-) rename src/{threaded_frame_source.cpp => async_video_provider.cpp} (80%) rename src/{threaded_frame_source.h => async_video_provider.h} (80%) create mode 100644 src/project.cpp create mode 100644 src/project.h delete mode 100644 src/video_context.cpp create mode 100644 src/video_controller.cpp rename src/{video_context.h => video_controller.h} (56%) diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj index ce9b19969..7d6b05077 100644 --- a/build/Aegisub/Aegisub.vcxproj +++ b/build/Aegisub/Aegisub.vcxproj @@ -205,6 +205,7 @@ + @@ -234,7 +235,7 @@ - + @@ -244,7 +245,7 @@ - + @@ -282,6 +283,7 @@ + @@ -395,6 +397,7 @@ + @@ -426,7 +429,6 @@ - @@ -436,7 +438,7 @@ - + diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters index c29884ca8..75c0d3d9f 100644 --- a/build/Aegisub/Aegisub.vcxproj.filters +++ b/build/Aegisub/Aegisub.vcxproj.filters @@ -348,10 +348,7 @@ Features\Update checker - - Video\Providers - - + Video @@ -624,6 +621,12 @@ Features\Resolution resampler + + Video\Providers + + + Main UI + @@ -950,10 +953,7 @@ Features\Update checker - - Video\Providers - - + Video @@ -1181,9 +1181,15 @@ Features\Resolution resampler + + Video\Providers + + + Main UI + - \ No newline at end of file + diff --git a/libaegisub/include/libaegisub/signal.h b/libaegisub/include/libaegisub/signal.h index bf465ac53..50394896f 100644 --- a/libaegisub/include/libaegisub/signal.h +++ b/libaegisub/include/libaegisub/signal.h @@ -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 @@ -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 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, which can't be copied. +inline std::vector make_vector(std::initializer_list connections) { + return std::vector(std::begin(connections), std::end(connections)); +} + } } /// @brief Define functions which forward their arguments to the connect method diff --git a/src/Makefile b/src/Makefile index 32b187536..e6d056023 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 \ diff --git a/src/ass_exporter.cpp b/src/ass_exporter.cpp index 33739c6fb..1935284cb 100644 --- a/src/ass_exporter.cpp +++ b/src/ass_exporter.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 #include @@ -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) { diff --git a/src/threaded_frame_source.cpp b/src/async_video_provider.cpp similarity index 80% rename from src/threaded_frame_source.cpp rename to src/async_video_provider.cpp index 16f3eb880..b57ff95c0 100644 --- a/src/threaded_frame_source.cpp +++ b/src/async_video_provider.cpp @@ -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 ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) { +std::shared_ptr AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) { std::shared_ptr 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 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& changes) throw() { +void AsyncVideoProvider::UpdateSubtitles(const AssFile *new_subs, std::set 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::setAsync([=]{ @@ -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 ThreadedFrameSource::GetFrame(int frame, double time, bool raw) { +std::shared_ptr AsyncVideoProvider::GetFrame(int frame, double time, bool raw) { std::shared_ptr 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); diff --git a/src/threaded_frame_source.h b/src/async_video_provider.h similarity index 80% rename from src/threaded_frame_source.h rename to src/async_video_provider.h index e347f4cd4..317c8cabb 100644 --- a/src/threaded_frame_source.h +++ b/src/async_video_provider.h @@ -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 #include #include -#include #include #include - #include 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 worker; /// Subtitles provider std::unique_ptr subs_provider; /// Video provider - std::unique_ptr video_provider; + std::unique_ptr 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 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 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 diff --git a/src/audio_box.cpp b/src/audio_box.cpp index f21e79238..61947d910 100644 --- a/src/audio_box.cpp +++ b/src/audio_box.cpp @@ -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)) diff --git a/src/audio_controller.cpp b/src/audio_controller.cpp index b37ba6952..709f1067d 100644 --- a/src/audio_controller.cpp +++ b/src/audio_controller.cpp @@ -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 -#include #include 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 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 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 buf(bufsize); - for(int64_t i = start_sample; i < end_sample; i += spr) { - size_t len = std::min(spr, end_sample - i); - provider->GetAudio(&buf[0], i, len); - out.write(&buf[0], len * bytes_per_sample); - } + return samples * 1000 / provider->GetSampleRate(); } diff --git a/src/audio_controller.h b/src/audio_controller.h index d2687d2a9..87f3dcdcd 100644 --- a/src/audio_controller.h +++ b/src/audio_controller.h @@ -27,24 +27,15 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_controller.h -/// @see audio_controller.cpp -/// @ingroup audio_ui - -#pragma once - -#include -#include -#include - -#include -#include -#include - #include #include #include +#include +#include +#include +#include + 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 AnnounceAudioOpen; - - /// The current audio stream was closed - agi::signal::Signal<> AnnounceAudioClose; - /// Playback is in progress and the current position was updated agi::signal::Signal AnnouncePlaybackPosition; @@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler { /// The audio output object std::unique_ptr player; - /// The audio provider - std::unique_ptr provider; - /// The current timing mode, if any; owned by the audio controller std::unique_ptr 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 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) diff --git a/src/audio_display.cpp b/src/audio_display.cpp index b1d8e4707..3843e2b9a 100644 --- a/src/audio_display.cpp +++ b/src/audio_display.cpp @@ -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 @@ -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()) , 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(); } diff --git a/src/audio_display.h b/src/audio_display.h index ca230382d..4c17d4be5 100644 --- a/src/audio_display.h +++ b/src/audio_display.h @@ -35,7 +35,6 @@ #include #include -#include #include #include @@ -103,7 +102,7 @@ public: class AudioDisplay: public wxWindow { agi::signal::Connection audio_open_connection; - std::deque connections; + std::vector connections; agi::Context *context; /// The audio renderer manager diff --git a/src/audio_karaoke.cpp b/src/audio_karaoke.cpp index 7879b235e..56ec1288a 100644 --- a/src/audio_karaoke.cpp +++ b/src/audio_karaoke.cpp @@ -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 #include #include - #include #include #include @@ -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()) { @@ -122,12 +121,11 @@ void AudioKaraoke::OnFileChanged(int type, std::set 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) { diff --git a/src/audio_karaoke.h b/src/audio_karaoke.h index c7c16942c..0cc7c3b39 100644 --- a/src/audio_karaoke.h +++ b/src/audio_karaoke.h @@ -14,26 +14,20 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_karaoke.h -/// @see audio_karaoke.cpp -/// @ingroup audio_ui -/// - #include #include #include #include #include - #include #include #include 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: diff --git a/src/audio_marker.cpp b/src/audio_marker.cpp index dbf96f8c3..c627d5b96 100644 --- a/src/audio_marker.cpp +++ b/src/audio_marker.cpp @@ -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 @@ -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("Colour/Audio Display/Keyframe")) @@ -55,8 +56,8 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { } void AudioMarkerProviderKeyframes::Update() { - std::vector 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()) { diff --git a/src/audio_marker.h b/src/audio_marker.h index f8ac06f3e..b953b6cd4 100644 --- a/src/audio_marker.h +++ b/src/audio_marker.h @@ -14,11 +14,6 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_marker.h -/// @see audio_marker.cpp -/// @ingroup audio_ui -/// - #pragma once #include @@ -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 marker; diff --git a/src/audio_provider_avs.cpp b/src/audio_provider_avs.cpp index 8af99ae2e..11c8922aa 100644 --- a/src/audio_provider_avs.cpp +++ b/src/audio_provider_avs.cpp @@ -64,8 +64,6 @@ public: }; AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) { - this->filename = filename; - agi::acs::CheckFileRead(filename); std::lock_guard lock(avs_wrapper.GetMutex()); diff --git a/src/audio_provider_pcm.cpp b/src/audio_provider_pcm.cpp index e669067c4..024b73d12 100644 --- a/src/audio_provider_pcm.cpp +++ b/src/audio_provider_pcm.cpp @@ -158,8 +158,6 @@ public: RiffWavPCMAudioProvider(agi::fs::path const& filename) : PCMAudioProvider(filename) { - this->filename = filename; - // Read header auto const& header = Read(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) diff --git a/src/audio_timing_karaoke.cpp b/src/audio_timing_karaoke.cpp index 78f8d9b4c..dd66288e8 100644 --- a/src/audio_timing_karaoke.cpp +++ b/src/audio_timing_karaoke.cpp @@ -39,7 +39,6 @@ #include #include #include -#include /// @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 slots; + std::vector 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(); }); diff --git a/src/auto4_base.cpp b/src/auto4_base.cpp index 05a0bb96e..16eb35acd 100644 --- a/src/auto4_base.cpp +++ b/src/auto4_base.cpp @@ -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() diff --git a/src/auto4_base.h b/src/auto4_base.h index 6b53bdb89..0faaacbc6 100644 --- a/src/auto4_base.h +++ b/src/auto4_base.h @@ -43,7 +43,6 @@ #include "compat.h" #include -#include #include #include @@ -215,8 +214,8 @@ namespace Automation4 { /// Manager for scripts specified by a subtitle file class LocalScriptManager final : public ScriptManager { - std::deque slots; agi::Context *context; + std::vector connections; void OnSubtitlesSave(); public: diff --git a/src/auto4_lua.cpp b/src/auto4_lua.cpp index 98960bea9..b8ef66615 100644 --- a/src/auto4_lua.cpp +++ b/src/auto4_lua.cpp @@ -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 @@ -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; diff --git a/src/base_grid.cpp b/src/base_grid.cpp index a36902ffa..a5cc1240e 100644 --- a/src/base_grid.cpp +++ b/src/base_grid.cpp @@ -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 @@ -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) { diff --git a/src/command/app.cpp b/src/command/app.cpp index 33b337093..d867140b7 100644 --- a/src/command/app.cpp +++ b/src/command/app.cpp @@ -36,7 +36,6 @@ #include #include -#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(); + return c->project->AudioProvider() && c->project->VideoProvider() && !c->dialog->Get(); } 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(); + return c->project->VideoProvider() && !c->dialog->Get(); } bool IsActive(const agi::Context *c) override { diff --git a/src/command/audio.cpp b/src/command/audio.cpp index 84b346fb7..45ee75eac 100644 --- a/src/command/audio.cpp +++ b/src/command/audio.cpp @@ -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 #include +#include #include @@ -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 + void write(const char(&str)[N]) { + out.write(str, N - 1); + } + + void write(std::vector const& data) { + out.write(data.data(), data.size()); + } + + template + void write(Src v) { + auto converted = static_cast(v); + out.write(reinterpret_cast(&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(bufsize + 36); + + out.write("WAVEfmt "); + out.write(16); // Size of chunk + out.write(1); // compression format (PCM) + out.write(provider->GetChannels()); + out.write(provider->GetSampleRate()); + out.write(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample()); + out.write(provider->GetChannels() * provider->GetBytesPerSample()); + out.write(provider->GetBytesPerSample() * 8); + + out.write("data"); + out.write(bufsize); + + // samples per read + size_t spr = 65536 / bytes_per_sample; + std::vector buf(bufsize); + for (int64_t i = start_sample; i < end_sample; i += spr) { + buf.resize(std::min(spr, end_sample - i)); + provider->GetAudio(&buf[0], i, buf.size()); + out.write(buf); + } } }; diff --git a/src/command/automation.cpp b/src/command/automation.cpp index b939aa3eb..83d435bb0 100644 --- a/src/command/automation.cpp +++ b/src/command/automation.cpp @@ -40,7 +40,7 @@ #include "../main.h" #include "../options.h" #include "../utils.h" -#include "../video_context.h" +#include "../video_controller.h" #include diff --git a/src/command/edit.cpp b/src/command/edit.cpp index fc2655e3a..5153921d5 100644 --- a/src/command/edit.cpp +++ b/src/command/edit.cpp @@ -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 #include @@ -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(); } }; diff --git a/src/command/keyframe.cpp b/src/command/keyframe.cpp index 5cf62fc5c..c1c16f6a1 100644 --- a/src/command/keyframe.cpp +++ b/src/command/keyframe.cpp @@ -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 #include 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); } }; } diff --git a/src/command/recent.cpp b/src/command/recent.cpp index 1a3133b11..21e954d8a 100644 --- a/src/command/recent.cpp +++ b/src/command/recent.cpp @@ -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 -#include -#include - 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)); } }; diff --git a/src/command/subtitle.cpp b/src/command/subtitle.cpp index ea9119936..b20cda562 100644 --- a/src/command/subtitle.cpp +++ b/src/command/subtitle.cpp @@ -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 #include @@ -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(), 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(); } }; diff --git a/src/command/time.cpp b/src/command/time.cpp index 8130532ed..9ebaa16ea 100644 --- a/src/command/time.cpp +++ b/src/command/time.cpp @@ -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 #include 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); diff --git a/src/command/timecode.cpp b/src/command/timecode.cpp index 69ab7ff5d..1a8bcfef1 100644 --- a/src/command/timecode.cpp +++ b/src/command/timecode.cpp @@ -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 +#include + 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); + } } }; } diff --git a/src/command/tool.cpp b/src/command/tool.cpp index 79dc0829e..5be716035 100644 --- a/src/command/tool.cpp +++ b/src/command/tool.cpp @@ -46,7 +46,7 @@ #include "../libresrc/libresrc.h" #include "../options.h" #include "../resolution_resampler.h" -#include "../video_context.h" +#include "../video_controller.h" #include #include diff --git a/src/command/video.cpp b/src/command/video.cpp index 260bcfe4b..48ff9d8a0 100644 --- a/src/command/video.cpp +++ b/src/command/video.cpp @@ -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 #include #include - #include #include #include @@ -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(); + return !!c->project->VideoProvider() && !c->dialog->Get(); } }; @@ -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); } }; diff --git a/src/command/vis_tool.cpp b/src/command/vis_tool.cpp index a4bbc0756..0cfd308b1 100644 --- a/src/command/vis_tool.cpp +++ b/src/command/vis_tool.cpp @@ -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 { diff --git a/src/context.cpp b/src/context.cpp index c742ee3cb..8923d0884 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -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 @@ -34,10 +35,11 @@ Context::Context() : ass(make_unique()) , textSelectionController(make_unique()) , subsController(make_unique(this)) +, project(make_unique(this)) , local_scripts(make_unique(this)) -, videoController(make_unique(this)) -, audioController(make_unique(this)) , selectionController(make_unique(this)) +, videoController(make_unique(this)) +, audioController(make_unique(this)) , initialLineState(make_unique(this)) , search(make_unique(this)) , dialog(make_unique()) diff --git a/src/dialog_detached_video.cpp b/src/dialog_detached_video.cpp index a181a021e..727eca4fd 100644 --- a/src/dialog_detached_video.cpp +++ b/src/dialog_detached_video.cpp @@ -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 @@ -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); diff --git a/src/dialog_jumpto.cpp b/src/dialog_jumpto.cpp index 91c9d5828..f86671cc5 100644 --- a/src/dialog_jumpto.cpp +++ b/src/dialog_jumpto.cpp @@ -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 #include @@ -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(jumpframe, c->videoController->GetLength() - 1)); + c->videoController->JumpToFrame(jumpframe); } void DialogJumpTo::OnEditTime (wxCommandEvent &) { diff --git a/src/dialog_properties.cpp b/src/dialog_properties.cpp index 7518c2258..a59f00714 100644 --- a/src/dialog_properties.cpp +++ b/src/dialog_properties.cpp @@ -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 #include @@ -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())); } diff --git a/src/dialog_resample.cpp b/src/dialog_resample.cpp index dc5049d38..2b022f488 100644 --- a/src/dialog_resample.cpp +++ b/src/dialog_resample.cpp @@ -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 #include @@ -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); diff --git a/src/dialog_shift_times.cpp b/src/dialog_shift_times.cpp index 116aa0a03..51edaa02d 100644 --- a/src/dialog_shift_times.cpp +++ b/src/dialog_shift_times.cpp @@ -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 #include @@ -100,7 +100,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context) , context(context) , history_filename(config::path->Decode("?user/shift_history.json")) , history(agi::make_unique()) -, 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(); diff --git a/src/dialog_styling_assistant.cpp b/src/dialog_styling_assistant.cpp index 814ff0377..d8e7c8b2a 100644 --- a/src/dialog_styling_assistant.cpp +++ b/src/dialog_styling_assistant.cpp @@ -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 @@ -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())); diff --git a/src/dialog_timing_processor.cpp b/src/dialog_timing_processor.cpp index 4c3bb0022..b43773ab5 100644 --- a/src/dialog_timing_processor.cpp +++ b/src/dialog_timing_processor.cpp @@ -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 @@ -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(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 DialogTimingProcessor::SortDialogues() { - std::set styles; + std::set> 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(from_wx(StyleList->GetString(i)))); } std::vector sorted; @@ -382,28 +383,27 @@ void DialogTimingProcessor::Process() { // Keyframe snapping if (keysEnable->IsChecked()) { - std::vector kf = c->videoController->GetKeyFrames(); - if (c->videoController->IsLoaded()) - kf.push_back(c->videoController->GetLength() - 1); + std::vector 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; - } } } diff --git a/src/dialog_translation.cpp b/src/dialog_translation.cpp index e3b3eba97..755d4436a 100644 --- a/src/dialog_translation.cpp +++ b/src/dialog_translation.cpp @@ -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 @@ -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); diff --git a/src/dialog_video_details.cpp b/src/dialog_video_details.cpp index 96acc1082..0ff50cf69 100644 --- a/src/dialog_video_details.cpp +++ b/src/dialog_video_details.cpp @@ -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 - #include #include #include @@ -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 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); diff --git a/src/dialog_video_properties.cpp b/src/dialog_video_properties.cpp index b9dc872d6..dd7b5d8d1 100644 --- a/src/dialog_video_properties.cpp +++ b/src/dialog_video_properties.cpp @@ -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 +#include #include #include #include @@ -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); +} diff --git a/src/dialog_video_properties.h b/src/dialog_video_properties.h index 5d48445a4..f2d293e7b 100644 --- a/src/dialog_video_properties.h +++ b/src/dialog_video_properties.h @@ -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); \ No newline at end of file +void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent); \ No newline at end of file diff --git a/src/export_framerate.cpp b/src/export_framerate.cpp index bec332145..fc9c8c484 100644 --- a/src/export_framerate.cpp +++ b/src/export_framerate.cpp @@ -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 #include - #include #include #include @@ -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; diff --git a/src/export_framerate.h b/src/export_framerate.h index 84c49d9e4..3ed57e445 100644 --- a/src/export_framerate.h +++ b/src/export_framerate.h @@ -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 @@ -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; diff --git a/src/frame_main.cpp b/src/frame_main.cpp index 3cd2cb176..24b55aa37 100644 --- a/src/frame_main.cpp +++ b/src/frame_main.cpp @@ -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 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()); - }); + 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(); + else if (video) sv = context->project->VideoProvider() && !context->dialog->Get(); 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()) 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); } diff --git a/src/frame_main.h b/src/frame_main.h index 2d0c3c890..985b131ce 100644 --- a/src/frame_main.h +++ b/src/frame_main.h @@ -27,31 +27,23 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file frame_main.h -/// @see frame_main.cpp -/// @ingroup main_ui -/// - #include #include #include - #include #include #include 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 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() }; diff --git a/src/grid_column.cpp b/src/grid_column.cpp index f12f42048..e720629ea 100644 --- a/src/grid_column.cpp +++ b/src/grid_column.cpp @@ -21,7 +21,7 @@ #include "compat.h" #include "include/aegisub/context.h" #include "options.h" -#include "video_context.h" +#include "video_controller.h" #include diff --git a/src/include/aegisub/audio_provider.h b/src/include/aegisub/audio_provider.h index b433aabba..6a35976de 100644 --- a/src/include/aegisub/audio_provider.h +++ b/src/include/aegisub/audio_provider.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(); } }; diff --git a/src/include/aegisub/context.h b/src/include/aegisub/context.h index 23c3b405c..bda867974 100644 --- a/src/include/aegisub/context.h +++ b/src/include/aegisub/context.h @@ -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 ass; std::unique_ptr textSelectionController; std::unique_ptr subsController; + std::unique_ptr project; std::unique_ptr local_scripts; - std::unique_ptr videoController; - std::unique_ptr audioController; std::unique_ptr selectionController; + std::unique_ptr videoController; + std::unique_ptr audioController; std::unique_ptr initialLineState; std::unique_ptr search; diff --git a/src/include/aegisub/video_provider.h b/src/include/aegisub/video_provider.h index c962c1a07..509b7f8d8 100644 --- a/src/include/aegisub/video_provider.h +++ b/src/include/aegisub/video_provider.h @@ -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 GetFrame(int n)=0; diff --git a/src/main.cpp b/src/main.cpp index b48113017..246d980c8 100644 --- a/src/main.cpp +++ b/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 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)); } diff --git a/src/project.cpp b/src/project.cpp new file mode 100644 index 000000000..22e497680 --- /dev/null +++ b/src/project.cpp @@ -0,0 +1,443 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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(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{}; + keyframes_file.clear(); + AnnounceKeyframesModified(keyframes); +} + +void Project::LoadList(std::vector 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(); +} diff --git a/src/project.h b/src/project.h new file mode 100644 index 000000000..44ad98281 --- /dev/null +++ b/src/project.h @@ -0,0 +1,104 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 +#include +#include + +#include +#include +#include + +class AsyncVideoProvider; +class AudioProvider; +class DialogProgress; +class wxString; +namespace agi { struct Context; } + +class Project { + // Things owned by this + std::unique_ptr audio_provider; + std::unique_ptr video_provider; + agi::vfr::Framerate timecodes; + std::vector keyframes; + + agi::fs::path audio_file; + agi::fs::path video_file; + agi::fs::path timecodes_file; + agi::fs::path keyframes_file; + + agi::signal::Signal AnnounceAudioProviderModified; + agi::signal::Signal AnnounceVideoProviderModified; + agi::signal::Signal AnnounceTimecodesModified; + agi::signal::Signal 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 const& Keyframes() const { return keyframes; } + + void LoadList(std::vector const& files); + + DEFINE_SIGNAL_ADDERS(AnnounceAudioProviderModified, AddAudioProviderListener) + DEFINE_SIGNAL_ADDERS(AnnounceVideoProviderModified, AddVideoProviderListener) + DEFINE_SIGNAL_ADDERS(AnnounceTimecodesModified, AddTimecodesListener) + DEFINE_SIGNAL_ADDERS(AnnounceKeyframesModified, AddKeyframesListener) +}; diff --git a/src/subs_controller.cpp b/src/subs_controller.cpp index 1f66d92d9..de26b776a 100644 --- a/src/subs_controller.cpp +++ b/src/subs_controller.cpp @@ -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 #include @@ -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); diff --git a/src/subs_controller.h b/src/subs_controller.h index 843054551..5ef34620f 100644 --- a/src/subs_controller.h +++ b/src/subs_controller.h @@ -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 diff --git a/src/subs_edit_box.cpp b/src/subs_edit_box.cpp index cc3e1d54f..ff4ab9789 100644 --- a/src/subs_edit_box.cpp +++ b/src/subs_edit_box.cpp @@ -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 #include @@ -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; diff --git a/src/subs_edit_box.h b/src/subs_edit_box.h index b51141f2c..e8638f524 100644 --- a/src/subs_edit_box.h +++ b/src/subs_edit_box.h @@ -33,7 +33,6 @@ /// #include -#include #include #include #include @@ -74,7 +73,7 @@ class SubsEditBox final : public wxPanel { TIME_DURATION }; - std::deque connections; + std::vector connections; /// Currently active dialogue line AssDialogue *line = nullptr; diff --git a/src/subtitle_format.cpp b/src/subtitle_format.cpp index c9fb991d5..150d2ce2a 100644 --- a/src/subtitle_format.cpp +++ b/src/subtitle_format.cpp @@ -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 #include diff --git a/src/subtitle_format_microdvd.cpp b/src/subtitle_format_microdvd.cpp index 4e5bcd291..bb60e1b9b 100644 --- a/src/subtitle_format_microdvd.cpp +++ b/src/subtitle_format_microdvd.cpp @@ -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 #include diff --git a/src/timeedit_ctrl.cpp b/src/timeedit_ctrl.cpp index 645037986..1e0eb1051 100644 --- a/src/timeedit_ctrl.cpp +++ b/src/timeedit_ctrl.cpp @@ -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 #include @@ -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())); } diff --git a/src/utils.h b/src/utils.h index a9bae56c8..34738f3da 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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); diff --git a/src/validators.h b/src/validators.h index 64aacf020..501de5acd 100644 --- a/src/validators.h +++ b/src/validators.h @@ -17,7 +17,7 @@ #include #include - +#include #include #include diff --git a/src/video_box.cpp b/src/video_box.cpp index 4dc069e85..24663dcd6 100644 --- a/src/video_box.cpp +++ b/src/video_box.cpp @@ -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())); diff --git a/src/video_box.h b/src/video_box.h index 9ac4f3e1d..1e00e73ad 100644 --- a/src/video_box.h +++ b/src/video_box.h @@ -29,7 +29,7 @@ #include -#include +#include #include 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 slots; + std::vector connections; agi::Context *context; ///< Project context wxTextCtrl *VideoPosition; ///< Current frame/time wxTextCtrl *VideoSubsPos; ///< Time relative to the active subtitle line diff --git a/src/video_context.cpp b/src/video_context.cpp deleted file mode 100644 index 2d762c892..000000000 --- a/src/video_context.cpp +++ /dev/null @@ -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 -#include -#include -#include - -#include - -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(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& 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 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(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()); - 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()); - 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())); -} diff --git a/src/video_controller.cpp b/src/video_controller.cpp new file mode 100644 index 000000000..8bd23079d --- /dev/null +++ b/src/video_controller.cpp @@ -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 +#include +#include + +#include + +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& 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(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())); +} diff --git a/src/video_context.h b/src/video_controller.h similarity index 56% rename from src/video_context.h rename to src/video_controller.h index 603d1b98e..bc232eda5 100644 --- a/src/video_context.h +++ b/src/video_controller.h @@ -27,28 +27,17 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file video_context.h -/// @see video_context.cpp -/// @ingroup video -/// - -#include #include #include -#include #include -#include -#include #include #include #include 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 Seek; - /// A new video was opened - agi::signal::Signal<> VideoOpen; - /// New keyframes opened (new keyframe data) - agi::signal::Signal const&> KeyframesOpen; - /// New timecodes opened (new timecode data) - agi::signal::Signal TimecodesOpen; /// Aspect ratio was changed (type, value) agi::signal::Signal 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 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 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 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& 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 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& 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; }; diff --git a/src/video_display.cpp b/src/video_display.cpp index 3a955a10a..179edbfa5 100644 --- a/src/video_display.cpp +++ b/src/video_display.cpp @@ -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 #include - #include #include #include @@ -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(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 { diff --git a/src/video_display.h b/src/video_display.h index 04b30444c..65837b727 100644 --- a/src/video_display.h +++ b/src/video_display.h @@ -36,14 +36,14 @@ #include "vector2d.h" -#include #include #include +#include #include // 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 slots; + std::vector connections; const agi::OptionValue* autohideTools; diff --git a/src/video_provider_ffmpegsource.cpp b/src/video_provider_ffmpegsource.cpp index 9254c273e..ea86e1a15 100644 --- a/src/video_provider_ffmpegsource.cpp +++ b/src/video_provider_ffmpegsource.cpp @@ -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 diff --git a/src/video_slider.cpp b/src/video_slider.cpp index 1490a838d..1c5cd1ca3 100644 --- a/src/video_slider.cpp +++ b/src/video_slider.cpp @@ -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 #include @@ -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); } } diff --git a/src/video_slider.h b/src/video_slider.h index 8ae03025c..0c0546d52 100644 --- a/src/video_slider.h +++ b/src/video_slider.h @@ -27,27 +27,22 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file video_slider.h -/// @see video_slider.cpp -/// @ingroup custom_control -/// +#include #include - #include -#include - 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 keyframes; ///< Currently loaded keyframes - std::vector slots; + std::vector 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 const& newKeyframes); diff --git a/src/visual_tool.cpp b/src/visual_tool.cpp index 2a6f8edd8..6559441ac 100644 --- a/src/visual_tool.cpp +++ b/src/visual_tool.cpp @@ -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" diff --git a/src/visual_tool.h b/src/visual_tool.h index b4696df4f..c1d071b6a 100644 --- a/src/visual_tool.h +++ b/src/visual_tool.h @@ -26,7 +26,6 @@ #include #include -#include #include #include @@ -79,7 +78,7 @@ class VisualToolBase { virtual void DoRefresh() { } protected: - std::deque connections; + std::vector connections; OpenGLWrapper gl; diff --git a/src/visual_tool_drag.cpp b/src/visual_tool_drag.cpp index 83bd58899..0e6d21306 100644 --- a/src/visual_tool_drag.cpp +++ b/src/visual_tool_drag.cpp @@ -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 @@ -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;