diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
index ec62f4f53..c3ed757db 100644
--- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
+++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
@@ -1383,14 +1383,6 @@
RelativePath="..\..\src\export_framerate.h"
>
-
-
-
-
+
+
+
+
diff --git a/aegisub/src/Makefile.am b/aegisub/src/Makefile.am
index 2ba3d17d7..c6b43f363 100644
--- a/aegisub/src/Makefile.am
+++ b/aegisub/src/Makefile.am
@@ -251,7 +251,6 @@ aegisub_2_2_SOURCES = \
export_clean_info.cpp \
export_fixstyle.cpp \
export_framerate.cpp \
- export_visible_lines.cpp \
fft.cpp \
frame_main.cpp \
frame_main_events.cpp \
@@ -293,6 +292,7 @@ aegisub_2_2_SOURCES = \
thesaurus.cpp \
thesaurus_myspell.cpp \
timeedit_ctrl.cpp \
+ threaded_frame_source.cpp \
toggle_bitmap.cpp \
tooltip_manager.cpp \
utils.cpp \
diff --git a/aegisub/src/export_visible_lines.cpp b/aegisub/src/export_visible_lines.cpp
deleted file mode 100644
index 7b6258d42..000000000
--- a/aegisub/src/export_visible_lines.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2005, Niels Martin Hansen
-// 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/
-//
-// $Id$
-
-/// @file export_visible_lines.cpp
-/// @brief Limit to Visible Lines export filter
-/// @ingroup export
-///
-
-#include "config.h"
-
-#include "ass_dialogue.h"
-#include "ass_file.h"
-#include "ass_override.h"
-#include "export_visible_lines.h"
-#include "video_context.h"
-
-/// @brief Constructor
-///
-AssLimitToVisibleFilter::AssLimitToVisibleFilter() {
- initialized = false;
- frame = -1;
-}
-
-void AssLimitToVisibleFilter::Init() {
- if (initialized) return;
- initialized = true;
- autoExporter = true;
- hidden = false;
- Register(_("Limit to Visible Lines"),1000000);
- description = _("Limit to Visible Lines");
-}
-
-/// @brief Process
-/// @param subs
-/// @param export_dialog
-void AssLimitToVisibleFilter::ProcessSubs(AssFile *subs, wxWindow *) {
- if (frame == -1) return;
-
- int time = VideoContext::Get()->TimeAtFrame(frame);
-
- for (entryIter cur = subs->Line.begin(); cur != subs->Line.end(); ) {
- AssDialogue *diag = dynamic_cast(*cur);
- if (diag && (diag->Start.GetMS() > time || diag->End.GetMS() <= time)) {
- delete *cur;
- cur = subs->Line.erase(cur);
- }
- else {
- ++cur;
- }
- }
-}
-
-/// @brief Set limitation time
-/// @param _frame
-///
-void AssLimitToVisibleFilter::SetFrame(int _frame) {
- instance.frame = _frame;
-}
-
-/// DOCME
-AssLimitToVisibleFilter AssLimitToVisibleFilter::instance;
diff --git a/aegisub/src/export_visible_lines.h b/aegisub/src/export_visible_lines.h
deleted file mode 100644
index cf46cb866..000000000
--- a/aegisub/src/export_visible_lines.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 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/
-//
-// $Id$
-
-/// @file export_visible_lines.h
-/// @see export_visible_lines.cpp
-/// @ingroup export
-///
-
-
-
-
-///////////
-// Headers
-#include "ass_export_filter.h"
-
-
-/// DOCME
-/// @class AssLimitToVisibleFilter
-/// @brief DOCME
-///
-/// DOCME
-class AssLimitToVisibleFilter : public AssExportFilter {
-private:
-
- /// DOCME
- static AssLimitToVisibleFilter instance;
-
- /// DOCME
- int frame;
-
- AssLimitToVisibleFilter();
- void Init();
-
-public:
- static void SetFrame(int frame=-1);
-
- void ProcessSubs(AssFile *subs, wxWindow *export_dialog);
-};
-
-
diff --git a/aegisub/src/threaded_frame_source.cpp b/aegisub/src/threaded_frame_source.cpp
new file mode 100644
index 000000000..9e2dd7661
--- /dev/null
+++ b/aegisub/src/threaded_frame_source.cpp
@@ -0,0 +1,212 @@
+// Copyright (c) 2010, Thomas Goyne
+// 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/
+//
+// $Id$
+
+/// @file threaded_frame_source.cpp
+/// @see threaded_frame_source.h
+/// @ingroup video
+///
+
+#include "threaded_frame_source.h"
+
+#ifndef AGI_PRE
+#include
+#include
+#endif
+
+#include "ass_dialogue.h"
+#include "ass_exporter.h"
+#include "ass_file.h"
+#include "compat.h"
+#include "subtitles_provider_manager.h"
+#include "video_provider_manager.h"
+
+// Test if a line is a dialogue line which is not visible at the given time
+struct invisible_line : public std::unary_function {
+ double time;
+ invisible_line(double time) : time(time * 1000.) { }
+ bool operator()(const AssEntry *entry) const {
+ const AssDialogue *diag = dynamic_cast(entry);
+ return diag && (diag->Start.GetMS() > time || diag->End.GetMS() <= time);
+ }
+};
+
+AegiVideoFrame const& ThreadedFrameSource::ProcFrame(int frameNum, double time, bool raw) {
+ AegiVideoFrame *frame;
+ {
+ wxMutexLocker locker(providerMutex);
+ frame = frameBuffer + frameBufferIdx;
+ frameBufferIdx = !frameBufferIdx;
+ try {
+ frame->CopyFrom(videoProvider->GetFrame(frameNum));
+ }
+ catch (const wchar_t *err) { throw VideoProviderErrorEvent(err); }
+ catch (wxString const& err) { throw VideoProviderErrorEvent(err); }
+ }
+
+ // This deliberately results in a call to LoadSubtitles while a render
+ // is pending making the queued render use the new file
+ if (!raw) {
+ try {
+ wxMutexLocker locker(fileMutex);
+ if (subs.get() && singleFrame != frameNum) {
+ // Generally edits and seeks come in groups; if the last thing done
+ // was seek it is more likely that the user will seek again and
+ // vice versa. As such, if this is the first frame requested after
+ // an edit, only export the currently visible lines (because the
+ // other lines will probably not be viewed before the file changes
+ // again), and if it's a different frame, export the entire file.
+ if (singleFrame == -1) {
+ singleFrame = frameNum;
+ // Copying a nontrivially sized AssFile is fairly slow, so
+ // instead muck around with its innards to just temporarily
+ // remove the non-visible lines without deleting them
+ std::list visible;
+ std::remove_copy_if(subs->Line.begin(), subs->Line.end(),
+ std::back_inserter(visible),
+ invisible_line(time));
+ try {
+ std::swap(subs->Line, visible);
+ provider->LoadSubtitles(subs.get());
+ }
+ catch(...) {
+ std::swap(subs->Line, visible);
+ throw;
+ }
+ }
+ else {
+ provider->LoadSubtitles(subs.get());
+ subs.reset();
+ }
+ }
+ }
+ catch (const wchar_t *err) { throw SubtitlesProviderErrorEvent(err); }
+ catch (wxString const& err) { throw SubtitlesProviderErrorEvent(err); }
+
+ provider->DrawSubtitles(*frame, time);
+ }
+ return *frame;
+}
+
+void *ThreadedFrameSource::Entry() {
+ while (!TestDestroy() && run) {
+ jobMutex.Lock();
+ if (nextSubs.get()) {
+ wxMutexLocker locker(fileMutex);
+ subs = nextSubs;
+ singleFrame = -1;
+ }
+ if (nextTime == -1.) {
+ jobReady.Wait();
+ continue;
+ }
+
+ double time = nextTime;
+ int frameNum = nextFrame;
+ nextTime = -1.;
+ jobMutex.Unlock();
+
+ try {
+ AegiVideoFrame const& frame = ProcFrame(frameNum, time);
+
+ std::tr1::shared_ptr evtLock(new wxMutexLocker(evtMutex));
+ FrameReadyEvent *evt = new FrameReadyEvent(&frame, time, evtLock);
+ evt->SetEventType(EVT_FRAME_READY);
+ parent->QueueEvent(evt);
+ }
+ catch (wxEvent const& err) {
+ // Pass error back to parent thread
+ parent->QueueEvent(err.Clone());
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+ThreadedFrameSource::ThreadedFrameSource(wxString videoFileName, wxEvtHandler *parent)
+: wxThread()
+, provider(SubtitlesProviderFactoryManager::GetProvider())
+, videoProvider(VideoProviderFactoryManager::GetProvider(videoFileName))
+, parent(parent)
+, nextTime(-1.)
+, jobReady(jobMutex)
+, frameBufferIdx(0)
+, run(true)
+{
+ Create();
+ Run();
+}
+
+void ThreadedFrameSource::LoadSubtitles(AssFile *subs) {
+ AssExporter exporter(subs);
+ exporter.AddAutoFilters();
+ AssFile *exported = exporter.ExportTransform();
+ wxMutexLocker locker(jobMutex);
+ // Set nextSubs and let the worker thread move it to subs so that we don't
+ // have to lock fileMutex on the GUI thread, as that can be locked for
+ // extended periods of time with slow-rendering subtitles
+ nextSubs.reset(exported);
+}
+
+void ThreadedFrameSource::RequestFrame(int frame, double time) {
+ wxMutexLocker locker(jobMutex);
+ nextTime = time;
+ nextFrame = frame;
+ jobReady.Signal();
+}
+
+AegiVideoFrame const& ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
+ return ProcFrame(frame, time, raw);
+}
+
+void ThreadedFrameSource::End() {
+ run = false;
+ jobReady.Signal();
+}
+
+ThreadedFrameSource::~ThreadedFrameSource() {
+ frameBuffer[0].Clear();
+ frameBuffer[1].Clear();
+}
+
+wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
+wxDEFINE_EVENT(EVT_VIDEO_ERROR, VideoProviderErrorEvent);
+wxDEFINE_EVENT(EVT_SUBTITLES_ERROR, SubtitlesProviderErrorEvent);
+
+VideoProviderErrorEvent::VideoProviderErrorEvent(wxString msg)
+: agi::Exception(STD_STR(msg), NULL)
+{
+ SetEventType(EVT_VIDEO_ERROR);
+}
+SubtitlesProviderErrorEvent::SubtitlesProviderErrorEvent(wxString msg)
+: agi::Exception(STD_STR(msg), NULL)
+{
+ SetEventType(EVT_SUBTITLES_ERROR);
+}
diff --git a/aegisub/src/threaded_frame_source.h b/aegisub/src/threaded_frame_source.h
new file mode 100644
index 000000000..cf139a5ae
--- /dev/null
+++ b/aegisub/src/threaded_frame_source.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2010, Thomas Goyne
+// 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/
+//
+// $Id$
+
+/// @file threaded_frame_source.h
+/// @see threaded_frame_source.cpp
+/// @ingroup video
+///
+
+#ifndef AGI_PRE
+#include
+
+#include
+#include
+#endif
+
+#include
+#include "video_frame.h"
+
+class AssFile;
+class SubtitlesProvider;
+class VideoProvider;
+
+/// @class ThreadedFrameSource
+/// @brief An asynchronous video decoding and subtitle rendering wrapper
+class ThreadedFrameSource : public wxThread {
+ /// Subtitles provider
+ std::auto_ptr provider;
+ /// Video provider
+ std::tr1::shared_ptr videoProvider;
+ /// Event handler to send FrameReady events to
+ wxEvtHandler *parent;
+
+ int nextFrame; ///< Next queued frame, or -1 for none
+ double nextTime; ///< Next queued time
+ std::auto_ptr nextSubs; ///< Next queued AssFile
+
+ /// Subtitles to be loaded the next time a frame is requested
+ std::auto_ptr subs;
+ /// If subs is set and this is not -1, frame which subs was limited to when
+ /// it was last sent to the subtitle provider
+ int singleFrame;
+
+ wxMutex fileMutex; ///< Mutex for subtitle file usage
+ wxMutex jobMutex; ///< Mutex for nextFrame/nextTime
+ wxMutex providerMutex; ///< Mutex for video provider
+ wxMutex evtMutex; ///< Mutex for FrameReadyEvents associated with this
+
+ wxCondition jobReady; ///< Signal for indicating that a frame has be requested
+
+ /// Frame buffers to render subtitles into
+ /// Two are needed so that a frame can be decoded/rendered while the GUI is
+ /// doing stuff with the other
+ AegiVideoFrame frameBuffer[2];
+ /// Next frame buffer to use
+ int frameBufferIdx;
+
+ bool run; ///< Should the thread continue to run
+
+ void *Entry();
+ AegiVideoFrame const& ProcFrame(int frameNum, double time, bool raw = false);
+public:
+ /// @brief Load the passed subtitle file
+ /// @param subs File to load
+ ///
+ /// This function blocks until is it is safe for the calling thread to
+ /// modify subs
+ void LoadSubtitles(AssFile *subs) throw();
+
+ /// @brief Queue a request for a frame
+ /// @brief frame Frame number
+ /// @brief time Exact start time of the frame in seconds
+ ///
+ /// This merely queues up a request and deletes any pending requests; there
+ /// is no guarantee that the requested frame will ever actually be produced
+ void RequestFrame(int frame, double time) throw();
+
+ /// @brief Synchronously get a frame
+ /// @brief frame Frame number
+ /// @brief time Exact start time of the frame in seconds
+ /// @brief raw Get raw frame without subtitles
+ AegiVideoFrame const& GetFrame(int frame, double time, bool raw = false);
+
+ /// @brief Non-blocking Delete
+ ///
+ /// Needed due to that calling Delete while the thread is waiting on
+ /// jobReady results in a deadlock; as such this is the only safe way to
+ /// destroy a ThreadedFrameSource
+ void End();
+
+ std::tr1::shared_ptr GetVideoProvider() const { return videoProvider; }
+
+ /// @brief Constructor
+ /// @param videoFileName File to open
+ /// @param parent Event handler to send FrameReady events to
+ ThreadedFrameSource(wxString videoFileName, wxEvtHandler *parent);
+ ~ThreadedFrameSource();
+};
+
+/// @class FrameReadyEvent
+/// @brief Event which signals that a requested frame is ready
+class FrameReadyEvent : public wxEvent {
+ /// Externally passed mutex that is kept locked as long as this or a copy
+ /// of this exists. Used to ensure that the next FrameReadyEvent is not
+ /// announced until this one is fully processed.
+ ///
+ /// Although tr1 does not require that shared_ptr be thread safe (due to
+ /// the standard having no concept of threads), all implementations have
+ /// at least a thread safe reference counter, which is all we happen to
+ /// need here.
+ std::tr1::shared_ptr mutex;
+public:
+ /// Frame which is ready; only guaranteed to be valid as long as this
+ /// event object exists
+ const AegiVideoFrame *frame;
+ /// Time which was used for subtitle rendering
+ double time;
+ wxEvent *Clone() const { return new FrameReadyEvent(*this); };
+ FrameReadyEvent(const AegiVideoFrame *frame, double time, std::tr1::shared_ptr mutex)
+ : mutex(mutex), frame(frame), time(time) {
+ }
+};
+
+// These exceptions are wxEvents so that they can be passed directly back to
+// the parent thread as events
+class VideoProviderErrorEvent : public wxEvent, public agi::Exception {
+public:
+ const char * GetName() const { return "video/error"; }
+ wxEvent *Clone() const { return new VideoProviderErrorEvent(*this); };
+ agi::Exception *Copy() const { return new VideoProviderErrorEvent(*this); };
+ VideoProviderErrorEvent(wxString msg);
+};
+
+class SubtitlesProviderErrorEvent : public wxEvent, public agi::Exception {
+public:
+ const char * GetName() const { return "subtitles/error"; }
+ wxEvent *Clone() const { return new SubtitlesProviderErrorEvent(*this); };
+ agi::Exception *Copy() const { return new SubtitlesProviderErrorEvent(*this); };
+ SubtitlesProviderErrorEvent(wxString msg);
+};
+
+wxDECLARE_EVENT(EVT_FRAME_READY, FrameReadyEvent)
+wxDECLARE_EVENT(EVT_VIDEO_ERROR, VideoProviderErrorEvent)
+wxDECLARE_EVENT(EVT_SUBTITLES_ERROR, SubtitlesProviderErrorEvent)
diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp
index 152707ba1..61b3a5d5c 100644
--- a/aegisub/src/video_context.cpp
+++ b/aegisub/src/video_context.cpp
@@ -54,13 +54,12 @@
#endif
#include "ass_dialogue.h"
-#include "ass_exporter.h"
#include "ass_file.h"
#include "ass_style.h"
#include "ass_time.h"
#include "audio_display.h"
#include "compat.h"
-#include "export_visible_lines.h"
+#include "include/aegisub/video_provider.h"
#include "keyframe.h"
#include
#include "main.h"
@@ -68,12 +67,11 @@
#include "standard_paths.h"
#include "subs_edit_box.h"
#include "subs_grid.h"
-#include "subtitles_provider_manager.h"
+#include "threaded_frame_source.h"
#include "utils.h"
#include "video_box.h"
#include "video_context.h"
#include "video_display.h"
-#include "video_provider_manager.h"
/// IDs
enum {
@@ -105,6 +103,8 @@ VideoContext::VideoContext()
, VFR_Input(videoFPS)
, VFR_Output(ovrFPS)
{
+ Bind(EVT_VIDEO_ERROR, &VideoContext::OnVideoError, this);
+ Bind(EVT_SUBTITLES_ERROR, &VideoContext::OnSubtitlesError, this);
}
VideoContext::~VideoContext () {
@@ -112,7 +112,6 @@ VideoContext::~VideoContext () {
delete audio->provider;
delete audio->player;
}
- tempFrame.Clear();
}
VideoContext *VideoContext::Get() {
@@ -143,11 +142,10 @@ void VideoContext::Reset() {
// Clean up video data
videoName.clear();
- tempFrame.Clear();
// Remove provider
+ videoProvider.reset();
provider.reset();
- subsProvider.reset();
}
void VideoContext::SetVideo(const wxString &filename) {
@@ -158,20 +156,17 @@ void VideoContext::SetVideo(const wxString &filename) {
try {
grid->CommitChanges(true);
- // Choose a provider
- provider.reset(VideoProviderFactoryManager::GetProvider(filename));
-
- // Get subtitles provider
try {
- subsProvider.reset(SubtitlesProviderFactoryManager::GetProvider());
+ provider.reset(new ThreadedFrameSource(filename, this), std::mem_fun(&ThreadedFrameSource::End));
+ videoProvider = provider->GetVideoProvider();
}
- catch (wxString err) { wxMessageBox(_T("Error while loading subtitles provider: ") + err,_T("Subtitles provider")); }
- catch (const wchar_t *err) { wxMessageBox(_T("Error while loading subtitles provider: ") + wxString(err),_T("Subtitles provider")); }
+ catch (wxString err) { wxMessageBox(L"Error while loading video: " + err, L"Video Error"); }
+ catch (const wchar_t *err) { wxMessageBox(L"Error while loading video: " + wxString(err), L"Video Error"); }
- keyFrames = provider->GetKeyFrames();
+ keyFrames = videoProvider->GetKeyFrames();
// Set frame rate
- videoFPS = provider->GetFPS();
+ videoFPS = videoProvider->GetFPS();
if (ovrFPS.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) {
@@ -181,7 +176,7 @@ void VideoContext::SetVideo(const wxString &filename) {
}
// Gather video parameters
- length = provider->GetFrameCount();
+ length = videoProvider->GetFrameCount();
// Set filename
videoName = filename;
@@ -193,7 +188,7 @@ void VideoContext::SetVideo(const wxString &filename) {
frame_n = 0;
// Show warning
- wxString warning = provider->GetWarning();
+ wxString warning = videoProvider->GetWarning();
if (!warning.empty()) wxMessageBox(warning,_T("Warning"),wxICON_WARNING | wxOK);
hasSubtitles = false;
@@ -204,16 +199,14 @@ void VideoContext::SetVideo(const wxString &filename) {
UpdateDisplays(true);
}
- catch (wxString &e) {
+ catch (const wxString &e) {
wxMessageBox(e,_T("Error setting video"),wxICON_ERROR | wxOK);
}
}
void VideoContext::AddDisplay(VideoDisplay *display) {
- for (std::list::iterator cur=displayList.begin();cur!=displayList.end();cur++) {
- if ((*cur) == display) return;
- }
- displayList.push_back(display);
+ if (std::find(displayList.begin(), displayList.end(), display) == displayList.end())
+ displayList.push_back(display);
}
void VideoContext::RemoveDisplay(VideoDisplay *display) {
@@ -247,26 +240,10 @@ void VideoContext::UpdateDisplays(bool full, bool seek) {
}
}
-void VideoContext::Refresh(bool full) {
- if (subsProvider.get()) {
- if (full) {
- AssLimitToVisibleFilter::SetFrame(-1);
- singleFrame = false;
- }
- else {
- AssLimitToVisibleFilter::SetFrame(frame_n);
- singleFrame = true;
- }
+void VideoContext::Refresh() {
+ if (!IsLoaded()) return;
- AssExporter exporter(grid->ass);
- exporter.AddAutoFilters();
- try {
- std::auto_ptr exported(exporter.ExportTransform());
- subsProvider->LoadSubtitles(exported.get());
- }
- catch (wxString err) { wxMessageBox(_T("Error while invoking subtitles provider: ") + err,_T("Subtitles provider")); }
- catch (const wchar_t *err) { wxMessageBox(_T("Error while invoking subtitles provider: ") + wxString(err),_T("Subtitles provider")); }
- }
+ provider->LoadSubtitles(grid->ass);
UpdateDisplays(false);
}
@@ -278,10 +255,6 @@ void VideoContext::JumpToFrame(int n) {
frame_n = n;
- if (singleFrame) {
- Refresh(true);
- }
-
UpdateDisplays(false, true);
static agi::OptionValue* highlight = OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame");
@@ -292,33 +265,19 @@ void VideoContext::JumpToTime(int ms, agi::vfr::Time end) {
JumpToFrame(FrameAtTime(ms, end));
}
-AegiVideoFrame VideoContext::GetFrame(int n,bool raw) {
- // Current frame if -1
- if (n == -1) n = frame_n;
+void VideoContext::GetFrameAsync(int n) {
+ provider->RequestFrame(n,videoFPS.TimeAtFrame(n)/1000.0);
+}
- AegiVideoFrame frame = provider->GetFrame(n);
-
- // Raster subtitles if available/necessary
- if (!raw && subsProvider.get()) {
- tempFrame.CopyFrom(frame);
- try {
- subsProvider->DrawSubtitles(tempFrame,videoFPS.TimeAtFrame(n)/1000.0);
- }
- catch (...) {
- wxLogError(L"Subtitle rendering for the current frame failed.\n");
- }
- return tempFrame;
- }
-
- // Return pure frame
- else return frame;
+AegiVideoFrame const& VideoContext::GetFrame(int n, bool raw) {
+ return provider->GetFrame(n, videoFPS.TimeAtFrame(n)/1000.0, raw);
}
int VideoContext::GetWidth() const {
- return provider->GetWidth();
+ return videoProvider->GetWidth();
}
int VideoContext::GetHeight() const {
- return provider->GetHeight();
+ return videoProvider->GetHeight();
}
void VideoContext::SaveSnapshot(bool raw) {
@@ -520,8 +479,8 @@ void VideoContext::SaveKeyframes(wxString filename) {
void VideoContext::CloseKeyframes() {
keyFramesFilename.clear();
- if (provider.get()) {
- keyFrames = provider->GetKeyFrames();
+ if (videoProvider.get()) {
+ keyFrames = videoProvider->GetKeyFrames();
}
else {
keyFrames.clear();
@@ -571,3 +530,15 @@ int VideoContext::FrameAtTime(int time, agi::vfr::Time type) const {
}
return videoFPS.FrameAtTime(time, type);
}
+
+void VideoContext::OnVideoError(VideoProviderErrorEvent const& err) {
+ wxLogError(
+ L"Failed seeking video. The video file may be corrupt or incomplete.\n"
+ L"Error message reported: %s",
+ lagi_wxString(err.GetMessage()).c_str());
+}
+void VideoContext::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
+ wxLogError(
+ L"Failed rendering subtitles. Error message reported: %s",
+ lagi_wxString(err.GetMessage()).c_str());
+}
diff --git a/aegisub/src/video_context.h b/aegisub/src/video_context.h
index 27a348870..96940f01c 100644
--- a/aegisub/src/video_context.h
+++ b/aegisub/src/video_context.h
@@ -65,8 +65,10 @@ class AudioProvider;
class AudioDisplay;
class AssDialogue;
class KeyFrameFile;
-class SubtitlesProvider;
+class SubtitlesProviderErrorEvent;
+class ThreadedFrameSource;
class VideoProvider;
+class VideoProviderErrorEvent;
class VideoDisplay;
namespace agi {
@@ -87,13 +89,10 @@ private:
std::list displayList;
/// DOCME
- AegiVideoFrame tempFrame;
+ std::tr1::shared_ptr videoProvider;
/// DOCME
- std::auto_ptr provider;
-
- /// DOCME
- std::auto_ptr subsProvider;
+ std::tr1::shared_ptr provider;
/// DOCME
std::vector keyFrames;
@@ -153,6 +152,9 @@ private:
bool singleFrame;
+ void OnVideoError(VideoProviderErrorEvent const& err);
+ void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
+
public:
/// DOCME
SubtitlesGrid *grid;
@@ -174,15 +176,16 @@ public:
/// @brief Get the video provider used for the currently open video
- VideoProvider *GetProvider() const { return provider.get(); }
- AegiVideoFrame GetFrame(int n,bool raw=false);
+ VideoProvider *GetProvider() const { return videoProvider.get(); }
+ AegiVideoFrame const& GetFrame(int n, bool raw = false);
+ void GetFrameAsync(int n);
/// @brief Save the currently displayed frame as an image
/// @param raw Should the frame have subtitles?
void SaveSnapshot(bool raw);
/// @brief Is there a video loaded?
- bool IsLoaded() const { return !!provider.get(); }
+ bool IsLoaded() const { return !!videoProvider.get(); }
/// @brief Is the video currently playing?
bool IsPlaying() const { return isPlaying; }
@@ -233,9 +236,7 @@ public:
void JumpToTime(int ms, agi::vfr::Time end = agi::vfr::START);
/// @brief Refresh the subtitle provider
- /// @param full Send the entire subtitle file to the renderer rather than
- /// just the lines visible on the current frame
- void Refresh(bool full = false);
+ void Refresh();
/// @brief Update the video display
/// @param full Recalculate size and slider lengths
diff --git a/aegisub/src/video_display.cpp b/aegisub/src/video_display.cpp
index 62ff9aa8b..0fbfb5516 100644
--- a/aegisub/src/video_display.cpp
+++ b/aegisub/src/video_display.cpp
@@ -60,6 +60,7 @@
#include "hotkeys.h"
#include "main.h"
#include "subs_grid.h"
+#include "threaded_frame_source.h"
#include "video_out_gl.h"
#include "video_box.h"
#include "video_context.h"
@@ -141,10 +142,12 @@ VideoDisplay::VideoDisplay(VideoBox *box, VideoSlider *ControlSlider, wxTextCtrl
, freeSize(false)
{
box->Bind(wxEVT_COMMAND_TOOL_CLICKED, &VideoDisplay::OnMode, this, Video_Mode_Standard, Video_Mode_Vector_Clip);
+ VideoContext::Get()->Bind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
SetCursor(wxNullCursor);
}
VideoDisplay::~VideoDisplay () {
+ VideoContext::Get()->Unbind(EVT_FRAME_READY, &VideoDisplay::UploadFrameData, this);
VideoContext::Get()->RemoveDisplay(this);
}
@@ -214,39 +217,23 @@ void VideoDisplay::SetFrame(int frameNumber) {
if (context->IsLoaded()) {
context->GetScriptSize(scriptW, scriptH);
tool->SetFrame(frameNumber);
-
- UploadFrameData();
+ context->GetFrameAsync(currentFrame);
}
- Render();
}
-void VideoDisplay::UploadFrameData() {
+void VideoDisplay::UploadFrameData(FrameReadyEvent &evt) {
if (!InitContext()) return;
- VideoContext *context = VideoContext::Get();
- AegiVideoFrame frame;
+
try {
- frame = context->GetFrame(currentFrame);
- }
- catch (const wxChar *err) {
- wxLogError(
- L"Failed seeking video. The video file may be corrupt or incomplete.\n"
- L"Error message reported: %s",
- err);
- }
- catch (...) {
- wxLogError(
- L"Failed seeking video. The video file may be corrupt or incomplete.\n"
- L"No further error message given.");
- }
- try {
- videoOut->UploadFrameData(frame);
+ videoOut->UploadFrameData(*evt.frame);
}
catch (const VideoOutInitException& err) {
wxLogError(
- L"Failed to initialize video display. Closing other running programs and updating your video card drivers may fix this.\n"
+ L"Failed to initialize video display. Closing other running "
+ L"programs and updating your video card drivers may fix this.\n"
L"Error message reported: %s",
err.GetMessage().c_str());
- context->Reset();
+ VideoContext::Get()->Reset();
}
catch (const VideoOutRenderException& err) {
wxLogError(
@@ -254,14 +241,14 @@ void VideoDisplay::UploadFrameData() {
L"Error message reported: %s",
err.GetMessage().c_str());
}
+ Render();
}
void VideoDisplay::Refresh() {
if (!tool.get()) tool.reset(new VisualToolCross(this, video, toolBar));
if (!InitContext()) return;
- UploadFrameData();
+ VideoContext::Get()->GetFrameAsync(currentFrame);
tool->Refresh();
- Render();
}
void VideoDisplay::SetFrameRange(int from, int to) {
@@ -597,14 +584,14 @@ void VideoDisplay::FromScriptCoords(int *x, int *y) const {
void VideoDisplay::OnCopyToClipboard(wxCommandEvent &) {
if (wxTheClipboard->Open()) {
- wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(-1).GetImage(),24)));
+ wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(currentFrame).GetImage(),24)));
wxTheClipboard->Close();
}
}
void VideoDisplay::OnCopyToClipboardRaw(wxCommandEvent &) {
if (wxTheClipboard->Open()) {
- wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(-1,true).GetImage(),24)));
+ wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(currentFrame,true).GetImage(),24)));
wxTheClipboard->Close();
}
}
diff --git a/aegisub/src/video_display.h b/aegisub/src/video_display.h
index b667c05e7..8b3711c18 100644
--- a/aegisub/src/video_display.h
+++ b/aegisub/src/video_display.h
@@ -43,6 +43,7 @@
#endif
// Prototypes
+class FrameReadyEvent;
class VideoSlider;
class VideoBox;
class VideoOutGL;
@@ -97,7 +98,7 @@ class VideoDisplay : public wxGLCanvas {
void DrawOverscanMask(int sizeH, int sizeV, wxColor color, double alpha) const;
/// Upload the image for the current frame to the video card
- void UploadFrameData();
+ void UploadFrameData(FrameReadyEvent&);
/// @brief Paint event
void OnPaint(wxPaintEvent& event);