DCL 3.7.4
Loading...
Searching...
No Matches
TagReader.cpp
Go to the documentation of this file.
1#include <dcl/Config.h>
2
3#include <string.h> // memcmp
4
5#ifdef __WINNT__
6#include <windows.h>
7#endif
8
9#include <dcl/String.h>
10#include <dcl/Charset.h>
11#include <dcl/File.h>
12#include <dcl/Dir.h>
13#include <dcl/Writer.h>
14#include <dcl/Files.h>
15#include <dcl/DateTime.h>
16
17#include "ID3v1.h"
18#include "ID3v2.h"
19#include "APEv2.h"
20#include "MediaInfo.h"
21
22#include "main.h"
23#include "TagReader.h"
24
25#define __TRACE_THIS 0
26#if __TRACE_THIS
27#define __DCL_TRACE1_N __DCL_TRACE1
28#define __DCL_TRACE2_N __DCL_TRACE2
29#define __DCL_TRACE3_N __DCL_TRACE3
30#else
31#define __DCL_TRACE1_N(fmt, arg)
32#define __DCL_TRACE2_N(fmt, arg1, arg2)
33#define __DCL_TRACE3_N(fmt, arg1, arg2, arg3)
34#endif
35
36__DCL_BEGIN_NAMESPACE
37
38#if __DCL_HAVE_THIS_FILE__
39#undef __THIS_FILE__
40static const char_t __UNUSED__ __THIS_FILE__[] = __T("media/TagReader.cpp");
41#endif
42
44{
45 if (__conn) {
46 //__DCL_TRACE1(L"Connection destroy [%p]\n", __conn);
47 delete __conn;
48 }
49}
50
53{
54 __conn = NULL;
55 __dirId = 0;
56 __fileId = 0;
57
58 size_t first = __args.database().search(L"DRIVER *=", true);
59 if (first != (size_t)-1) {
60 String driver = __args.database().substring(L"DRIVER *= *[^;]+", true);
61 if (driver.isEmpty()) {
62 driver = __args.database().substring(first);
63 }
64 if (!driver.isEmpty()) {
65 if (driver.indexOf(L'{') != (size_t)-1) {
66 driver = L"DCLODBC";
67 }
68 else {
69 driver = driver.substring(driver.indexOf(L'=') + 1).trim();
70 }
71 }
72 if (!driver.isEmpty()) {
73 __conn = new SQLConnection(driver);
74 __conn->open(__args.database());
75 __args.output() << L"Database connected. ["
76 << __conn->getServerInfo()
77 << L"] Using ["
78 << __args.database()
79 << L"]" << endl;
80 }
81 }
82
83 __doubleRule.assign(L'=', 80);
84 __singleRule.assign(L'-', 80);
85}
86
87void TagReader::readTag(const String& _path, FileInfo& _info)
88{
89 File file(_path);
90#if defined(__DCL_DEBUG) && __TRACE_THIS
91 {
92 File::off_t _n = file.size();
93 __DCL_TRACE2(L"[%ls] size[%lld]\n", file.path().data(), _n);
94 }
95#endif
96 char buf[160]; // 128 + 32
97
98 int __UNUSED__ id3v2Version = 0; // 0, 2, 3, 4
99 int apeVersion = 0; // 0, 1, 2
100 int __UNUSED__ id3v1Version = 0; // 0, 1
101
102 ID3v2& id3v2 = _info.id3v2;
103 APEv2& apev2 = _info.apev2;
104 ID3v1& id3v1 = _info.id3v1;
105
106 // 파일의 시작에서 ID3v2와 APEv2를 검사한다.
107 // "ID3" "APETAGEX"
108 size_t n = file.read(buf, 32);
109 if (n == 32) {
110 if (memcmp(buf, "ID3", 3) == 0) {
111 // ID3v2가 파일의 맨 앞에 있다.
112 if (_info.id3v2.read(file, buf)) {
113 id3v2Version = id3v2.version();
114 }
115 }
116 else if (memcmp(buf, "APETAGEX", 8) == 0) {
117 // APEv2가 파일의 맨 앞에 있다.
118 if (apev2.read(file, buf, 0)) {
119 apeVersion = apev2.version() / 1000;
120 }
121 }
122 }
123
124 // 파일의 끝에서 APEv2와 ID3v1을 검사한다.
125 file.seek(-160, File::END);
126 n = file.read(buf, 160);
127 if (n == 160) {
128 __DCL_TRACE2_N(L"read [%zd][%ls]\n",
129 n, String::tryString(buf, 160).data());
130 if (apeVersion == 0) {
131 if (memcmp(buf, "APETAGEX", 8) == 0) {
132 // APEv2 또는 1이 파일의 뒤쪽 ID3v1 앞에 있다.
133 if (apev2.read(file, buf, -160)) {
134 apeVersion = apev2.version() / 1000;
135 }
136 }
137 else if (memcmp(&buf[128], "APETAGEX", 8) == 0) {
138 // APEv2 또는 1이 파일의 뒤쪽에 있다.
139 if (apev2.read(file, buf, -32)) {
140 apeVersion = apev2.version() / 1000;
141 }
142 }
143 }
144
145 if (memcmp(&buf[32], "TAG", 3) == 0) {
146 if (id3v1.read(&buf[32])) {
147 id3v1Version = id3v1.version();
148 }
149 }
150 }
151}
152
153void TagReader::read(const String& _dirname, const String& _filename)
154{
155 FileInfo info(_dirname, _filename);
156 String path = _dirname + _filename;
157
158 info.size = Files::size(path);
159 time_t atime, mtime, ctime;
160 if (Files::time(path, &atime, &mtime, &ctime)) {
161 info.atime.assign(atime);
162 info.mtime.assign(mtime);
163 info.ctime.assign(ctime);
164 }
165
166 try {
167 if (_filename.toLowerCase().endsWith(L".mp3")) {
168 readTag(path, info);
169 }
170
171 MediaInfo mi(path);
172 info.inform = mi.inform_r();
173 info.format = mi.format();
174 if (mi.videoCount() > 0) {
175 info.width = mi.videoWidth(0);
176 info.height = mi.videoHeight(0);
177 info.duration = mi.videoDurationAsSeconds(0);
178 }
179 else if (mi.audioCount() > 0) {
180 info.duration = mi.audioDurationAsSeconds(0);
181 }
182 else if (mi.imageCount() > 0) {
183 info.width = mi.imageWidth(0);
184 info.height = mi.imageHeight(0);
185 }
186 }
187 catch (Exception* e) {
188 info.notes = e->toString();
189 args().errout() << e->toStringAll() << endl;
190 e->destroy();
191 }
192
193 print(info);
194
195 if (!__args.dryrun() && __conn && __conn->connected()) {
196 __conn->startTrans();
197 try {
198 persist(info);
199 __conn->commitTrans();
200 }
201 catch (Exception* e) {
202 __conn->rollbackTrans();
203 args().errout() << e->toStringAll() << endl;
204 e->destroy();
205 }
206 }
207}
208
209void TagReader::readDir(const String& _path)
210{
211 Dir dir(_path);
212 Dir::Entry entry;
213
214 // __DCL_TRACE1_N(L"[%ls]\n", dir.path().data());
215 while (dir.read(entry)) {
216 // __DCL_TRACE1_N(L"[%ls]\n", entry.toString().data());
217 String name = entry.name();
218 if (entry.isDir()) {
219 if (name.compare(L"..", name.length()) != 0) {
220 readDir(dir.path() + entry.name());
221 }
222 }
223 else {
224 read(dir.path(), name);
225 }
226 }
227}
228
229void TagReader::print(const FileInfo& _info) const
230{
231 Writer& out = args().output();
232
233 const ID3v2& id3v2 = _info.id3v2;
234 const APEv2& apev2 = _info.apev2;
235 const ID3v1& id3v1 = _info.id3v1;
236
237 out << __doubleRule << endl;
238 out << L"DIR [" << _info.dirname << L"]" << endl;
239 out << L"FILE[" << _info.filename << L"]" << endl;
240
241 out.printf(L" tags[%03d]",
242 id3v2.version() * 100 + apev2.version() / 100 + id3v1.version());
243 out << L" size[" << _info.size << L"]"
244 << L" mtime[" << _info.mtime.toString() << L"]"
245 << L" notes[" << _info.notes << L"]"
246 << endl;
247
248 out << L" format[" << _info.format << L"]"
249 << L" width[" << _info.width << L"]"
250 << L" height[" << _info.height << L"]"
251 << L" duration[" << _info.duration << L"]"
252 << endl;
253
254 if (!args().verbose()) {
255 return;
256 }
257
258 if (id3v2.version() > 0) {
259 out << __singleRule << endl;
260 out << id3v2.toString() << endl;
261
262 for (size_t i = 0; i < id3v2.frames().size(); i++) {
263 const ID3v2Frame& frame = *(ID3v2Frame*)id3v2.frames()[i];
264 out << frame.toString() << endl;
265 }
266 }
267
268 if (apev2.version() > 0) {
269 out << __singleRule << endl;
270 out << apev2.toString() << endl;
271
272 for (size_t i = 0; i < apev2.items().size(); i++) {
273 const APEv2Item& item = *(APEv2Item*)apev2.items()[i];
274 out << item.toString() << endl;
275 }
276 }
277
278 if (id3v1.version() > 0) {
279 out << __singleRule << endl;
280 out << id3v1.toString() << endl;
281 }
282
283 out << __singleRule << endl;
284 out << _info.inform << endl;
285}
286
287void TagReader::persist(const FileInfo& _info)
288{
289 Writer& out = args().output();
290 out << __singleRule << endl;
291
292 if (__dirId == 0) {
293 String sql = L""
294 "SELECT COALESCE(MAX(DIR_ID), 1000) FROM STOR_DIR"
295 ;
296 SQLQuery q(__conn);
297 q.execute(sql);
298 q.fetch();
299 __dirId = q.fields()[0].asInt32();
300 }
301
302 if (__fileId == 0) {
303 String sql = L""
304 "SELECT COALESCE(MAX(FILE_ID), 10000) FROM STOR_FILE"
305 ;
306 SQLQuery q(__conn);
307 q.execute(sql);
308 q.fetch();
309 __fileId = q.fields()[0].asInt64();
310 }
311
313 if (__dirname != _info.dirname) {
314 __dirname = _info.dirname;
315 __dirId++;
316
317 String sql = L""
318 "INSERT INTO STOR_DIR ("
319 "DIR_ID, PATH, CREA_TS, MODI_TS"
320 ")"
321 "\nVALUES ("
322 ":DIR_ID, :PATH, :CREA_TS, :MODI_TS"
323 ")" ;
324 SQLQuery q(__conn);
325 q.prepare(sql);
326 q.params()[0].setValue(__dirId);
327 q.params()[1].setValue(__dirname);
328 q.params()[2].setValue(now);
329 q.params()[3].setValue(now);
330 q.execute();
331
332 out << L" INSERT STOR_DIR [" << __dirId << L"]["
333 << __dirname << L"]" << endl;
334 }
335
336 __fileId++;
337 String sql = L""
338 "INSERT INTO STOR_FILE ("
339 "FILE_ID, NAME, USER_ID, STOR_ID, TYPE_ID"
340 ", DIR_ID, FILE_NM, FILE_SZ, FILE_AT, FILE_MT, FILE_CT, FILE_ER"
341 ", MEDI_FM, MEDI_WI, MEDI_HE, MEDI_DU, MEDI_NF"
342 ", READ_CO, CREA_TS, MODI_TS"
343 ")"
344 "\nVALUES ("
345 ":FILE_ID, :NAME, 0, 0, 0"
346 ", :DIR_ID, :FILE_NM, :FILE_SZ, :FILE_AT, :FILE_MT, :FILE_CT, :FILE_ER"
347 ", :MEDI_FM, :MEDI_WI, :MEDI_HE, :MEDI_DU, :MEDI_NF"
348 ", 0, :CREA_TS, :MODI_TS"
349 ")";
350 SQLQuery q(__conn);
351 q.prepare(sql);
352 q.params().byName(L"FILE_ID").setValue(__fileId);
353 q.params().byName(L"NAME").setValue(_info.filename);
354 q.params().byName(L"DIR_ID").setValue(__dirId);
355 q.params().byName(L"FILE_NM").setValue(_info.filename);
356 q.params().byName(L"FILE_SZ").setValue(_info.size);
357 q.params().byName(L"FILE_AT").setValue(_info.atime);
358 q.params().byName(L"FILE_MT").setValue(_info.mtime);
359 q.params().byName(L"FILE_CT").setValue(_info.ctime);
360 q.params().byName(L"FILE_ER").setValue(_info.notes);
361 q.params().byName(L"MEDI_FM").setValue(_info.format);
362 q.params().byName(L"MEDI_WI").setValue(_info.width);
363 q.params().byName(L"MEDI_HE").setValue(_info.height);
364 q.params().byName(L"MEDI_DU").setValue(_info.duration);
365 q.params().byName(L"MEDI_NF").setValue(_info.inform);
366 q.params().byName(L"CREA_TS").setValue(now);
367 q.params().byName(L"MODI_TS").setValue(now);
368 q.execute();
369
370 out << L" INSERT STOR_FILE [" << __fileId << L"]["
371 << _info.filename << L"]" << endl;
372
373 if (_info.id3v1.version() > 0) {
374 if (_info.id3v1.title().length() > 0) {
375 String sql = L""
376 "INSERT INTO ID3V1 ("
377 "FILE_ID, TITLE, ARTIST, ALBUM, YEAR_"
378 ", COMMENT_, TRACK, GENRE"
379 ")"
380 "\nVALUES ("
381 ":FILE_ID, :TITLE, :ARTIST, :ALBUM, :YEAR_"
382 ", :COMMENT_, :TRACK, :GENRE"
383 ")";
384 SQLQuery q(__conn);
385 q.prepare(sql);
386 q.params().byName(L"FILE_ID").setValue(__fileId);
387 q.params().byName(L"TITLE").setValue(_info.id3v1.title());
388
389 if (_info.id3v1.artist().isEmpty())
390 q.params().byName(L"ARTIST").setNull();
391 else
392 q.params().byName(L"ARTIST").setValue(_info.id3v1.artist());
393
394 if (_info.id3v1.album().isEmpty())
395 q.params().byName(L"ALBUM").setNull();
396 else
397 q.params().byName(L"ALBUM").setValue(_info.id3v1.album());
398
399 if (_info.id3v1.year().isEmpty())
400 q.params().byName(L"YEAR_").setNull();
401 else
402 q.params().byName(L"YEAR_").setValue(_info.id3v1.year());
403
404 if (_info.id3v1.comment().isEmpty())
405 q.params().byName(L"COMMENT_").setNull();
406 else
407 q.params().byName(L"COMMENT_").setValue(_info.id3v1.comment());
408
409 q.params().byName(L"TRACK").setValue(_info.id3v1.track());
410 q.params().byName(L"GENRE").setValue(_info.id3v1.genre());
411 q.execute();
412
413 if (__args.verbose()) {
414 out << L" INSERT ID3V1" << endl;
415 }
416 }
417 else {
418 args().errout() << L"Title is NULL "
419 << _info.id3v1.toString()
420 << L" [" << _info.dirname << _info.filename << L"]" << endl;
421 }
422 }
423
424 if (_info.id3v2.version() > 0) {
425 String sql = L""
426 "INSERT INTO ID3V2 ("
427 "FILE_ID, VERSION, FLAGS, SIZE_, FSBITS"
428 ")"
429 "\nVALUES ("
430 ":FILE_ID, :VERSION, :FLAGS, :SIZE_, :FSBITS"
431 ")";
432 SQLQuery q(__conn);
433 q.prepare(sql);
434 q.params().byName(L"FILE_ID").setValue(__fileId);
435 q.params().byName(L"VERSION").setValue(_info.id3v2.version());
436 q.params().byName(L"FLAGS").setValue(_info.id3v2.flags());
437 q.params().byName(L"SIZE_").setValue(_info.id3v2.size());
438 q.params().byName(L"FSBITS").setValue(_info.id3v2.fsbits());
439 q.execute();
440
441 sql = L""
442 "INSERT INTO ID3V2_FRAME ("
443 "FILE_ID, NO_, FRAME_ID, SIZE_, FLAGS"
444 ", ENCODING, TYPE_, URL, DESCRIPTION, TEXT_, BINARY_"
445 ")"
446 "\nVALUES ("
447 ":FILE_ID, :NO_, :FRAME_ID, :SIZE_, :FLAGS"
448 ", :ENCODING, :TYPE_, :URL, :DESCRIPTION, :TEXT_, :BINARY_"
449 ")";
450 q.prepare(sql);
451 for (size_t i = 0; i < _info.id3v2.frames().size(); i++) {
452 ID3v2Frame& frame = *(ID3v2Frame*)_info.id3v2.frames()[i];
453 q.params().byName(L"FILE_ID").setValue(__fileId);
454 q.params().byName(L"NO_").setValue((int)(i + 1));
455 q.params().byName(L"FRAME_ID").setValue(frame.id());
456 q.params().byName(L"SIZE_").setValue(frame.size());
457 q.params().byName(L"FLAGS").setValue(frame.flags());
458 q.params().byName(L"ENCODING").setValue(frame.encoding());
459 q.params().byName(L"TYPE_").setValue(frame.type());
460
461 if (frame.url().isEmpty())
462 q.params().byName(L"URL").setNull();
463 else
464 q.params().byName(L"URL").setValue(frame.url());
465
466 if (frame.description().isEmpty())
467 q.params().byName(L"DESCRIPTION").setNull();
468 else
469 q.params().byName(L"DESCRIPTION").setValue(frame.description());
470
471 if (frame.text().isEmpty())
472 q.params().byName(L"TEXT_").setNull();
473 else
474 q.params().byName(L"TEXT_").setValue(frame.text());
475
476 if (frame.binary().length() == 0 || 100 < frame.binary().length())
477 // APIC, PIC 제외
478 q.params().byName(L"BINARY_").setNull();
479 else
480 q.params().byName(L"BINARY_").setValue(frame.binary());
481
482 q.execute();
483 }
484
485 if (__args.verbose()) {
486 out << L" INSERT ID3V2 ID3V2_FRAME["
487 << _info.id3v2.frames().size() << L"]" << endl;
488 }
489 }
490
491 if (_info.apev2.version() > 0) {
492 String sql = L""
493 "INSERT INTO APE ("
494 "FILE_ID, VERSION, SIZE_, COUNT_, FLAGS"
495 ")"
496 "\nVALUES ("
497 ":FILE_ID, :VERSION, :SIZE_, :COUNT_, :FLAGS"
498 ")";
499 SQLQuery q(__conn);
500 q.prepare(sql);
501 q.params().byName(L"FILE_ID").setValue(__fileId);
502 q.params().byName(L"VERSION").setValue(_info.apev2.version());
503 q.params().byName(L"SIZE_").setValue(_info.apev2.size());
504 q.params().byName(L"COUNT_").setValue(_info.apev2.count());
505 q.params().byName(L"FLAGS").setValue((int32_t)_info.apev2.flags());
506 q.execute();
507
508 sql = L""
509 "INSERT INTO APE_ITEM ("
510 "FILE_ID, NO_, SIZE_, FLAGS"
511 ", KEY_, VALUE_"
512 ")"
513 "\nVALUES ("
514 ":FILE_ID, :NO_, :SIZE_, :FLAGS"
515 ", :KEY_, :VALUE_"
516 ")";
517 q.prepare(sql);
518 for (size_t i = 0; i < _info.apev2.items().size(); i++) {
519 APEv2Item& item = *(APEv2Item*)_info.apev2.items()[i];
520 q.params().byName(L"FILE_ID").setValue(__fileId);
521 q.params().byName(L"NO_").setValue((int)(i + 1));
522 q.params().byName(L"SIZE_").setValue(item.size());
523 q.params().byName(L"FLAGS").setValue((int32_t)item.flags());
524 q.params().byName(L"KEY_").setValue(item.key());
525
526 if (item.value().isEmpty())
527 q.params().byName(L"VALUE_").setNull();
528 else
529 q.params().byName(L"VALUE_").setValue(item.value());
530
531 q.execute();
532 }
533
534 if (__args.verbose()) {
535 out << L" INSERT APE APE_ITEM["
536 << _info.apev2.items().size() << L"]" << endl;
537 }
538 }
539}
540
541__DCL_END_NAMESPACE
#define __THIS_FILE__
Definition _trace.h:14
#define NULL
Definition Config.h:312
#define __UNUSED__
Definition Config.h:341
wchar_t char_t
Definition Config.h:247
#define __DCL_THROWS2(e1, e2)
Definition Config.h:153
#define __DCL_TRACE2_N(fmt, arg1, arg2)
#define __T(str)
Definition Object.h:60
#define __DCL_TRACE2(fmt, arg1, arg2)
Definition Object.h:400
DCLCVAR const struct __endl endl
Definition APEv2.h:16
void assign(time_t _timer)
Definition DateTime.cpp:750
String toString() const
Definition DateTime.cpp:843
static DateTime getCurrentLocalTime()
Definition DateTime.cpp:954
Definition Dir.h:41
virtual String toString() const
Definition Exception.cpp:40
virtual void destroy()
Definition Exception.cpp:74
String toStringAll() const
Definition Exception.cpp:45
Definition File.h:42
@ END
Definition File.h:211
off_t seek(off_t _offset, int _whence) __DCL_THROWS1(IOException *)
Definition File.cpp:596
virtual size_t read(void *_buf, size_t _n) __DCL_THROWS1(IOException *)
Definition File.cpp:482
const String & path() const
Definition File.h:251
off_t size() const __DCL_THROWS1(IOException *)
Definition File.cpp:645
static bool time(const String &_path, time_t *_atime, time_t *_mtime, time_t *_ctime)
Definition Files.cpp:140
static uint64_t size(const String &_path) __DCL_THROWS1(IOException *)
Definition Files.cpp:160
Definition ID3v1.h:13
uint32_t size() const
Definition ID3v2.h:140
char type() const
Definition ID3v2.h:152
short flags() const
Definition ID3v2.h:144
const String & url() const
Definition ID3v2.h:156
const String id() const
Definition ID3v2.h:136
char encoding() const
Definition ID3v2.h:148
const String & description() const
Definition ID3v2.h:160
const String & text() const
Definition ID3v2.h:164
const ByteString & binary() const
Definition ID3v2.h:168
virtual String toString() const
Definition ID3v2.cpp:596
Definition ID3v2.h:16
unsigned videoCount() const
unsigned imageHeight(size_t streamNumber) const
uint32_t audioDurationAsSeconds(size_t streamNumber) const
String inform_r() const
String format() const
unsigned videoWidth(size_t streamNumber) const
unsigned imageCount() const
uint32_t videoDurationAsSeconds(size_t streamNumber) const
unsigned videoHeight(size_t streamNumber) const
unsigned audioCount() const
unsigned imageWidth(size_t streamNumber) const
SQLParam & byName(const wchar_t *_name) _CONST __DCL_THROWS1(InvalidIndexException *)
Definition SQLQuery.cpp:157
void prepare(const String &_sql) __DCL_THROWS1(SQLException *)
Definition SQLQuery.cpp:282
_CONST SQLParams & params() _CONST
Definition SQL.inl:106
_CONST SQLFields & fields() _CONST
Definition SQL.inl:101
void execute() __DCL_THROWS1(SQLException *)
Definition SQLQuery.cpp:316
void fetch() __DCL_THROWS1(SQLException *)
Definition SQLQuery.cpp:336
String __doubleRule
Definition TagReader.h:84
TagReader(const MainArguments &_args) __DCL_THROWS2(SQLDriverException *
Definition TagReader.cpp:51
SQLException *void readTag(const String &_path, FileInfo &_info)
Definition TagReader.cpp:87
void persist(const FileInfo &_info)
String __dirname
Definition TagReader.h:78
SQLConnection * __conn
Definition TagReader.h:76
int32_t __dirId
Definition TagReader.h:79
const MainArguments & args() const
Definition TagReader.h:70
String __singleRule
Definition TagReader.h:85
void read(const String &_dirname, const String &_filename)
void print(const FileInfo &_info) const
const MainArguments & __args
Definition TagReader.h:75
int64_t __fileId
Definition TagReader.h:80
void readDir(const String &_path)
const String & filename
Definition TagReader.h:42
const String & dirname
Definition TagReader.h:41