From bc309f44e0d497f257f277b888cdad77f16163c1 Mon Sep 17 00:00:00 2001 From: Yanhui Shen Date: Sun, 2 Sep 2018 20:24:23 +0800 Subject: [PATCH] Introduce FormatProbe --- CMakeLists.txt | 13 +++++ README.md | 1 + apps/cli/ctx.cc | 3 ++ apps/ncurses/ServerContext.cc | 3 ++ apps/qt5/MainWindow.cpp | 3 ++ core/Player.cc | 5 ++ core/PlayerImpl.h | 77 +++++++++++++++++++++++++---- plugins/alac/Decoder.cc | 8 ++- plugins/fdk-aac/decoder/Decoder.cc | 6 +++ plugins/flac/Decoder.cc | 6 +++ plugins/format-probe/FormatProbe.cc | 67 +++++++++++++++++++++++++ plugins/format-probe/Plugin.cc | 9 ++++ plugins/format-probe/ProbeCaf.h | 5 ++ plugins/format-probe/ProbeMp4.h | 53 ++++++++++++++++++++ plugins/lpcm/Decoder.cc | 6 +++ plugins/mac/Decoder.cc | 6 +++ plugins/mpg123/Decoder.cc | 6 +++ plugins/vorbis/Decoder.cc | 6 +++ plugins/wavpack/Decoder.cc | 6 +++ plugins/wma/Decoder.cc | 6 +++ sdk/core/Player.h | 1 + sdk/plugin/Decoder.h | 11 +++++ sdk/plugin/DecoderProto.h | 4 +- sdk/plugin/FormatProbe.h | 36 ++++++++++++++ sdk/plugin/FormatProbeProto.h | 28 +++++++++++ sdk/plugin/Interface.h | 9 ++++ sdk/util/PluginDef.h | 11 ++--- 27 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 plugins/format-probe/FormatProbe.cc create mode 100644 plugins/format-probe/Plugin.cc create mode 100644 plugins/format-probe/ProbeCaf.h create mode 100644 plugins/format-probe/ProbeMp4.h create mode 100644 sdk/plugin/FormatProbe.h create mode 100644 sdk/plugin/FormatProbeProto.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6babc58..8def538 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,9 @@ endif() option(WITH_PLUGIN_ALSA "ALSA output" ${TRY_PLUGIN_ALSA}) option(WITH_PLUGIN_LIBAO "Libao output" OFF) +#==== Format Probe ====# +option(WITH_PLUGIN_FORMAT_PROBE "Format Probe" ON) + #==== Codecs ====# if(LIB_FAAC AND LIB_MP4V2) set(TRY_PLUGIN_FAAC ON) @@ -298,6 +301,16 @@ if(WITH_PLUGIN_LIBAO) install(TARGETS AoOutput LIBRARY DESTINATION lib/mous) endif() +if(WITH_PLUGIN_FORMAT_PROBE) + file(GLOB PLUGIN_FORMAT_PROBE_SRC plugins/format-probe/*) + add_library(FormatProbe SHARED ${PLUGIN_FORMAT_PROBE_SRC}) + if(LIB_MP4V2) + target_compile_definitions(FormatProbe PRIVATE ENABLE_MP4) + target_link_libraries(FormatProbe ${LIB_MP4V2}) + endif() + install(TARGETS FormatProbe LIBRARY DESTINATION lib/mous) +endif() + if(WITH_PLUGIN_LPCM) file(GLOB LPCM_CODEC_SRC plugins/lpcm/*) add_library(LpcmCodec SHARED ${LPCM_CODEC_SRC}) diff --git a/README.md b/README.md index 4b422b6..45246c6 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ | mpg123 | MP3 decoding (\*.mp3) | mpg123 | | lame | MP3 encoding (\*.mp3) | lame | | fdk-aac | AAC codec (\*.m4a, \*.mp4) | fdk-aac, mp4v2 | +| alac | ALAC codec (\*.m4a, \*.mp4) | mp4v2 | | wavpack | WavPack codec (\*.wv) | wavpack | | vorbis | Ogg Vorbis codec (\*.ogg) | libvorbis, libogg | | flac | FLAC codec (\*.flac) | flac | diff --git a/apps/cli/ctx.cc b/apps/cli/ctx.cc index 714bbd9..f5fce40 100644 --- a/apps/cli/ctx.cc +++ b/apps/cli/ctx.cc @@ -24,6 +24,9 @@ Context::Context() } PluginFinder() + .OnPlugin(PluginType::FormatProbe, [this](const std::shared_ptr& plugin) { + player.LoadFormatProbePlugin(plugin); + }) .OnPlugin(PluginType::Decoder, [this](const std::shared_ptr& plugin) { decoderPlugins.push_back(plugin); player.LoadDecoderPlugin(plugin); diff --git a/apps/ncurses/ServerContext.cc b/apps/ncurses/ServerContext.cc index 3ebe86f..d7ed15b 100644 --- a/apps/ncurses/ServerContext.cc +++ b/apps/ncurses/ServerContext.cc @@ -40,6 +40,9 @@ bool ServerContext::Init() bool hasOutput = false; const auto env = GlobalAppEnv::Instance(); PluginFinder() + .OnPlugin(PluginType::FormatProbe, [this](const std::shared_ptr& plugin) { + player.LoadFormatProbePlugin(plugin); + }) .OnPlugin(PluginType::Decoder, [&, this](const std::shared_ptr& plugin) { player.LoadDecoderPlugin(plugin); hasDecoder = true; diff --git a/apps/qt5/MainWindow.cpp b/apps/qt5/MainWindow.cpp index ae073c5..98ddee2 100644 --- a/apps/qt5/MainWindow.cpp +++ b/apps/qt5/MainWindow.cpp @@ -90,6 +90,9 @@ void MainWindow::InitMousCore() int tagParserPluginCount = 0; PluginFinder() + .OnPlugin(PluginType::FormatProbe, [this](const std::shared_ptr& plugin) { + m_Player->LoadFormatProbePlugin(plugin); + }) .OnPlugin(PluginType::Decoder, [&, this](const std::shared_ptr& plugin) { m_Player->LoadDecoderPlugin(plugin); m_ConvFactory->LoadDecoderPlugin(plugin); diff --git a/core/Player.cc b/core/Player.cc index 6d57fff..57ef85b 100644 --- a/core/Player.cc +++ b/core/Player.cc @@ -19,6 +19,11 @@ PlayerStatus Player::Status() const return impl->Status(); } +void Player::LoadFormatProbePlugin(const std::shared_ptr& plugin) +{ + return impl->LoadFormatProbePlugin(plugin); +} + void Player::LoadDecoderPlugin(const std::shared_ptr& plugin) { return impl->LoadDecoderPlugin(plugin); diff --git a/core/PlayerImpl.h b/core/PlayerImpl.h index 790079b..d4361bd 100644 --- a/core/PlayerImpl.h +++ b/core/PlayerImpl.h @@ -18,6 +18,7 @@ using namespace scx; #include #include #include +#include namespace mous { @@ -192,18 +193,44 @@ class Player::Impl PlayerStatus Status() const { return m_Status; } + void LoadFormatProbePlugin(const std::shared_ptr& plugin) + { + auto probe = std::make_shared(plugin); + if (!probe || !*probe) { + return; + } + const auto suffixes = probe->FileSuffix(); + for (const auto& suffix: suffixes) { + const auto& str = ToLower(suffix); + auto iter = m_Probes.find(str); + if (iter == m_Probes.end()) { + m_Probes.emplace(suffix, probe); + } + } + } + void LoadDecoderPlugin(const std::shared_ptr& plugin) { auto decoder = std::make_shared(plugin); if (!decoder || !*decoder) { return; } - const vector& list = decoder->FileSuffix(); - for (const string& item : list) { - const string& suffix = ToLower(item); - auto iter = m_Decoders.find(suffix); + const auto& suffixes = decoder->FileSuffix(); + const auto& encodings = decoder->Encodings(); + for (const auto& suffix : suffixes) { + const auto& str = ToLower(suffix); + const auto key = "suffix/" + str; + const auto iter = m_Decoders.find(key); + if (iter == m_Decoders.end()) { + m_Decoders.emplace(key, decoder); + } + } + for (const auto& encoding : encodings) { + const auto& str = ToLower(encoding); + const auto key = "encoding/" + str; + const auto iter = m_Decoders.find(key); if (iter == m_Decoders.end()) { - m_Decoders.emplace(suffix, decoder); + m_Decoders.emplace(key, decoder); } } } @@ -236,14 +263,22 @@ class Player::Impl m_Output->Close(); m_Output.reset(); } + + m_Probes.clear(); } vector SupportedSuffixes() const { vector list; list.reserve(m_Decoders.size()); + std::string prefix("suffix/"); for (const auto& entry : m_Decoders) { - list.push_back(entry.first); + const auto pos = entry.first.find(prefix); + if (pos == std::string::npos) { + continue; + } + auto&& suffix = entry.first.substr(prefix.size()); + list.push_back(std::move(suffix)); } return list; } @@ -275,12 +310,33 @@ class Player::Impl return ErrorCode::PlayerNoOutput; } - string suffix = ToLower(FileHelper::FileSuffix(path)); - auto iter = m_Decoders.find(suffix); - if (iter == m_Decoders.end()) { + m_Decoder.reset(); + { + const auto& suffix = ToLower(FileHelper::FileSuffix(path)); + // detect encoding + std::string encoding; + { + auto iter = m_Probes.find(suffix); + if (iter != m_Probes.end()) { + encoding = iter->second->Probe(path); + } + } + if (encoding.size() > 0) { + const auto iter = m_Decoders.find("encoding/" + encoding); + if (iter != m_Decoders.end()) { + m_Decoder = iter->second; + } + } + if (!m_Decoder) { + auto iter = m_Decoders.find("suffix/" + suffix); + if (iter != m_Decoders.end()) { + m_Decoder = iter->second; + } + } + } + if (!m_Decoder) { return ErrorCode::PlayerNoDecoder; } - m_Decoder = iter->second; ErrorCode err = m_Decoder->Open(path); if (err != ErrorCode::Ok) { @@ -548,6 +604,7 @@ class Player::Impl double m_UnitPerMs = 0; std::map> m_Decoders; + std::map> m_Probes; scx::Signal m_SigFinished; }; diff --git a/plugins/alac/Decoder.cc b/plugins/alac/Decoder.cc index b1fa258..6f5c1aa 100644 --- a/plugins/alac/Decoder.cc +++ b/plugins/alac/Decoder.cc @@ -181,6 +181,12 @@ static const BaseOption** GetOptions(void* ptr) { static const char** GetSuffixes(void* ptr) { (void) ptr; - static const char* suffixes[] { "alac", nullptr }; + static const char* suffixes[] { "m4a", nullptr }; return suffixes; +} + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "alac", nullptr }; + return encodings; } \ No newline at end of file diff --git a/plugins/fdk-aac/decoder/Decoder.cc b/plugins/fdk-aac/decoder/Decoder.cc index 174b93c..47c2023 100644 --- a/plugins/fdk-aac/decoder/Decoder.cc +++ b/plugins/fdk-aac/decoder/Decoder.cc @@ -236,3 +236,9 @@ const char** GetSuffixes(void* ptr) { static const char* suffixes[] { "m4a", "mp4", nullptr }; return suffixes; } + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "aac", nullptr }; + return encodings; +} \ No newline at end of file diff --git a/plugins/flac/Decoder.cc b/plugins/flac/Decoder.cc index 9d0d8ed..443eb15 100644 --- a/plugins/flac/Decoder.cc +++ b/plugins/flac/Decoder.cc @@ -200,3 +200,9 @@ static const char** GetSuffixes(void* ptr) { static const char* suffixes[] { "flac", nullptr }; return suffixes; } + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "flac", nullptr }; + return encodings; +} \ No newline at end of file diff --git a/plugins/format-probe/FormatProbe.cc b/plugins/format-probe/FormatProbe.cc new file mode 100644 index 0000000..f9ac657 --- /dev/null +++ b/plugins/format-probe/FormatProbe.cc @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +using namespace scx; +#include +using namespace mous; +#ifdef ENABLE_CAF +#include "ProbeCaf.h" +#endif +#ifdef ENABLE_MP4 +#include "ProbeMp4.h" +#endif + +using ProbeFunc = const char* (*)(void* ptr, const char* path); + +namespace { + struct Self { + const std::map probes { +#ifdef ENABLE_CAF + { "caf", ProbeCaf }, +#endif +#ifdef ENABLE_MP4 + { "alac", ProbeMp4 }, + { "m4a", ProbeMp4 }, + { "mp4", ProbeMp4 }, +#endif + }; + }; +} + +static void* Create(void) { + return new Self; +} + +static void Destroy(void* ptr) { + delete SELF; +} + +static const char* Probe(void* ptr, const char* path) { + const auto& suffix = ToLower(FileHelper::FileSuffix(path)); + const auto iter = SELF->probes.find(suffix); + return iter != SELF->probes.end() ? iter->second(ptr, path) : nullptr; +} + +static const BaseOption** GetOptions(void* ptr) { + (void) ptr; + return nullptr; +} + +const char** GetSuffixes(void* ptr) { + (void) ptr; + static const char* suffixes[] { + "alac", +#ifdef ENABLE_CAF + "caf", +#endif +#ifdef ENABLE_MP4 + "m4a", + "mp4", +#endif + nullptr + }; + return suffixes; +} \ No newline at end of file diff --git a/plugins/format-probe/Plugin.cc b/plugins/format-probe/Plugin.cc new file mode 100644 index 0000000..a22360f --- /dev/null +++ b/plugins/format-probe/Plugin.cc @@ -0,0 +1,9 @@ +#include +using namespace mous; + +MOUS_EXPORT_PLUGIN( + PluginType::FormatProbe, + "format-probe", + "Format Probe", + 2 +) diff --git a/plugins/format-probe/ProbeCaf.h b/plugins/format-probe/ProbeCaf.h new file mode 100644 index 0000000..132e9d7 --- /dev/null +++ b/plugins/format-probe/ProbeCaf.h @@ -0,0 +1,5 @@ +#pragma once + +static const char* ProbeCaf(void* ptr, const char* path) { + return nullptr; +} \ No newline at end of file diff --git a/plugins/format-probe/ProbeMp4.h b/plugins/format-probe/ProbeMp4.h new file mode 100644 index 0000000..98afe3f --- /dev/null +++ b/plugins/format-probe/ProbeMp4.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +namespace { + struct MP4File { + explicit MP4File(const char* path) { + handle = MP4Read(path); + } + ~MP4File() { + if (handle) { + MP4Close(handle); + handle = nullptr; + } + } + operator bool() const { + return handle; + } + MP4FileHandle handle = nullptr; + }; +} + +static const char* ProbeMp4(void* ptr, const char* path) { + MP4File file(path); + if (!file) { + return nullptr; + } + MP4TrackId trackid = MP4_INVALID_TRACK_ID; + auto ntrack = MP4GetNumberOfTracks(file.handle); + for (decltype(ntrack) itrack = 0; itrack < ntrack; ++itrack) { + trackid = MP4FindTrackId(file.handle, itrack); + if (trackid != MP4_INVALID_TRACK_ID) { + const auto type = MP4GetTrackType(file.handle, trackid); + if (MP4_IS_AUDIO_TRACK_TYPE(type)) { + break; + }; + } + } + if (trackid == MP4_INVALID_TRACK_ID) { + return nullptr; + } + const char* media_data_name = MP4GetTrackMediaDataName(file.handle, trackid); + if (strcasecmp(media_data_name, "mp4a") == 0) { + const auto type = MP4GetTrackEsdsObjectTypeId(file.handle, trackid); + if (type == MP4_MPEG4_AUDIO_TYPE) { + return "aac"; + } + } + if (strcasecmp(media_data_name, "alac") == 0) { + return "alac"; + } + return nullptr; +} \ No newline at end of file diff --git a/plugins/lpcm/Decoder.cc b/plugins/lpcm/Decoder.cc index 357a2a1..3baccf6 100644 --- a/plugins/lpcm/Decoder.cc +++ b/plugins/lpcm/Decoder.cc @@ -141,4 +141,10 @@ static const char** GetSuffixes(void* ptr) { (void) ptr; static const char* suffixes[] { "wav", nullptr }; return suffixes; +} + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "lpcm", nullptr }; + return encodings; } \ No newline at end of file diff --git a/plugins/mac/Decoder.cc b/plugins/mac/Decoder.cc index febf4c3..df9663a 100644 --- a/plugins/mac/Decoder.cc +++ b/plugins/mac/Decoder.cc @@ -157,3 +157,9 @@ static const char** GetSuffixes(void* ptr) { static const char* suffixes[] { "ape", nullptr }; return suffixes; } + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "ape", nullptr }; + return encodings; +} \ No newline at end of file diff --git a/plugins/mpg123/Decoder.cc b/plugins/mpg123/Decoder.cc index 168dfc6..1e423f1 100644 --- a/plugins/mpg123/Decoder.cc +++ b/plugins/mpg123/Decoder.cc @@ -173,3 +173,9 @@ static const char** GetSuffixes(void* ptr) { static const char* suffixes[] { "mp3", nullptr }; return suffixes; } + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "mp3", nullptr }; + return encodings; +} \ No newline at end of file diff --git a/plugins/vorbis/Decoder.cc b/plugins/vorbis/Decoder.cc index 244e12b..b445576 100644 --- a/plugins/vorbis/Decoder.cc +++ b/plugins/vorbis/Decoder.cc @@ -134,3 +134,9 @@ static const char** GetSuffixes(void* ptr) { static const char* suffixes[] { "ogg", nullptr }; return suffixes; } + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "vorbis", nullptr }; + return encodings; +} \ No newline at end of file diff --git a/plugins/wavpack/Decoder.cc b/plugins/wavpack/Decoder.cc index 41d6cdf..4f83e20 100644 --- a/plugins/wavpack/Decoder.cc +++ b/plugins/wavpack/Decoder.cc @@ -149,6 +149,12 @@ static const char** GetSuffixes(void* ptr) { return suffixes; } +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "wavpack", nullptr }; + return encodings; +} + static void FormatSamples(int bps, unsigned char* dst, int32_t *src, uint32_t samcnt) { int32_t temp; diff --git a/plugins/wma/Decoder.cc b/plugins/wma/Decoder.cc index 8297f77..d94e66f 100644 --- a/plugins/wma/Decoder.cc +++ b/plugins/wma/Decoder.cc @@ -183,4 +183,10 @@ static const char** GetSuffixes(void* ptr) { (void) ptr; static const char* suffixes[] { "wma", nullptr }; return suffixes; +} + +static const char** GetEncodings(void* ptr) { + (void) ptr; + static const char* encodings[] { "wma", nullptr }; + return encodings; } \ No newline at end of file diff --git a/sdk/core/Player.h b/sdk/core/Player.h index a1b7aaa..ed48ca9 100644 --- a/sdk/core/Player.h +++ b/sdk/core/Player.h @@ -33,6 +33,7 @@ class Player public: PlayerStatus Status() const; + void LoadFormatProbePlugin(const std::shared_ptr& plugin); void LoadDecoderPlugin(const std::shared_ptr& plugin); void LoadOutputPlugin(const std::shared_ptr& plugin); void UnloadPlugin(const std::string& path); diff --git a/sdk/plugin/Decoder.h b/sdk/plugin/Decoder.h index e310cd7..877a107 100644 --- a/sdk/plugin/Decoder.h +++ b/sdk/plugin/Decoder.h @@ -78,6 +78,17 @@ class Decoder { } return suffixes; } + + std::vector Encodings() const { + std::vector suffixes; + const char** p = m_interface->get_encodings(m_data); + if (p) { + for (; *p; ++p) { + suffixes.emplace_back(*p); + } + } + return suffixes; + } }; } diff --git a/sdk/plugin/DecoderProto.h b/sdk/plugin/DecoderProto.h index 94f5b97..c7e2059 100644 --- a/sdk/plugin/DecoderProto.h +++ b/sdk/plugin/DecoderProto.h @@ -24,6 +24,7 @@ static int32_t GetBitRate(void* ptr); static uint64_t GetDuration(void* ptr); static const mous::BaseOption** GetOptions(void* ptr); static const char** GetSuffixes(void* ptr); +static const char** GetEncodings(void* ptr); static mous::DecoderInterface decoder_interface { Create, @@ -42,7 +43,8 @@ static mous::DecoderInterface decoder_interface { GetBitRate, GetDuration, GetOptions, - GetSuffixes + GetSuffixes, + GetEncodings }; extern "C" { diff --git a/sdk/plugin/FormatProbe.h b/sdk/plugin/FormatProbe.h new file mode 100644 index 0000000..edf501a --- /dev/null +++ b/sdk/plugin/FormatProbe.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "Interface.h" +#include "InterfaceWrapper.h" + +namespace mous { + +class FormatProbe { + COPY_INTERFACE_WRAPPER_COMMON(FormatProbe, FormatProbeInterface, "MousGetFormatProbeInterface"); + +public: + std::string Probe(const std::string& path) const { + const char* format = m_interface->probe(m_data, path.c_str()); + return { format ? format : "" }; + } + + std::vector FileSuffix() const { + std::vector suffixes; + const char** p = m_interface->get_suffixes(m_data); + if (p) { + for (; *p; ++p) { + suffixes.emplace_back(*p); + } + } + return suffixes; + } +}; + +} diff --git a/sdk/plugin/FormatProbeProto.h b/sdk/plugin/FormatProbeProto.h new file mode 100644 index 0000000..1af770b --- /dev/null +++ b/sdk/plugin/FormatProbeProto.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "Interface.h" + +static void* Create(); +static void Destroy(void* ptr); +static const char* Probe(void* ptr, const char* path); +static const mous::BaseOption** GetOptions(void* ptr); +static const char** GetSuffixes(void* ptr); + +static mous::FormatProbeInterface format_probe_interface { + Create, + Destroy, + Probe, + GetOptions, + GetSuffixes +}; + +extern "C" { + const mous::FormatProbeInterface* MousGetFormatProbeInterface() { + return &format_probe_interface; + } +} diff --git a/sdk/plugin/Interface.h b/sdk/plugin/Interface.h index 006f6b8..58a0533 100644 --- a/sdk/plugin/Interface.h +++ b/sdk/plugin/Interface.h @@ -45,6 +45,7 @@ struct DecoderInterface { uint64_t (*get_duration)(void* ptr); const BaseOption** (*get_options)(void* ptr); const char** (*get_suffixes)(void* ptr); + const char** (*get_encodings)(void* ptr); }; struct EncoderInterface { @@ -102,4 +103,12 @@ struct SheetParserInterface { const char** (*get_suffixes)(void* ptr); }; +struct FormatProbeInterface { + void* (*create)(void); + void (*destroy)(void* ptr); + const char* (*probe)(void* ptr, const char* path); + const mous::BaseOption** (*get_options)(void* ptr); + const char** (*get_suffixes)(void* ptr); +}; + } diff --git a/sdk/util/PluginDef.h b/sdk/util/PluginDef.h index f80236a..326e4be 100644 --- a/sdk/util/PluginDef.h +++ b/sdk/util/PluginDef.h @@ -14,29 +14,26 @@ enum class PluginType: uint32_t { Encoder = 1u << 1, Output = 1u << 2, SheetParser = 1u << 3, - TagParser = 1u << 4 + TagParser = 1u << 4, + FormatProbe = 1u << 5 }; inline const char* ToString(PluginType type) { switch (type) { case PluginType::None: return "None"; - case PluginType::Decoder: return "Decoder"; - case PluginType::Encoder: return "Encoder"; - case PluginType::Output: return "Output"; - case PluginType::SheetParser: return "SheetParser"; - case PluginType::TagParser: return "TagParser"; - + case PluginType::FormatProbe: + return "FormatProbe"; default: return "Unknown"; }