DummyVideoProvider::DummyVideoProvider(agi::fs::path const& filename) {
	if (!boost::starts_with(filename.string(), "?dummy"))
		throw agi::fs::FileNotFound(std::string("Attempted creating dummy video provider with non-dummy filename"));

	std::vector<std::string> toks;
	auto const& fields = filename.string().substr(7);
	boost::split(toks, fields, boost::is_any_of(":"));
	if (toks.size() != 8)
		throw VideoOpenError("Too few fields in dummy video parameter list");

	size_t i = 0;
	double fps;
	int frames, width, height, red, green, blue;

	using agi::util::try_parse;
	if (!try_parse(toks[i++], &fps))    throw VideoOpenError("Unable to parse fps field in dummy video parameter list");
	if (!try_parse(toks[i++], &frames)) throw VideoOpenError("Unable to parse framecount field in dummy video parameter list");
	if (!try_parse(toks[i++], &width))  throw VideoOpenError("Unable to parse width field in dummy video parameter list");
	if (!try_parse(toks[i++], &height)) throw VideoOpenError("Unable to parse height field in dummy video parameter list");
	if (!try_parse(toks[i++], &red))    throw VideoOpenError("Unable to parse red colour field in dummy video parameter list");
	if (!try_parse(toks[i++], &green))  throw VideoOpenError("Unable to parse green colour field in dummy video parameter list");
	if (!try_parse(toks[i++], &blue))   throw VideoOpenError("Unable to parse blue colour field in dummy video parameter list");

	bool pattern = toks[i] == "c";

	Create(fps, frames, width, height, red, green, blue, pattern);
}
Exemple #2
0
bool Project::DoLoadSubtitles(agi::fs::path const& path, std::string encoding, ProjectProperties &properties) {
	try {
		if (encoding.empty())
			encoding = CharSetDetect::GetEncoding(path);
	}
	catch (agi::UserCancelException const&) {
		return false;
	}
	catch (agi::fs::FileNotFound const&) {
		config::mru->Remove("Subtitle", path);
		ShowError(path.string() + " not found.");
		return false;
	}

	if (encoding != "binary") {
		// Try loading as timecodes and keyframes first since we can't
		// distinguish them based on filename alone, and just ignore failures
		// rather than trying to differentiate between malformed timecodes
		// files and things that aren't timecodes files at all
		try { DoLoadTimecodes(path); return false; } catch (...) { }
		try { DoLoadKeyframes(path); return false; } catch (...) { }
	}

	try {
		properties = context->subsController->Load(path, encoding);
	}
	catch (agi::UserCancelException const&) { return false; }
	catch (agi::fs::FileNotFound const&) {
		config::mru->Remove("Subtitle", path);
		ShowError(path.string() + " not found.");
		return false;
	}
	catch (agi::Exception const& e) {
		ShowError(e.GetMessage());
		return false;
	}
	catch (std::exception const& e) {
		ShowError(std::string(e.what()));
		return false;
	}
	catch (...) {
		ShowError(wxString("Unknown error"));
		return false;
	}

	Selection sel;
	AssDialogue *active_line = nullptr;
	if (!context->ass->Events.empty()) {
		int row = mid<int>(0, properties.active_row, context->ass->Events.size() - 1);
		active_line = &*std::next(context->ass->Events.begin(), row);
		sel.insert(active_line);
	}
	context->selectionController->SetSelectionAndActive(std::move(sel), active_line);
	context->subsGrid->ScrollTo(properties.scroll_position);

	return true;
}
/// @brief Constructor
/// @param filename The filename to open
YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&) {
	fps_rat.num = -1;
	fps_rat.den = 1;

	try {
#ifdef WIN32
		sf = _wfopen(filename.c_str(), L"rb");
#else
		sf = fopen(filename.c_str(), "rb");
#endif

		if (!sf) throw agi::fs::FileNotFound(filename);

		CheckFileFormat();

		ParseFileHeader(ReadHeader(0));

		if (w <= 0 || h <= 0)
			throw VideoOpenError("Invalid resolution");
		if (fps_rat.num <= 0 || fps_rat.den <= 0) {
			fps_rat.num = 25;
			fps_rat.den = 1;
			LOG_D("provider/video/yuv4mpeg") << "framerate info unavailable, assuming 25fps";
		}
		if (pixfmt == Y4M_PIXFMT_NONE)
			pixfmt = Y4M_PIXFMT_420JPEG;
		if (imode == Y4M_ILACE_NOTSET)
			imode = Y4M_ILACE_UNKNOWN;

		luma_sz = w * h;
		switch (pixfmt) {
		case Y4M_PIXFMT_420JPEG:
		case Y4M_PIXFMT_420MPEG2:
		case Y4M_PIXFMT_420PALDV:
			chroma_sz	= (w * h) >> 2; break;
		case Y4M_PIXFMT_422:
			chroma_sz	= (w * h) >> 1; break;
			/// @todo add support for more pixel formats
		default:
			throw VideoOpenError("Unsupported pixel format");
		}
		frame_sz	= luma_sz + chroma_sz*2;

		num_frames = IndexFile();
		if (num_frames <= 0 || seek_table.empty())
			throw VideoOpenError("Unable to determine file length");

		fseeko(sf, 0, SEEK_SET);
	}
	catch (...) {
		if (sf) fclose(sf);
		throw;
	}
}
/// @brief	Generates an unique name for the ffms2 index file and prepares the cache folder if it doesn't exist
/// @param filename	The name of the source file
/// @return			Returns the generated filename.
agi::fs::path FFmpegSourceProvider::GetCacheFilename(agi::fs::path const& filename) {
	// Get the size of the file to be hashed
	uintmax_t len = agi::fs::Size(filename);

	// Get the hash of the filename
	boost::crc_32_type hash;
	hash.process_bytes(filename.string().c_str(), filename.string().size());

	// Generate the filename
	auto result = config::path->Decode("?local/ffms2cache/" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".ffindex");

	// Ensure that folder exists
	agi::fs::CreateDirectory(result.parent_path());

	return result;
}
Exemple #5
0
void SubsController::Save(agi::fs::path const& filename, std::string const& encoding) {
	const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
	if (!writer)
		throw "Unknown file type.";

	int old_autosaved_commit_id = autosaved_commit_id, old_saved_commit_id = saved_commit_id;
	try {
		autosaved_commit_id = saved_commit_id = commit_id;

		// Have to set this now for the sake of things that want to save paths
		// relative to the script in the header
		this->filename = filename;
		config::path->SetToken("?script", filename.parent_path());

		FileSave();

		writer->WriteFile(context->ass, filename, encoding);
	}
	catch (...) {
		autosaved_commit_id = old_autosaved_commit_id;
		saved_commit_id = old_saved_commit_id;
		throw;
	}

	SetFileName(filename);
}
std::unique_ptr<VideoProvider> VideoProviderFactory::GetProvider(agi::fs::path const& video_file, std::string const& colormatrix) {
	std::vector<std::string> factories = GetClasses(OPT_GET("Video/Provider")->GetString());
	factories.insert(factories.begin(), "YUV4MPEG");
	factories.insert(factories.begin(), "Dummy");

	bool found = false;
	bool supported = false;
	std::string errors;
	errors.reserve(1024);

	for (auto const& factory : factories) {
		std::string err;
		try {
			auto provider = Create(factory, video_file, colormatrix);
			LOG_I("manager/video/provider") << factory << ": opened " << video_file;
			return provider->WantsCaching() ? agi::util::make_unique<VideoProviderCache>(std::move(provider)) : std::move(provider);
		}
		catch (agi::fs::FileNotFound const&) {
			err = "file not found.";
			// Keep trying other providers as this one may just not be able to
			// open a valid path
		}
		catch (VideoNotSupported const&) {
			found = true;
			err = "video is not in a supported format.";
		}
		catch (VideoOpenError const& ex) {
			supported = true;
			err = ex.GetMessage();
		}
		catch (agi::vfr::Error const& ex) {
			supported = true;
			err = ex.GetMessage();
		}

		errors += factory + ": " + err + "\n";
		LOG_D("manager/video/provider") << factory << ": " << err;
	}

	// No provider could open the file
	LOG_E("manager/video/provider") << "Could not open " << video_file;
	std::string msg = "Could not open " + video_file.string() + ":\n" + errors;

	if (!found) throw agi::fs::FileNotFound(video_file.string());
	if (!supported) throw VideoNotSupported(msg);
	throw VideoOpenError(msg);
}
std::unique_ptr<VideoProvider> VideoProviderFactory::GetProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) {
	auto preferred = OPT_GET("Video/Provider")->GetString();
	auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred);

	bool found = false;
	bool supported = false;
	std::string errors;
	errors.reserve(1024);

	for (auto factory : sorted) {
		std::string err;
		try {
			auto provider = factory->create(filename, colormatrix, br);
			if (!provider) continue;
			LOG_I("manager/video/provider") << factory->name << ": opened " << filename;
			return provider->WantsCaching() ? CreateCacheVideoProvider(std::move(provider)) : std::move(provider);
		}
		catch (agi::fs::FileNotFound const&) {
			err = "file not found.";
			// Keep trying other providers as this one may just not be able to
			// open a valid path
		}
		catch (VideoNotSupported const&) {
			found = true;
			err = "video is not in a supported format.";
		}
		catch (VideoOpenError const& ex) {
			supported = true;
			err = ex.GetMessage();
		}
		catch (agi::vfr::Error const& ex) {
			supported = true;
			err = ex.GetMessage();
		}

		errors += std::string(factory->name) + ": " + err + "\n";
		LOG_D("manager/video/provider") << factory->name << ": " << err;
	}

	// No provider could open the file
	LOG_E("manager/video/provider") << "Could not open " << filename;
	std::string msg = "Could not open " + filename.string() + ":\n" + errors;

	if (!found) throw agi::fs::FileNotFound(filename.string());
	if (!supported) throw VideoNotSupported(msg);
	throw VideoOpenError(msg);
}
Exemple #8
0
void AssFile::InsertAttachment(agi::fs::path const& filename) {
	AssEntryGroup group = AssEntryGroup::GRAPHIC;

	auto ext = boost::to_lower_copy(filename.extension().string());
	if (ext == ".ttf" || ext == ".ttc" || ext == ".pfb")
		group = AssEntryGroup::FONT;

	Attachments.emplace_back(filename, group);
}
	bool LoadFile(lua_State *L, agi::fs::path const& filename) {
		std::unique_ptr<std::istream> file(agi::io::Open(filename, true));
		file->seekg(0, std::ios::end);
		size_t size = file->tellg();
		file->seekg(0, std::ios::beg);

		std::string buff;
		buff.resize(size);

		// Discard the BOM if present
		file->read(&buff[0], 3);
		size_t start = file->gcount();
		if (start == 3 && buff[0] == -17 && buff[1] == -69 && buff[2] == -65) {
			buff.resize(size - 3);
			start = 0;
		}

		file->read(&buff[start], size - start);

		if (!agi::fs::HasExtension(filename, "moon"))
			return luaL_loadbuffer(L, &buff[0], buff.size(), filename.string().c_str()) == 0;

		// We have a MoonScript file, so we need to load it with that
		// It might be nice to have a dedicated lua state for compiling
		// MoonScript to Lua
		if (luaL_dostring(L, "return require('moonscript').loadstring"))
			return false; // Leaves error message on stack
		lua_pushlstring(L, &buff[0], buff.size());
		lua_pushstring(L, filename.string().c_str());
		if (lua_pcall(L, 2, 2, 0))
			return false; // Leaves error message on stack

		// loadstring returns nil, error on error or a function on success
		if (lua_isnil(L, 1)) {
			lua_remove(L, 1);
			return false;
		}

		lua_pop(L, 1); // Remove the extra nil for the stackchecker
		return true;
	}
Exemple #10
0
void Project::LoadVideo(agi::fs::path path) {
	if (path.empty()) return;
	if (!DoLoadVideo(path)) return;
	if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file && video_provider->HasAudio())
		DoLoadAudio(video_file, true);

	double dar = video_provider->GetDAR();
	if (dar > 0)
		context->videoController->SetAspectRatio(dar);
	else
		context->videoController->SetAspectRatio(AspectRatio::Default);
	context->videoController->JumpToFrame(0);
}
Exemple #11
0
void VideoContext::LoadKeyframes(agi::fs::path const& filename) {
	if (filename == keyframes_filename || filename.empty()) return;
	try {
		keyframes = agi::keyframe::Load(filename);
		keyframes_filename = filename;
		KeyframesOpen(keyframes);
		config::mru->Add("Keyframes", filename);
	}
	catch (agi::keyframe::Error const& err) {
		wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
		config::mru->Remove("Keyframes", filename);
	}
	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);
	}
}
Exemple #12
0
void AudioController::OpenAudio(agi::fs::path const& url)
{
	if (url.empty())
		throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", 0);

	std::unique_ptr<AudioProvider> new_provider;
	try {
		new_provider = AudioProviderFactory::GetProvider(url);
		config::path->SetToken("?audio", url);
	}
	catch (agi::UserCancelException const&) {
		throw;
	}
	catch (...) {
		config::mru->Remove("Audio", url);
		throw;
	}

	CloseAudio();
	provider = std::move(new_provider);

	try
	{
		player = AudioPlayerFactory::GetAudioPlayer(provider.get());
	}
	catch (...)
	{
		provider.reset();
		throw;
	}

	audio_url = url;

	config::mru->Add("Audio", url);

	try
	{
		AnnounceAudioOpen(provider.get());
	}
	catch (...)
	{
		CloseAudio();
		throw;
	}
}
Exemple #13
0
void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
	if (filename == timecodes_filename || filename.empty()) return;
	try {
		ovr_fps = agi::vfr::Framerate(filename);
		timecodes_filename = filename;
		config::mru->Add("Timecodes", filename);
		OnSubtitlesCommit(0, std::set<const AssEntry*>());
		TimecodesOpen(ovr_fps);
	}
	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);
	}
	catch (const agi::vfr::Error& e) {
		wxLogError("Timecode file parse error: %s", to_wx(e.GetMessage()));
		config::mru->Remove("Timecodes", filename);
	}
}
AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group)
: filename(name.filename().string())
, group(group)
{
	// SSA stuffs some information about the font in the embedded filename, but
	// nothing else uses it so just do the absolute minimum (0 is the encoding)
	if (boost::iends_with(filename.get(), ".ttf"))
		filename = filename.get().substr(0, filename.get().size() - 4) + "_0" + filename.get().substr(filename.get().size() - 4);

	std::vector<char> data;
	std::unique_ptr<std::istream> file(agi::io::Open(name, true));
	file->seekg(0, std::ios::end);
	data.resize(file->tellg());
	file->seekg(0, std::ios::beg);
	file->read(&data[0], data.size());

	entry_data = (group == AssEntryGroup::FONT ? "fontname: " : "filename: ") + filename.get() + "\r\n";
	entry_data = entry_data.get() + agi::ass::UUEncode(data);
}
void TTXTSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
	target->LoadDefault(false);

	// Load XML document
	wxXmlDocument doc;
	if (!doc.Load(filename.wstring())) throw TTXTParseError("Failed loading TTXT XML file.", nullptr);

	// Check root node name
	if (doc.GetRoot()->GetName() != "TextStream") throw TTXTParseError("Invalid TTXT file.", nullptr);

	// Check version
	wxString verStr = doc.GetRoot()->GetAttribute("version", "");
	int version = -1;
	if (verStr == "1.0")
		version = 0;
	else if (verStr == "1.1")
		version = 1;
	else
		throw TTXTParseError("Unknown TTXT version: " + from_wx(verStr), nullptr);

	// Get children
	AssDialogue *diag = nullptr;
	int lines = 0;
	for (wxXmlNode *child = doc.GetRoot()->GetChildren(); child; child = child->GetNext()) {
		// Line
		if (child->GetName() == "TextSample") {
			if ((diag = ProcessLine(child, diag, version))) {
				lines++;
				target->Line.push_back(*diag);
			}
		}
		// Header
		else if (child->GetName() == "TextStreamHeader") {
			ProcessHeader(child);
		}
	}

	// No lines?
	if (lines == 0)
		target->Line.push_back(*new AssDialogue);
}
Exemple #16
0
void AudioController::SaveClip(agi::fs::path const& filename, TimeRange const& range) const
{
	int64_t start_sample = SamplesFromMilliseconds(range.begin());
	int64_t end_sample = SamplesFromMilliseconds(range.end());
	if (filename.empty() || start_sample > provider->GetNumSamples() || range.length() == 0) return;

	agi::io::Save outfile(filename, true);
	std::ofstream& out(outfile.Get());

	size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
	size_t bufsize = (end_sample - start_sample) * bytes_per_sample;

	int intval;
	short shortval;

	out << "RIFF";
	out.write((char*)&(intval=bufsize+36),4);
	out<< "WAVEfmt ";
	out.write((char*)&(intval=16),4);
	out.write((char*)&(shortval=1),2);
	out.write((char*)&(shortval=provider->GetChannels()),2);
	out.write((char*)&(intval=provider->GetSampleRate()),4);
	out.write((char*)&(intval=provider->GetSampleRate()*provider->GetChannels()*provider->GetBytesPerSample()),4);
	out.write((char*)&(intval=provider->GetChannels()*provider->GetBytesPerSample()),2);
	out.write((char*)&(shortval=provider->GetBytesPerSample()<<3),2);
	out << "data";
	out.write((char*)&bufsize,4);

	//samples per read
	size_t spr = 65536 / bytes_per_sample;
	std::vector<char> buf(bufsize);
	for(int64_t i = start_sample; i < end_sample; i += spr) {
		size_t len = std::min<size_t>(spr, end_sample - i);
		provider->GetAudio(&buf[0], i, len);
		out.write(&buf[0], len * bytes_per_sample);
	}
}
void TTXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
	// Convert to TTXT
	AssFile copy(*src);
	ConvertToTTXT(copy);

	// Create XML structure
	wxXmlDocument doc;
	wxXmlNode *root = new wxXmlNode(nullptr, wxXML_ELEMENT_NODE, "TextStream");
	root->AddAttribute("version", "1.1");
	doc.SetRoot(root);

	// Create header
	WriteHeader(root);

	// Create lines
	const AssDialogue *prev = nullptr;
	for (auto current : copy.Line | agi::of_type<AssDialogue>()) {
		WriteLine(root, prev, current);
		prev = current;
	}

	// Save XML
	doc.Save(filename.wstring());
}
bool TXTSubtitleFormat::CanWriteFile(agi::fs::path const& filename) const {
	auto str = filename.string();
	return boost::iends_with(str, ".txt") && !(boost::iends_with(str, ".encore.txt") || boost::iends_with(str, ".transtation.txt"));
}
Exemple #19
0
void SubsController::SetFileName(agi::fs::path const& path) {
	filename = path;
	config::path->SetToken("?script", path.parent_path());
	config::mru->Add("Subtitle", path);
	OPT_SET("Path/Last/Subtitles")->SetString(filename.parent_path().string());
}
Exemple #20
0
void SubsController::Load(agi::fs::path const& filename, std::string charset) {
	try {
		if (charset.empty())
			charset = CharSetDetect::GetEncoding(filename);
	}
	catch (agi::UserCancelException const&) {
		return;
	}

	// Make sure that file isn't actually a timecode file
	if (charset != "binary") {
		try {
			TextFileReader testSubs(filename, charset);
			std::string cur = testSubs.ReadLineFromFile();
			if (boost::starts_with(cur, "# timecode")) {
				context->videoController->LoadTimecodes(filename);
				return;
			}
		}
		catch (...) {
			// if trying to load the file as timecodes fails it's fairly
			// safe to assume that it is in fact not a timecode file
		}
	}

	const SubtitleFormat *reader = SubtitleFormat::GetReader(filename);

	try {
		AssFile temp;
		reader->ReadFile(&temp, filename, charset);

		bool found_style = false;
		bool found_dialogue = false;

		// Check if the file has at least one style and at least one dialogue line
		for (auto const& line : temp.Line) {
			AssEntryGroup type = line.Group();
			if (type == AssEntryGroup::STYLE) found_style = true;
			if (type == AssEntryGroup::DIALOGUE) found_dialogue = true;
			if (found_style && found_dialogue) break;
		}

		// And if it doesn't add defaults for each
		if (!found_style)
			temp.InsertLine(new AssStyle);
		if (!found_dialogue)
			temp.InsertLine(new AssDialogue);

		context->ass->swap(temp);
	}
	catch (agi::UserCancelException const&) {
		return;
	}
	catch (agi::fs::FileNotFound const&) {
		wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
		config::mru->Remove("Subtitle", filename);
		return;
	}
	catch (agi::Exception const& err) {
		wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
		return;
	}
	catch (std::exception const& err) {
		wxMessageBox(to_wx(err.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
		return;
	}
	catch (...) {
		wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
		return;
	}

	SetFileName(filename);

	// Push the initial state of the file onto the undo stack
	undo_stack.clear();
	redo_stack.clear();
	autosaved_commit_id = saved_commit_id = commit_id + 1;
	context->ass->Commit("", AssFile::COMMIT_NEW);

	// Save backup of file
	if (CanSave() && OPT_GET("App/Auto/Backup")->GetBool()) {
		auto path_str = OPT_GET("Path/Auto/Backup")->GetString();
		agi::fs::path path;
		if (path_str.empty())
			path = filename.parent_path();
		else
			path = config::path->Decode(path_str);
		agi::fs::CreateDirectory(path);
		agi::fs::Copy(filename, path/(filename.stem().string() + ".ORIGINAL" + filename.extension().string()));
	}

	FileOpen(filename);
}
Exemple #21
0
void VideoContext::SetVideo(const agi::fs::path &filename) {
	Reset();
	if (filename.empty()) {
		VideoOpen();
		return;
	}

	bool commit_subs = false;
	try {
		provider.reset(new ThreadedFrameSource(filename, context->ass->GetScriptInfo("YCbCr Matrix"), this));
		video_provider = provider->GetVideoProvider();
		video_filename = filename;

		// Check that the script resolution matches the video resolution
		int sx = context->ass->GetScriptInfoAsInt("PlayResX");
		int sy = context->ass->GetScriptInfoAsInt("PlayResY");
		int vx = GetWidth();
		int vy = GetHeight();

		// If the script resolution hasn't been set at all just force it to the
		// video resolution
		if (sx == 0 && sy == 0) {
			context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
			context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
			commit_subs = true;
		}
		// If it has been set to something other than a multiple of the video
		// resolution, ask the user if they want it to be fixed
		else if (sx % vx != 0 || sy % vy != 0) {
			switch (OPT_GET("Video/Check Script Res")->GetInt()) {
			case 1: // Ask to change on mismatch
				if (wxYES != wxMessageBox(
					wxString::Format(_("The resolution of the loaded video and the resolution specified for the subtitles don't match.\n\nVideo resolution:\t%d x %d\nScript resolution:\t%d x %d\n\nChange subtitles resolution to match video?"), vx, vy, sx, sy),
					_("Resolution mismatch"),
					wxYES_NO | wxCENTER,
					context->parent))

					break;
				// Fallthrough to case 2
			case 2: // Always change script res
				context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
				context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
				commit_subs = true;
				break;
			default: // Never change
				break;
			}
		}

		keyframes = video_provider->GetKeyFrames();

		// Set frame rate
		video_fps = video_provider->GetFPS();
		if (ovr_fps.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) {
				ovr_fps = agi::vfr::Framerate();
				timecodes_filename.clear();
			}
		}

		// Set aspect ratio
		double dar = video_provider->GetDAR();
		if (dar > 0)
			SetAspectRatio(dar);

		// Set filename
		config::mru->Add("Video", filename);
		config::path->SetToken("?video", filename);

		// Show warning
		std::string warning = video_provider->GetWarning();
		if (!warning.empty())
			wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);

		has_subtitles = false;
		if (agi::fs::HasExtension(filename, "mkv"))
			has_subtitles = MatroskaWrapper::HasSubtitles(filename);

		provider->LoadSubtitles(context->ass);
		VideoOpen();
		KeyframesOpen(keyframes);
		TimecodesOpen(FPS());
	}
	catch (agi::UserCancelException const&) { }
	catch (agi::fs::FileSystemError const& err) {
		config::mru->Remove("Video", filename);
		wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
	}
	catch (VideoProviderError const& err) {
		wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
	}

	if (commit_subs)
		context->ass->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
	else
		JumpToFrame(0);
}
AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix)
{
	agi::acs::CheckFileRead(filename);

	std::lock_guard<std::mutex> lock(avs.GetMutex());

#ifdef _WIN32
	if (agi::fs::HasExtension(filename, "avi")) {
		// Try to read the keyframes before actually opening the file as trying
		// to open the file while it's already open can cause problems with
		// badly written VFW decoders
		AVIFileInit();

		PAVIFILE pfile;
		long hr = AVIFileOpen(&pfile, filename.c_str(), OF_SHARE_DENY_WRITE, 0);
		if (hr) {
			warning = "Unable to open AVI file for reading keyframes:\n";
			switch (hr) {
				case AVIERR_BADFORMAT:
					warning += "The file is corrupted, incomplete or has an otherwise bad format.";
					break;
				case AVIERR_MEMORY:
					warning += "The file could not be opened because of insufficient memory.";
					break;
				case AVIERR_FILEREAD:
					warning += "An error occurred reading the file. There might be a problem with the storage media.";
					break;
				case AVIERR_FILEOPEN:
					warning += "The file could not be opened. It might be in use by another application, or you do not have permission to access it.";
					break;
				case REGDB_E_CLASSNOTREG:
					warning += "There is no handler installed for the file extension. This might indicate a fundamental problem in your Video for Windows installation, and can be caused by extremely stripped Windows installations.";
					break;
				default:
					warning += "Unknown error.";
					break;
			}
			goto file_exit;
		}

		PAVISTREAM ppavi;
		if (hr = AVIFileGetStream(pfile, &ppavi, streamtypeVIDEO, 0)) {
			warning = "Unable to open AVI video stream for reading keyframes:\n";
			switch (hr) {
				case AVIERR_NODATA:
					warning += "The file does not contain a usable video stream.";
					break;
				case AVIERR_MEMORY:
					warning += "Not enough memory.";
					break;
				default:
					warning += "Unknown error.";
					break;
			}
			goto file_release;
		}

		AVISTREAMINFO avis;
		if (FAILED(AVIStreamInfo(ppavi,&avis,sizeof(avis)))) {
			warning = "Unable to read keyframes from AVI file:\nCould not get stream information.";
			goto stream_release;
		}

		for (size_t i = 0; i < avis.dwLength; i++) {
			if (AVIStreamIsKeyFrame(ppavi, i))
				keyframes.push_back(i);
		}

		// If every frame is a keyframe then just discard the keyframe data as it's useless
		if (keyframes.size() == (size_t)avis.dwLength)
			keyframes.clear();

		// Clean up
stream_release:
		AVIStreamRelease(ppavi);
file_release:
		AVIFileRelease(pfile);
file_exit:
		AVIFileExit();
	}
#endif

	try {
		auto script = Open(filename);

		// Check if video was loaded properly
		if (!script.IsClip() || !script.AsClip()->GetVideoInfo().HasVideo())
			throw VideoNotSupported("No usable video found");

		vi = script.AsClip()->GetVideoInfo();
		if (!vi.IsRGB()) {
			/// @todo maybe read ColorMatrix hints for d2v files?
			AVSValue args[2] = { script, "Rec601" };
			bool force_bt601 = OPT_GET("Video/Force BT.601")->GetBool() || colormatrix == "TV.601";
			bool bt709 = vi.width > 1024 || vi.height >= 600;
			if (bt709 && (!force_bt601 || colormatrix == "TV.709")) {
				args[1] = "Rec709";
				colorspace = "TV.709";
			}
			else
				colorspace = "TV.601";
			const char *argnames[2] = { 0, "matrix" };
			script = avs.GetEnv()->Invoke("ConvertToRGB32", AVSValue(args, 2), argnames);
		}
		else
			colorspace = "None";

		RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip();
		vi = RGB32Video->GetVideoInfo();
		fps = (double)vi.fps_numerator / vi.fps_denominator;
	}
	catch (AvisynthError const& err) {
		throw VideoOpenError("Avisynth error: " + std::string(err.msg));
	}
}