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