--- /dev/null
+extern "C" {
+#include <unistd.h>
+}
+
+#include <ostream>
+#include <sstream>
+#include <string>
+
+#include <id3v2frame.h>
+#include <id3v2tag.h>
+#include <mpegfile.h>
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::string;
+using std::stringstream;
+
+const string FRAME_ID_OPT("id");
+const string FRAME_TEXT_OPT("text");
+
+static bool diag = true;
+static enum { PRINT_BOTH, PRINT_FILE, PRINT_MATCH, PRINT_NONE,
+ PRINT_DEFAULT } print = PRINT_DEFAULT;
+static enum { GREP_BOTH, GREP_ID, GREP_TEXT } grep_frame = GREP_BOTH;
+
+// TODO: use regexec(3)
+static bool
+grep(const string &pattern, TagLib::MPEG::File &file, const char *fn)
+{
+ if (!file.hasID3v2Tag()) {
+ return false;
+ }
+ bool found = false;
+ for (const auto *frame : file.ID3v2Tag()->frameList()) {
+ stringstream sstream;
+ switch (grep_frame) {
+ case GREP_BOTH:
+ sstream << frame->frameID() << "=" << frame->toString();
+ break;
+ case GREP_ID:
+ sstream << frame->frameID();
+ break;
+ case GREP_TEXT:
+ sstream << frame->toString();
+ break;
+ }
+ const string &s = sstream.str();
+ if (s.find(pattern, 0) == string::npos) {
+ continue;
+ }
+ found = true;
+ if (print == PRINT_BOTH) {
+ cout << fn << ":" << s << endl;
+ } else if (print == PRINT_MATCH) {
+ cout << s << endl;
+ }
+ }
+ if (found && print == PRINT_FILE) {
+ cout << fn << endl;
+ }
+ return found;
+}
+
+int
+main(int argc, char **argv)
+{
+ char *pattern_c = nullptr;
+ int c;
+ while ((c = getopt(argc, argv, "e:HhlqsQ:")) != -1) {
+ switch (c) {
+ case 'e':
+ pattern_c = optarg;
+ break;
+ case 'H':
+ print = PRINT_BOTH;
+ break;
+ case 'h':
+ print = PRINT_MATCH;
+ break;
+ case 'l':
+ print = PRINT_FILE;
+ break;
+ case 'q':
+ print = PRINT_NONE;
+ break;
+ case 's':
+ diag = false;
+ break;
+ case 'Q':
+ if (optarg == FRAME_ID_OPT) {
+ grep_frame = GREP_ID;
+ } else if (optarg == FRAME_TEXT_OPT) {
+ grep_frame = GREP_TEXT;
+ } else {
+ cerr << "invalid option -- '-Q" << optarg << endl;
+ return 2;
+ }
+ break;
+ default:
+ // getopt printed an error.
+ return 2;
+ }
+ }
+ argc -= optind; argv += optind;
+
+ // TODO: multiple patterns
+ if (pattern_c == nullptr) {
+ if (argc == 0) {
+ cerr << "no pattern" << endl;
+ return 2;
+ }
+ pattern_c = *argv++;
+ argc--;
+ }
+ string pattern(pattern_c);
+
+ if (print == PRINT_DEFAULT) {
+ print = argc > 1 ? PRINT_BOTH : PRINT_MATCH;
+ }
+
+ int bad_files = 0;
+ bool found = false;
+ for (; *argv != nullptr; argv++) {
+ char *fn = *argv;
+ TagLib::MPEG::File f(fn);
+ if (!f.isValid()) {
+ if (diag) {
+ cerr << "failed to open " << fn << endl;
+ }
+ bad_files++;
+ continue;
+ }
+ found |= grep(pattern, f, fn);
+ }
+
+ if (bad_files > 0) {
+ return 3;
+ }
+ if (found) {
+ return 0;
+ }
+ return 1;
+}