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