Aegisub/src/command/video.cpp

1109 lines
34 KiB
C++
Raw Normal View History

// Copyright (c) 2005-2010, Niels Martin Hansen
// Copyright (c) 2005-2010, Rodrigo Braz Monteiro
// Copyright (c) 2010, Amar Takhar
// 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 "command.h"
#include "../ass_dialogue.h"
#include "../ass_file.h"
#include "../ass_style.h"
#include "../async_video_provider.h"
#include "../compat.h"
#include "../dialog_detached_video.h"
#include "../dialog_manager.h"
#include "../dialogs.h"
#include "../format.h"
#include "../frame_main.h"
#include "../include/aegisub/context.h"
#include "../include/aegisub/subtitles_provider.h"
#include "../libresrc/libresrc.h"
#include "../options.h"
#include "../project.h"
#include "../selection_controller.h"
#include "../text_selection_controller.h"
#include "../utils.h"
#include "../video_controller.h"
#include "../video_display.h"
#include "../video_frame.h"
#include <libaegisub/ass/time.h>
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/of_type_adaptor.h>
#include <libaegisub/util.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/sliced.hpp>
#include <wx/msgdlg.h>
#include <wx/textdlg.h>
namespace {
using cmd::Command;
using namespace boost::adaptors;
struct validator_video_loaded : public Command {
CMD_TYPE(COMMAND_VALIDATE)
2013-11-21 18:13:36 +01:00
bool Validate(const agi::Context *c) override {
return !!c->project->VideoProvider();
}
};
struct validator_video_attached : public Command {
CMD_TYPE(COMMAND_VALIDATE)
2013-11-21 18:13:36 +01:00
bool Validate(const agi::Context *c) override {
return !!c->project->VideoProvider() && !c->dialog->Get<DialogDetachedVideo>();
}
};
struct video_aspect_cinematic final : public validator_video_loaded {
CMD_NAME("video/aspect/cinematic")
STR_MENU("&Cinematic (2.35)")
STR_DISP("Cinematic (2.35)")
STR_HELP("Force video to 2.35 aspect ratio")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return c->videoController->GetAspectRatioType() == AspectRatio::Cinematic;
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
c->videoController->SetAspectRatio(AspectRatio::Cinematic);
c->frame->SetDisplayMode(1,-1);
}
};
struct video_aspect_custom final : public validator_video_loaded {
CMD_NAME("video/aspect/custom")
STR_MENU("C&ustom...")
STR_DISP("Custom")
STR_HELP("Force video to a custom aspect ratio")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return c->videoController->GetAspectRatioType() == AspectRatio::Custom;
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
std::string value = from_wx(wxGetTextFromUser(
_("Enter aspect ratio in either:\n decimal (e.g. 2.35)\n fractional (e.g. 16:9)\n specific resolution (e.g. 853x480)"),
_("Enter aspect ratio"),
std::to_wstring(c->videoController->GetAspectRatioValue())));
if (value.empty()) return;
double numval = 0;
if (agi::util::try_parse(value, &numval)) {
//Nothing to see here, move along
}
else {
std::vector<std::string> chunks;
split(chunks, value, boost::is_any_of(":/xX"));
if (chunks.size() == 2) {
double num, den;
if (agi::util::try_parse(chunks[0], &num) && agi::util::try_parse(chunks[1], &den))
numval = num / den;
}
}
if (numval < 0.5 || numval > 5.0)
wxMessageBox(_("Invalid value! Aspect ratio must be between 0.5 and 5.0."),_("Invalid Aspect Ratio"),wxOK | wxICON_ERROR | wxCENTER);
else {
c->videoController->SetAspectRatio(numval);
c->frame->SetDisplayMode(1,-1);
}
}
};
struct video_aspect_default final : public validator_video_loaded {
CMD_NAME("video/aspect/default")
STR_MENU("&Default")
STR_DISP("Default")
STR_HELP("Use video's original aspect ratio")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return c->videoController->GetAspectRatioType() == AspectRatio::Default;
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
c->videoController->SetAspectRatio(AspectRatio::Default);
c->frame->SetDisplayMode(1,-1);
}
};
struct video_aspect_full final : public validator_video_loaded {
CMD_NAME("video/aspect/full")
STR_MENU("&Fullscreen (4:3)")
STR_DISP("Fullscreen (4:3)")
STR_HELP("Force video to 4:3 aspect ratio")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return c->videoController->GetAspectRatioType() == AspectRatio::Fullscreen;
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
c->videoController->SetAspectRatio(AspectRatio::Fullscreen);
c->frame->SetDisplayMode(1,-1);
}
};
struct video_aspect_wide final : public validator_video_loaded {
CMD_NAME("video/aspect/wide")
STR_MENU("&Widescreen (16:9)")
STR_DISP("Widescreen (16:9)")
STR_HELP("Force video to 16:9 aspect ratio")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_RADIO)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return c->videoController->GetAspectRatioType() == AspectRatio::Widescreen;
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
c->videoController->SetAspectRatio(AspectRatio::Widescreen);
c->frame->SetDisplayMode(1,-1);
}
};
struct video_close final : public validator_video_loaded {
CMD_NAME("video/close")
CMD_ICON(close_video_menu)
STR_MENU("&Close Video")
STR_DISP("Close Video")
STR_HELP("Close the currently open video file")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->project->CloseVideo();
}
};
struct video_copy_coordinates final : public validator_video_loaded {
CMD_NAME("video/copy_coordinates")
STR_MENU("Copy coordinates to Clipboard")
STR_DISP("Copy coordinates to Clipboard")
STR_HELP("Copy the current coordinates of the mouse over the video to the clipboard")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
SetClipboard(c->videoDisplay->GetMousePosition().Str());
}
};
struct video_cycle_subtitles_provider final : public cmd::Command {
CMD_NAME("video/subtitles_provider/cycle")
STR_MENU("Cycle active subtitles provider")
STR_DISP("Cycle active subtitles provider")
STR_HELP("Cycle through the available subtitles providers")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
auto providers = SubtitlesProviderFactory::GetClasses();
if (providers.empty()) return;
auto it = find(begin(providers), end(providers), OPT_GET("Subtitle/Provider")->GetString());
if (it != end(providers)) ++it;
if (it == end(providers)) it = begin(providers);
OPT_SET("Subtitle/Provider")->SetString(*it);
c->frame->StatusTimeout(fmt_tl("Subtitles provider set to %s", *it), 5000);
}
};
struct video_reload_subtitles_provider final : public cmd::Command {
CMD_NAME("video/subtitles_provider/reload")
STR_MENU("Reload active subtitles provider")
STR_DISP("Reload active subtitles provider")
STR_HELP("Reloads the current subtitles provider")
void operator()(agi::Context* c) override {
auto providers = SubtitlesProviderFactory::GetClasses();
if (providers.empty()) return;
auto it = find(begin(providers), end(providers), OPT_GET("Subtitle/Provider")->GetString());
OPT_SET("Subtitle/Provider")->SetString(*it);
c->frame->StatusTimeout(fmt_tl("Subtitles provider set to %s", *it), 5000);
}
};
struct video_detach final : public validator_video_loaded {
CMD_NAME("video/detach")
CMD_ICON(detach_video_menu)
STR_MENU("&Detach Video")
STR_DISP("Detach Video")
STR_HELP("Detach the video display from the main window, displaying it in a separate Window")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_TOGGLE)
2013-11-21 18:13:36 +01:00
bool IsActive(const agi::Context *c) override {
return !!c->dialog->Get<DialogDetachedVideo>();
}
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
if (DialogDetachedVideo *d = c->dialog->Get<DialogDetachedVideo>())
d->Close();
else
c->dialog->Show<DialogDetachedVideo>(c);
}
};
struct video_details final : public validator_video_loaded {
CMD_NAME("video/details")
CMD_ICON(show_video_details_menu)
STR_MENU("Show &Video Details")
STR_DISP("Show Video Details")
STR_HELP("Show video details")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
ShowVideoDetailsDialog(c);
}
};
struct video_focus_seek final : public validator_video_loaded {
CMD_NAME("video/focus_seek")
STR_MENU("Toggle video slider focus")
STR_DISP("Toggle video slider focus")
STR_HELP("Toggle focus between the video slider and the previous thing to have focus")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
wxWindow *curFocus = wxWindow::FindFocus();
if (curFocus == c->videoSlider) {
if (c->previousFocus) c->previousFocus->SetFocus();
}
else {
c->previousFocus = curFocus;
c->videoSlider->SetFocus();
}
}
};
wxImage get_image(agi::Context *c, bool raw, bool subsonly = false) {
auto frame = c->videoController->GetFrameN();
if (subsonly) {
return GetImageWithAlpha(c->project->VideoProvider()->GetSubtitles(c->project->Timecodes().TimeAtFrame(frame)));
} else {
return GetImage(*c->project->VideoProvider()->GetFrame(frame, c->project->Timecodes().TimeAtFrame(frame), raw));
}
}
struct video_frame_copy final : public validator_video_loaded {
CMD_NAME("video/frame/copy")
STR_MENU("Copy image to Clipboard")
STR_DISP("Copy image to Clipboard")
STR_HELP("Copy the currently displayed frame to the clipboard")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
SetClipboard(wxBitmap(get_image(c, false), 24));
}
};
struct video_frame_copy_raw final : public validator_video_loaded {
CMD_NAME("video/frame/copy/raw")
STR_MENU("Copy image to Clipboard (no subtitles)")
STR_DISP("Copy image to Clipboard (no subtitles)")
STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
SetClipboard(wxBitmap(get_image(c, true), 24));
}
};
struct video_frame_copy_subs final : public validator_video_loaded {
CMD_NAME("video/frame/copy/subs")
STR_MENU("Copy image to Clipboard (only subtitles)")
STR_DISP("Copy image to Clipboard (only subtitles)")
STR_HELP("Copy the currently displayed subtitles to the clipboard, with transparent background")
void operator()(agi::Context *c) override {
SetClipboard(wxBitmap(get_image(c, false, true), 32));
}
};
struct video_frame_next final : public validator_video_loaded {
CMD_NAME("video/frame/next")
STR_MENU("Next Frame")
STR_DISP("Next Frame")
STR_HELP("Seek to the next frame")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->NextFrame();
}
};
struct video_frame_next_boundary final : public validator_video_loaded {
CMD_NAME("video/frame/next/boundary")
STR_MENU("Next Boundary")
STR_DISP("Next Boundary")
STR_HELP("Seek to the next beginning or end of a subtitle")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
AssDialogue *active_line = c->selectionController->GetActiveLine();
if (!active_line) return;
int target = c->videoController->FrameAtTime(active_line->Start, agi::vfr::START);
if (target > c->videoController->GetFrameN()) {
c->videoController->JumpToFrame(target);
return;
}
target = c->videoController->FrameAtTime(active_line->End, agi::vfr::END);
if (target > c->videoController->GetFrameN()) {
c->videoController->JumpToFrame(target);
return;
}
c->selectionController->NextLine();
AssDialogue *new_line = c->selectionController->GetActiveLine();
if (new_line != active_line)
c->videoController->JumpToTime(new_line->Start);
}
};
struct video_frame_next_keyframe final : public validator_video_loaded {
CMD_NAME("video/frame/next/keyframe")
STR_MENU("Next Keyframe")
STR_DISP("Next Keyframe")
STR_HELP("Seek to the next keyframe")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
auto const& kf = c->project->Keyframes();
2013-09-16 21:10:00 +02:00
auto pos = lower_bound(kf.begin(), kf.end(), c->videoController->GetFrameN() + 1);
c->videoController->JumpToFrame(pos == kf.end() ? c->project->VideoProvider()->GetFrameCount() - 1 : *pos);
}
};
struct video_frame_next_large final : public validator_video_loaded {
CMD_NAME("video/frame/next/large")
STR_MENU("Fast jump forward")
STR_DISP("Fast jump forward")
STR_HELP("Fast jump forward")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->JumpToFrame(
c->videoController->GetFrameN() +
OPT_GET("Video/Slider/Fast Jump Step")->GetInt());
}
};
struct video_frame_prev final : public validator_video_loaded {
CMD_NAME("video/frame/prev")
STR_MENU("Previous Frame")
STR_DISP("Previous Frame")
STR_HELP("Seek to the previous frame")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->PrevFrame();
}
};
struct video_frame_prev_boundary final : public validator_video_loaded {
CMD_NAME("video/frame/prev/boundary")
STR_MENU("Previous Boundary")
STR_DISP("Previous Boundary")
STR_HELP("Seek to the previous beginning or end of a subtitle")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
AssDialogue *active_line = c->selectionController->GetActiveLine();
if (!active_line) return;
int target = c->videoController->FrameAtTime(active_line->End, agi::vfr::END);
if (target < c->videoController->GetFrameN()) {
c->videoController->JumpToFrame(target);
return;
}
target = c->videoController->FrameAtTime(active_line->Start, agi::vfr::START);
if (target < c->videoController->GetFrameN()) {
c->videoController->JumpToFrame(target);
return;
}
c->selectionController->PrevLine();
AssDialogue *new_line = c->selectionController->GetActiveLine();
if (new_line != active_line)
c->videoController->JumpToTime(new_line->End, agi::vfr::END);
}
};
struct video_frame_prev_keyframe final : public validator_video_loaded {
CMD_NAME("video/frame/prev/keyframe")
STR_MENU("Previous Keyframe")
STR_DISP("Previous Keyframe")
STR_HELP("Seek to the previous keyframe")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
auto const& kf = c->project->Keyframes();
if (kf.empty()) {
c->videoController->JumpToFrame(0);
return;
}
2013-09-16 21:10:00 +02:00
auto pos = lower_bound(kf.begin(), kf.end(), c->videoController->GetFrameN());
if (pos != kf.begin())
--pos;
c->videoController->JumpToFrame(*pos);
}
};
struct video_frame_prev_large final : public validator_video_loaded {
CMD_NAME("video/frame/prev/large")
STR_MENU("Fast jump backwards")
STR_DISP("Fast jump backwards")
STR_HELP("Fast jump backwards")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->JumpToFrame(
c->videoController->GetFrameN() -
OPT_GET("Video/Slider/Fast Jump Step")->GetInt());
}
};
static void save_snapshot(agi::Context *c, bool raw, bool subsonly = false) {
2013-10-17 02:29:03 +02:00
auto option = OPT_GET("Path/Screenshot")->GetString();
agi::fs::path basepath;
auto videoname = c->project->VideoName();
2013-10-17 02:29:03 +02:00
bool is_dummy = boost::starts_with(videoname.string(), "?dummy");
// Is it a path specifier and not an actual fixed path?
if (option[0] == '?') {
// If dummy video is loaded, we can't save to the video location
2013-10-17 02:29:03 +02:00
if (boost::starts_with(option, "?video") && is_dummy) {
// So try the script location instead
option = "?script";
}
// Find out where the ?specifier points to
basepath = c->path->Decode(option);
// If where ever that is isn't defined, we can't save there
if ((basepath == "\\") || (basepath == "/")) {
// So save to the current user's home dir instead
basepath = std::string(wxGetHomeDir());
}
}
// Actual fixed (possibly relative) path, decode it
else
basepath = c->path->MakeAbsolute(option, "?user/");
2013-10-17 02:29:03 +02:00
basepath /= is_dummy ? "dummy" : videoname.stem();
// Get full path
int session_shot_count = 1;
std::string path;
do {
path = agi::format("%s_%03d_%d.png", basepath.string(), session_shot_count++, c->videoController->GetFrameN());
} while (agi::fs::FileExists(path));
get_image(c, raw, subsonly).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
}
struct video_frame_save final : public validator_video_loaded {
CMD_NAME("video/frame/save")
STR_MENU("Save PNG snapshot")
STR_DISP("Save PNG snapshot")
STR_HELP("Save the currently displayed frame to a PNG file in the video's directory")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
save_snapshot(c, false);
}
};
struct video_frame_save_raw final : public validator_video_loaded {
CMD_NAME("video/frame/save/raw")
STR_MENU("Save PNG snapshot (no subtitles)")
STR_DISP("Save PNG snapshot (no subtitles)")
STR_HELP("Save the currently displayed frame without the subtitles to a PNG file in the video's directory")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
save_snapshot(c, true);
}
};
struct video_frame_save_subs final : public validator_video_loaded {
CMD_NAME("video/frame/save/subs")
STR_MENU("Save PNG snapshot (only subtitles)")
STR_DISP("Save PNG snapshot (only subtitles)")
STR_HELP("Save the currently displayed subtitles with transparent background to a PNG file in the video's directory")
void operator()(agi::Context *c) override {
save_snapshot(c, false, true);
}
};
struct video_jump final : public validator_video_loaded {
CMD_NAME("video/jump")
CMD_ICON(jumpto_button)
STR_MENU("&Jump to...")
STR_DISP("Jump to")
STR_HELP("Jump to frame or time")
2013-11-21 18:13:36 +01:00
void operator()(agi::Context *c) override {
c->videoController->Stop();
ShowJumpToDialog(c);
c->videoSlider->SetFocus();
}
};