extern "C" { #include #include } #include #include #include #include #include #include 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 file_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; static int grep(const regex_t ®ex, TagLib::MPEG::File &file, const char *fn) { if (!file.hasID3v2Tag()) { return REG_NOMATCH; } bool found = false; for (const auto *frame : file.ID3v2Tag()->frameList()) { // BUG: In GREP_ID and GREP_TEXT modes, I meant to still print // '$ID=$TEXT' for the output line. 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(); int status = regexec(®ex, s.c_str(), 0, nullptr, 0); if (status == REG_NOMATCH) { continue; } if (status != 0) { return status; } 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 ? 0 : REG_NOMATCH; } static void regex_error(int status, const regex_t ®ex) { char message[512]; regerror(status, ®ex, message, sizeof(message)); cerr << message << endl; } int main(int argc, char **argv) { bool ignore_case = false, use_ere = false; char *pattern = nullptr; int c; while ((c = getopt(argc, argv, "Eie:HhlqsQ:")) != -1) { switch (c) { case 'E': use_ere = true; break; case 'i': ignore_case = true; break; case 'e': pattern = 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': file_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 == nullptr) { if (argc == 0) { cerr << "no pattern" << endl; return 2; } pattern = *argv++; argc--; } int cflags = REG_NOSUB; if (ignore_case) cflags |= REG_ICASE; if (use_ere) cflags |= REG_EXTENDED; regex_t regex; int status = regcomp(®ex, pattern, cflags); if (status != 0) { regex_error(status, regex); return 3; } 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 (file_diag) { cerr << "failed to open " << fn << endl; } bad_files++; continue; } int status = grep(regex, f, fn); if (status == 0) { found = true; } else if (status != REG_NOMATCH) { regex_error(status, regex); return 3; } } if (bad_files > 0) { return 4; } if (found) { return 0; } return 1; }