Move video decoding and subtitle rendering to a worker thread

Makes the click event handler for the grid no longer slow when video
autoscroll is enabled, making it harder to accidently select multiple
lines.

Makes seeking speed no longer limited by decoding/rendering speed;
seeking faster than video can be decoded simply results in dropped
frames.

Makes editing the file while a slow-rendering frame is visible far more
responsive.

Originally committed to SVN as r4702.
This commit is contained in:
Thomas Goyne 2010-07-23 05:58:39 +00:00
parent 926b6152f1
commit 397b234fba
10 changed files with 461 additions and 275 deletions

View file

@ -1383,14 +1383,6 @@
RelativePath="..\..\src\export_framerate.h"
>
</File>
<File
RelativePath="..\..\src\export_visible_lines.cpp"
>
</File>
<File
RelativePath="..\..\src\export_visible_lines.h"
>
</File>
</Filter>
<Filter
Name="Video backend"
@ -1403,6 +1395,14 @@
RelativePath="..\..\src\keyframe.h"
>
</File>
<File
RelativePath="..\..\src\threaded_frame_source.cpp"
>
</File>
<File
RelativePath="..\..\src\threaded_frame_source.h"
>
</File>
<File
RelativePath="..\..\src\video_frame.cpp"
>

View file

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

View file

@ -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<AssDialogue*>(*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;

View file

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

View file

@ -0,0 +1,212 @@
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
// 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 <iterator>
#include <functional>
#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<const AssEntry*, bool> {
double time;
invisible_line(double time) : time(time * 1000.) { }
bool operator()(const AssEntry *entry) const {
const AssDialogue *diag = dynamic_cast<const AssDialogue*>(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<AssEntry*> 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<wxMutexLocker> 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);
}

View file

@ -0,0 +1,171 @@
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
// 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 <tr1/memory>
#include <wx/event.h>
#include <wx/thread.h>
#endif
#include <libaegisub/exception.h>
#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<SubtitlesProvider> provider;
/// Video provider
std::tr1::shared_ptr<VideoProvider> 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<AssFile> nextSubs; ///< Next queued AssFile
/// Subtitles to be loaded the next time a frame is requested
std::auto_ptr<AssFile> 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<VideoProvider> 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<wxMutexLocker> 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<wxMutexLocker> 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)

View file

@ -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 <libaegisub/access.h>
#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<VideoDisplay*>::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<AssFile> 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());
}

View file

@ -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<VideoDisplay*> displayList;
/// DOCME
AegiVideoFrame tempFrame;
std::tr1::shared_ptr<VideoProvider> videoProvider;
/// DOCME
std::auto_ptr<VideoProvider> provider;
/// DOCME
std::auto_ptr<SubtitlesProvider> subsProvider;
std::tr1::shared_ptr<ThreadedFrameSource> provider;
/// DOCME
std::vector<int> 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

View file

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

View file

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