/* * Copyright (C) 2019 Emeric Poupon * * This file is part of LMS. * * LMS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LMS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ #include #include #include #include #include #include #include "core/ILogger.hpp" #include "core/String.hpp" #include "audio/AudioTypes.hpp" #include "audio/Exception.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IImageReader.hpp" #include "audio/ITagReader.hpp" namespace lms::audio { std::ostream& operator<<(std::ostream& os, const AudioProperties& audioProperties) { os << "\tDuration: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast>(audioProperties.duration) << std::endl; os << "\tContainer: " << containerTypeToString(audioProperties.container) << std::endl; os << "\tCodec: " << codecTypeToString(audioProperties.codec) << std::endl; os << "\tBitrate: " << audioProperties.bitrate << " bps" << std::endl; if (audioProperties.bitsPerSample) os << "\tBitsPerSample: " << *audioProperties.bitsPerSample << std::endl; os << "\tChannelCount: " << audioProperties.channelCount << std::endl; os << "\tSampleRate: " << audioProperties.sampleRate << std::endl; return os; } void displayTags(const ITagReader& tagReader, TagType type) { std::vector strs; tagReader.visitTagValues(type, [&](std::string_view value) { strs.push_back(std::string{ value }); }); if (strs.empty()) return; std::cout << "\t" << tagTypeToString(type) << ": "; if (strs.size() == 1) { std::cout << "'" << strs.front() << "'\n"; } else { std::cout << "["; bool first{ true }; for (std::string_view str : strs) { if (!first) std::cout << ", "; first = false; std::cout << "'" << str << "'"; } std::cout << "]\n"; }; } void displayLyrics(const ITagReader& tagReader) { tagReader.visitLyricsTags([](std::string_view language, std::string_view lyrics) { std::cout << "Lyrics"; if (!language.empty()) std::cout << " (" << language << ")"; std::cout << ":\n" << lyrics << "\n\n"; }); } void displayPerformers(const ITagReader& tagReader) { tagReader.visitPerformerTags([](std::string_view role, std::string_view artist) { std::cout << "\tPerformer"; if (!role.empty()) std::cout << " '" << role << "'"; std::cout << ": '" << artist << "'\n"; }); } void displayTags(const ITagReader& tagReader) { std::cout << "Tags:\n"; for (int i{}; i < static_cast(TagType::Count); ++i) displayTags(tagReader, static_cast(i)); displayPerformers(tagReader); displayLyrics(tagReader); } std::ostream& operator<<(std::ostream& os, const Image& image) { os << "\ttype = " << imageTypeToString(image.type) << std::endl; if (!image.description.empty()) os << "\tdesc: " << image.description << std::endl; os << "\tmimeType: " << image.mimeType << std::endl; os << "\tsize: " << image.data.size() << std::endl; return os; } void displayImages(const IImageReader& imageReader) { imageReader.visitImages([](const Image& image) { std::cout << "Image:\n" << image << "\n"; }); } void displayInfo(const IAudioFileInfo& fileInfo) { std::cout << "Audio properties:\n" << fileInfo.getAudioProperties() << std::endl; displayImages(fileInfo.getImageReader()); displayTags(fileInfo.getTagReader()); std::cout << "\n"; } } // namespace lms::audio int main(int argc, char* argv[]) { try { using namespace lms; namespace program_options = boost::program_options; program_options::options_description options{ "Options" }; // clang-format off options.add_options() ("help,h", "Display this help message") ("parser", program_options::value()->default_value(std::string{ "taglib" }, "taglib"), "Parser to be used (value can be \"taglib\", \"ffmpeg\")"); // clang-format on program_options::options_description hiddenOptions{ "Hidden options" }; hiddenOptions.add_options()("file", program_options::value>()->composing(), "file"); program_options::options_description allOptions; allOptions.add(options).add(hiddenOptions); program_options::positional_options_description positional; positional.add("file", -1); // Handle remaining arguments as input files program_options::variables_map vm; // Parse command line arguments with positional option handling program_options::store(program_options::command_line_parser(argc, argv) .options(allOptions) .positional(positional) .run(), vm); program_options::notify(vm); auto displayHelp = [&](std::ostream& os) { os << "Usage: " << argv[0] << " [options] file..." << std::endl; os << options << std::endl; }; if (vm.count("help")) { displayHelp(std::cout); return EXIT_SUCCESS; } if (vm.count("file") == 0) { std::cerr << "No input file provided" << std::endl; displayHelp(std::cerr); return EXIT_FAILURE; } if (vm.count("parser") != 1) { std::cerr << "You must specify a parser to be used" << std::endl; return EXIT_FAILURE; } audio::ParserOptions parserOptions; parserOptions.enableExtraDebugLogs = true; if (core::stringUtils::stringCaseInsensitiveEqual(vm["parser"].as(), "taglib")) parserOptions.parser = audio::ParserOptions::Parser::TagLib; else if (core::stringUtils::stringCaseInsensitiveEqual(vm["parser"].as(), "ffmpeg")) parserOptions.parser = audio::ParserOptions::Parser::FFmpeg; else throw program_options::validation_error{ program_options::validation_error::invalid_option_value, "parser" }; const auto& inputFiles{ vm["file"].as>() }; // log to stdout core::Service logger{ core::logging::createLogger(core::logging::Severity::DEBUG) }; for (const std::string& inputFile : inputFiles) { const std::filesystem::path file{ inputFile }; try { std::cout << "Parsing file " << file << ":\n"; const auto audioFileInfo{ audio::parseAudioFile(file, parserOptions) }; displayInfo(*audioFileInfo); } catch (const audio::Exception& e) { std::cerr << "Failed to parse file " << file << ": " << e.what() << std::endl; continue; } } } catch (std::exception& e) { std::cerr << "Caught exception: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }