]> diplodocus.org Git - flac-archive/blob - id3v2-grep.cc
549644ee2d9681c69b4a1c9ab537452731cf2806
[flac-archive] / id3v2-grep.cc
1 extern "C" {
2 #include <regex.h>
3 #include <unistd.h>
4 }
5
6 #include <iostream>
7 #include <sstream>
8 #include <string>
9
10 #include <id3v2frame.h>
11 #include <id3v2tag.h>
12 #include <mpegfile.h>
13
14 using std::cerr;
15 using std::cout;
16 using std::endl;
17 using std::string;
18 using std::stringstream;
19
20 const string FRAME_ID_OPT("id");
21 const string FRAME_TEXT_OPT("text");
22
23 static bool file_diag = true;
24 static enum { PRINT_BOTH, PRINT_FILE, PRINT_MATCH, PRINT_NONE,
25 PRINT_DEFAULT } print = PRINT_DEFAULT;
26 static enum { GREP_BOTH, GREP_ID, GREP_TEXT } grep_frame = GREP_BOTH;
27
28 static int
29 grep(const regex_t &regex, TagLib::MPEG::File &file, const char *fn)
30 {
31 if (!file.hasID3v2Tag()) {
32 return REG_NOMATCH;
33 }
34 bool found = false;
35 for (const auto *frame : file.ID3v2Tag()->frameList()) {
36 stringstream sstream;
37 switch (grep_frame) {
38 case GREP_BOTH:
39 sstream << frame->frameID() << "=" << frame->toString();
40 break;
41 case GREP_ID:
42 sstream << frame->frameID();
43 break;
44 case GREP_TEXT:
45 sstream << frame->toString();
46 break;
47 }
48 const string &s = sstream.str();
49 int status = regexec(&regex, s.c_str(), 0, nullptr, 0);
50 if (status == REG_NOMATCH) {
51 continue;
52 }
53 if (status != 0) {
54 return status;
55 }
56 found = true;
57 if (print == PRINT_BOTH) {
58 cout << fn << ":" << s << endl;
59 } else if (print == PRINT_MATCH) {
60 cout << s << endl;
61 }
62 }
63 if (found && print == PRINT_FILE) {
64 cout << fn << endl;
65 }
66 return found ? 0 : REG_NOMATCH;
67 }
68
69 static void
70 regex_error(int status, const regex_t &regex)
71 {
72 char message[512];
73 regerror(status, &regex, message, sizeof(message));
74 cerr << message << endl;
75 }
76
77 int
78 main(int argc, char **argv)
79 {
80 bool ignore_case = false, use_ere = false;
81 char *pattern = nullptr;
82 int c;
83 while ((c = getopt(argc, argv, "Eie:HhlqsQ:")) != -1) {
84 switch (c) {
85 case 'E':
86 use_ere = true;
87 break;
88 case 'i':
89 ignore_case = true;
90 break;
91 case 'e':
92 pattern = optarg;
93 break;
94 case 'H':
95 print = PRINT_BOTH;
96 break;
97 case 'h':
98 print = PRINT_MATCH;
99 break;
100 case 'l':
101 print = PRINT_FILE;
102 break;
103 case 'q':
104 print = PRINT_NONE;
105 break;
106 case 's':
107 file_diag = false;
108 break;
109 case 'Q':
110 if (optarg == FRAME_ID_OPT) {
111 grep_frame = GREP_ID;
112 } else if (optarg == FRAME_TEXT_OPT) {
113 grep_frame = GREP_TEXT;
114 } else {
115 cerr << "invalid option -- '-Q" << optarg << endl;
116 return 2;
117 }
118 break;
119 default:
120 // getopt printed an error.
121 return 2;
122 }
123 }
124 argc -= optind; argv += optind;
125
126 // TODO: multiple patterns
127 if (pattern == nullptr) {
128 if (argc == 0) {
129 cerr << "no pattern" << endl;
130 return 2;
131 }
132 pattern = *argv++;
133 argc--;
134 }
135 int cflags = REG_NOSUB;
136 if (ignore_case) cflags |= REG_ICASE;
137 if (use_ere) cflags |= REG_EXTENDED;
138 regex_t regex;
139 int status = regcomp(&regex, pattern, cflags);
140 if (status != 0) {
141 regex_error(status, regex);
142 return 3;
143 }
144
145 if (print == PRINT_DEFAULT) {
146 print = argc > 1 ? PRINT_BOTH : PRINT_MATCH;
147 }
148
149 int bad_files = 0;
150 bool found = false;
151 for (; *argv != nullptr; argv++) {
152 char *fn = *argv;
153 TagLib::MPEG::File f(fn);
154 if (!f.isValid()) {
155 if (file_diag) {
156 cerr << "failed to open " << fn << endl;
157 }
158 bad_files++;
159 continue;
160 }
161 int status = grep(regex, f, fn);
162 if (status == 0) {
163 found = true;
164 } else if (status != REG_NOMATCH) {
165 regex_error(status, regex);
166 return 3;
167 }
168 }
169
170 if (bad_files > 0) {
171 return 4;
172 }
173 if (found) {
174 return 0;
175 }
176 return 1;
177 }