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