Redesign project file handling

Add a new Project class which is responsible for everything related to
opening and closing audio, video, subtitles, timecodes and keyframes.
This pulls almost everything not directly related to playing audio/video
out of the audio and video controllers, pulls more crap out of
FrameMain, and happens to make things a little simpler in the process.
This commit is contained in:
Thomas Goyne 2014-05-21 16:23:28 -07:00
parent a345b8c4d5
commit 19e8f19e52
80 changed files with 1493 additions and 1717 deletions

View File

@ -205,6 +205,7 @@
<ClInclude Include="$(SrcDir)placeholder_ctrl.h" />
<ClInclude Include="$(SrcDir)preferences.h" />
<ClInclude Include="$(SrcDir)preferences_base.h" />
<ClInclude Include="$(SrcDir)project.h" />
<ClInclude Include="$(SrcDir)resolution_resampler.h" />
<ClInclude Include="$(SrcDir)scintilla_text_ctrl.h" />
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
@ -234,7 +235,7 @@
<ClInclude Include="$(SrcDir)text_file_writer.h" />
<ClInclude Include="$(SrcDir)text_selection_controller.h" />
<ClInclude Include="$(SrcDir)thesaurus.h" />
<ClInclude Include="$(SrcDir)threaded_frame_source.h" />
<ClInclude Include="$(SrcDir)async_video_provider.h" />
<ClInclude Include="$(SrcDir)time_range.h" />
<ClInclude Include="$(SrcDir)timeedit_ctrl.h" />
<ClInclude Include="$(SrcDir)toggle_bitmap.h" />
@ -244,7 +245,7 @@
<ClInclude Include="$(SrcDir)vector2d.h" />
<ClInclude Include="$(SrcDir)version.h" />
<ClInclude Include="$(SrcDir)video_box.h" />
<ClInclude Include="$(SrcDir)video_context.h" />
<ClInclude Include="$(SrcDir)video_controller.h" />
<ClInclude Include="$(SrcDir)video_display.h" />
<ClInclude Include="$(SrcDir)video_frame.h" />
<ClInclude Include="$(SrcDir)video_out_gl.h" />
@ -282,6 +283,7 @@
<ClCompile Include="$(SrcDir)ass_style.cpp" />
<ClCompile Include="$(SrcDir)ass_style_storage.cpp" />
<ClCompile Include="$(SrcDir)ass_time.cpp" />
<ClCompile Include="$(SrcDir)async_video_provider.cpp" />
<ClCompile Include="$(SrcDir)audio_box.cpp" />
<ClCompile Include="$(SrcDir)audio_colorscheme.cpp" />
<ClCompile Include="$(SrcDir)audio_controller.cpp" />
@ -395,6 +397,7 @@
<ClCompile Include="$(SrcDir)persist_location.cpp" />
<ClCompile Include="$(SrcDir)preferences.cpp" />
<ClCompile Include="$(SrcDir)preferences_base.cpp" />
<ClCompile Include="$(SrcDir)project.cpp" />
<ClCompile Include="$(SrcDir)resolution_resampler.cpp" />
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
@ -426,7 +429,6 @@
<ClCompile Include="$(SrcDir)text_file_writer.cpp" />
<ClCompile Include="$(SrcDir)text_selection_controller.cpp" />
<ClCompile Include="$(SrcDir)thesaurus.cpp" />
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp" />
<ClCompile Include="$(SrcDir)timeedit_ctrl.cpp" />
<ClCompile Include="$(SrcDir)toggle_bitmap.cpp" />
<ClCompile Include="$(SrcDir)toolbar.cpp" />
@ -436,7 +438,7 @@
<ClCompile Include="$(SrcDir)vector2d.cpp" />
<ClCompile Include="$(SrcDir)version.cpp" />
<ClCompile Include="$(SrcDir)video_box.cpp" />
<ClCompile Include="$(SrcDir)video_context.cpp" />
<ClCompile Include="$(SrcDir)video_controller.cpp" />
<ClCompile Include="$(SrcDir)video_display.cpp" />
<ClCompile Include="$(SrcDir)video_frame.cpp" />
<ClCompile Include="$(SrcDir)video_out_gl.cpp" />

View File

@ -348,10 +348,7 @@
<ClInclude Include="$(SrcDir)dialog_version_check.h">
<Filter>Features\Update checker</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)threaded_frame_source.h">
<Filter>Video\Providers</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)video_context.h">
<ClInclude Include="$(SrcDir)video_controller.h">
<Filter>Video</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)video_frame.h">
@ -624,6 +621,12 @@
<ClInclude Include="$(SrcDir)dialog_video_properties.h">
<Filter>Features\Resolution resampler</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)async_video_provider.h">
<Filter>Video\Providers</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)project.h">
<Filter>Main UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
@ -950,10 +953,7 @@
<ClCompile Include="$(SrcDir)dialog_version_check.cpp">
<Filter>Features\Update checker</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp">
<Filter>Video\Providers</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)video_context.cpp">
<ClCompile Include="$(SrcDir)video_controller.cpp">
<Filter>Video</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)video_slider.cpp">
@ -1181,9 +1181,15 @@
<ClCompile Include="$(SrcDir)dialog_video_properties.cpp">
<Filter>Features\Resolution resampler</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)async_video_provider.cpp">
<Filter>Video\Providers</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)project.cpp">
<Filter>Main UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="$(SrcDir)res\res.rc" />
<ResourceCompile Include="$(SrcDir)res\strings.rc" />
</ItemGroup>
</Project>
</Project>

View File

@ -12,10 +12,6 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
/// @file signal.h
/// @brief
/// @ingroup libaegisub
#pragma once
#include <boost/container/map.hpp>
@ -45,11 +41,29 @@ namespace detail {
};
}
/// A connection which is not automatically closed
///
/// Connections initially start out owned by the signal. If a slot knows that it
/// will outlive a signal and does not need to be able to block a connection, it
/// can simply ignore the return value of Connect.
///
/// If a slot needs to be able to disconnect from a signal, it should store the
/// returned connection in a Connection, which transfers ownership of the
/// connection to the slot. If there is any chance that the signal will outlive
/// the slot, this must be done.
class UnscopedConnection {
friend class Connection;
detail::ConnectionToken *token;
public:
UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
};
/// Object representing a connection to a signal
class Connection {
std::unique_ptr<detail::ConnectionToken> token;
public:
Connection() = default;
Connection(UnscopedConnection src) BOOST_NOEXCEPT : token(src.token) { token->claimed = true; }
Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { }
Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; }
Connection& operator=(Connection&& that) BOOST_NOEXCEPT { token = std::move(that.token); return *this; }
@ -69,23 +83,6 @@ public:
void Unblock() { if (token) token->blocked = false; }
};
/// A connection which is not automatically closed
///
/// Connections initially start out owned by the signal. If a slot knows that it
/// will outlive a signal and does not need to be able to block a connection, it
/// can simply ignore the return value of Connect.
///
/// If a slot needs to be able to disconnect from a signal, it should store the
/// returned connection in a Connection, which transfers ownership of the
/// connection to the slot. If there is any chance that the signal will outlive
/// the slot, this must be done.
class UnscopedConnection {
detail::ConnectionToken *token;
public:
UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
operator Connection() { return Connection(token); }
};
namespace detail {
/// Polymorphic base class for slots
///
@ -198,6 +195,15 @@ public:
}
};
/// Create a vector of scoped connections from an initializer list
///
/// Required due to that initializer lists copy their input, and trying to pass
/// an initializer list directly to a vector results in a
/// std::initializer_list<Connection>, which can't be copied.
inline std::vector<Connection> make_vector(std::initializer_list<UnscopedConnection> connections) {
return std::vector<Connection>(std::begin(connections), std::end(connections));
}
} }
/// @brief Define functions which forward their arguments to the connect method

View File

@ -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 \

View File

@ -38,8 +38,8 @@
#include "ass_file.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "project.h"
#include "subtitle_format.h"
#include "video_context.h"
#include <memory>
#include <wx/sizer.h>
@ -51,7 +51,7 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
for (auto& filter : *AssExportFilterChain::GetFilterList()) {
// Make sure to construct static box sizer first, so it won't overlap
// the controls on wxMac.
wxSizer *box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
auto box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
wxWindow *window = filter.GetConfigDialogWindow(parent, c);
if (window) {
box->Add(window, 0, wxEXPAND, 0);
@ -92,7 +92,7 @@ void AssExporter::Export(agi::fs::path const& filename, std::string const& chars
if (!writer)
throw "Unknown file type.";
writer->WriteFile(&subs, filename, c->videoController->FPS(), charset);
writer->WriteFile(&subs, filename, c->project->Timecodes(), charset);
}
wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {

View File

@ -14,18 +14,12 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file threaded_frame_source.cpp
/// @see threaded_frame_source.h
/// @ingroup video
///
#include "threaded_frame_source.h"
#include "async_video_provider.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "export_fixstyle.h"
#include "include/aegisub/subtitles_provider.h"
#include "include/aegisub/video_provider.h"
#include "video_frame.h"
#include "video_provider_manager.h"
@ -40,11 +34,11 @@ enum {
SUBS_FILE_ALREADY_LOADED = -2
};
std::shared_ptr<VideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr<VideoFrame> frame;
try {
frame = video_provider->GetFrame(frame_number);
frame = source_provider->GetFrame(frame_number);
}
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
@ -89,20 +83,20 @@ static std::unique_ptr<SubtitlesProvider> get_subs_provider(wxEvtHandler *evt_ha
}
}
ThreadedFrameSource::ThreadedFrameSource(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
AsyncVideoProvider::AsyncVideoProvider(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
: worker(agi::dispatch::Create())
, subs_provider(get_subs_provider(parent, br))
, video_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
, source_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
, parent(parent)
{
}
ThreadedFrameSource::~ThreadedFrameSource() {
AsyncVideoProvider::~AsyncVideoProvider() {
// Block until all currently queued jobs are complete
worker->Sync([]{});
}
void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
void AsyncVideoProvider::LoadSubtitles(const AssFile *new_subs) throw() {
uint_fast32_t req_version = ++version;
auto copy = new AssFile(*new_subs);
@ -113,7 +107,7 @@ void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
});
}
void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> const& changes) throw() {
void AsyncVideoProvider::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> const& changes) throw() {
uint_fast32_t req_version = ++version;
// Copy just the lines which were changed, then replace the lines at the
@ -141,7 +135,7 @@ void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<cons
});
}
void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
void AsyncVideoProvider::RequestFrame(int new_frame, double new_time) throw() {
uint_fast32_t req_version = ++version;
worker->Async([=]{
@ -151,7 +145,7 @@ void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
});
}
void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
void AsyncVideoProvider::ProcAsync(uint_fast32_t req_version) {
// Only actually produce the frame if there's no queued changes waiting
if (req_version < version || frame_number < 0) return;
@ -166,14 +160,14 @@ void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
}
}
std::shared_ptr<VideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
std::shared_ptr<VideoFrame> AsyncVideoProvider::GetFrame(int frame, double time, bool raw) {
std::shared_ptr<VideoFrame> ret;
worker->Sync([&]{ ret = ProcFrame(frame, time, raw); });
return ret;
}
void ThreadedFrameSource::SetColorSpace(std::string const& matrix) {
worker->Async([=] { video_provider->SetColorSpace(matrix); });
void AsyncVideoProvider::SetColorSpace(std::string const& matrix) {
worker->Async([=] { source_provider->SetColorSpace(matrix); });
}
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);

View File

@ -14,19 +14,14 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file threaded_frame_source.h
/// @see threaded_frame_source.cpp
/// @ingroup video
///
#include "include/aegisub/video_provider.h"
#include <libaegisub/exception.h>
#include <libaegisub/fs_fwd.h>
#include <atomic>
#include <deque>
#include <memory>
#include <set>
#include <wx/event.h>
class AssDialogue;
@ -40,16 +35,15 @@ namespace agi {
namespace dispatch { class Queue; }
}
/// @class ThreadedFrameSource
/// @brief An asynchronous video decoding and subtitle rendering wrapper
class ThreadedFrameSource {
/// An asynchronous video decoding and subtitle rendering wrapper
class AsyncVideoProvider {
/// Asynchronous work queue
std::unique_ptr<agi::dispatch::Queue> worker;
/// Subtitles provider
std::unique_ptr<SubtitlesProvider> subs_provider;
/// Video provider
std::unique_ptr<VideoProvider> video_provider;
std::unique_ptr<VideoProvider> source_provider;
/// Event handler to send FrameReady events to
wxEvtHandler *parent;
@ -103,17 +97,26 @@ public:
/// @brief raw Get raw frame without subtitles
std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
/// Get a reference to the video provider this is using
VideoProvider *GetVideoProvider() const { return video_provider.get(); }
/// Ask the video provider to change YCbCr matricies
void SetColorSpace(std::string const& matrix);
int GetFrameCount() const { return source_provider->GetFrameCount(); }
int GetWidth() const { return source_provider->GetWidth(); }
int GetHeight() const { return source_provider->GetHeight(); }
double GetDAR() const { return source_provider->GetDAR(); }
agi::vfr::Framerate GetFPS() const { return source_provider->GetFPS(); }
std::vector<int> GetKeyFrames() const { return source_provider->GetKeyFrames(); }
std::string GetColorSpace() const { return source_provider->GetColorSpace(); }
std::string GetRealColorSpace() const { return source_provider->GetRealColorSpace(); }
std::string GetWarning() const { return source_provider->GetWarning(); }
std::string GetDecoderName() const { return source_provider->GetDecoderName(); }
bool ShouldSetVideoProperties() const { return source_provider->ShouldSetVideoProperties(); }
/// @brief Constructor
/// @param videoFileName File to open
/// @param parent Event handler to send FrameReady events to
ThreadedFrameSource(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
~ThreadedFrameSource();
AsyncVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
~AsyncVideoProvider();
};
/// Event which signals that a requested frame is ready

View File

@ -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))

View File

@ -27,36 +27,23 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_controller.cpp
/// @brief Manage open audio and abstract state away from display
/// @ingroup audio_ui
///
#include "audio_controller.h"
#include "ass_file.h"
#include "audio_timing.h"
#include "compat.h"
#include "dialog_progress.h"
#include "include/aegisub/audio_player.h"
#include "include/aegisub/audio_provider.h"
#include "include/aegisub/context.h"
#include "pen.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "utils.h"
#include "video_context.h"
#include <libaegisub/io.h>
#include <libaegisub/path.h>
#include <algorithm>
AudioController::AudioController(agi::Context *context)
: context(context)
, subtitle_save_slot(context->subsController->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
, playback_timer(this)
, provider_connection(context->project->AddAudioProviderListener(&AudioController::OnAudioProvider, this))
{
Bind(wxEVT_TIMER, &AudioController::OnPlaybackTimer, this, playback_timer.GetId());
@ -66,25 +53,18 @@ AudioController::AudioController(agi::Context *context)
#endif
OPT_SUB("Audio/Player", &AudioController::OnAudioPlayerChanged, this);
OPT_SUB("Audio/Provider", &AudioController::OnAudioProviderChanged, this);
OPT_SUB("Audio/Cache/Type", &AudioController::OnAudioProviderChanged, this);
#ifdef WITH_FFMS2
// As with the video ones, it'd be nice to figure out a decent way to move
// this to the provider itself
OPT_SUB("Provider/Audio/FFmpegSource/Decode Error Handling", &AudioController::OnAudioProviderChanged, this);
#endif
}
AudioController::~AudioController()
{
CloseAudio();
Stop();
}
void AudioController::OnPlaybackTimer(wxTimerEvent &)
{
int64_t pos = player->GetCurrentPosition();
if (!player) return;
int64_t pos = player->GetCurrentPosition();
if (!player->IsPlaying() ||
(playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
{
@ -107,113 +87,40 @@ void AudioController::OnComputerSuspending(wxPowerEvent &)
void AudioController::OnComputerResuming(wxPowerEvent &)
{
if (provider)
{
try
{
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
}
catch (...)
{
CloseAudio();
}
}
OnAudioPlayerChanged();
}
#endif
void AudioController::OnAudioPlayerChanged()
{
if (!IsAudioOpen()) return;
if (!provider) return;
Stop();
player.reset();
try
{
player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
player = AudioPlayerFactory::GetAudioPlayer(provider, context->parent);
}
catch (...)
{
CloseAudio();
throw;
context->project->CloseAudio();
}
}
void AudioController::OnAudioProviderChanged()
{
if (IsAudioOpen())
// url is cloned because CloseAudio clears it and OpenAudio takes a const reference
OpenAudio(agi::fs::path(audio_url));
}
void AudioController::OpenAudio(agi::fs::path const& url)
{
if (url.empty())
throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", nullptr);
std::unique_ptr<AudioProvider> new_provider;
try {
DialogProgress progress(context->parent);
new_provider = AudioProviderFactory::GetProvider(url, &progress);
config::path->SetToken("?audio", url);
}
catch (agi::UserCancelException const&) {
throw;
}
catch (...) {
config::mru->Remove("Audio", url);
throw;
}
CloseAudio();
player = AudioPlayerFactory::GetAudioPlayer(new_provider.get(), context->parent);
provider = std::move(new_provider);
audio_url = url;
config::mru->Add("Audio", url);
try
{
AnnounceAudioOpen(provider.get());
}
catch (...)
{
CloseAudio();
throw;
}
}
void AudioController::CloseAudio()
void AudioController::OnAudioProvider(AudioProvider *new_provider)
{
provider = new_provider;
Stop();
player.reset();
provider.reset();
player = nullptr;
provider = nullptr;
audio_url.clear();
config::path->SetToken("?audio", "");
AnnounceAudioClose();
}
bool AudioController::IsAudioOpen() const
{
return player && provider;
OnAudioPlayerChanged();
}
void AudioController::SetTimingController(std::unique_ptr<AudioTimingController> new_controller)
{
timing_controller = std::move(new_controller);
if (timing_controller)
{
timing_controller->AddUpdatedPrimaryRangeListener(&AudioController::OnTimingControllerUpdatedPrimaryRange, this);
}
AnnounceTimingControllerChanged();
}
@ -224,17 +131,9 @@ void AudioController::OnTimingControllerUpdatedPrimaryRange()
player->SetEndPosition(SamplesFromMilliseconds(timing_controller->GetPrimaryPlaybackRange().end()));
}
void AudioController::OnSubtitlesSave()
{
if (IsAudioOpen())
context->ass->SetScriptInfo("Audio URI", config::path->MakeRelative(audio_url, "?script").generic_string());
else
context->ass->SetScriptInfo("Audio URI", "");
}
void AudioController::PlayRange(const TimeRange &range)
{
if (!IsAudioOpen()) return;
if (!player) return;
player->Play(SamplesFromMilliseconds(range.begin()), SamplesFromMilliseconds(range.length()));
playback_mode = PM_Range;
@ -252,8 +151,6 @@ void AudioController::PlayPrimaryRange()
void AudioController::PlayToEndOfPrimary(int start_ms)
{
if (!IsAudioOpen()) return;
PlayRange(TimeRange(start_ms, GetPrimaryPlaybackRange().end()));
if (playback_mode == PM_Range)
playback_mode = PM_PrimaryRange;
@ -261,7 +158,7 @@ void AudioController::PlayToEndOfPrimary(int start_ms)
void AudioController::PlayToEnd(int start_ms)
{
if (!IsAudioOpen()) return;
if (!player) return;
int64_t start_sample = SamplesFromMilliseconds(start_ms);
player->Play(start_sample, provider->GetNumSamples()-start_sample);
@ -273,7 +170,7 @@ void AudioController::PlayToEnd(int start_ms)
void AudioController::Stop()
{
if (!IsAudioOpen()) return;
if (!player) return;
player->Stop();
playback_mode = PM_NotPlaying;
@ -284,7 +181,7 @@ void AudioController::Stop()
bool AudioController::IsPlaying()
{
return IsAudioOpen() && playback_mode != PM_NotPlaying;
return player && playback_mode != PM_NotPlaying;
}
int AudioController::GetPlaybackPosition()
@ -297,88 +194,31 @@ int AudioController::GetPlaybackPosition()
int AudioController::GetDuration() const
{
if (!provider) return 0;
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
}
TimeRange AudioController::GetPrimaryPlaybackRange() const
{
if (timing_controller)
{
return timing_controller->GetPrimaryPlaybackRange();
}
else
{
return TimeRange(0, 0);
}
return TimeRange{0, 0};
}
void AudioController::SetVolume(double volume)
{
if (!IsAudioOpen()) return;
if (!player) return;
player->SetVolume(volume);
}
int64_t AudioController::SamplesFromMilliseconds(int64_t ms) const
{
/// @todo There might be some subtle rounding errors here.
if (!provider) return 0;
int64_t sr = provider->GetSampleRate();
int64_t millisamples = ms * sr;
return (millisamples + 999) / 1000;
return (ms * provider->GetSampleRate() + 999) / 1000;
}
int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
{
/// @todo There might be some subtle rounding errors here.
if (!provider) return 0;
int64_t sr = provider->GetSampleRate();
int64_t millisamples = samples * 1000;
return millisamples / sr;
}
void AudioController::SaveClip(agi::fs::path const& filename, TimeRange const& range) const
{
int64_t start_sample = SamplesFromMilliseconds(range.begin());
int64_t end_sample = SamplesFromMilliseconds(range.end());
if (filename.empty() || start_sample > provider->GetNumSamples() || range.length() == 0) return;
agi::io::Save outfile(filename, true);
std::ostream& out(outfile.Get());
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
int intval;
short shortval;
out << "RIFF";
out.write((char*)&(intval=bufsize+36),4);
out<< "WAVEfmt ";
out.write((char*)&(intval=16),4);
out.write((char*)&(shortval=1),2);
out.write((char*)&(shortval=provider->GetChannels()),2);
out.write((char*)&(intval=provider->GetSampleRate()),4);
out.write((char*)&(intval=provider->GetSampleRate()*provider->GetChannels()*provider->GetBytesPerSample()),4);
out.write((char*)&(intval=provider->GetChannels()*provider->GetBytesPerSample()),2);
out.write((char*)&(shortval=provider->GetBytesPerSample()<<3),2);
out << "data";
out.write((char*)&bufsize,4);
//samples per read
size_t spr = 65536 / bytes_per_sample;
std::vector<char> buf(bufsize);
for(int64_t i = start_sample; i < end_sample; i += spr) {
size_t len = std::min<size_t>(spr, end_sample - i);
provider->GetAudio(&buf[0], i, len);
out.write(&buf[0], len * bytes_per_sample);
}
return samples * 1000 / provider->GetSampleRate();
}

View File

@ -27,24 +27,15 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_controller.h
/// @see audio_controller.cpp
/// @ingroup audio_ui
#pragma once
#include <boost/filesystem/path.hpp>
#include <cstdint>
#include <memory>
#include <wx/event.h>
#include <wx/timer.h>
#include <wx/power.h>
#include <libaegisub/exception.h>
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include <cstdint>
#include <wx/event.h>
#include <wx/power.h>
#include <wx/timer.h>
class AudioPlayer;
class AudioProvider;
class AudioTimingController;
@ -52,17 +43,10 @@ namespace agi { struct Context; }
class TimeRange;
/// @class AudioController
/// @brief Manage an open audio stream
/// @brief Manage playback of an open audio stream
///
/// Creates and destroys audio providers and players. This behaviour should at
/// some point be moved to a separate class, as it adds too many
/// responsibilities to this class, but at the time of writing, it would extend
/// the scope of reworking components too much.
///
/// There is not supposed to be a way to get direct access to the audio
/// providers or players owned by a controller. If some operation that isn't
/// possible in the existing design is needed, the controller should be
/// extended in some way to allow it.
/// AudioController owns an AudioPlayer and uses it to play audio from the
/// project's current audio provider.
class AudioController final : public wxEvtHandler {
/// Project context this controller belongs to
agi::Context *context;
@ -70,12 +54,6 @@ class AudioController final : public wxEvtHandler {
/// Slot for subtitles save signal
agi::signal::Connection subtitle_save_slot;
/// A new audio stream was opened (and any previously open was closed)
agi::signal::Signal<AudioProvider*> AnnounceAudioOpen;
/// The current audio stream was closed
agi::signal::Signal<> AnnounceAudioClose;
/// Playback is in progress and the current position was updated
agi::signal::Signal<int> AnnouncePlaybackPosition;
@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler {
/// The audio output object
std::unique_ptr<AudioPlayer> player;
/// The audio provider
std::unique_ptr<AudioProvider> provider;
/// The current timing mode, if any; owned by the audio controller
std::unique_ptr<AudioTimingController> timing_controller;
/// The URL of the currently open audio, if any
agi::fs::path audio_url;
enum PlaybackMode {
PM_NotPlaying,
PM_Range,
@ -110,6 +81,12 @@ class AudioController final : public wxEvtHandler {
/// Timer used for playback position updates
wxTimer playback_timer;
/// The audio provider
AudioProvider *provider = nullptr;
agi::signal::Connection provider_connection;
void OnAudioProvider(AudioProvider *new_provider);
/// Event handler for the playback timer
void OnPlaybackTimer(wxTimerEvent &event);
@ -119,15 +96,9 @@ class AudioController final : public wxEvtHandler {
/// @brief Timing controller signals that the rendering style ranges have changed
void OnTimingControllerUpdatedStyleRanges();
/// Subtitles save slot which adds the audio uri to the subtitles
void OnSubtitlesSave();
/// Handler for the current audio player changing
void OnAudioPlayerChanged();
/// Handler for the current audio provider changing
void OnAudioProviderChanged();
#ifdef wxHAS_POWER_EVENTS
/// Handle computer going into suspend mode by stopping audio and closing device
void OnComputerSuspending(wxPowerEvent &event);
@ -146,31 +117,9 @@ class AudioController final : public wxEvtHandler {
int64_t SamplesFromMilliseconds(int64_t ms) const;
public:
/// @brief Constructor
AudioController(agi::Context *context);
/// @brief Destructor
~AudioController();
/// @brief Open an audio stream
/// @param url URL of the stream to open
void OpenAudio(agi::fs::path const& url);
/// @brief Closes the current audio stream
void CloseAudio();
/// @brief Determine whether audio is currently open
/// @return True if an audio stream is open and can be played back
bool IsAudioOpen() const;
/// @brief Get the URL for the current open audio stream
/// @return The URL for the audio stream
///
/// The returned URL can be passed into OpenAudio() later to open the same
/// stream again.
agi::fs::path GetAudioURL() const { return audio_url; }
/// @brief Start or restart audio playback, playing a range
/// @param range The range of audio to play back
///
@ -233,13 +182,6 @@ public:
/// @param new_mode The new timing controller or nullptr
void SetTimingController(std::unique_ptr<AudioTimingController> new_controller);
/// @brief Save a portion of the decoded loaded audio to a wav file
/// @param filename File to save to
/// @param range Time range to save
void SaveClip(agi::fs::path const& filename, TimeRange const& range) const;
DEFINE_SIGNAL_ADDERS(AnnounceAudioOpen, AddAudioOpenListener)
DEFINE_SIGNAL_ADDERS(AnnounceAudioClose, AddAudioCloseListener)
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackPosition, AddPlaybackPositionListener)
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackStop, AddPlaybackStopListener)
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)

View File

@ -48,9 +48,10 @@
#include "include/aegisub/context.h"
#include "include/aegisub/hotkey.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "utils.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -547,7 +548,7 @@ public:
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
, audio_open_connection(controller->AddAudioOpenListener(&AudioDisplay::OnAudioOpen, this))
, audio_open_connection(context->project->AddAudioProviderListener(&AudioDisplay::OnAudioOpen, this))
, context(context)
, audio_renderer(agi::make_unique<AudioRenderer>())
, controller(controller)
@ -1193,16 +1194,16 @@ void AudioDisplay::OnAudioOpen(AudioProvider *provider)
{
if (connections.empty())
{
connections.push_back(controller->AddAudioCloseListener([=] { OnAudioOpen(nullptr); }));
connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
connections.push_back(OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
connections.push_back(OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this));
connections.push_back(OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
connections.push_back(OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this));
connections.push_back(OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this));
connections = agi::signal::make_vector({
controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this),
controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this),
controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this),
OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this),
OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this),
OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this),
});
OnTimingController();
}

View File

@ -35,7 +35,6 @@
#include <chrono>
#include <cstdint>
#include <deque>
#include <map>
#include <memory>
@ -103,7 +102,7 @@ public:
class AudioDisplay: public wxWindow {
agi::signal::Connection audio_open_connection;
std::deque<agi::signal::Connection> connections;
std::vector<agi::signal::Connection> connections;
agi::Context *context;
/// The audio renderer manager

View File

@ -32,6 +32,7 @@
#include "compat.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "utils.h"
@ -40,7 +41,6 @@
#include <algorithm>
#include <boost/locale/boundary.hpp>
#include <numeric>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/dcclient.h>
@ -63,8 +63,7 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN)
, c(c)
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
, audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this))
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
, kara(agi::make_unique<AssKaraoke>())
{
@ -122,12 +121,11 @@ void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const&
}
}
void AudioKaraoke::OnAudioOpened() {
SetEnabled(enabled);
}
void AudioKaraoke::OnAudioClosed() {
c->audioController->SetTimingController(nullptr);
void AudioKaraoke::OnAudioOpened(AudioProvider *provider) {
if (provider)
SetEnabled(enabled);
else
c->audioController->SetTimingController(nullptr);
}
void AudioKaraoke::SetEnabled(bool en) {

View File

@ -14,26 +14,20 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_karaoke.h
/// @see audio_karaoke.cpp
/// @ingroup audio_ui
///
#include <libaegisub/signal.h>
#include <memory>
#include <set>
#include <unordered_map>
#include <vector>
#include <wx/bitmap.h>
#include <wx/timer.h>
#include <wx/window.h>
class AssDialogue;
class AssKaraoke;
class AudioProvider;
class wxButton;
namespace agi { struct Context; }
/// @class AudioKaraoke
@ -147,8 +141,7 @@ class AudioKaraoke final : public wxWindow {
void OnMouse(wxMouseEvent &event);
void OnPaint(wxPaintEvent &event);
void OnSize(wxSizeEvent &event);
void OnAudioOpened();
void OnAudioClosed();
void OnAudioOpened(AudioProvider *provider);
void OnScrollTimer(wxTimerEvent &event);
public:

View File

@ -24,7 +24,8 @@
#include "include/aegisub/context.h"
#include "options.h"
#include "pen.h"
#include "video_context.h"
#include "project.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -42,9 +43,9 @@ public:
};
AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
: vc(c->videoController.get())
, keyframe_slot(vc->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
, timecode_slot(vc->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
: p(c->project.get())
, keyframe_slot(p->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
, timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
, enabled_opt(OPT_GET(opt_name))
, style(agi::make_unique<Pen>("Colour/Audio Display/Keyframe"))
@ -55,8 +56,8 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons
AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }
void AudioMarkerProviderKeyframes::Update() {
std::vector<int> const& keyframes = vc->GetKeyFrames();
agi::vfr::Framerate const& timecodes = vc->FPS();
auto const& keyframes = p->Keyframes();
auto const& timecodes = p->Timecodes();
if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
if (!markers.empty()) {

View File

@ -14,11 +14,6 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_marker.h
/// @see audio_marker.cpp
/// @ingroup audio_ui
///
#pragma once
#include <libaegisub/signal.h>
@ -30,7 +25,8 @@
class AudioMarkerKeyframe;
class Pen;
class VideoContext;
class Project;
class VideoController;
class TimeRange;
class VideoPositionMarker;
class wxPen;
@ -113,8 +109,8 @@ public:
/// Marker provider for video keyframes
class AudioMarkerProviderKeyframes final : public AudioMarkerProvider {
/// Video controller to get keyframes from
VideoContext *vc;
/// Project to get keyframes from
Project *p;
agi::signal::Connection keyframe_slot;
agi::signal::Connection timecode_slot;
@ -146,7 +142,7 @@ public:
/// Marker provider for the current video playback position
class VideoPositionMarkerProvider final : public AudioMarkerProvider {
VideoContext *vc;
VideoController *vc;
std::unique_ptr<VideoPositionMarker> marker;

View File

@ -64,8 +64,6 @@ public:
};
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
this->filename = filename;
agi::acs::CheckFileRead(filename);
std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());

View File

@ -158,8 +158,6 @@ public:
RiffWavPCMAudioProvider(agi::fs::path const& filename)
: PCMAudioProvider(filename)
{
this->filename = filename;
// Read header
auto const& header = Read<RIFFChunk>(0);
@ -292,8 +290,6 @@ public:
Wave64AudioProvider(agi::fs::path const& filename)
: PCMAudioProvider(filename)
{
this->filename = filename;
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
if (file->size() < smallest_possible_file)

View File

@ -39,7 +39,6 @@
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/sliced.hpp>
#include <deque>
/// @class KaraokeMarker
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
@ -55,6 +54,8 @@ public:
void Move(int new_pos) { position = new_pos; }
KaraokeMarker(int position) : position(position) { }
KaraokeMarker(int position, Pen *pen, FeetStyle style)
: position(position)
, pen(pen)
@ -62,11 +63,6 @@ public:
{
}
KaraokeMarker(int position)
: position(position)
{
}
operator int() const { return position; }
};
@ -79,7 +75,7 @@ public:
/// This does not support \kt, as it inherently requires that the end time of
/// one syllable be the same as the start time of the next one.
class AudioTimingControllerKaraoke final : public AudioTimingController {
std::deque<agi::signal::Connection> slots;
std::vector<agi::signal::Connection> connections;
agi::signal::Connection& file_changed_slot;
agi::Context *c; ///< Project context
@ -161,8 +157,8 @@ AudioTimingControllerKaraoke::AudioTimingControllerKaraoke(agi::Context *c, AssK
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Karaoke Mode")
, video_position_provider(c)
{
slots.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
slots.push_back(OPT_SUB("Audio/Auto/Commit", [=](agi::OptionValue const& opt) { auto_commit = opt.GetBool(); }));
connections.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
connections.push_back(OPT_SUB("Audio/Auto/Commit", [=](agi::OptionValue const& opt) { auto_commit = opt.GetBool(); }));
keyframes_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });
video_position_provider.AddMarkerMovedListener([=]{ AnnounceMarkerMoved(); });

View File

@ -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()

View File

@ -43,7 +43,6 @@
#include "compat.h"
#include <boost/filesystem/path.hpp>
#include <deque>
#include <memory>
#include <vector>
@ -215,8 +214,8 @@ namespace Automation4 {
/// Manager for scripts specified by a subtitle file
class LocalScriptManager final : public ScriptManager {
std::deque<agi::signal::Connection> slots;
agi::Context *context;
std::vector<agi::signal::Connection> connections;
void OnSubtitlesSave();
public:

View File

@ -39,15 +39,18 @@
#include "ass_info.h"
#include "ass_file.h"
#include "ass_style.h"
#include "async_video_provider.h"
#include "auto4_lua_factory.h"
#include "command/command.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "main.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "video_context.h"
#include "video_controller.h"
#include "utils.h"
#include <libaegisub/access.h>
@ -169,7 +172,7 @@ namespace {
const agi::Context *c = get_context(L);
int ms = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
if (c && c->project->Timecodes().IsLoaded())
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
else
lua_pushnil(L);
@ -182,7 +185,7 @@ namespace {
const agi::Context *c = get_context(L);
int frame = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
if (c && c->project->Timecodes().IsLoaded())
push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
else
lua_pushnil(L);
@ -192,9 +195,10 @@ namespace {
int video_size(lua_State *L)
{
const agi::Context *c = get_context(L);
if (c && c->videoController->IsLoaded()) {
push_value(L, c->videoController->GetWidth());
push_value(L, c->videoController->GetHeight());
if (c && c->project->VideoProvider()) {
auto provider = c->project->VideoProvider();
push_value(L, provider->GetWidth());
push_value(L, provider->GetHeight());
push_value(L, c->videoController->GetAspectRatioValue());
push_value(L, (int)c->videoController->GetAspectRatioType());
return 4;
@ -209,7 +213,7 @@ namespace {
{
const agi::Context *c = get_context(L);
if (c)
push_value(L, c->videoController->GetKeyFrames());
push_value(L, c->project->Keyframes());
else
lua_pushnil(L);
return 1;

View File

@ -40,10 +40,11 @@
#include "frame_main.h"
#include "grid_column.h"
#include "options.h"
#include "project.h"
#include "utils.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/util.h>
@ -123,30 +124,32 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
UpdateStyle();
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
connections.push_back(context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this));
connections.push_back(context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this));
connections.push_back(context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this));
connections = agi::signal::make_vector({
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this),
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this),
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this),
connections.push_back(context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this));
connections.push_back(context->selectionController->AddSelectionListener([&]{ Refresh(false); }));
context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this),
context->selectionController->AddSelectionListener([&]{ Refresh(false); }),
connections.push_back(OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this));
OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this),
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this),
OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this),
connections.push_back(OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this));
connections.push_back(OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }));
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this),
OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }),
});
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
}
@ -634,10 +637,10 @@ AssDialogue *BaseGrid::GetDialogue(int n) const {
}
bool BaseGrid::IsDisplayed(const AssDialogue *line) const {
if (!context->videoController->IsLoaded()) return false;
if (!context->project->VideoProvider()) return false;
int frame = context->videoController->GetFrameN();
return context->videoController->FrameAtTime(line->Start,agi::vfr::START) <= frame
&& context->videoController->FrameAtTime(line->End,agi::vfr::END) >= frame;
return context->project->Timecodes().FrameAtTime(line->Start, agi::vfr::START) <= frame
&& context->project->Timecodes().FrameAtTime(line->End, agi::vfr::END) >= frame;
}
void BaseGrid::OnCharHook(wxKeyEvent &event) {

View File

@ -36,7 +36,6 @@
#include <libaegisub/log.h>
#include <libaegisub/make_unique.h>
#include "../audio_controller.h"
#include "../compat.h"
#include "../dialog_about.h"
#include "../dialog_detached_video.h"
@ -48,9 +47,9 @@
#include "../libresrc/libresrc.h"
#include "../main.h"
#include "../options.h"
#include "../project.h"
#include "../preferences.h"
#include "../utils.h"
#include "../video_context.h"
namespace {
using cmd::Command;
@ -79,7 +78,7 @@ struct app_display_audio_subs final : public Command {
}
bool Validate(const agi::Context *c) override {
return c->audioController->IsAudioOpen();
return !!c->project->AudioProvider();
}
bool IsActive(const agi::Context *c) override {
@ -99,7 +98,7 @@ struct app_display_full final : public Command {
}
bool Validate(const agi::Context *c) override {
return c->audioController->IsAudioOpen() && c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
return c->project->AudioProvider() && c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
}
bool IsActive(const agi::Context *c) override {
@ -115,7 +114,7 @@ struct app_display_subs final : public Command {
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
void operator()(agi::Context *c) override {
c->frame->SetDisplayMode(0,0);
c->frame->SetDisplayMode(0, 0);
}
bool IsActive(const agi::Context *c) override {
@ -131,11 +130,11 @@ struct app_display_video_subs final : public Command {
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
void operator()(agi::Context *c) override {
c->frame->SetDisplayMode(1,0);
c->frame->SetDisplayMode(1, 0);
}
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
return c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
}
bool IsActive(const agi::Context *c) override {

View File

@ -37,15 +37,18 @@
#include "../audio_karaoke.h"
#include "../audio_timing.h"
#include "../compat.h"
#include "../include/aegisub/audio_provider.h"
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../selection_controller.h"
#include "../utils.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/make_unique.h>
#include <libaegisub/fs.h>
#include <libaegisub/io.h>
#include <wx/msgdlg.h>
@ -55,7 +58,7 @@ namespace {
struct validate_audio_open : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->audioController->IsAudioOpen();
return !!c->project->AudioProvider();
}
};
@ -67,35 +70,11 @@ struct audio_close final : public validate_audio_open {
STR_HELP("Close the currently open audio file")
void operator()(agi::Context *c) override {
c->audioController->CloseAudio();
c->project->CloseAudio();
}
};
namespace {
struct audio_open_from_file : public Command {
protected:
void do_open(agi::Context *c, agi::fs::path const& filename) {
try {
c->audioController->OpenAudio(filename);
}
catch (agi::UserCancelException const&) {}
catch (agi::fs::FileNotFound const& e) {
wxMessageBox(_("The audio file was not found: ") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
catch (agi::AudioDataNotFoundError const& e) {
wxMessageBox(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
catch (agi::AudioProviderOpenError const& e) {
wxMessageBox(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
catch (agi::Exception const& e) {
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
}
};
};
struct audio_open final : public audio_open_from_file {
struct audio_open final : public Command {
CMD_NAME("audio/open")
CMD_ICON(open_audio_menu)
STR_MENU("&Open Audio File...")
@ -107,9 +86,8 @@ struct audio_open final : public audio_open_from_file {
+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
+ _("All Files") + " (*.*)|*.*";
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);
if (filename.empty()) return;
do_open(c, filename);
if (!filename.empty())
c->project->LoadAudio(filename);
}
};
@ -120,12 +98,7 @@ struct audio_open_blank final : public Command {
STR_HELP("Open a 150 minutes blank audio clip, for debugging")
void operator()(agi::Context *c) override {
try {
c->audioController->OpenAudio("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000");
}
catch (agi::Exception const& e) {
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
c->project->LoadAudio("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000");
}
};
@ -136,16 +109,11 @@ struct audio_open_noise final : public Command {
STR_HELP("Open a 150 minutes noise-filled audio clip, for debugging")
void operator()(agi::Context *c) override {
try {
c->audioController->OpenAudio("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000");
}
catch (agi::Exception const& e) {
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
c->project->LoadAudio("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000");
}
};
struct audio_open_video final : public audio_open_from_file {
struct audio_open_video final : public Command {
CMD_NAME("audio/open/video")
CMD_ICON(open_audio_from_video_menu)
STR_MENU("Open Audio from &Video")
@ -154,11 +122,11 @@ struct audio_open_video final : public audio_open_from_file {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded();
return !c->project->VideoName().empty();
}
void operator()(agi::Context *c) override {
do_open(c, c->videoController->GetVideoName());
c->project->LoadAudio(c->project->VideoName());
}
};
@ -194,6 +162,29 @@ struct audio_view_waveform final : public Command {
}
};
class writer {
agi::io::Save outfile;
std::ostream& out;
public:
writer(agi::fs::path const& filename) : outfile(filename, true), out(outfile.Get()) { }
template<int N>
void write(const char(&str)[N]) {
out.write(str, N - 1);
}
void write(std::vector<char> const& data) {
out.write(data.data(), data.size());
}
template<typename Dest, typename Src>
void write(Src v) {
auto converted = static_cast<Dest>(v);
out.write(reinterpret_cast<char *>(&converted), sizeof(Dest));
}
};
struct audio_save_clip final : public Command {
CMD_NAME("audio/save/clip")
STR_MENU("Create audio clip")
@ -202,22 +193,55 @@ struct audio_save_clip final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->audioController->IsAudioOpen() && !c->selectionController->GetSelectedSet().empty();
return c->project->AudioProvider() && !c->selectionController->GetSelectedSet().empty();
}
void operator()(agi::Context *c) override {
auto const& sel = c->selectionController->GetSelectedSet();
if (sel.empty()) return;
auto filename = SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent);
if (filename.empty()) return;
AssTime start = INT_MAX, end = 0;
for (auto line : sel) {
start = std::min(start, line->Start);
end = std::max(end, line->End);
}
c->audioController->SaveClip(
SaveFileSelector(_("Save audio clip"), "", "", "wav", "", c->parent),
TimeRange(start, end));
auto provider = c->project->AudioProvider();
auto start_sample = (start * provider->GetSampleRate() + 999) / 1000;
auto end_sample = (end * provider->GetSampleRate() + 999) / 1000;
if (start_sample >= provider->GetNumSamples() || start_sample <= end_sample) return;
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
writer out{filename};
out.write("RIFF");
out.write<int32_t>(bufsize + 36);
out.write("WAVEfmt ");
out.write<int32_t>(16); // Size of chunk
out.write<int16_t>(1); // compression format (PCM)
out.write<int16_t>(provider->GetChannels());
out.write<int32_t>(provider->GetSampleRate());
out.write<int32_t>(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample());
out.write<int16_t>(provider->GetChannels() * provider->GetBytesPerSample());
out.write<int16_t>(provider->GetBytesPerSample() * 8);
out.write("data");
out.write<int32_t>(bufsize);
// samples per read
size_t spr = 65536 / bytes_per_sample;
std::vector<char> buf(bufsize);
for (int64_t i = start_sample; i < end_sample; i += spr) {
buf.resize(std::min<size_t>(spr, end_sample - i));
provider->GetAudio(&buf[0], i, buf.size());
out.write(buf);
}
}
};

View File

@ -40,7 +40,7 @@
#include "../main.h"
#include "../options.h"
#include "../utils.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/make_unique.h>

View File

@ -43,13 +43,14 @@
#include "../initial_line_state.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../search_replace_engine.h"
#include "../selection_controller.h"
#include "../subs_controller.h"
#include "../subs_edit_ctrl.h"
#include "../text_selection_controller.h"
#include "../utils.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/address_of_adaptor.h>
#include <libaegisub/of_type_adaptor.h>
@ -83,7 +84,7 @@ struct validate_sel_nonempty : public Command {
struct validate_video_and_sel_nonempty : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded() && !c->selectionController->GetSelectedSet().empty();
return c->project->VideoProvider() && !c->selectionController->GetSelectedSet().empty();
}
};

View File

@ -34,9 +34,10 @@
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../utils.h"
#include "../video_context.h"
#include <libaegisub/keyframe.h>
#include <libaegisub/make_unique.h>
namespace {
@ -51,11 +52,11 @@ struct keyframe_close final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->OverKeyFramesLoaded();
return c->project->CanCloseKeyframes();
}
void operator()(agi::Context *c) override {
c->videoController->CloseKeyframes();
c->project->CloseKeyframes();
}
};
@ -74,7 +75,7 @@ struct keyframe_open final : public Command {
c->parent);
if (!filename.empty())
c->videoController->LoadKeyframes(filename);
c->project->LoadKeyframes(filename);
}
};
@ -87,13 +88,15 @@ struct keyframe_save final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->KeyFramesLoaded();
return !c->project->Keyframes().empty();
}
void operator()(agi::Context *c) override {
auto filename = SaveFileSelector(_("Save keyframes file"), "Path/Last/Keyframes", "", "*.key.txt", "Text files (*.txt)|*.txt", c->parent);
if (!filename.empty())
c->videoController->SaveKeyframes(filename);
if (filename.empty()) return;
agi::keyframe::Save(filename, c->project->Keyframes());
config::mru->Add("Keyframes", filename);
}
};
}

View File

@ -29,19 +29,15 @@
#include "command.h"
#include "../audio_controller.h"
#include "../compat.h"
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../subs_controller.h"
#include "../video_context.h"
#include <libaegisub/make_unique.h>
#include <wx/event.h>
#include <wx/msgdlg.h>
namespace {
using cmd::Command;
@ -58,13 +54,7 @@ struct recent_audio_entry : public Command {
STR_HELP("Open recent audio")
void operator()(agi::Context *c, int id) {
try {
c->audioController->OpenAudio(config::mru->GetEntry("Audio", id));
}
catch (agi::UserCancelException const&) { }
catch (agi::Exception const& e) {
wxMessageBox(to_wx(e.GetChainedMessage()), "Error loading file", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
c->project->LoadAudio(config::mru->GetEntry("Audio", id));
}
};
@ -75,7 +65,7 @@ struct recent_keyframes_entry : public Command {
STR_HELP("Open recent keyframes")
void operator()(agi::Context *c, int id) {
c->videoController->LoadKeyframes(config::mru->GetEntry("Keyframes", id));
c->project->LoadKeyframes(config::mru->GetEntry("Keyframes", id));
}
};
@ -87,7 +77,7 @@ struct recent_subtitle_entry : public Command {
void operator()(agi::Context *c, int id) {
if (c->subsController->TryToClose() == wxCANCEL) return;
c->subsController->Load(config::mru->GetEntry("Subtitle", id));
c->project->LoadSubtitles(config::mru->GetEntry("Subtitle", id));
}
};
@ -98,7 +88,7 @@ struct recent_timecodes_entry : public Command {
STR_HELP("Open recent timecodes")
void operator()(agi::Context *c, int id) {
c->videoController->LoadTimecodes(config::mru->GetEntry("Timecodes", id));
c->project->LoadTimecodes(config::mru->GetEntry("Timecodes", id));
}
};
@ -109,7 +99,7 @@ struct recent_video_entry : public Command {
STR_HELP("Open recent videos")
void operator()(agi::Context *c, int id) {
c->videoController->SetVideo(config::mru->GetEntry("Video", id));
c->project->LoadVideo(config::mru->GetEntry("Video", id));
}
};

View File

@ -43,12 +43,13 @@
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../search_replace_engine.h"
#include "../selection_controller.h"
#include "../subs_controller.h"
#include "../subtitle_format.h"
#include "../utils.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/address_of_adaptor.h>
#include <libaegisub/charset_conv.h>
@ -71,7 +72,7 @@ struct validate_nonempty_selection : public Command {
struct validate_nonempty_selection_video_loaded : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded() && !c->selectionController->GetSelectedSet().empty();
return c->project->VideoProvider() && !c->selectionController->GetSelectedSet().empty();
}
};
@ -227,7 +228,7 @@ struct subtitle_new final : public Command {
void operator()(agi::Context *c) override {
if (c->subsController->TryToClose() != wxCANCEL)
c->subsController->Close();
c->project->CloseSubtitles();
}
};
@ -242,7 +243,7 @@ struct subtitle_open final : public Command {
if (c->subsController->TryToClose() == wxCANCEL) return;
auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "","", SubtitleFormat::GetWildcards(0), c->parent);
if (!filename.empty())
c->subsController->Load(filename);
c->project->LoadSubtitles(filename);
}
};
@ -256,7 +257,7 @@ struct subtitle_open_autosave final : public Command {
if (c->subsController->TryToClose() == wxCANCEL) return;
DialogAutosave dialog(c->parent);
if (dialog.ShowModal() == wxID_OK)
c->subsController->Load(dialog.ChosenFile());
c->project->LoadSubtitles(dialog.ChosenFile());
}
};
@ -276,7 +277,7 @@ struct subtitle_open_charset final : public Command {
wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
if (charset.empty()) return;
c->subsController->Load(filename, from_wx(charset));
c->project->LoadSubtitles(filename, from_wx(charset));
}
};
@ -289,11 +290,11 @@ struct subtitle_open_video final : public Command {
void operator()(agi::Context *c) override {
if (c->subsController->TryToClose() == wxCANCEL) return;
c->subsController->Load(c->videoController->GetVideoName(), "binary");
c->subsController->Load(c->project->VideoName(), "binary");
}
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded() && c->videoController->HasSubtitles();
return c->project->CanLoadSubtitlesFromVideo();
}
};
@ -384,7 +385,6 @@ struct subtitle_select_visible final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
void operator()(agi::Context *c) override {
if (!c->videoController->IsLoaded()) return;
c->videoController->Stop();
Selection new_selection;
@ -404,7 +404,7 @@ struct subtitle_select_visible final : public Command {
}
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded();
return !!c->project->VideoProvider();
}
};

View File

@ -33,6 +33,7 @@
#include "../ass_dialogue.h"
#include "../ass_file.h"
#include "../async_video_provider.h"
#include "../audio_controller.h"
#include "../audio_timing.h"
#include "../dialog_manager.h"
@ -40,38 +41,39 @@
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../selection_controller.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/make_unique.h>
#include <algorithm>
namespace {
using cmd::Command;
using cmd::Command;
struct validate_video_loaded : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded();
struct validate_video_loaded : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return !!c->project->VideoProvider();
}
};
struct validate_adjoinable : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
auto sel = c->selectionController->GetSortedSelection();
if (sel.empty()) return false;
for (size_t i = 1; i < sel.size(); ++i) {
if (sel[i]->Row != sel[i - 1]->Row + 1)
return false;
}
};
return true;
}
};
struct validate_adjoinable : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
auto sel = c->selectionController->GetSortedSelection();
if (sel.empty()) return false;
for (size_t i = 1; i < sel.size(); ++i) {
if (sel[i]->Row != sel[i - 1]->Row + 1)
return false;
}
return true;
}
};
static void adjoin_lines(agi::Context *c, bool set_start) {
void adjoin_lines(agi::Context *c, bool set_start) {
auto const& sel = c->selectionController->GetSelectedSet();
AssDialogue *prev = nullptr;
size_t seen = 0;
@ -129,8 +131,6 @@ struct time_frame_current final : public validate_video_loaded {
STR_HELP("Shift selection so that the active line starts at current frame")
void operator()(agi::Context *c) override {
if (!c->videoController->IsLoaded()) return;
auto const& sel = c->selectionController->GetSelectedSet();
const auto active_line = c->selectionController->GetActiveLine();
@ -162,8 +162,7 @@ struct time_shift final : public Command {
static void snap_subs_video(agi::Context *c, bool set_start) {
auto const& sel = c->selectionController->GetSelectedSet();
if (!c->videoController->IsLoaded() || sel.empty()) return;
if (sel.empty()) return;
int start = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::START);
int end = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::END);
@ -198,19 +197,19 @@ struct time_snap_scene final : public validate_video_loaded {
STR_HELP("Set start and end of subtitles to the keyframes around current video frame")
void operator()(agi::Context *c) override {
VideoContext *con = c->videoController.get();
if (!con->IsLoaded() || !con->KeyFramesLoaded()) return;
auto const& keyframes = c->project->Keyframes();
if (keyframes.empty()) return;
VideoController *con = c->videoController.get();
int curFrame = con->GetFrameN();
int prev = 0;
int next = 0;
auto const& keyframes = con->GetKeyFrames();
if (curFrame < keyframes.front())
next = keyframes.front();
else if (curFrame >= keyframes.back()) {
prev = keyframes.back();
next = con->GetLength();
next = c->project->VideoProvider()->GetFrameCount();
}
else {
auto kf = std::lower_bound(keyframes.begin(), keyframes.end(), curFrame);

View File

@ -31,14 +31,18 @@
#include "command.h"
#include "../async_video_provider.h"
#include "../compat.h"
#include "../include/aegisub/context.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../utils.h"
#include "../video_context.h"
#include <libaegisub/make_unique.h>
#include <wx/msgdlg.h>
namespace {
using cmd::Command;
@ -51,11 +55,11 @@ struct timecode_close final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->OverTimecodesLoaded();
return c->project->CanCloseTimecodes();
}
void operator()(agi::Context *c) override {
c->videoController->CloseTimecodes();
c->project->CloseTimecodes();
}
};
@ -70,7 +74,7 @@ struct timecode_open final : public Command {
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
auto filename = OpenFileSelector(_("Open Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
if (!filename.empty())
c->videoController->LoadTimecodes(filename);
c->project->LoadTimecodes(filename);
}
};
@ -83,14 +87,22 @@ struct timecode_save final : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->TimecodesLoaded();
return c->project->Timecodes().IsLoaded();
}
void operator()(agi::Context *c) override {
auto str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
auto filename = SaveFileSelector(_("Save Timecodes File"), "Path/Last/Timecodes", "", "", str, c->parent);
if (!filename.empty())
c->videoController->SaveTimecodes(filename);
if (filename.empty()) return;
try {
auto provider = c->project->VideoProvider();
c->project->Timecodes().Save(filename, provider ? provider->GetFrameCount() : -1);
config::mru->Add("Timecodes", filename);
}
catch (agi::Exception const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, c->parent);
}
}
};
}

View File

@ -46,7 +46,7 @@
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../resolution_resampler.h"
#include "../video_context.h"
#include "../video_controller.h"
#include <libaegisub/fs.h>
#include <libaegisub/path.h>

View File

@ -33,6 +33,7 @@
#include "../ass_dialogue.h"
#include "../ass_time.h"
#include "../async_video_provider.h"
#include "../compat.h"
#include "../dialog_detached_video.h"
#include "../dialog_dummy_video.h"
@ -44,10 +45,11 @@
#include "../include/aegisub/subtitles_provider.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../selection_controller.h"
#include "../utils.h"
#include "../video_box.h"
#include "../video_context.h"
#include "../video_controller.h"
#include "../video_display.h"
#include "../video_frame.h"
#include "../video_slider.h"
@ -61,7 +63,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/format.hpp>
#include <wx/clipbrd.h>
#include <wx/msgdlg.h>
#include <wx/textdlg.h>
@ -72,14 +73,14 @@ namespace {
struct validator_video_loaded : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded();
return !!c->project->VideoProvider();
}
};
struct validator_video_attached : public Command {
CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override {
return c->videoController->IsLoaded() && !c->dialog->Get<DialogDetachedVideo>();
return !!c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
}
};
@ -206,7 +207,7 @@ struct video_close final : public validator_video_loaded {
STR_HELP("Close the currently open video file")
void operator()(agi::Context *c) override {
c->videoController->SetVideo("");
c->project->CloseVideo();
}
};
@ -291,6 +292,10 @@ struct video_focus_seek final : public validator_video_loaded {
}
};
wxImage get_image(agi::Context *c, bool raw) {
return GetImage(*c->project->VideoProvider()->GetFrame(c->videoController->GetFrameN(), raw));
}
struct video_frame_copy final : public validator_video_loaded {
CMD_NAME("video/frame/copy")
STR_MENU("Copy image to Clipboard")
@ -298,7 +303,7 @@ struct video_frame_copy final : public validator_video_loaded {
STR_HELP("Copy the currently displayed frame to the clipboard")
void operator()(agi::Context *c) override {
SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN())), 24));
SetClipboard(wxBitmap(get_image(c, false), 24));
}
};
@ -309,7 +314,7 @@ struct video_frame_copy_raw final : public validator_video_loaded {
STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles")
void operator()(agi::Context *c) override {
SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), true)), 24));
SetClipboard(wxBitmap(get_image(c, true), 24));
}
};
@ -360,10 +365,10 @@ struct video_frame_next_keyframe final : public validator_video_loaded {
STR_HELP("Seek to the next keyframe")
void operator()(agi::Context *c) override {
auto const& kf = c->videoController->GetKeyFrames();
auto const& kf = c->project->Keyframes();
auto pos = lower_bound(kf.begin(), kf.end(), c->videoController->GetFrameN() + 1);
c->videoController->JumpToFrame(pos == kf.end() ? c->videoController->GetLength() - 1 : *pos);
c->videoController->JumpToFrame(pos == kf.end() ? c->project->VideoProvider()->GetFrameCount() - 1 : *pos);
}
};
@ -427,7 +432,7 @@ struct video_frame_prev_keyframe final : public validator_video_loaded {
STR_HELP("Seek to the previous keyframe")
void operator()(agi::Context *c) override {
auto const& kf = c->videoController->GetKeyFrames();
auto const& kf = c->project->Keyframes();
if (kf.empty()) {
c->videoController->JumpToFrame(0);
return;
@ -459,7 +464,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
auto option = OPT_GET("Path/Screenshot")->GetString();
agi::fs::path basepath;
auto videoname = c->videoController->GetVideoName();
auto videoname = c->project->VideoName();
bool is_dummy = boost::starts_with(videoname.string(), "?dummy");
// Is it a path specifier and not an actual fixed path?
@ -490,7 +495,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
path = str(boost::format("%s_%03d_%d.png") % basepath.string() % session_shot_count++ % c->videoController->GetFrameN());
} while (agi::fs::FileExists(path));
GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), raw)).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
get_image(c, raw).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
}
struct video_frame_save final : public validator_video_loaded {
@ -524,10 +529,8 @@ struct video_jump final : public validator_video_loaded {
void operator()(agi::Context *c) override {
c->videoController->Stop();
if (c->videoController->IsLoaded()) {
DialogJumpTo(c).ShowModal();
c->videoSlider->SetFocus();
}
DialogJumpTo(c).ShowModal();
c->videoSlider->SetFocus();
}
};
@ -539,9 +542,8 @@ struct video_jump_end final : public validator_video_loaded {
STR_HELP("Jump the video to the end frame of current subtitle")
void operator()(agi::Context *c) override {
if (AssDialogue *active_line = c->selectionController->GetActiveLine()) {
if (auto active_line = c->selectionController->GetActiveLine())
c->videoController->JumpToTime(active_line->End, agi::vfr::END);
}
}
};
@ -553,7 +555,7 @@ struct video_jump_start final : public validator_video_loaded {
STR_HELP("Jump the video to the start frame of current subtitle")
void operator()(agi::Context *c) override {
if (AssDialogue *active_line = c->selectionController->GetActiveLine())
if (auto active_line = c->selectionController->GetActiveLine())
c->videoController->JumpToTime(active_line->Start);
}
};
@ -570,7 +572,7 @@ struct video_open final : public Command {
+ _("All Files") + " (*.*)|*.*";
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
if (!filename.empty())
c->videoController->SetVideo(filename);
c->project->LoadVideo(filename);
}
};
@ -584,7 +586,7 @@ struct video_open_dummy final : public Command {
void operator()(agi::Context *c) override {
std::string fn = DialogDummyVideo::CreateDummyVideo(c->parent);
if (!fn.empty())
c->videoController->SetVideo(fn);
c->project->LoadVideo(fn);
}
};

View File

@ -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 {

View File

@ -21,11 +21,12 @@
#include "auto4_base.h"
#include "dialog_manager.h"
#include "initial_line_state.h"
#include "project.h"
#include "search_replace_engine.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "text_selection_controller.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -34,10 +35,11 @@ Context::Context()
: ass(make_unique<AssFile>())
, textSelectionController(make_unique<TextSelectionController>())
, subsController(make_unique<SubsController>(this))
, project(make_unique<Project>(this))
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
, videoController(make_unique<VideoContext>(this))
, audioController(make_unique<AudioController>(this))
, selectionController(make_unique<SelectionController>(this))
, videoController(make_unique<VideoController>(this))
, audioController(make_unique<AudioController>(this))
, initialLineState(make_unique<InitialLineState>(this))
, search(make_unique<SearchReplaceEngine>(this))
, dialog(make_unique<DialogManager>())

View File

@ -38,9 +38,10 @@
#include "include/aegisub/hotkey.h"
#include "options.h"
#include "persist_location.h"
#include "project.h"
#include "utils.h"
#include "video_box.h"
#include "video_context.h"
#include "video_controller.h"
#include "video_display.h"
#include <libaegisub/make_unique.h>
@ -55,12 +56,12 @@ DialogDetachedVideo::DialogDetachedVideo(agi::Context *context)
, context(context)
, old_display(context->videoDisplay)
, old_slider(context->videoSlider)
, video_open(context->videoController->AddVideoOpenListener(&DialogDetachedVideo::OnVideoOpen, this))
, video_open(context->project->AddVideoProviderListener(&DialogDetachedVideo::OnVideoOpen, this))
{
// Set obscure stuff
SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES);
SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring()));
old_display->Unload();
@ -108,8 +109,7 @@ void DialogDetachedVideo::OnClose(wxCloseEvent &evt) {
OPT_SET("Video/Detached/Enabled")->SetBool(false);
if (context->videoController->IsLoaded())
context->videoController->JumpToFrame(context->videoController->GetFrameN());
context->videoController->JumpToFrame(context->videoController->GetFrameN());
evt.Skip();
}
@ -128,8 +128,8 @@ void DialogDetachedVideo::OnKeyDown(wxKeyEvent &evt) {
}
void DialogDetachedVideo::OnVideoOpen() {
if (context->videoController->IsLoaded())
SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
if (context->project->VideoProvider())
SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring()));
else {
Close();
OPT_SET("Video/Detached/Enabled")->SetBool(true);

View File

@ -34,12 +34,14 @@
#include "dialog_jumpto.h"
#include "include/aegisub/context.h"
#include "ass_time.h"
#include "async_video_provider.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "project.h"
#include "timeedit_ctrl.h"
#include "validators.h"
#include "video_context.h"
#include "video_controller.h"
#include <wx/button.h>
#include <wx/sizer.h>
@ -57,7 +59,7 @@ DialogJumpTo::DialogJumpTo(agi::Context *c)
auto LabelTime = new wxStaticText(this, -1, _("Time: "));
JumpFrame = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(-1,-1),wxTE_PROCESS_ENTER, IntValidator((int)jumpframe));
JumpFrame->SetMaxLength(std::to_string(c->videoController->GetLength() - 1).size());
JumpFrame->SetMaxLength(std::to_string(c->project->VideoProvider()->GetFrameCount() - 1).size());
JumpTime = new TimeEdit(this, -1, c, AssTime(c->videoController->TimeAtFrame(jumpframe)).GetAssFormated(), wxSize(-1,-1));
auto TimesSizer = new wxGridSizer(2, 5, 5);
@ -95,7 +97,7 @@ void DialogJumpTo::OnInitDialog(wxInitDialogEvent&) {
void DialogJumpTo::OnOK(wxCommandEvent &) {
EndModal(0);
c->videoController->JumpToFrame(std::min<int>(jumpframe, c->videoController->GetLength() - 1));
c->videoController->JumpToFrame(jumpframe);
}
void DialogJumpTo::OnEditTime (wxCommandEvent &) {

View File

@ -35,13 +35,14 @@
#include "dialog_properties.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "compat.h"
#include "help_button.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "project.h"
#include "resolution_resampler.h"
#include "validators.h"
#include "video_context.h"
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp>
@ -80,7 +81,7 @@ DialogProperties::DialogProperties(agi::Context *c)
ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,IntValidator(c->ass->GetScriptInfoAsInt("PlayResY")));
wxButton *FromVideo = new wxButton(this,-1,_("From &video"));
if (!c->videoController->IsLoaded())
if (!c->project->VideoProvider())
FromVideo->Enable(false);
else
FromVideo->Bind(wxEVT_BUTTON, &DialogProperties::OnSetFromVideo, this);
@ -172,6 +173,6 @@ int DialogProperties::SetInfoIfDifferent(std::string const& key, std::string con
}
void DialogProperties::OnSetFromVideo(wxCommandEvent &) {
ResX->SetValue(std::to_wstring(c->videoController->GetWidth()));
ResY->SetValue(std::to_wstring(c->videoController->GetHeight()));
ResX->SetValue(std::to_wstring(c->project->VideoProvider()->GetWidth()));
ResY->SetValue(std::to_wstring(c->project->VideoProvider()->GetHeight()));
}

View File

@ -20,14 +20,14 @@
#include "dialog_resample.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "compat.h"
#include "help_button.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "libresrc/libresrc.h"
#include "project.h"
#include "resolution_resampler.h"
#include "validators.h"
#include "video_context.h"
#include <boost/range/size.hpp>
#include <wx/checkbox.h>
@ -57,10 +57,10 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
settings.source_y = script_h;
settings.source_matrix = script_mat = MatrixFromString(c->ass->GetScriptInfo("YCbCr Matrix"));
if (c->videoController->IsLoaded()) {
settings.dest_x = video_w = c->videoController->GetWidth();
settings.dest_y = video_h = c->videoController->GetHeight();
settings.dest_matrix = video_mat = MatrixFromString(c->videoController->GetProvider()->GetRealColorSpace());
if (auto provider = c->project->VideoProvider()) {
settings.dest_x = video_w = provider->GetWidth();
settings.dest_y = video_h = provider->GetHeight();
settings.dest_matrix = video_mat = MatrixFromString(provider->GetRealColorSpace());
}
else {
settings.dest_x = script_w;
@ -186,7 +186,7 @@ void DialogResample::SetSourceFromScript(wxCommandEvent&) {
}
void DialogResample::UpdateButtons() {
from_video->Enable(c->videoController->IsLoaded() &&
from_video->Enable(c->project->VideoProvider() &&
(dest_x->GetValue() != video_w || dest_y->GetValue() != video_h));
from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h);

View File

@ -29,10 +29,10 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "timeedit_ctrl.h"
#include "video_context.h"
#include <libaegisub/fs.h>
#include <libaegisub/io.h>
@ -100,7 +100,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
, context(context)
, history_filename(config::path->Decode("?user/shift_history.json"))
, history(agi::make_unique<json::Array>())
, timecodes_loaded_slot(context->videoController->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this))
, timecodes_loaded_slot(context->project->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this))
, selected_set_changed_slot(context->selectionController->AddSelectionListener(&DialogShiftTimes::OnSelectedSetChanged, this))
{
SetIcon(GETICON(shift_times_toolbutton_16));
@ -138,7 +138,7 @@ DialogShiftTimes::DialogShiftTimes(agi::Context *context)
clear_button->Bind(wxEVT_BUTTON, &DialogShiftTimes::OnClear, this);
// Set initial control states
OnTimecodesLoaded(context->videoController->FPS());
OnTimecodesLoaded(context->project->Timecodes());
OnSelectedSetChanged();
LoadHistory();

View File

@ -33,8 +33,9 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "persist_location.h"
#include "project.h"
#include "selection_controller.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -111,11 +112,11 @@ DialogStyling::DialogStyling(agi::Context *context)
actions_box->AddStretchSpacer(1);
play_audio = new wxButton(this, -1, _("Play &Audio"));
play_audio->Enable(c->audioController->IsAudioOpen());
play_audio->Enable(!!c->project->AudioProvider());
actions_box->Add(play_audio, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
play_video = new wxButton(this, -1, _("Play &Video"));
play_video->Enable(c->videoController->IsLoaded());
play_video->Enable(!!c->project->VideoProvider());
actions_box->Add(play_video, 0, wxBOTTOM | wxRIGHT, 5);
actions_box->AddStretchSpacer(1);
@ -180,8 +181,8 @@ void DialogStyling::Commit(bool next) {
void DialogStyling::OnActivate(wxActivateEvent &) {
if (!IsActive()) return;
play_video->Enable(c->videoController->IsLoaded());
play_audio->Enable(c->audioController->IsAudioOpen());
play_video->Enable(!!c->project->VideoProvider());
play_audio->Enable(!!c->project->AudioProvider());
style_list->Set(to_wx(c->ass->GetStyles()));

View File

@ -37,14 +37,15 @@
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
#include "async_video_provider.h"
#include "compat.h"
#include "help_button.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "utils.h"
#include "video_context.h"
#include <libaegisub/address_of_adaptor.h>
@ -118,24 +119,24 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
adjOverlap = OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent Overlap")->GetInt();
// Styles box
wxSizer *LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles"));
auto LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles"));
StyleList = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(150,150), to_wx(c->ass->GetStyles()));
StyleList->SetToolTip(_("Select styles to process. Unchecked ones will be ignored."));
wxButton *all = new wxButton(this,-1,_("&All"));
auto all = new wxButton(this,-1,_("&All"));
all->SetToolTip(_("Select all styles"));
wxButton *none = new wxButton(this,-1,_("&None"));
auto none = new wxButton(this,-1,_("&None"));
none->SetToolTip(_("Deselect all styles"));
// Options box
wxStaticBoxSizer *optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options"));
auto optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options"));
onlySelection = new wxCheckBox(this,-1,_("Affect &selection only"));
onlySelection->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool());
optionsSizer->Add(onlySelection,1,wxALL,0);
// Lead-in/out box
wxStaticBoxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Lead-in/Lead-out"));
auto LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Lead-in/Lead-out"));
hasLeadIn = make_check(LeadSizer, _("Add lead &in:"),
"Tool/Timing Post Processor/Enable/Lead/IN",
@ -150,12 +151,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
LeadSizer->AddStretchSpacer(1);
// Adjacent subs sizer
wxStaticBoxSizer *AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Make adjacent subtitles continuous"));
auto AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Make adjacent subtitles continuous"));
adjsEnable = make_check(AdjacentSizer, _("&Enable"),
"Tool/Timing Post Processor/Enable/Adjacent",
_("Enable snapping of subtitles together if they are within a certain distance of each other"));
wxSizer *adjBoxes = new wxBoxSizer(wxHORIZONTAL);
auto adjBoxes = new wxBoxSizer(wxHORIZONTAL);
make_ctrl(this, adjBoxes, _("Max gap:"), &adjGap, adjsEnable,
_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds"));
make_ctrl(this, adjBoxes, _("Max overlap:"), &adjOverlap, adjsEnable,
@ -164,19 +165,19 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
adjacentBias = new wxSlider(this, -1, mid<int>(0, OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble() * 100, 100), 0, 100, wxDefaultPosition, wxSize(-1,20));
adjacentBias->SetToolTip(_("Sets how to set the adjoining of lines. If set totally to left, it will extend or shrink start time of the second line; if totally to right, it will extend or shrink the end time of the first line."));
wxSizer *adjSliderSizer = new wxBoxSizer(wxHORIZONTAL);
auto adjSliderSizer = new wxBoxSizer(wxHORIZONTAL);
adjSliderSizer->Add(new wxStaticText(this, -1, _("Bias: Start <- ")), wxSizerFlags().Center());
adjSliderSizer->Add(adjacentBias, wxSizerFlags(1).Center());
adjSliderSizer->Add(new wxStaticText(this, -1, _(" -> End")), wxSizerFlags().Center());
wxSizer *adjRightSizer = new wxBoxSizer(wxVERTICAL);
auto adjRightSizer = new wxBoxSizer(wxVERTICAL);
adjRightSizer->Add(adjBoxes, wxSizerFlags().Expand());
adjRightSizer->Add(adjSliderSizer, wxSizerFlags().Expand().Border(wxTOP));
AdjacentSizer->Add(adjRightSizer);
// Keyframes sizer
wxStaticBoxSizer *KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
wxSizer *KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
auto KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping"));
auto KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
keysEnable = new wxCheckBox(this, -1, _("E&nable"));
keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold"));
@ -184,7 +185,7 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
KeyframesFlexSizer->Add(keysEnable,0,wxRIGHT|wxEXPAND,10);
// Keyframes are only available if timecodes are loaded
bool keysAvailable = c->videoController->KeyFramesLoaded() && c->videoController->TimecodesLoaded();
bool keysAvailable = !c->project->Keyframes().empty() && c->project->Timecodes().IsLoaded();
if (!keysAvailable) {
keysEnable->SetValue(false);
keysEnable->Enable(false);
@ -208,12 +209,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
KeyframesSizer->AddStretchSpacer(1);
// Button sizer
wxStdDialogButtonSizer *ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
auto ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
ApplyButton = ButtonSizer->GetAffirmativeButton();
ButtonSizer->GetHelpButton()->Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Timing Processor"));
// Right Sizer
wxSizer *RightSizer = new wxBoxSizer(wxVERTICAL);
auto RightSizer = new wxBoxSizer(wxVERTICAL);
RightSizer->Add(optionsSizer,0,wxBOTTOM|wxEXPAND,5);
RightSizer->Add(LeadSizer,0,wxBOTTOM|wxEXPAND,5);
RightSizer->Add(AdjacentSizer,0,wxBOTTOM|wxEXPAND,5);
@ -222,7 +223,7 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
RightSizer->Add(ButtonSizer,0,wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND,0);
// Style buttons sizer
wxSizer *StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
auto StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL);
StyleButtonsSizer->Add(all,1,0,0);
StyleButtonsSizer->Add(none,1,0,0);
@ -231,12 +232,12 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c)
LeftSizer->Add(StyleButtonsSizer, wxSizerFlags().Expand());
// Top Sizer
wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL);
auto TopSizer = new wxBoxSizer(wxHORIZONTAL);
TopSizer->Add(LeftSizer,0,wxRIGHT|wxEXPAND,5);
TopSizer->Add(RightSizer,1,wxALL|wxEXPAND,0);
// Main Sizer
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
auto MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5);
SetSizerAndFit(MainSizer);
CenterOnParent();
@ -289,10 +290,10 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) {
}
std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
std::set<std::string> styles;
std::set<boost::flyweight<std::string>> styles;
for (size_t i = 0; i < StyleList->GetCount(); ++i) {
if (StyleList->IsChecked(i))
styles.insert(from_wx(StyleList->GetString(i)));
styles.insert(boost::flyweight<std::string>(from_wx(StyleList->GetString(i))));
}
std::vector<AssDialogue*> sorted;
@ -382,28 +383,27 @@ void DialogTimingProcessor::Process() {
// Keyframe snapping
if (keysEnable->IsChecked()) {
std::vector<int> kf = c->videoController->GetKeyFrames();
if (c->videoController->IsLoaded())
kf.push_back(c->videoController->GetLength() - 1);
std::vector<int> kf = c->project->Keyframes();
auto fps = c->project->Timecodes();
if (auto provider = c->project->VideoProvider())
kf.push_back(provider->GetFrameCount() - 1);
for (AssDialogue *cur : sorted) {
// Get start/end frames
int startF = c->videoController->FrameAtTime(cur->Start, agi::vfr::START);
int endF = c->videoController->FrameAtTime(cur->End, agi::vfr::END);
int startF = fps.FrameAtTime(cur->Start, agi::vfr::START);
int endF = fps.FrameAtTime(cur->End, agi::vfr::END);
// Get closest for start
int closest = get_closest_kf(kf, startF);
int time = c->videoController->TimeAtFrame(closest, agi::vfr::START);
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart)) {
int time = fps.TimeAtFrame(closest, agi::vfr::START);
if ((closest > startF && time - cur->Start <= beforeStart) || (closest < startF && cur->Start - time <= afterStart))
cur->Start = time;
}
// Get closest for end
closest = get_closest_kf(kf, endF) - 1;
time = c->videoController->TimeAtFrame(closest, agi::vfr::END);
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd)) {
time = fps.TimeAtFrame(closest, agi::vfr::END);
if ((closest > endF && time - cur->End <= beforeEnd) || (closest < endF && cur->End - time <= afterEnd))
cur->End = time;
}
}
}

View File

@ -32,10 +32,11 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "persist_location.h"
#include "project.h"
#include "subs_edit_ctrl.h"
#include "selection_controller.h"
#include "utils.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -127,12 +128,12 @@ DialogTranslation::DialogTranslation(agi::Context *c)
wxStaticBoxSizer *actions_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Actions"));
wxButton *play_audio = new wxButton(this, -1, _("Play &Audio"));
play_audio->Enable(c->audioController->IsAudioOpen());
play_audio->Enable(!!c->project->AudioProvider());
play_audio->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayAudioButton, this);
actions_box->Add(play_audio, 0, wxALL, 5);
wxButton *play_video = new wxButton(this, -1, _("Play &Video"));
play_video->Enable(c->videoController->IsLoaded());
play_video->Enable(!!c->project->VideoProvider());
play_video->Bind(wxEVT_BUTTON, &DialogTranslation::OnPlayVideoButton, this);
actions_box->Add(play_video, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);

View File

@ -35,13 +35,12 @@
#include "dialog_video_details.h"
#include "ass_time.h"
#include "async_video_provider.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "video_context.h"
#include "project.h"
#include <boost/rational.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
@ -49,10 +48,11 @@
DialogVideoDetails::DialogVideoDetails(agi::Context *c)
: wxDialog(c->parent , -1, _("Video Details"))
{
auto width = c->videoController->GetWidth();
auto height = c->videoController->GetHeight();
auto framecount = c->videoController->GetLength();
auto fps = c->videoController->FPS();
auto provider = c->project->VideoProvider();
auto width = provider->GetWidth();
auto height = provider->GetHeight();
auto framecount = provider->GetFrameCount();
auto fps = provider->GetFPS();
boost::rational<int> ar(width, height);
auto fg = new wxFlexGridSizer(2, 5, 10);
@ -60,11 +60,11 @@ DialogVideoDetails::DialogVideoDetails(agi::Context *c)
fg->Add(new wxStaticText(this, -1, name), 0, wxALIGN_CENTRE_VERTICAL);
fg->Add(new wxTextCtrl(this, -1, value, wxDefaultPosition, wxSize(300,-1), wxTE_READONLY), 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
};
make_field(_("File name:"), c->videoController->GetVideoName().wstring());
make_field(_("File name:"), c->project->VideoName().wstring());
make_field(_("FPS:"), wxString::Format("%.3f", fps.FPS()));
make_field(_("Resolution:"), wxString::Format("%dx%d (%d:%d)", width, height, ar.numerator(), ar.denominator()));
make_field(_("Length:"), wxString::Format(_("%d frames (%s)"), framecount, to_wx(AssTime(fps.TimeAtFrame(framecount - 1)).GetAssFormated(true))));
make_field(_("Decoder:"), to_wx(c->videoController->GetProvider()->GetDecoderName()));
make_field(_("Decoder:"), to_wx(provider->GetDecoderName()));
wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Video"));
video_sizer->Add(fg);

View File

@ -17,11 +17,12 @@
#include "dialog_video_properties.h"
#include "ass_file.h"
#include "include/aegisub/video_provider.h"
#include "async_video_provider.h"
#include "options.h"
#include "resolution_resampler.h"
#include <wx/dialog.h>
#include <wx/intl.h>
#include <wx/radiobox.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
@ -79,9 +80,7 @@ public:
Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { EndModal(0); }, wxID_CANCEL);
}
};
}
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent) {
bool update_video_properties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent) {
bool commit_subs = false;
// When opening dummy video only want to set the script properties if
@ -156,3 +155,9 @@ bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxW
return true;
}
}
}
void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent) {
if (update_video_properties(file, new_provider, parent))
file->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
}

View File

@ -15,9 +15,8 @@
// Aegisub Project http://www.aegisub.org/
class AssFile;
class VideoProvider;
class AsyncVideoProvider;
class wxWindow;
/// Update the video properties for a newly opened video, possibly prompting the user about what to do
/// @return Does the file need to be committed?
bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxWindow *parent);
void UpdateVideoProperties(AssFile *file, const AsyncVideoProvider *new_provider, wxWindow *parent);

View File

@ -27,24 +27,19 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file export_framerate.cpp
/// @brief Transform Framerate export filter
/// @ingroup export
///
#include "export_framerate.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "project.h"
#include "utils.h"
#include "video_context.h"
#include <libaegisub/of_type_adaptor.h>
#include <utility>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/panel.h>
@ -65,18 +60,18 @@ void AssTransformFramerateFilter::ProcessSubs(AssFile *subs, wxWindow *) {
}
wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
wxWindow *base = new wxPanel(parent, -1);
LoadSettings(true, c);
wxWindow *base = new wxPanel(parent, -1);
// Input sizer
wxSizer *InputSizer = new wxBoxSizer(wxHORIZONTAL);
auto InputSizer = new wxBoxSizer(wxHORIZONTAL);
wxString initialInput;
wxButton *FromVideo = new wxButton(base,-1,_("From &video"));
if (Input->IsLoaded()) {
initialInput = wxString::Format("%2.3f",Input->FPS());
auto FromVideo = new wxButton(base,-1,_("From &video"));
if (Input.IsLoaded()) {
initialInput = wxString::Format("%2.3f", Input.FPS());
FromVideo->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) {
InputFramerate->SetValue(wxString::Format("%g", c->videoController->FPS().FPS()));
InputFramerate->SetValue(wxString::Format("%g", c->project->Timecodes().FPS()));
});
}
else {
@ -89,9 +84,9 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
InputSizer->AddStretchSpacer(1);
// Output sizers
wxSizer *OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
wxSizer *OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
wxSizer *OutputSizer = new wxBoxSizer(wxVERTICAL);
auto OutputSizerTop = new wxBoxSizer(wxHORIZONTAL);
auto OutputSizerBottom = new wxBoxSizer(wxHORIZONTAL);
auto OutputSizer = new wxBoxSizer(wxVERTICAL);
// Output top line
RadioOutputVFR = new wxRadioButton(base,-1,_("V&ariable"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
@ -100,7 +95,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
// Output bottom line
RadioOutputCFR = new wxRadioButton(base,-1,_("&Constant: "));
wxString initialOutput = initialInput;
if (!Output->IsVFR()) {
if (!Output.IsVFR()) {
RadioOutputVFR->Enable(false);
RadioOutputCFR->SetValue(true);
}
@ -117,7 +112,7 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
OutputSizer->Add(OutputSizerBottom,0,wxLEFT,5);
// Main window
wxSizer *MainSizer = new wxFlexGridSizer(3,2,5,10);
auto MainSizer = new wxFlexGridSizer(3,2,5,10);
MainSizer->Add(new wxStaticText(base,-1,_("Input framerate: ")),0,wxEXPAND | wxALIGN_CENTER_VERTICAL,0);
MainSizer->Add(InputSizer,0,wxEXPAND,0);
MainSizer->Add(new wxStaticText(base,-1,_("Output: ")),0,wxALIGN_CENTER_VERTICAL,0);
@ -133,29 +128,27 @@ void AssTransformFramerateFilter::LoadSettings(bool is_default, agi::Context *c)
this->c = c;
if (is_default) {
Input = &c->videoController->VideoFPS();
Output = &c->videoController->FPS();
auto provider = c->project->VideoProvider();
Output = c->project->Timecodes();
Input = provider ? provider->GetFPS() : Output;
}
else {
double temp;
InputFramerate->GetValue().ToDouble(&temp);
t1 = temp;
Input = &t1;
Input = temp;
if (RadioOutputCFR->GetValue()) {
OutputFramerate->GetValue().ToDouble(&temp);
t2 = temp;
Output = &t2;
Output = temp;
}
else Output = &c->videoController->FPS();
else Output = c->project->Timecodes();
if (Reverse->IsChecked()) {
if (Reverse->IsChecked())
std::swap(Input, Output);
}
}
}
/// Truncate a time to centisecond precision
int FORCEINLINE trunc_cs(int time) {
static int trunc_cs(int time) {
return (time / 10) * 10;
}
@ -198,7 +191,7 @@ void AssTransformFramerateFilter::TransformTimeTags(std::string const& name, Ass
}
void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
if (!Input->IsLoaded() || !Output->IsLoaded()) return;
if (!Input.IsLoaded() || !Output.IsLoaded()) return;
for (auto& curDialogue : subs->Events) {
line = &curDialogue;
newK = 0;
@ -217,14 +210,14 @@ void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) {
}
int AssTransformFramerateFilter::ConvertTime(int time) {
int frame = Output->FrameAtTime(time);
int frameStart = Output->TimeAtFrame(frame);
int frameEnd = Output->TimeAtFrame(frame + 1);
int frame = Output.FrameAtTime(time);
int frameStart = Output.TimeAtFrame(frame);
int frameEnd = Output.TimeAtFrame(frame + 1);
int frameDur = frameEnd - frameStart;
double dist = double(time - frameStart) / frameDur;
int newStart = Input->TimeAtFrame(frame);
int newEnd = Input->TimeAtFrame(frame + 1);
int newStart = Input.TimeAtFrame(frame);
int newEnd = Input.TimeAtFrame(frame + 1);
int newDur = newEnd - newStart;
return newStart + newDur * dist;

View File

@ -27,11 +27,6 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file export_framerate.h
/// @see export_framerate.cpp
/// @ingroup export
///
#include "ass_export_filter.h"
#include <libaegisub/vfr.h>
@ -53,10 +48,8 @@ class AssTransformFramerateFilter final : public AssExportFilter {
int oldK = 0;
// Yes, these are backwards. It sort of makes sense if you think about what it's doing.
const agi::vfr::Framerate *Input = nullptr; ///< Destination frame rate
const agi::vfr::Framerate *Output = nullptr; ///< Source frame rate
agi::vfr::Framerate t1,t2;
agi::vfr::Framerate Input; ///< Destination frame rate
agi::vfr::Framerate Output; ///< Source frame rate
wxTextCtrl *InputFramerate; ///< Input frame rate text box
wxTextCtrl *OutputFramerate; ///< Output frame rate text box
@ -85,7 +78,6 @@ class AssTransformFramerateFilter final : public AssExportFilter {
/// is in and the beginning of the next frame
int ConvertTime(int time);
public:
/// Constructor
AssTransformFramerateFilter();
void ProcessSubs(AssFile *subs, wxWindow *) override;
wxWindow *GetConfigDialogWindow(wxWindow *parent, agi::Context *c) override;

View File

@ -37,9 +37,9 @@
#include "include/aegisub/menu.h"
#include "include/aegisub/toolbar.h"
#include "include/aegisub/hotkey.h"
#include "include/aegisub/video_provider.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "audio_controller.h"
#include "audio_box.h"
#include "base_grid.h"
@ -51,13 +51,14 @@
#include "libresrc/libresrc.h"
#include "main.h"
#include "options.h"
#include "project.h"
#include "subs_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "utils.h"
#include "version.h"
#include "video_box.h"
#include "video_context.h"
#include "video_controller.h"
#include "video_display.h"
#include "video_slider.h"
@ -86,91 +87,16 @@ enum {
#define StartupLog(a) LOG_I("frame_main/init") << a
#endif
wxDEFINE_EVENT(FILE_LIST_DROPPED, wxThreadEvent);
static void get_files_to_load(wxArrayString const& list, std::string &subs, std::string &audio, std::string &video) {
// Keep these lists sorted
// Video formats
const wxString videoList[] = {
"asf",
"avi",
"avs",
"d2v",
"m2ts",
"m4v",
"mkv",
"mov",
"mp4",
"mpeg",
"mpg",
"ogm",
"rm",
"rmvb",
"ts",
"webm"
"wmv",
"y4m",
"yuv"
};
// Subtitle formats
const wxString subsList[] = {
"ass",
"srt",
"ssa",
"sub",
"ttxt",
"txt"
};
// Audio formats
const wxString audioList[] = {
"aac",
"ac3",
"ape",
"dts",
"flac",
"m4a",
"mka",
"mp3",
"ogg",
"w64",
"wav",
"wma"
};
// Scan list
for (wxFileName file : list) {
if (file.IsRelative()) file.MakeAbsolute();
if (!file.FileExists()) continue;
wxString ext = file.GetExt().Lower();
if (subs.empty() && std::binary_search(std::begin(subsList), std::end(subsList), ext))
subs = from_wx(file.GetFullPath());
if (video.empty() && std::binary_search(std::begin(videoList), std::end(videoList), ext))
video = from_wx(file.GetFullPath());
if (audio.empty() && std::binary_search(std::begin(audioList), std::end(audioList), ext))
audio = from_wx(file.GetFullPath());
}
}
/// Handle files drag and dropped onto Aegisub
class AegisubFileDropTarget final : public wxFileDropTarget {
FrameMain *parent;
agi::Context *context;
public:
AegisubFileDropTarget(FrameMain *parent) : parent(parent) { }
bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) override {
std::string subs, audio, video;
get_files_to_load(filenames, subs, audio, video);
if (subs.empty() && audio.empty() && video.empty())
return false;
auto evt = new wxThreadEvent(FILE_LIST_DROPPED);
evt->SetPayload(filenames);
parent->QueueEvent(evt);
AegisubFileDropTarget(agi::Context *context) : context(context) { }
bool OnDropFiles(wxCoord, wxCoord, wxArrayString const& filenames) override {
std::vector<agi::fs::path> files;
for (wxString const& fn : filenames)
files.push_back(from_wx(fn));
context->project->LoadList(files);
return true;
}
};
@ -194,9 +120,8 @@ FrameMain::FrameMain()
context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
context->subsController->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
context->subsController->AddFileSaveListener(&FrameMain::UpdateTitle, this);
context->audioController->AddAudioOpenListener(&FrameMain::OnAudioOpen, this);
context->audioController->AddAudioCloseListener(&FrameMain::OnAudioClose, this);
context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);
context->project->AddAudioProviderListener(&FrameMain::OnAudioOpen, this);
context->project->AddVideoProviderListener(&FrameMain::OnVideoOpen, this);
StartupLog("Initializing context frames");
context->parent = this;
@ -206,7 +131,9 @@ FrameMain::FrameMain()
if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
StartupLog("Initialize toolbar");
InitToolbar();
wxSystemOptions::SetOption("msw.remap", 0);
OPT_SUB("App/Show Toolbar", &FrameMain::EnableToolBar, this);
EnableToolBar(*OPT_GET("App/Show Toolbar"));
StartupLog("Initialize menu bar");
menu::GetMenuBar("main", this, context.get());
@ -228,10 +155,7 @@ FrameMain::FrameMain()
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this);
StartupLog("Set up drag/drop target");
SetDropTarget(new AegisubFileDropTarget(this));
Bind(FILE_LIST_DROPPED, [=](wxThreadEvent &evt) {
LoadList(evt.GetPayload<wxArrayString>());
});
SetDropTarget(new AegisubFileDropTarget(context.get()));
StartupLog("Load default file");
context->subsController->Close();
@ -247,18 +171,12 @@ FrameMain::FrameMain()
FrameMain::~FrameMain () {
wxGetApp().frame = nullptr;
context->videoController->SetVideo("");
context->audioController->CloseAudio();
context->project->CloseAudio();
context->project->CloseVideo();
DestroyChildren();
}
void FrameMain::InitToolbar() {
wxSystemOptions::SetOption("msw.remap", 0);
OPT_SUB("App/Show Toolbar", &FrameMain::EnableToolBar, this);
EnableToolBar(*OPT_GET("App/Show Toolbar"));
}
void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
if (opt.GetBool()) {
if (!GetToolBar()) {
@ -275,7 +193,7 @@ void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
void FrameMain::InitContents() {
StartupLog("Create background panel");
wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
auto Panel = new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
StartupLog("Create subtitles grid");
context->subsGrid = new BaseGrid(Panel, context.get());
@ -313,10 +231,10 @@ void FrameMain::SetDisplayMode(int video, int audio) {
bool sv = false, sa = false;
if (video == -1) sv = showVideo;
else if (video) sv = context->videoController->IsLoaded() && !context->dialog->Get<DialogDetachedVideo>();
else if (video) sv = context->project->VideoProvider() && !context->dialog->Get<DialogDetachedVideo>();
if (audio == -1) sa = showAudio;
else if (audio) sa = context->audioController->IsAudioOpen();
else if (audio) sa = !!context->project->AudioProvider();
// See if anything changed
if (sv == showVideo && sa == showAudio) return;
@ -357,15 +275,14 @@ void FrameMain::UpdateTitle() {
if (GetTitle() != newTitle) SetTitle(newTitle);
}
void FrameMain::OnVideoOpen() {
if (!context->videoController->IsLoaded()) {
void FrameMain::OnVideoOpen(AsyncVideoProvider *provider) {
if (!provider) {
SetDisplayMode(0, -1);
return;
}
Freeze();
int vidx = context->videoController->GetWidth(),
vidy = context->videoController->GetHeight();
int vidx = provider->GetWidth(), vidy = provider->GetHeight();
// Set zoom level based on video resolution and window size
double zoom = context->videoDisplay->GetZoom();
@ -380,30 +297,12 @@ void FrameMain::OnVideoOpen() {
if (OPT_GET("Video/Detached/Enabled")->GetBool() && !context->dialog->Get<DialogDetachedVideo>())
cmd::call("video/detach", context.get());
Thaw();
if (!blockAudioLoad && OPT_GET("Video/Open Audio")->GetBool() && context->audioController->GetAudioURL() != context->videoController->GetVideoName()) {
try {
context->audioController->OpenAudio(context->videoController->GetVideoName());
}
catch (agi::UserCancelException const&) { }
// Opening a video with no audio data isn't an error, so just log
// and move on
catch (agi::fs::FileSystemError const&) {
LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " found by video provider but not audio provider";
}
catch (agi::AudioDataNotFoundError const& e) {
LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " has no audio data: " << e.GetChainedMessage();
}
catch (agi::AudioOpenError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error loading audio", wxOK | wxICON_ERROR | wxCENTER);
}
}
}
void FrameMain::OnVideoDetach(agi::OptionValue const& opt) {
if (opt.GetBool())
SetDisplayMode(0, -1);
else if (context->videoController->IsLoaded())
else if (context->project->VideoProvider())
SetDisplayMode(1, -1);
}
@ -413,41 +312,9 @@ void FrameMain::StatusTimeout(wxString text,int ms) {
StatusClear.Start(ms,true);
}
bool FrameMain::LoadList(wxArrayString list) {
std::string audio, video, subs;
get_files_to_load(list, subs, audio, video);
blockVideoLoad = !video.empty();
blockAudioLoad = !audio.empty();
// Load files
if (subs.size())
context->subsController->Load(subs);
if (blockVideoLoad) {
blockVideoLoad = false;
context->videoController->SetVideo(video);
}
if (blockAudioLoad) {
blockAudioLoad = false;
try {
context->audioController->OpenAudio(audio);
} catch (agi::UserCancelException const&) { }
}
bool loaded_any = subs.size() || audio.size() || video.size();
if (loaded_any)
Refresh(false);
return loaded_any;
}
BEGIN_EVENT_TABLE(FrameMain, wxFrame)
EVT_TIMER(ID_APP_TIMER_STATUSCLEAR, FrameMain::OnStatusClear)
EVT_CLOSE(FrameMain::OnCloseWindow)
EVT_CHAR_HOOK(FrameMain::OnKeyDown)
EVT_MOUSEWHEEL(FrameMain::OnMouseWheel)
END_EVENT_TABLE()
@ -476,95 +343,15 @@ void FrameMain::OnStatusClear(wxTimerEvent &) {
SetStatusText("",1);
}
void FrameMain::OnAudioOpen(AudioProvider *) {
SetDisplayMode(-1, 1);
}
void FrameMain::OnAudioClose() {
SetDisplayMode(-1, 0);
void FrameMain::OnAudioOpen(AudioProvider *provider) {
if (provider)
SetDisplayMode(-1, 1);
else
SetDisplayMode(-1, 0);
}
void FrameMain::OnSubtitlesOpen() {
UpdateTitle();
auto vc = context->videoController.get();
/// @todo figure out how to move this to the relevant controllers without
/// prompting for each file loaded/unloaded
// Load stuff from the new script
auto video = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
auto vfr = config::path->MakeAbsolute(context->ass->GetScriptInfo("VFR File"), "?script");
auto keyframes = config::path->MakeAbsolute(context->ass->GetScriptInfo("Keyframes File"), "?script");
auto audio = config::path->MakeAbsolute(context->ass->GetScriptInfo("Audio URI"), "?script");
bool videoChanged = !blockVideoLoad && video != vc->GetVideoName();
bool timecodesChanged = vfr != vc->GetTimecodesName();
bool keyframesChanged = keyframes != vc->GetKeyFramesName();
bool audioChanged = !blockAudioLoad && audio != context->audioController->GetAudioURL();
// Check if there is anything to change
int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
if (autoLoadMode == 0 || (!videoChanged && !timecodesChanged && !keyframesChanged && !audioChanged)) {
SetDisplayMode(1, 1);
return;
}
if (autoLoadMode == 2) {
if (wxMessageBox(_("Do you want to load/unload the associated files?"), _("(Un)Load files?"), wxYES_NO | wxCENTRE, this) != wxYES) {
SetDisplayMode(1, 1);
if (vc->IsLoaded() && vc->GetProvider()->GetColorSpace() != context->ass->GetScriptInfo("YCbCr Matrix"))
vc->Reload();
return;
}
}
if (audioChanged)
blockAudioLoad = true;
// Video
if (videoChanged) {
vc->SetVideo(video);
if (vc->IsLoaded()) {
vc->JumpToFrame(context->ass->GetUIStateAsInt("Video Position"));
std::string arString = context->ass->GetUIState("Video Aspect Ratio");
if (boost::starts_with(arString, "c")) {
double ar = 0.;
agi::util::try_parse(arString.substr(1), &ar);
vc->SetAspectRatio(ar);
}
else {
int ar = 0;
if (agi::util::try_parse(arString, &ar) && ar >= 0 && ar < 4)
vc->SetAspectRatio((AspectRatio)ar);
}
double videoZoom = 0.;
if (agi::util::try_parse(context->ass->GetUIState("Video Zoom Percent"), &videoZoom))
context->videoDisplay->SetZoom(videoZoom);
}
}
else if (vc->IsLoaded() && vc->GetProvider()->GetColorSpace() != context->ass->GetScriptInfo("YCbCr Matrix"))
vc->Reload();
vc->LoadTimecodes(vfr);
vc->LoadKeyframes(keyframes);
// Audio
if (audioChanged) {
blockAudioLoad = false;
try {
if (audio.empty())
context->audioController->CloseAudio();
else
context->audioController->OpenAudio(audio);
}
catch (agi::UserCancelException const&) { }
catch (agi::fs::FileSystemError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error opening audio", wxOK | wxICON_ERROR | wxCENTER, this);
}
}
SetDisplayMode(1, 1);
}

View File

@ -27,31 +27,23 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file frame_main.h
/// @see frame_main.cpp
/// @ingroup main_ui
///
#include <libaegisub/fs_fwd.h>
#include <memory>
#include <vector>
#include <wx/frame.h>
#include <wx/sizer.h>
#include <wx/timer.h>
class AegisubApp;
class AegisubFileDropTarget;
class AsyncVideoProvider;
class AudioBox;
class AudioProvider;
class VideoBox;
namespace agi { struct Context; class OptionValue; }
class FrameMain : public wxFrame {
friend class AegisubApp;
friend class AegisubFileDropTarget;
std::unique_ptr<agi::Context> context;
@ -64,13 +56,7 @@ class FrameMain : public wxFrame {
bool showVideo = true; ///< Is the video display shown?
bool showAudio = true; ///< Is the audio display shown?
wxTimer StatusClear; ///< Status bar timeout timer
/// Block video loading; used when both video and subtitles are opened at
/// the same time, so that the video associated with the subtitles (if any)
/// isn't loaded
bool blockVideoLoad = false;
bool blockAudioLoad = false;
void InitToolbar();
void InitContents();
void UpdateTitle();
@ -81,13 +67,9 @@ class FrameMain : public wxFrame {
void OnStatusClear(wxTimerEvent &event);
void OnCloseWindow (wxCloseEvent &event);
// AudioControllerAudioEventListener implementation
void OnAudioOpen(AudioProvider *provider);
void OnAudioClose();
void OnVideoOpen();
void OnVideoOpen(AsyncVideoProvider *provider);
void OnVideoDetach(agi::OptionValue const& opt);
void OnSubtitlesOpen();
void EnableToolBar(agi::OptionValue const& opt);
@ -116,7 +98,5 @@ public:
bool IsVideoShown() const { return showVideo; }
bool IsAudioShown() const { return showAudio; }
bool LoadList(wxArrayString list);
DECLARE_EVENT_TABLE()
};

View File

@ -21,7 +21,7 @@
#include "compat.h"
#include "include/aegisub/context.h"
#include "options.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/character_count.h>

View File

@ -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();
}
};

View File

@ -23,13 +23,14 @@ class AssDialogue;
class AudioKaraoke;
class DialogManager;
class FrameMain;
class Project;
class SearchReplaceEngine;
class InitialLineState;
class SelectionController;
class SubsController;
class BaseGrid;
class TextSelectionController;
class VideoContext;
class VideoController;
class VideoDisplay;
class wxWindow;
namespace Automation4 { class ScriptManager; }
@ -42,10 +43,11 @@ struct Context {
std::unique_ptr<AssFile> ass;
std::unique_ptr<TextSelectionController> textSelectionController;
std::unique_ptr<SubsController> subsController;
std::unique_ptr<Project> project;
std::unique_ptr<Automation4::ScriptManager> local_scripts;
std::unique_ptr<VideoContext> videoController;
std::unique_ptr<AudioController> audioController;
std::unique_ptr<SelectionController> selectionController;
std::unique_ptr<VideoController> videoController;
std::unique_ptr<AudioController> audioController;
std::unique_ptr<InitialLineState> initialLineState;
std::unique_ptr<SearchReplaceEngine> search;

View File

@ -44,7 +44,7 @@ struct VideoFrame;
class VideoProvider {
public:
virtual ~VideoProvider() {}
virtual ~VideoProvider() = default;
/// Override this method to actually get frames
virtual std::shared_ptr<VideoFrame> GetFrame(int n)=0;

View File

@ -49,10 +49,9 @@
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "subs_controller.h"
#include "project.h"
#include "subtitle_format.h"
#include "subtitles_provider_libass.h"
#include "video_context.h"
#include "version.h"
#include "utils.h"
@ -317,15 +316,14 @@ bool AegisubApp::OnInit() {
// Get parameter subs
StartupLog("Parse command line");
wxArrayString subs;
std::vector<agi::fs::path> files;
for (int i = 1; i < argc; ++i)
subs.push_back(argv[i]);
if (!subs.empty())
frame->LoadList(subs);
files.push_back(from_wx(argv[i]));
if (!files.empty())
frame->context->project->LoadList(files);
}
catch (const char *err) {
wxMessageBox(err,"Fatal error while initializing");
wxMessageBox(err, "Fatal error while initializing");
return false;
}
catch (wxString const& err) {
@ -333,10 +331,9 @@ bool AegisubApp::OnInit() {
return false;
}
catch (agi::Exception const& e) {
wxMessageBox(to_wx(e.GetMessage()),"Fatal error while initializing");
wxMessageBox(to_wx(e.GetMessage()), "Fatal error while initializing");
return false;
}
#ifndef _DEBUG
catch (...) {
wxMessageBox("Unhandled exception","Fatal error while initializing");
@ -470,5 +467,5 @@ int AegisubApp::OnRun() {
void AegisubApp::MacOpenFile(const wxString &filename) {
if (frame && !filename.empty())
frame->context->subsController->Load(agi::fs::path(filename));
frame->context->project->LoadSubtitles(agi::fs::path(filename));
}

443
src/project.cpp Normal file
View File

@ -0,0 +1,443 @@
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#include "project.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "audio_controller.h"
#include "charset_detect.h"
#include "compat.h"
#include "dialog_progress.h"
#include "dialog_video_properties.h"
#include "include/aegisub/audio_provider.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "mkv_wrap.h"
#include "options.h"
#include "subs_controller.h"
#include "video_controller.h"
#include "video_display.h"
#include <libaegisub/fs.h>
#include <libaegisub/keyframe.h>
#include <libaegisub/log.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/path.h>
#include <libaegisub/util.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <wx/msgdlg.h>
Project::Project(agi::Context *c) : context(c) {
OPT_SUB("Audio/Cache/Type", &Project::ReloadAudio, this);
OPT_SUB("Audio/Provider", &Project::ReloadAudio, this);
OPT_SUB("Provider/Audio/FFmpegSource/Decode Error Handling", &Project::ReloadAudio, this);
OPT_SUB("Provider/Avisynth/Allow Ancient", &Project::ReloadVideo, this);
OPT_SUB("Provider/Avisynth/Memory Max", &Project::ReloadVideo, this);
OPT_SUB("Provider/Video/FFmpegSource/Decoding Threads", &Project::ReloadVideo, this);
OPT_SUB("Provider/Video/FFmpegSource/Unsafe Seeking", &Project::ReloadVideo, this);
OPT_SUB("Subtitle/Provider", &Project::ReloadVideo, this);
OPT_SUB("Video/Force BT.601", &Project::ReloadVideo, this);
OPT_SUB("Video/Provider", &Project::ReloadVideo, this);
c->subsController->AddFileSaveListener(&Project::OnSubtitlesSave, this);
}
Project::~Project() { }
void Project::OnSubtitlesSave() {
context->ass->SetScriptInfo("Audio File",
config::path->MakeRelative(audio_file, "?script").generic_string());
context->ass->SetScriptInfo("Video File",
config::path->MakeRelative(video_file, "?script").generic_string());
context->ass->SetScriptInfo("VFR File",
config::path->MakeRelative(timecodes_file, "?script").generic_string());
context->ass->SetScriptInfo("Keyframes File",
config::path->MakeRelative(keyframes_file, "?script").generic_string());
}
void Project::ReloadAudio() {
if (audio_provider)
LoadAudio(audio_file);
}
void Project::ReloadVideo() {
if (video_provider)
LoadAudio(video_file);
}
void Project::ShowError(wxString const& message) {
wxMessageBox(message, "Error loading file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
}
void Project::ShowError(std::string const& message) {
ShowError(to_wx(message));
}
void Project::DoLoadSubtitles(agi::fs::path const& path, std::string encoding) {
try {
if (encoding.empty())
encoding = CharSetDetect::GetEncoding(path);
}
catch (agi::UserCancelException const&) {
return;
}
if (encoding != "binary") {
// Try loading as timecodes and keyframes first since we can't
// distinguish them based on filename alone, and just ignore failures
// rather than trying to differentiate between malformed timecodes
// files and things that aren't timecodes files at all
try { return DoLoadTimecodes(path); } catch (...) { }
try { return DoLoadKeyframes(path); } catch (...) { }
}
try {
context->subsController->Load(path, encoding);
}
catch (agi::UserCancelException const&) { return; }
catch (agi::fs::FileNotFound const&) {
config::mru->Remove("Subtitle", path);
return ShowError(path.string() + " not found.");
}
catch (agi::Exception const& e) {
return ShowError(e.GetChainedMessage());
}
catch (std::exception const& e) {
return ShowError(std::string(e.what()));
}
catch (...) {
return ShowError(wxString("Unknown error"));
}
}
void Project::LoadSubtitles(agi::fs::path const& path, std::string encoding) {
DoLoadSubtitles(path, encoding);
LoadUnloadFiles();
}
void Project::CloseSubtitles() {
context->subsController->Close();
config::path->SetToken("?script", "");
LoadUnloadFiles();
}
void Project::LoadUnloadFiles() {
auto load_linked = OPT_GET("App/Auto/Load Linked Files")->GetInt();
if (!load_linked) return;
auto audio = config::path->MakeAbsolute(context->ass->GetScriptInfo("Audio File"), "?script");
auto video = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
auto timecodes = config::path->MakeAbsolute(context->ass->GetScriptInfo("VFR File"), "?script");
auto keyframes = config::path->MakeAbsolute(context->ass->GetScriptInfo("Keyframes File"), "?script");
if (video == video_file && audio == audio_file && keyframes == keyframes_file && timecodes == timecodes_file)
return;
if (load_linked == 2) {
if (wxMessageBox(_("Do you want to load/unload the associated files?"), _("(Un)Load files?"), wxYES_NO | wxCENTRE, context->parent) != wxYES)
return;
}
bool loaded_video = false;
if (video != video_file) {
if (video.empty())
CloseVideo();
else if ((loaded_video = DoLoadVideo(video))) {
auto vc = context->videoController.get();
vc->JumpToFrame(context->ass->GetUIStateAsInt("Video Position"));
std::string arString = context->ass->GetUIState("Video Aspect Ratio");
if (boost::starts_with(arString, "c")) {
double ar = 0.;
agi::util::try_parse(arString.substr(1), &ar);
vc->SetAspectRatio(ar);
}
else {
int ar = 0;
if (agi::util::try_parse(arString, &ar) && ar >= 0 && ar < 4)
vc->SetAspectRatio((AspectRatio)ar);
}
double videoZoom = 0.;
if (agi::util::try_parse(context->ass->GetUIState("Video Zoom Percent"), &videoZoom))
context->videoDisplay->SetZoom(videoZoom);
}
}
if (!timecodes.empty()) LoadTimecodes(timecodes);
if (!keyframes.empty()) LoadKeyframes(keyframes);
if (audio != audio_file) {
if (audio.empty())
CloseAudio();
else
DoLoadAudio(audio, false);
}
else if (loaded_video && OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
DoLoadAudio(video, true);
}
void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
if (!progress)
progress = new DialogProgress(context->parent);
try {
try {
audio_provider = AudioProviderFactory::GetProvider(path, progress);
}
catch (agi::UserCancelException const&) { return; }
catch (...) {
config::mru->Remove("Audio", path);
throw;
}
}
catch (agi::fs::FileNotFound const& e) {
return ShowError(_("The audio file was not found: ") + to_wx(e.GetChainedMessage()));
}
catch (agi::AudioDataNotFoundError const& e) {
if (quiet) {
LOG_D("video/open/audio") << "File " << video_file << " has no audio data: " << e.GetChainedMessage();
return;
}
else
return ShowError(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()));
}
catch (agi::AudioProviderOpenError const& e) {
return ShowError(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetChainedMessage()));
}
catch (agi::Exception const& e) {
return ShowError(e.GetChainedMessage());
}
audio_file = path;
config::path->SetToken("?audio", path);
config::mru->Add("Audio", path);
AnnounceAudioProviderModified(audio_provider.get());
}
void Project::LoadAudio(agi::fs::path const& path) {
DoLoadAudio(path, false);
}
void Project::CloseAudio() {
AnnounceAudioProviderModified(nullptr);
audio_provider.reset();
audio_file.clear();
config::path->SetToken("?audio", "");
}
bool Project::DoLoadVideo(agi::fs::path const& path) {
if (!progress)
progress = new DialogProgress(context->parent);
try {
auto old_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
video_provider = agi::make_unique<AsyncVideoProvider>(path, old_matrix, context->videoController.get(), progress);
}
catch (agi::UserCancelException const&) { return false; }
catch (agi::fs::FileSystemError const& err) {
config::mru->Remove("Video", path);
ShowError(to_wx(err.GetMessage()));
return false;
}
catch (VideoProviderError const& err) {
ShowError(to_wx(err.GetMessage()));
return false;
}
UpdateVideoProperties(context->ass.get(), video_provider.get(), context->parent);
video_provider->LoadSubtitles(context->ass.get());
timecodes = video_provider->GetFPS();
keyframes = video_provider->GetKeyFrames();
timecodes_file.clear();
keyframes_file.clear();
video_file = path;
config::mru->Add("Video", path);
config::path->SetToken("?video", path);
std::string warning = video_provider->GetWarning();
if (!warning.empty())
wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
video_has_subtitles = false;
if (agi::fs::HasExtension(path, "mkv"))
video_has_subtitles = MatroskaWrapper::HasSubtitles(path);
AnnounceVideoProviderModified(video_provider.get());
AnnounceKeyframesModified(keyframes);
AnnounceTimecodesModified(timecodes);
return true;
}
void Project::LoadVideo(agi::fs::path const& path) {
if (!DoLoadVideo(path)) return;
if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
DoLoadAudio(video_file, true);
}
void Project::CloseVideo() {
AnnounceVideoProviderModified(nullptr);
video_provider.reset();
video_file.clear();
config::path->SetToken("?video", "");
video_has_subtitles = false;
}
void Project::DoLoadTimecodes(agi::fs::path const& path) {
timecodes = agi::vfr::Framerate(path);
timecodes_file = path;
config::mru->Add("Timecodes", path);
AnnounceTimecodesModified(timecodes);
}
void Project::LoadTimecodes(agi::fs::path const& path) {
try {
DoLoadTimecodes(path);
}
catch (agi::fs::FileSystemError const& e) {
ShowError(e.GetChainedMessage());
config::mru->Remove("Timecodes", path);
}
catch (agi::vfr::Error const& e) {
ShowError("Failed to parse timecodes file: " + e.GetChainedMessage());
config::mru->Remove("Timecodes", path);
}
}
void Project::CloseTimecodes() {
timecodes = video_provider ? video_provider->GetFPS() : agi::vfr::Framerate{};
timecodes_file.clear();
AnnounceTimecodesModified(timecodes);
}
void Project::DoLoadKeyframes(agi::fs::path const& path) {
keyframes = agi::keyframe::Load(path);
keyframes_file = path;
config::mru->Add("Keyframes", path);
AnnounceKeyframesModified(keyframes);
}
void Project::LoadKeyframes(agi::fs::path const& path) {
try {
DoLoadKeyframes(path);
}
catch (agi::fs::FileSystemError const& e) {
ShowError(e.GetChainedMessage());
config::mru->Remove("Keyframes", path);
}
catch (agi::keyframe::Error const& e) {
ShowError("Failed to parse keyframes file: " + e.GetChainedMessage());
config::mru->Remove("Keyframes", path);
}
}
void Project::CloseKeyframes() {
keyframes = video_provider ? video_provider->GetKeyFrames() : std::vector<int>{};
keyframes_file.clear();
AnnounceKeyframesModified(keyframes);
}
void Project::LoadList(std::vector<agi::fs::path> const& files) {
// Keep these lists sorted
// Video formats
const char *videoList[] = {
".asf",
".avi",
".avs",
".d2v",
".m2ts",
".m4v",
".mkv",
".mov",
".mp4",
".mpeg",
".mpg",
".ogm",
".rm",
".rmvb",
".ts",
".webm"
".wmv",
".y4m",
".yuv"
};
// Subtitle formats
const char *subsList[] = {
".ass",
".srt",
".ssa",
".sub",
".ttxt",
".txt"
};
// Audio formats
const char *audioList[] = {
".aac",
".ac3",
".ape",
".dts",
".flac",
".m4a",
".mka",
".mp3",
".ogg",
".w64",
".wav",
".wma"
};
auto search = [](const char **begin, const char **end, std::string const& str) {
return std::binary_search(begin, end, str.c_str(), [](const char *a, const char *b) {
return strcmp(a, b) < 0;
});
};
agi::fs::path audio, video, subs;
for (auto file : files) {
if (file.is_relative()) file = absolute(file);
if (!agi::fs::FileExists(file)) continue;
auto ext = file.extension().string();
boost::to_lower(ext);
if (subs.empty() && search(std::begin(subsList), std::end(subsList), ext))
subs = file;
if (video.empty() && search(std::begin(videoList), std::end(videoList), ext))
video = file;
if (audio.empty() && search(std::begin(audioList), std::end(audioList), ext))
audio = file;
}
if (!subs.empty())
DoLoadSubtitles(subs);
if (!video.empty())
DoLoadVideo(video);
if (!audio.empty())
DoLoadAudio(audio, false);
else if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
DoLoadAudio(video_file, true);
if (!subs.empty())
LoadUnloadFiles();
}

104
src/project.h Normal file
View File

@ -0,0 +1,104 @@
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include <libaegisub/vfr.h>
#include <boost/filesystem/path.hpp>
#include <memory>
#include <vector>
class AsyncVideoProvider;
class AudioProvider;
class DialogProgress;
class wxString;
namespace agi { struct Context; }
class Project {
// Things owned by this
std::unique_ptr<AudioProvider> audio_provider;
std::unique_ptr<AsyncVideoProvider> video_provider;
agi::vfr::Framerate timecodes;
std::vector<int> keyframes;
agi::fs::path audio_file;
agi::fs::path video_file;
agi::fs::path timecodes_file;
agi::fs::path keyframes_file;
agi::signal::Signal<AudioProvider *> AnnounceAudioProviderModified;
agi::signal::Signal<AsyncVideoProvider *> AnnounceVideoProviderModified;
agi::signal::Signal<agi::vfr::Framerate const&> AnnounceTimecodesModified;
agi::signal::Signal<std::vector<int> const&> AnnounceKeyframesModified;
bool video_has_subtitles = false;
DialogProgress *progress = nullptr;
// Things not
agi::Context *context = nullptr;
void ShowError(wxString const& message);
void ShowError(std::string const& message);
void DoLoadSubtitles(agi::fs::path const& path, std::string encoding="");
void DoLoadAudio(agi::fs::path const& path, bool quiet);
bool DoLoadVideo(agi::fs::path const& path);
void DoLoadTimecodes(agi::fs::path const& path);
void DoLoadKeyframes(agi::fs::path const& path);
void LoadUnloadFiles();
void OnSubtitlesSave();
void ReloadAudio();
void ReloadVideo();
public:
Project(agi::Context *context);
~Project();
void LoadSubtitles(agi::fs::path const& path, std::string encoding="");
void CloseSubtitles();
bool CanLoadSubtitlesFromVideo() const { return video_has_subtitles; }
void LoadAudio(agi::fs::path const& path);
void CloseAudio();
AudioProvider *AudioProvider() const { return audio_provider.get(); }
agi::fs::path const& AudioName() const { return audio_file; }
void LoadVideo(agi::fs::path const& path);
void CloseVideo();
AsyncVideoProvider *VideoProvider() const { return video_provider.get(); }
agi::fs::path const& VideoName() const { return video_file; }
void LoadTimecodes(agi::fs::path const& path);
void CloseTimecodes();
bool CanCloseTimecodes() const { return !timecodes_file.empty(); }
agi::vfr::Framerate const& Timecodes() const { return timecodes; }
void LoadKeyframes(agi::fs::path const& path);
void CloseKeyframes();
bool CanCloseKeyframes() const { return !keyframes_file.empty(); }
std::vector<int> const& Keyframes() const { return keyframes; }
void LoadList(std::vector<agi::fs::path> const& files);
DEFINE_SIGNAL_ADDERS(AnnounceAudioProviderModified, AddAudioProviderListener)
DEFINE_SIGNAL_ADDERS(AnnounceVideoProviderModified, AddVideoProviderListener)
DEFINE_SIGNAL_ADDERS(AnnounceTimecodesModified, AddTimecodesListener)
DEFINE_SIGNAL_ADDERS(AnnounceKeyframesModified, AddKeyframesListener)
};

View File

@ -22,18 +22,16 @@
#include "ass_info.h"
#include "ass_style.h"
#include "ass_style_storage.h"
#include "charset_detect.h"
#include "compat.h"
#include "command/command.h"
#include "frame_main.h"
#include "include/aegisub/context.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "subtitle_format.h"
#include "text_file_reader.h"
#include "text_selection_controller.h"
#include "utils.h"
#include "video_context.h"
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
@ -175,61 +173,15 @@ void SubsController::SetSelectionController(SelectionController *selection_contr
void SubsController::Load(agi::fs::path const& filename, std::string charset) {
AssFile temp;
try {
try {
if (charset.empty())
charset = CharSetDetect::GetEncoding(filename);
}
catch (agi::UserCancelException const&) {
return;
}
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->project->Timecodes(), charset);
// Make sure that file isn't actually a timecode file
if (charset != "binary") {
try {
TextFileReader testSubs(filename, charset);
std::string cur = testSubs.ReadLineFromFile();
if (boost::starts_with(cur, "# timecode")) {
context->videoController->LoadTimecodes(filename);
return;
}
}
catch (...) {
// if trying to load the file as timecodes fails it's fairly
// safe to assume that it is in fact not a timecode file
}
}
// Make sure the file has at least one style and one dialogue line
if (temp.Styles.empty())
temp.Styles.push_back(*new AssStyle);
if (temp.Events.empty())
temp.Events.push_back(*new AssDialogue);
SubtitleFormat::GetReader(filename, charset)->ReadFile(&temp, filename, context->videoController->FPS(), charset);
// Make sure the file has at least one style and one dialogue line
if (temp.Styles.empty())
temp.Styles.push_back(*new AssStyle);
if (temp.Events.empty())
temp.Events.push_back(*new AssDialogue);
context->ass->swap(temp);
}
catch (agi::UserCancelException const&) {
return;
}
catch (agi::fs::FileNotFound const&) {
wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
config::mru->Remove("Subtitle", filename);
return;
}
catch (agi::Exception const& err) {
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
catch (std::exception const& err) {
wxMessageBox(to_wx(err.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
catch (...) {
wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
context->ass->swap(temp);
SetFileName(filename);

View File

@ -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

View File

@ -45,6 +45,7 @@
#include "initial_line_state.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "placeholder_ctrl.h"
#include "selection_controller.h"
#include "subs_edit_ctrl.h"
@ -52,7 +53,6 @@
#include "timeedit_ctrl.h"
#include "tooltip_manager.h"
#include "validators.h"
#include "video_context.h"
#include <libaegisub/character_count.h>
#include <libaegisub/util.h>
@ -228,10 +228,12 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
OnSize(evt);
file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this);
connections.push_back(context->videoController->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this));
connections.push_back(context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this));
connections.push_back(context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this));
connections.push_back(context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this));
connections = agi::signal::make_vector({
context->project->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this),
context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this),
context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this),
context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this),
});
context->textSelectionController->SetControl(edit_ctrl);
edit_ctrl->SetFocus();
@ -394,14 +396,6 @@ void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) {
commit_id = -1;
UpdateFields(AssFile::COMMIT_DIAG_FULL, false);
/// @todo VideoContext should be doing this
if (c->videoController->IsLoaded()) {
if (OPT_GET("Video/Subtitle Sync")->GetBool()) {
c->videoController->Stop();
c->videoController->JumpToTime(line->Start);
}
}
}
void SubsEditBox::OnSelectedSetChanged() {
@ -488,8 +482,10 @@ void SubsEditBox::CommitTimes(TimeField field) {
break;
case TIME_DURATION:
if (by_frame->GetValue())
d->End = c->videoController->TimeAtFrame(c->videoController->FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
if (by_frame->GetValue()) {
auto const& fps = c->project->Timecodes();
d->End = fps.TimeAtFrame(fps.FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END);
}
else
d->End = d->Start + duration->GetTime();
initial_times[d].second = d->End;

View File

@ -33,7 +33,6 @@
///
#include <array>
#include <deque>
#include <boost/container/map.hpp>
#include <boost/flyweight/flyweight_fwd.hpp>
#include <memory>
@ -74,7 +73,7 @@ class SubsEditBox final : public wxPanel {
TIME_DURATION
};
std::deque<agi::signal::Connection> connections;
std::vector<agi::signal::Connection> connections;
/// Currently active dialogue line
AssDialogue *line = nullptr;

View File

@ -51,7 +51,7 @@
#include "subtitle_format_transtation.h"
#include "subtitle_format_ttxt.h"
#include "subtitle_format_txt.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/fs.h>
#include <libaegisub/make_unique.h>

View File

@ -40,7 +40,7 @@
#include "options.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
#include "video_context.h"
#include "video_controller.h"
#include <libaegisub/fs.h>
#include <libaegisub/util.h>

View File

@ -40,8 +40,8 @@
#include "compat.h"
#include "include/aegisub/context.h"
#include "options.h"
#include "project.h"
#include "utils.h"
#include "video_context.h"
#include <wx/clipbrd.h>
#include <wx/dataobj.h>
@ -88,17 +88,17 @@ void TimeEdit::SetTime(AssTime new_time) {
}
int TimeEdit::GetFrame() const {
return c->videoController->FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START);
return c->project->Timecodes().FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START);
}
void TimeEdit::SetFrame(int fn) {
SetTime(c->videoController->TimeAtFrame(fn, isEnd ? agi::vfr::END : agi::vfr::START));
SetTime(c->project->Timecodes().TimeAtFrame(fn, isEnd ? agi::vfr::END : agi::vfr::START));
}
void TimeEdit::SetByFrame(bool enableByFrame) {
if (enableByFrame == byFrame) return;
byFrame = enableByFrame && c->videoController->TimecodesLoaded();
byFrame = enableByFrame && c->project->Timecodes().IsLoaded();
UpdateText();
}
@ -107,7 +107,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
if (byFrame) {
long temp = 0;
GetValue().ToLong(&temp);
time = c->videoController->TimeAtFrame(temp, isEnd ? agi::vfr::END : agi::vfr::START);
time = c->project->Timecodes().TimeAtFrame(temp, isEnd ? agi::vfr::END : agi::vfr::START);
}
else if (insert)
time = from_wx(GetValue());
@ -115,7 +115,7 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
void TimeEdit::UpdateText() {
if (byFrame)
ChangeValue(std::to_wstring(c->videoController->FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START)));
ChangeValue(std::to_wstring(c->project->Timecodes().FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START)));
else
ChangeValue(to_wx(time.GetAssFormated()));
}

View File

@ -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);

View File

@ -17,7 +17,7 @@
#include <libaegisub/exception.h>
#include <string>
#include <wx/combobox.h>
#include <wx/radiobox.h>
#include <wx/validate.h>

View File

@ -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()));

View File

@ -29,7 +29,7 @@
#include <libaegisub/signal.h>
#include <deque>
#include <vector>
#include <wx/panel.h>
namespace agi { struct Context; }
@ -38,7 +38,7 @@ class wxTextCtrl;
/// @class VideoBox
/// @brief The box containing the video display and associated controls
class VideoBox final : public wxPanel {
std::deque<agi::signal::Connection> slots;
std::vector<agi::signal::Connection> connections;
agi::Context *context; ///< Project context
wxTextCtrl *VideoPosition; ///< Current frame/time
wxTextCtrl *VideoSubsPos; ///< Time relative to the active subtitle line

View File

@ -1,445 +0,0 @@
// Copyright (c) 2005-2007, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
/// @file video_context.cpp
/// @brief Keep track of loaded video
/// @ingroup video
///
#include "video_context.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
#include "audio_controller.h"
#include "compat.h"
#include "dialog_progress.h"
#include "dialog_video_properties.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "mkv_wrap.h"
#include "options.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "time_range.h"
#include "threaded_frame_source.h"
#include "utils.h"
#include "video_frame.h"
#include <libaegisub/fs.h>
#include <libaegisub/keyframe.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <wx/msgdlg.h>
VideoContext::VideoContext(agi::Context *c)
: context(c)
, playback(this)
, playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
{
context->ass->AddCommitListener(&VideoContext::OnSubtitlesCommit, this);
context->subsController->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
Bind(EVT_VIDEO_ERROR, &VideoContext::OnVideoError, this);
Bind(EVT_SUBTITLES_ERROR, &VideoContext::OnSubtitlesError, this);
Bind(wxEVT_TIMER, &VideoContext::OnPlayTimer, this);
OPT_SUB("Subtitle/Provider", &VideoContext::Reload, this);
OPT_SUB("Video/Provider", &VideoContext::Reload, this);
// It would be nice to find a way to move these to the individual providers
OPT_SUB("Provider/Avisynth/Allow Ancient", &VideoContext::Reload, this);
OPT_SUB("Provider/Avisynth/Memory Max", &VideoContext::Reload, this);
OPT_SUB("Provider/Video/FFmpegSource/Decoding Threads", &VideoContext::Reload, this);
OPT_SUB("Provider/Video/FFmpegSource/Unsafe Seeking", &VideoContext::Reload, this);
OPT_SUB("Video/Force BT.601", &VideoContext::Reload, this);
}
VideoContext::~VideoContext () { }
void VideoContext::Reset() {
config::path->SetToken("?video", "");
// Remove video data
Stop();
frame_n = 0;
// Clean up video data
video_filename.clear();
color_matrix.clear();
// Remove provider
provider.reset();
video_provider = nullptr;
keyframes.clear();
keyframes_filename.clear();
video_fps = agi::vfr::Framerate();
KeyframesOpen(keyframes);
if (!ovr_fps.IsLoaded()) TimecodesOpen(video_fps);
}
void VideoContext::SetVideo(const agi::fs::path &filename) {
Reset();
if (filename.empty()) {
VideoOpen();
return;
}
bool commit_subs = false;
try {
if (!progress)
progress = new DialogProgress(context->parent);
auto old_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
provider = agi::make_unique<ThreadedFrameSource>(filename, old_matrix, this, progress);
video_provider = provider->GetVideoProvider();
video_filename = filename;
color_matrix = video_provider->GetColorSpace();
keyframes = video_provider->GetKeyFrames();
video_fps = video_provider->GetFPS();
commit_subs = UpdateVideoProperties(context->ass.get(), video_provider, context->parent);
// Set frame rate
if (ovr_fps.IsLoaded()) {
int ovr = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"),
_("Replace timecodes?"), wxYES_NO | wxICON_QUESTION);
if (ovr == wxYES) {
ovr_fps = agi::vfr::Framerate();
timecodes_filename.clear();
}
}
// Set aspect ratio
double dar = video_provider->GetDAR();
if (dar > 0)
SetAspectRatio(dar);
// Set filename
config::mru->Add("Video", filename);
config::path->SetToken("?video", filename);
// Show warning
std::string warning = video_provider->GetWarning();
if (!warning.empty())
wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
has_subtitles = false;
if (agi::fs::HasExtension(filename, "mkv"))
has_subtitles = MatroskaWrapper::HasSubtitles(filename);
provider->LoadSubtitles(context->ass.get());
VideoOpen();
KeyframesOpen(keyframes);
TimecodesOpen(FPS());
}
catch (agi::UserCancelException const&) { }
catch (agi::fs::FileSystemError const& err) {
config::mru->Remove("Video", filename);
wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
}
catch (VideoProviderError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
}
if (commit_subs)
context->ass->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
else
JumpToFrame(0);
}
void VideoContext::Reload() {
if (IsLoaded()) {
int frame = frame_n;
SetVideo(agi::fs::path(video_filename)); // explicitly copy videoFile since it's cleared in SetVideo
JumpToFrame(frame);
}
}
void VideoContext::OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed) {
if (!IsLoaded()) return;
if ((type & AssFile::COMMIT_SCRIPTINFO) || type == AssFile::COMMIT_NEW) {
auto new_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
if (!new_matrix.empty() && new_matrix != color_matrix) {
color_matrix = new_matrix;
provider->SetColorSpace(new_matrix);
}
}
if (changed.empty() || no_amend)
provider->LoadSubtitles(context->ass.get());
else
provider->UpdateSubtitles(context->ass.get(), changed);
if (!IsPlaying())
GetFrameAsync(frame_n);
no_amend = false;
}
void VideoContext::OnSubtitlesSave() {
no_amend = true;
context->ass->SetScriptInfo("VFR File", config::path->MakeRelative(GetTimecodesName(), "?script").generic_string());
context->ass->SetScriptInfo("Keyframes File", config::path->MakeRelative(GetKeyFramesName(), "?script").generic_string());
if (!IsLoaded()) {
context->ass->SetScriptInfo("Video File", "");
context->ass->SaveUIState("Video Aspect Ratio", "");
context->ass->SaveUIState("Video Position", "");
return;
}
std::string ar;
if (ar_type == AspectRatio::Custom)
ar = "c" + std::to_string(ar_value);
else
ar = std::to_string((int)ar_type);
context->ass->SetScriptInfo("Video File", config::path->MakeRelative(video_filename, "?script").generic_string());
context->ass->SaveUIState("Video Aspect Ratio", ar);
context->ass->SaveUIState("Video Position", std::to_string(frame_n));
}
void VideoContext::JumpToFrame(int n) {
if (!IsLoaded()) return;
bool was_playing = IsPlaying();
if (was_playing)
Stop();
frame_n = mid(0, n, GetLength() - 1);
GetFrameAsync(frame_n);
Seek(frame_n);
if (was_playing)
Play();
}
void VideoContext::JumpToTime(int ms, agi::vfr::Time end) {
JumpToFrame(FrameAtTime(ms, end));
}
void VideoContext::GetFrameAsync(int n) {
provider->RequestFrame(n, TimeAtFrame(n));
}
std::shared_ptr<VideoFrame> VideoContext::GetFrame(int n, bool raw) {
return provider->GetFrame(n, TimeAtFrame(n), raw);
}
int VideoContext::GetWidth() const { return video_provider->GetWidth(); }
int VideoContext::GetHeight() const { return video_provider->GetHeight(); }
int VideoContext::GetLength() const { return video_provider->GetFrameCount(); }
void VideoContext::NextFrame() {
if (!video_provider || IsPlaying() || frame_n == video_provider->GetFrameCount())
return;
JumpToFrame(frame_n + 1);
if (playAudioOnStep->GetBool())
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
}
void VideoContext::PrevFrame() {
if (!video_provider || IsPlaying() || frame_n == 0)
return;
JumpToFrame(frame_n - 1);
if (playAudioOnStep->GetBool())
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
}
void VideoContext::Play() {
if (IsPlaying()) {
Stop();
return;
}
if (!IsLoaded()) return;
start_ms = TimeAtFrame(frame_n);
end_frame = GetLength() - 1;
context->audioController->PlayToEnd(start_ms);
playback_start_time = std::chrono::steady_clock::now();
playback.Start(10);
}
void VideoContext::PlayLine() {
Stop();
AssDialogue *curline = context->selectionController->GetActiveLine();
if (!curline) return;
context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
// Round-trip conversion to convert start to exact
int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
start_ms = TimeAtFrame(startFrame);
end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
JumpToFrame(startFrame);
playback_start_time = std::chrono::steady_clock::now();
playback.Start(10);
}
void VideoContext::Stop() {
if (IsPlaying()) {
playback.Stop();
context->audioController->Stop();
}
}
void VideoContext::OnPlayTimer(wxTimerEvent &) {
using namespace std::chrono;
int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
if (next_frame == frame_n) return;
if (next_frame >= end_frame)
Stop();
else {
frame_n = next_frame;
GetFrameAsync(frame_n);
Seek(frame_n);
}
}
double VideoContext::GetARFromType(AspectRatio type) const {
switch (type) {
case AspectRatio::Default: return (double)GetWidth()/(double)GetHeight();
case AspectRatio::Fullscreen: return 4.0/3.0;
case AspectRatio::Widescreen: return 16.0/9.0;
case AspectRatio::Cinematic: return 2.35;
}
throw agi::InternalError("Bad AR type", nullptr);
}
void VideoContext::SetAspectRatio(double value) {
ar_type = AspectRatio::Custom;
ar_value = mid(.5, value, 5.);
ARChange(ar_type, ar_value);
}
void VideoContext::SetAspectRatio(AspectRatio type) {
ar_value = mid(.5, GetARFromType(type), 5.);
ar_type = type;
ARChange(ar_type, ar_value);
}
void VideoContext::LoadKeyframes(agi::fs::path const& filename) {
if (filename == keyframes_filename || filename.empty()) return;
try {
keyframes = agi::keyframe::Load(filename);
keyframes_filename = filename;
KeyframesOpen(keyframes);
config::mru->Add("Keyframes", filename);
}
catch (agi::keyframe::Error const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
config::mru->Remove("Keyframes", filename);
}
catch (agi::fs::FileSystemError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
config::mru->Remove("Keyframes", filename);
}
}
void VideoContext::SaveKeyframes(agi::fs::path const& filename) {
agi::keyframe::Save(filename, GetKeyFrames());
config::mru->Add("Keyframes", filename);
}
void VideoContext::CloseKeyframes() {
keyframes_filename.clear();
if (video_provider)
keyframes = video_provider->GetKeyFrames();
else
keyframes.clear();
KeyframesOpen(keyframes);
}
void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
if (filename == timecodes_filename || filename.empty()) return;
try {
ovr_fps = agi::vfr::Framerate(filename);
timecodes_filename = filename;
config::mru->Add("Timecodes", filename);
OnSubtitlesCommit(0, std::set<const AssDialogue*>());
TimecodesOpen(ovr_fps);
}
catch (agi::fs::FileSystemError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error opening timecodes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
config::mru->Remove("Timecodes", filename);
}
catch (const agi::vfr::Error& e) {
wxLogError("Timecode file parse error: %s", to_wx(e.GetMessage()));
config::mru->Remove("Timecodes", filename);
}
}
void VideoContext::SaveTimecodes(agi::fs::path const& filename) {
try {
FPS().Save(filename, IsLoaded() ? GetLength() : -1);
config::mru->Add("Timecodes", filename);
}
catch (agi::fs::FileSystemError const& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, context->parent);
}
}
void VideoContext::CloseTimecodes() {
ovr_fps = agi::vfr::Framerate();
timecodes_filename.clear();
OnSubtitlesCommit(0, std::set<const AssDialogue*>());
TimecodesOpen(video_fps);
}
int VideoContext::TimeAtFrame(int frame, agi::vfr::Time type) const {
return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).TimeAtFrame(frame, type);
}
int VideoContext::FrameAtTime(int time, agi::vfr::Time type) const {
return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).FrameAtTime(time, type);
}
void VideoContext::OnVideoError(VideoProviderErrorEvent const& err) {
wxLogError(
"Failed seeking video. The video file may be corrupt or incomplete.\n"
"Error message reported: %s",
to_wx(err.GetMessage()));
}
void VideoContext::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
wxLogError(
"Failed rendering subtitles. Error message reported: %s",
to_wx(err.GetMessage()));
}

274
src/video_controller.cpp Normal file
View File

@ -0,0 +1,274 @@
// Copyright (c) 2005-2007, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
#include "video_controller.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
#include "audio_controller.h"
#include "compat.h"
#include "dialog_progress.h"
#include "dialog_video_properties.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "mkv_wrap.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "time_range.h"
#include "async_video_provider.h"
#include "utils.h"
#include "video_frame.h"
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <wx/msgdlg.h>
VideoController::VideoController(agi::Context *c)
: context(c)
, playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
, connections(agi::signal::make_vector({
context->ass->AddCommitListener(&VideoController::OnSubtitlesCommit, this),
context->project->AddVideoProviderListener(&VideoController::OnNewVideoProvider, this),
context->selectionController->AddActiveLineListener(&VideoController::OnActiveLineChanged, this),
context->subsController->AddFileSaveListener(&VideoController::OnSubtitlesSave, this),
}))
{
Bind(EVT_VIDEO_ERROR, &VideoController::OnVideoError, this);
Bind(EVT_SUBTITLES_ERROR, &VideoController::OnSubtitlesError, this);
playback.Bind(wxEVT_TIMER, &VideoController::OnPlayTimer, this);
}
void VideoController::OnNewVideoProvider(AsyncVideoProvider *new_provider) {
Stop();
frame_n = 0;
provider = new_provider;
if (!provider) {
color_matrix.clear();
return;
}
color_matrix = provider->GetColorSpace();
double dar = provider->GetDAR();
if (dar > 0)
SetAspectRatio(dar);
JumpToFrame(0);
}
void VideoController::OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed) {
if (!provider) return;
if ((type & AssFile::COMMIT_SCRIPTINFO) || type == AssFile::COMMIT_NEW) {
auto new_matrix = context->ass->GetScriptInfo("YCbCr Matrix");
if (!new_matrix.empty() && new_matrix != color_matrix) {
color_matrix = new_matrix;
provider->SetColorSpace(new_matrix);
}
}
if (changed.empty() || no_amend)
provider->LoadSubtitles(context->ass.get());
else
provider->UpdateSubtitles(context->ass.get(), changed);
if (!IsPlaying())
provider->GetFrame(frame_n, TimeAtFrame(frame_n));
no_amend = false;
}
void VideoController::OnSubtitlesSave() {
no_amend = true;
if (!provider) {
context->ass->SaveUIState("Video Aspect Ratio", "");
context->ass->SaveUIState("Video Position", "");
return;
}
std::string ar;
if (ar_type == AspectRatio::Custom)
ar = "c" + std::to_string(ar_value);
else
ar = std::to_string((int)ar_type);
context->ass->SaveUIState("Video Aspect Ratio", ar);
context->ass->SaveUIState("Video Position", std::to_string(frame_n));
}
void VideoController::OnActiveLineChanged(AssDialogue *line) {
if (line && provider && OPT_GET("Video/Subtitle Sync")->GetBool())
JumpToTime(line->Start);
}
void VideoController::RequestFrame() {
provider->RequestFrame(frame_n, TimeAtFrame(frame_n));
}
void VideoController::JumpToFrame(int n) {
if (!provider) return;
bool was_playing = IsPlaying();
if (was_playing)
Stop();
frame_n = mid(0, n, provider->GetFrameCount() - 1);
RequestFrame();
Seek(frame_n);
if (was_playing)
Play();
}
void VideoController::JumpToTime(int ms, agi::vfr::Time end) {
JumpToFrame(FrameAtTime(ms, end));
}
void VideoController::NextFrame() {
if (!provider || IsPlaying() || frame_n == provider->GetFrameCount())
return;
JumpToFrame(frame_n + 1);
if (playAudioOnStep->GetBool())
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
}
void VideoController::PrevFrame() {
if (!provider || IsPlaying() || frame_n == 0)
return;
JumpToFrame(frame_n - 1);
if (playAudioOnStep->GetBool())
context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
}
void VideoController::Play() {
if (IsPlaying()) {
Stop();
return;
}
if (!provider) return;
start_ms = TimeAtFrame(frame_n);
end_frame = provider->GetFrameCount() - 1;
context->audioController->PlayToEnd(start_ms);
playback_start_time = std::chrono::steady_clock::now();
playback.Start(10);
}
void VideoController::PlayLine() {
Stop();
AssDialogue *curline = context->selectionController->GetActiveLine();
if (!curline) return;
context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
// Round-trip conversion to convert start to exact
int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
start_ms = TimeAtFrame(startFrame);
end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
JumpToFrame(startFrame);
playback_start_time = std::chrono::steady_clock::now();
playback.Start(10);
}
void VideoController::Stop() {
if (IsPlaying()) {
playback.Stop();
context->audioController->Stop();
}
}
void VideoController::OnPlayTimer(wxTimerEvent &) {
using namespace std::chrono;
int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
if (next_frame == frame_n) return;
if (next_frame >= end_frame)
Stop();
else {
frame_n = next_frame;
RequestFrame();
Seek(frame_n);
}
}
double VideoController::GetARFromType(AspectRatio type) const {
switch (type) {
case AspectRatio::Default: return (double)provider->GetWidth()/provider->GetHeight();
case AspectRatio::Fullscreen: return 4.0/3.0;
case AspectRatio::Widescreen: return 16.0/9.0;
case AspectRatio::Cinematic: return 2.35;
}
throw agi::InternalError("Bad AR type", nullptr);
}
void VideoController::SetAspectRatio(double value) {
ar_type = AspectRatio::Custom;
ar_value = mid(.5, value, 5.);
ARChange(ar_type, ar_value);
}
void VideoController::SetAspectRatio(AspectRatio type) {
ar_value = mid(.5, GetARFromType(type), 5.);
ar_type = type;
ARChange(ar_type, ar_value);
}
int VideoController::TimeAtFrame(int frame, agi::vfr::Time type) const {
return context->project->Timecodes().TimeAtFrame(frame, type);
}
int VideoController::FrameAtTime(int time, agi::vfr::Time type) const {
return context->project->Timecodes().FrameAtTime(time, type);
}
void VideoController::OnVideoError(VideoProviderErrorEvent const& err) {
wxLogError(
"Failed seeking video. The video file may be corrupt or incomplete.\n"
"Error message reported: %s",
to_wx(err.GetMessage()));
}
void VideoController::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
wxLogError(
"Failed rendering subtitles. Error message reported: %s",
to_wx(err.GetMessage()));
}

View File

@ -27,28 +27,17 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file video_context.h
/// @see video_context.cpp
/// @ingroup video
///
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include <libaegisub/vfr.h>
#include <boost/filesystem/path.hpp>
#include <chrono>
#include <ctime>
#include <list>
#include <memory>
#include <set>
#include <wx/timer.h>
class AssDialogue;
class DialogProgress;
class ThreadedFrameSource;
class VideoProvider;
class AsyncVideoProvider;
struct SubtitlesProviderErrorEvent;
struct VideoFrame;
struct VideoProviderErrorEvent;
@ -66,47 +55,22 @@ enum class AspectRatio {
Custom
};
/// @class VideoContext
/// @brief Manage a bunch of things vaguely related to video playback
///
/// VideoContext's core responsibility is opening and playing videos. Along
/// with that, it also manages video timecodes and keyframes, and some
/// video-related UI properties
class VideoContext final : public wxEvtHandler {
/// Manage stuff related to video playback
class VideoController final : public wxEvtHandler {
/// Current frame number changed (new frame number)
agi::signal::Signal<int> Seek;
/// A new video was opened
agi::signal::Signal<> VideoOpen;
/// New keyframes opened (new keyframe data)
agi::signal::Signal<std::vector<int> const&> KeyframesOpen;
/// New timecodes opened (new timecode data)
agi::signal::Signal<agi::vfr::Framerate const&> TimecodesOpen;
/// Aspect ratio was changed (type, value)
agi::signal::Signal<AspectRatio, double> ARChange;
agi::Context *context;
DialogProgress *progress = nullptr;
/// The video provider owned by the threaded frame source, or nullptr if no
/// video is open
VideoProvider *video_provider = nullptr;
/// Asynchronous provider of video frames
std::unique_ptr<ThreadedFrameSource> provider;
/// Filename of currently open video
agi::fs::path video_filename;
AsyncVideoProvider *provider = nullptr;
/// Last seen script color matrix
std::string color_matrix;
/// List of frame numbers which are keyframes
std::vector<int> keyframes;
/// File name of the currently open keyframes or empty if keyframes are not overridden
agi::fs::path keyframes_filename;
/// Playback timer used to periodically check if we should go to the next
/// frame while playing video
wxTimer playback;
@ -132,16 +96,11 @@ class VideoContext final : public wxEvtHandler {
/// The current AR type
AspectRatio ar_type = AspectRatio::Default;
/// Does the currently loaded video file have subtitles muxed into it?
bool has_subtitles = false;
/// Filename of the currently loaded timecodes file, or empty if timecodes
/// have not been overridden
agi::fs::path timecodes_filename;
/// Cached option for audio playing when frame stepping
const agi::OptionValue* playAudioOnStep;
std::vector<agi::signal::Connection> connections;
/// Amending the frame source's copy of the subtitle file requires that it
/// be kept in perfect sync. Saving the file can add lines to the file
/// without a commit, breaking this sync, so force a non-amend after each
@ -150,58 +109,22 @@ class VideoContext final : public wxEvtHandler {
void OnPlayTimer(wxTimerEvent &event);
/// The timecodes from the video file
agi::vfr::Framerate video_fps;
/// External timecode which have been loaded, if any
agi::vfr::Framerate ovr_fps;
void OnVideoError(VideoProviderErrorEvent const& err);
void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
void OnSubtitlesSave();
void OnNewVideoProvider(AsyncVideoProvider *provider);
void OnActiveLineChanged(AssDialogue *line);
/// Close the video, keyframes and timecodes
void Reset();
void RequestFrame();
public:
VideoContext(agi::Context *context);
~VideoContext();
/// @brief Get the video provider used for the currently open video
VideoProvider *GetProvider() const { return video_provider; }
/// Synchronously get a video frame
/// @param n Frame number to get
/// @param raw If true, subtitles are not rendered on the frame
/// @return The requested frame
std::shared_ptr<VideoFrame> GetFrame(int n, bool raw = false);
/// Asynchronously get a video frame, triggering a EVT_FRAME_READY event when it's ready
/// @param n Frame number to get
void GetFrameAsync(int n);
/// Is there a video loaded?
bool IsLoaded() const { return !!video_provider; }
/// Get the file name of the currently open video, if any
agi::fs::path GetVideoName() const { return video_filename; }
VideoController(agi::Context *context);
/// Is the video currently playing?
bool IsPlaying() const { return playback.IsRunning(); }
/// Does the video file loaded have muxed subtitles that we can load?
bool HasSubtitles() const { return has_subtitles; }
/// Get the width of the currently open video
int GetWidth() const;
/// Get the height of the currently open video
int GetHeight() const;
/// Get the length in frames of the currently open video
int GetLength() const;
/// Get the current frame number
int GetFrameN() const { return frame_n; }
@ -221,12 +144,6 @@ public:
/// Get the current aspect ratio of the video
double GetAspectRatioValue() const { return ar_value; }
/// @brief Open a new video
/// @param filename Video to open, or empty to close the current video
void SetVideo(const agi::fs::path &filename);
/// @brief Close and reopen the current video
void Reload();
/// @brief Jump to the beginning of a frame
/// @param n Frame number to jump to
void JumpToFrame(int n);
@ -247,29 +164,8 @@ public:
void Stop();
DEFINE_SIGNAL_ADDERS(Seek, AddSeekListener)
DEFINE_SIGNAL_ADDERS(VideoOpen, AddVideoOpenListener)
DEFINE_SIGNAL_ADDERS(KeyframesOpen, AddKeyframesListener)
DEFINE_SIGNAL_ADDERS(TimecodesOpen, AddTimecodesListener)
DEFINE_SIGNAL_ADDERS(ARChange, AddARChangeListener)
const std::vector<int>& GetKeyFrames() const { return keyframes; };
agi::fs::path GetKeyFramesName() const { return keyframes_filename; }
void LoadKeyframes(agi::fs::path const& filename);
void SaveKeyframes(agi::fs::path const& filename);
void CloseKeyframes();
bool OverKeyFramesLoaded() const { return !keyframes_filename.empty(); }
bool KeyFramesLoaded() const { return !keyframes.empty(); }
agi::fs::path GetTimecodesName() const { return timecodes_filename; }
void LoadTimecodes(agi::fs::path const& filename);
void SaveTimecodes(agi::fs::path const& filename);
void CloseTimecodes();
bool OverTimecodesLoaded() const { return ovr_fps.IsLoaded(); }
bool TimecodesLoaded() const { return video_fps.IsLoaded() || ovr_fps.IsLoaded(); };
const agi::vfr::Framerate& FPS() const { return ovr_fps.IsLoaded() ? ovr_fps : video_fps; }
const agi::vfr::Framerate& VideoFPS() const { return video_fps; }
int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
};

View File

@ -35,26 +35,26 @@
#include "video_display.h"
#include "ass_file.h"
#include "async_video_provider.h"
#include "command/command.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "include/aegisub/hotkey.h"
#include "include/aegisub/menu.h"
#include "options.h"
#include "project.h"
#include "retina_helper.h"
#include "spline_curve.h"
#include "subs_controller.h"
#include "threaded_frame_source.h"
#include "utils.h"
#include "video_out_gl.h"
#include "video_context.h"
#include "video_controller.h"
#include "video_frame.h"
#include "visual_tool.h"
#include <libaegisub/make_unique.h>
#include <algorithm>
#include <wx/combobox.h>
#include <wx/dataobj.h>
#include <wx/dcclient.h>
@ -85,17 +85,12 @@ public:
#define E(cmd) cmd; if (GLenum err = glGetError()) throw OpenGlException(#cmd, err)
VideoDisplay::VideoDisplay(
wxToolBar *visualSubToolBar,
bool freeSize,
wxComboBox *zoomBox,
wxWindow* parent,
agi::Context *c)
VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBox, wxWindow *parent, agi::Context *c)
: wxGLCanvas(parent, -1, attribList)
, autohideTools(OPT_GET("Tool/Visual/Autohide"))
, con(c)
, zoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125)
, toolBar(visualSubToolBar)
, toolBar(toolbar)
, zoomBox(zoomBox)
, freeSize(freeSize)
, retina_helper(agi::make_unique<RetinaHelper>(this))
@ -111,10 +106,11 @@ VideoDisplay::VideoDisplay(
zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this);
con->videoController->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
slots.push_back(con->videoController->AddVideoOpenListener(&VideoDisplay::UpdateSize, this));
slots.push_back(con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this));
slots.push_back(con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this));
connections = agi::signal::make_vector({
con->project->AddVideoProviderListener(&VideoDisplay::UpdateSize, this),
con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this),
con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this),
});
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);
@ -132,8 +128,7 @@ VideoDisplay::VideoDisplay(
c->videoDisplay = this;
if (con->videoController->IsLoaded())
con->videoController->JumpToFrame(con->videoController->GetFrameN());
con->videoController->JumpToFrame(con->videoController->GetFrameN());
SetLayoutDirection(wxLayout_LeftToRight);
}
@ -164,7 +159,7 @@ void VideoDisplay::UploadFrameData(FrameReadyEvent &evt) {
}
void VideoDisplay::Render() try {
if (!con->videoController->IsLoaded() || !InitContext() || (!videoOut && !pending_frame))
if (!con->project->VideoProvider() || !InitContext() || (!videoOut && !pending_frame))
return;
if (!videoOut)
@ -185,7 +180,7 @@ void VideoDisplay::Render() try {
"programs and updating your video card drivers may fix this.\n"
"Error message reported: %s",
err.GetMessage());
con->videoController->SetVideo("");
con->project->CloseVideo();
return;
}
catch (const VideoOutRenderException& err) {
@ -235,7 +230,7 @@ catch (const agi::Exception &err) {
"An error occurred trying to render the video frame on the screen.\n"
"Error message reported: %s",
err.GetChainedMessage());
con->videoController->SetVideo("");
con->project->CloseVideo();
}
void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_percent) const {
@ -277,7 +272,8 @@ void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_per
}
void VideoDisplay::PositionVideo() {
if (!con->videoController->IsLoaded() || !IsShownOnScreen()) return;
auto provider = con->project->VideoProvider();
if (!provider || !IsShownOnScreen()) return;
viewport_left = 0;
viewport_bottom = GetClientSize().GetHeight() * scale_factor - videoSize.GetHeight();
@ -286,8 +282,8 @@ void VideoDisplay::PositionVideo() {
viewport_height = videoSize.GetHeight();
if (freeSize) {
int vidW = con->videoController->GetWidth();
int vidH = con->videoController->GetHeight();
int vidW = provider->GetWidth();
int vidH = provider->GetHeight();
AspectRatio arType = con->videoController->GetAspectRatioType();
double displayAr = double(viewport_width) / viewport_height;
@ -315,9 +311,10 @@ void VideoDisplay::PositionVideo() {
}
void VideoDisplay::UpdateSize() {
if (!con->videoController->IsLoaded() || !IsShownOnScreen()) return;
auto provider = con->project->VideoProvider();
if (!provider || !IsShownOnScreen()) return;
videoSize.Set(con->videoController->GetWidth(), con->videoController->GetHeight());
videoSize.Set(provider->GetWidth(), provider->GetHeight());
videoSize *= zoomValue;
if (con->videoController->GetAspectRatioType() != AspectRatio::Default)
videoSize.SetWidth(videoSize.GetHeight() * con->videoController->GetAspectRatioValue());
@ -346,7 +343,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
if (freeSize) {
videoSize = GetClientSize() * scale_factor;
PositionVideo();
zoomValue = double(viewport_height) / con->videoController->GetHeight();
zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight();
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
}
else {

View File

@ -36,14 +36,14 @@
#include "vector2d.h"
#include <deque>
#include <memory>
#include <typeinfo>
#include <vector>
#include <wx/glcanvas.h>
// Prototypes
class RetinaHelper;
class VideoContext;
class VideoController;
class VideoOutGL;
class VisualToolBase;
class wxComboBox;
@ -59,7 +59,7 @@ namespace agi {
class VideoDisplay final : public wxGLCanvas {
/// Signals the display is connected to
std::deque<agi::signal::Connection> slots;
std::vector<agi::signal::Connection> connections;
const agi::OptionValue* autohideTools;

View File

@ -39,7 +39,7 @@
#include "compat.h"
#include "options.h"
#include "utils.h"
#include "video_context.h"
#include "video_controller.h"
#include "video_frame.h"
#include <libaegisub/fs.h>

View File

@ -34,13 +34,15 @@
#include "video_slider.h"
#include "async_video_provider.h"
#include "base_grid.h"
#include "command/command.h"
#include "include/aegisub/context.h"
#include "include/aegisub/hotkey.h"
#include "options.h"
#include "project.h"
#include "utils.h"
#include "video_context.h"
#include "video_controller.h"
#include <wx/dcbuffer.h>
#include <wx/settings.h>
@ -48,19 +50,19 @@
VideoSlider::VideoSlider (wxWindow* parent, agi::Context *c)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE)
, c(c)
, connections(agi::signal::make_vector({
OPT_SUB("Video/Slider/Show Keyframes", [=] { Refresh(false); }),
c->videoController->AddSeekListener(&VideoSlider::SetValue, this),
c->project->AddVideoProviderListener(&VideoSlider::VideoOpened, this),
c->project->AddKeyframesListener(&VideoSlider::KeyframesChanged, this),
}))
{
SetClientSize(20,25);
SetMinSize(wxSize(20, 25));
SetBackgroundStyle(wxBG_STYLE_PAINT);
slots.push_back(OPT_SUB("Video/Slider/Show Keyframes", [=] { Refresh(false); }));
slots.push_back(c->videoController->AddSeekListener(&VideoSlider::SetValue, this));
slots.push_back(c->videoController->AddVideoOpenListener(&VideoSlider::VideoOpened, this));
slots.push_back(c->videoController->AddKeyframesListener(&VideoSlider::KeyframesChanged, this));
c->videoSlider = this;
VideoOpened();
VideoOpened(c->project->VideoProvider());
}
void VideoSlider::SetValue(int value) {
@ -69,10 +71,9 @@ void VideoSlider::SetValue(int value) {
Refresh(false);
}
void VideoSlider::VideoOpened() {
if (c->videoController->IsLoaded()) {
max = c->videoController->GetLength() - 1;
keyframes = c->videoController->GetKeyFrames();
void VideoSlider::VideoOpened(AsyncVideoProvider *provider) {
if (provider) {
max = provider->GetFrameCount() - 1;
Refresh(false);
}
}

View File

@ -27,27 +27,22 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file video_slider.h
/// @see video_slider.cpp
/// @ingroup custom_control
///
#include <libaegisub/signal.h>
#include <vector>
#include <wx/window.h>
#include <libaegisub/signal.h>
namespace agi { struct Context; }
class VideoContext;
class VideoController;
class AsyncVideoProvider;
/// @class VideoSlider
/// @brief Slider for displaying and adjusting the video position
class VideoSlider: public wxWindow {
agi::Context *c; ///< Associated project context
std::vector<int> keyframes; ///< Currently loaded keyframes
std::vector<agi::signal::Connection> slots;
std::vector<agi::signal::Connection> connections;
int val = 0; ///< Current frame number
int max = 1; ///< Last frame number
@ -60,7 +55,7 @@ class VideoSlider: public wxWindow {
void SetValue(int value);
/// Video open event handler
void VideoOpened();
void VideoOpened(AsyncVideoProvider *new_provider);
/// Keyframe open even handler
void KeyframesChanged(std::vector<int> const& newKeyframes);

View File

@ -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"

View File

@ -26,7 +26,6 @@
#include <libaegisub/owning_intrusive_list.h>
#include <libaegisub/signal.h>
#include <deque>
#include <set>
#include <wx/event.h>
@ -79,7 +78,7 @@ class VisualToolBase {
virtual void DoRefresh() { }
protected:
std::deque<agi::signal::Connection> connections;
std::vector<agi::signal::Connection> connections;
OpenGLWrapper gl;

View File

@ -27,7 +27,7 @@
#include "options.h"
#include "selection_controller.h"
#include "utils.h"
#include "video_context.h"
#include "video_controller.h"
#include "video_display.h"
#include <libaegisub/make_unique.h>
@ -79,7 +79,7 @@ void VisualToolDrag::UpdateToggleButtons() {
void VisualToolDrag::OnSubTool(wxCommandEvent &) {
// Toggle \move <-> \pos
VideoContext *vc = c->videoController.get();
VideoController *vc = c->videoController.get();
for (auto line : selection) {
Vector2D p1, p2;
int t1, t2;