diff --git a/libaegisub/include/libaegisub/fs.h b/libaegisub/include/libaegisub/fs.h index 6b3e212ce..1dc1f0476 100644 --- a/libaegisub/include/libaegisub/fs.h +++ b/libaegisub/include/libaegisub/fs.h @@ -24,6 +24,8 @@ #include #include +#pragma once + #undef CreateDirectory namespace agi { diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp index d2597cee4..d5904a532 100644 --- a/src/audio_provider_factory.cpp +++ b/src/audio_provider_factory.cpp @@ -16,11 +16,13 @@ #include "audio_provider_factory.h" +#include "compat.h" #include "factory_manager.h" #include "options.h" #include "utils.h" #include +#include #include #include #include @@ -39,6 +41,7 @@ struct factory { const char *name; std::unique_ptr (*create)(fs::path const&, BackgroundRunner *); bool hidden; + std::function wants_to_open = [](auto p) { return false; }; }; const factory providers[] = { @@ -48,13 +51,13 @@ const factory providers[] = { {"FFmpegSource", CreateFFmpegSourceAudioProvider, false}, #endif #ifdef WITH_AVISYNTH - {"Avisynth", CreateAvisynthAudioProvider, false}, + {"Avisynth", CreateAvisynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "avs"); }}, #endif #ifdef WITH_BESTSOURCE {"BestSource", CreateBSAudioProvider, false}, #endif #ifdef WITH_VAPOURSYNTH - {"VapourSynth", CreateVapourSynthAudioProvider, false}, + {"VapourSynth", CreateVapourSynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "py") || agi::fs::HasExtension(p, "vpy"); }}, #endif }; } @@ -63,52 +66,76 @@ std::vector GetAudioProviderNames() { return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers))); } -std::unique_ptr GetAudioProvider(fs::path const& filename, - Path const& path_helper, - BackgroundRunner *br) { +std::unique_ptr SelectAudioProvider(fs::path const& filename, + Path const& path_helper, + BackgroundRunner *br) { auto preferred = OPT_GET("Audio/Provider")->GetString(); auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred); - std::unique_ptr provider; - bool found_file = false; - bool found_audio = false; - std::string msg_all; // error messages from all attempted providers - std::string msg_partial; // error messages from providers that could partially load the file (knows container, missing codec) + RearrangeWithPriority(sorted, filename); - for (auto const& factory : sorted) { + bool found_file = false; + std::string errors; + + auto tried_providers = sorted.begin(); + + for (; tried_providers < sorted.end(); tried_providers++) { + auto factory = *tried_providers; + std::string err; try { - provider = factory->create(filename, br); + auto provider = factory->create(filename, br); if (!provider) continue; LOG_I("audio_provider") << "Using audio provider: " << factory->name; + return provider; + } + catch (AudioDataNotFound const& ex) { + found_file = true; + err = ex.GetMessage(); + } + catch (AudioProviderError const& ex) { + found_file = true; + err = ex.GetMessage(); + } + + errors += std::string(factory->name) + ": " + err + "\n"; + LOG_D("audio_provider") << factory->name << ": " << err; + if (factory->name == preferred) break; - } - catch (fs::FileNotFound const& err) { - LOG_D("audio_provider") << err.GetMessage(); - msg_all += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n"; - } - catch (AudioDataNotFound const& err) { - LOG_D("audio_provider") << err.GetMessage(); - found_file = true; - msg_all += std::string(factory->name) + ": " + err.GetMessage() + "\n"; - } - catch (AudioProviderError const& err) { - LOG_D("audio_provider") << err.GetMessage(); - found_audio = true; - found_file = true; - std::string thismsg = std::string(factory->name) + ": " + err.GetMessage() + "\n"; - msg_all += thismsg; - msg_partial += thismsg; - } } - if (!provider) { - if (found_audio) - throw AudioProviderError(msg_partial); - if (found_file) - throw AudioDataNotFound(msg_all); - throw fs::FileNotFound(filename); + std::vector remaining_providers(tried_providers + 1, sorted.end()); + + if (!remaining_providers.size()) { + // No provider could open the file + LOG_E("audio_provider") << "Could not open " << filename; + std::string msg = "Could not open " + filename.string() + ":\n" + errors; + + if (!found_file) throw AudioDataNotFound(filename.string()); + throw AudioProviderError(msg); } + std::vector names; + for (auto const& f : remaining_providers) + names.push_back(f->name); + + int choice = wxGetSingleChoiceIndex(agi::format("Could not open %s with the preferred provider:\n\n%s\nPlease choose a different audio provider to try:", filename.string(), errors), _("Error loading audio"), to_wx(names)); + if (choice == -1) { + throw agi::UserCancelException("audio loading cancelled by user"); + } + + auto factory = remaining_providers[choice]; + auto provider = factory->create(filename, br); + if (!provider) + throw AudioProviderError("Audio provider returned null pointer"); + LOG_I("audio_provider") << factory->name << ": opened " << filename; + return provider; +} + +std::unique_ptr GetAudioProvider(fs::path const& filename, + Path const& path_helper, + BackgroundRunner *br) { + std::unique_ptr provider = SelectAudioProvider(filename, path_helper, br); + bool needs_cache = provider->NeedsCache(); // Give it a converter if needed diff --git a/src/audio_provider_vs.cpp b/src/audio_provider_vs.cpp index 2d81f06bb..bdc6bc161 100644 --- a/src/audio_provider_vs.cpp +++ b/src/audio_provider_vs.cpp @@ -86,9 +86,7 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename num_samples = vi->numSamples; } catch (VapourSynthError const& err) { - // Unlike the video provider manager, the audio provider factory catches AudioProviderErrors and picks whichever source doesn't throw one. - // So just rethrow the Error here with an extra label so the user will see the error message and know the audio wasn't loaded with VS - throw VapourSynthError(agi::format("VapourSynth error: %s", err.GetMessage())); + throw agi::AudioProviderError(err.GetMessage()); } template diff --git a/src/factory_manager.h b/src/factory_manager.h index 89a20d0ba..e88ba7718 100644 --- a/src/factory_manager.h +++ b/src/factory_manager.h @@ -17,6 +17,8 @@ #include #include +#include + namespace { template std::vector GetClasses(Container const& c) { @@ -50,4 +52,21 @@ auto GetSorted(Container const& c, std::string const& preferred) -> std::vector< } return sorted; } + +template +auto RearrangeWithPriority(Container &c, agi::fs::path const& filename) { + size_t end_of_hidden = 0; + for (size_t i = 0; i < c.size(); i++) { + auto provider = c[i]; + if (provider->hidden) { + end_of_hidden = i; + } else { + if (provider->wants_to_open(filename)) { + c.erase(c.begin() + i); + c.insert(c.begin() + end_of_hidden + 1, provider); + break; + } + } + } +} } diff --git a/src/project.cpp b/src/project.cpp index 89196b9e3..62690fa1f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -260,10 +260,10 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) { return; } else - return ShowError(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetMessage())); + return ShowError(_("None of the available audio providers recognised the selected file as containing audio data:\n\n") + to_wx(e.GetMessage())); } catch (agi::AudioProviderError const& e) { - return ShowError(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetMessage())); + return ShowError(_("None of the available audio providers have a codec available to handle the selected file:\n\n") + to_wx(e.GetMessage())); } catch (agi::Exception const& e) { return ShowError(e.GetMessage()); diff --git a/src/video_provider_manager.cpp b/src/video_provider_manager.cpp index 9374a7c84..cf3c6f1b2 100644 --- a/src/video_provider_manager.cpp +++ b/src/video_provider_manager.cpp @@ -16,15 +16,18 @@ #include "video_provider_manager.h" +#include "compat.h" #include "factory_manager.h" #include "include/aegisub/video_provider.h" #include "options.h" -#include #include +#include #include +#include + std::unique_ptr CreateDummyVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateYUV4MPEGVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); @@ -39,6 +42,7 @@ namespace { const char *name; std::unique_ptr (*create)(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); bool hidden; + std::function wants_to_open = [](auto p) { return false; }; }; const factory providers[] = { @@ -48,13 +52,13 @@ namespace { {"FFmpegSource", CreateFFmpegSourceVideoProvider, false}, #endif #ifdef WITH_AVISYNTH - {"Avisynth", CreateAvisynthVideoProvider, false}, + {"Avisynth", CreateAvisynthVideoProvider, false, [](auto p) { return agi::fs::HasExtension(p, "avs"); }}, #endif #ifdef WITH_BESTSOURCE {"BestSource (SLOW)", CreateBSVideoProvider, false}, #endif #ifdef WITH_VAPOURSYNTH - {"VapourSynth", CreateVapourSynthVideoProvider, false}, + {"VapourSynth", CreateVapourSynthVideoProvider, false, [](auto p) { return agi::fs::HasExtension(p, "py") || agi::fs::HasExtension(p, "vpy"); }}, #endif }; } @@ -67,22 +71,31 @@ std::unique_ptr VideoProviderFactory::GetProvider(agi::fs::path c auto preferred = OPT_GET("Video/Provider")->GetString(); auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred); + RearrangeWithPriority(sorted, filename); + bool found = false; bool supported = false; std::string errors; errors.reserve(1024); - for (auto factory : sorted) { + auto tried_providers = sorted.begin(); + + auto finalize_provider = [&](std::unique_ptr provider) { + return provider->WantsCaching() ? CreateCacheVideoProvider(std::move(provider)) : std::move(provider); + }; + + for (; tried_providers < sorted.end(); tried_providers++) { + auto factory = *tried_providers; 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); + return finalize_provider(std::move(provider)); } - catch (VideoNotSupported const&) { + catch (VideoNotSupported const& ex) { found = true; - err = "video is not in a supported format."; + err = "video is not in a supported format: " + ex.GetMessage(); } catch (VideoOpenError const& ex) { supported = true; @@ -95,13 +108,41 @@ std::unique_ptr VideoProviderFactory::GetProvider(agi::fs::path c errors += std::string(factory->name) + ": " + err + "\n"; LOG_D("manager/video/provider") << factory->name << ": " << err; + if (factory->name == preferred) + break; } - // 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; + // We failed to load the video using the preferred video provider (and the ones before it). + // The user might want to know about this, so show a dialog and let them choose what other provider to try. + std::vector remaining_providers(tried_providers + 1, sorted.end()); - if (!found) throw agi::fs::FileNotFound(filename.string()); - if (!supported) throw VideoNotSupported(msg); - throw VideoOpenError(msg); + if (!remaining_providers.size()) { + // 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); + } + + std::vector names; + for (auto const& f : remaining_providers) + names.push_back(f->name); + + int choice = wxGetSingleChoiceIndex(agi::format("Could not open %s with the preferred provider:\n\n%s\nPlease choose a different video provider to try:", filename.string(), errors), _("Error loading video"), to_wx(names)); + if (choice == -1) { + throw agi::UserCancelException("video loading cancelled by user"); + } + + try { + auto factory = remaining_providers[choice]; + auto provider = factory->create(filename, colormatrix, br); + if (!provider) + throw VideoNotSupported("Video provider returned null pointer"); + LOG_I("manager/video/provider") << factory->name << ": opened " << filename; + return finalize_provider(std::move(provider)); + } catch (agi::vfr::Error const& ex) { + throw VideoOpenError(ex.GetMessage()); + } } diff --git a/src/video_provider_vs.cpp b/src/video_provider_vs.cpp index da4a3d1a5..57f9e2cb0 100644 --- a/src/video_provider_vs.cpp +++ b/src/video_provider_vs.cpp @@ -284,7 +284,7 @@ VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename vs.GetAPI()->freeMap(result); vs.GetAPI()->freeNode(node); vs.GetScriptAPI()->freeScript(script); - throw VideoProviderError(agi::format("Failed to convert to RGB24: %s", error)); + throw VideoOpenError(agi::format("Failed to convert to RGB24: %s", error)); } int err; vs.GetAPI()->freeNode(node); @@ -292,7 +292,7 @@ VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename vs.GetAPI()->freeMap(result); if (err) { vs.GetScriptAPI()->freeScript(script); - throw VideoProviderError("Failed to get resize output node"); + throw VideoOpenError("Failed to get resize output node"); } // Finally, try to get the first frame again, so if the filter does crash, it happens before loading finishes @@ -308,7 +308,7 @@ VapourSynthVideoProvider::VapourSynthVideoProvider(agi::fs::path const& filename } } catch (VapourSynthError const& err) { // for the entire constructor - throw VideoProviderError(agi::format("VapourSynth error: %s", err.GetMessage())); + throw VideoOpenError(err.GetMessage()); } const VSFrame *VapourSynthVideoProvider::GetVSFrame(int n) {