mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 14:59:24 -04:00
Compare commits
767 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e36a9cabb9 | ||
|
|
597dcde72a | ||
|
|
6a96a6426a | ||
|
|
69c65284a5 | ||
|
|
97aaa0f979 | ||
|
|
0dac721ce2 | ||
|
|
bbeeca6fdb | ||
|
|
7e90313690 | ||
|
|
1d3c95f692 | ||
|
|
8c3801f18d | ||
|
|
c9bdd416ef | ||
|
|
9f28e037fe | ||
|
|
92c070ba9e | ||
|
|
75e3ec73aa | ||
|
|
3e47a036fb | ||
|
|
9b995544e4 | ||
|
|
d8e5077961 | ||
|
|
6422054540 | ||
|
|
1b878102f0 | ||
|
|
0a85f9b227 | ||
|
|
31f3109b47 | ||
|
|
76f8ff388f | ||
|
|
7627ae48ed | ||
|
|
b2a6768704 | ||
|
|
7d270a7e20 | ||
|
|
bf53dc6131 | ||
|
|
ff8b6a91e7 | ||
|
|
1a82419872 | ||
|
|
5a3265d031 | ||
|
|
a27199b772 | ||
|
|
6c27a32de8 | ||
|
|
11abffc0f6 | ||
|
|
2aea23aed2 | ||
|
|
e8ef0e0a4b | ||
|
|
520da50bc5 | ||
|
|
c0a909b709 | ||
|
|
8d09f12847 | ||
|
|
710166e32d | ||
|
|
01054009ac | ||
|
|
7d8aa7b8bd | ||
|
|
98a57744c3 | ||
|
|
8c4b484e67 | ||
|
|
2166d703e0 | ||
|
|
887f3b28e5 | ||
|
|
6a9db50c03 | ||
|
|
07d95e0dc0 | ||
|
|
a9acca5d81 | ||
|
|
dadfe79799 | ||
|
|
552185a8b8 | ||
|
|
46eacaeba4 | ||
|
|
455e827e1e | ||
|
|
c08e0b1357 | ||
|
|
f3016c0892 | ||
|
|
f8f7cb0904 | ||
|
|
d037b8c908 | ||
|
|
c349ba3a31 | ||
|
|
bb006e41d7 | ||
|
|
ab2267f9aa | ||
|
|
73662037eb | ||
|
|
aa339eba87 | ||
|
|
45a3bdb695 | ||
|
|
5a155ef4ce | ||
|
|
184a151d41 | ||
|
|
cc2ccbc20c | ||
|
|
fa46185ca4 | ||
|
|
fffff35ca5 | ||
|
|
31894f47f6 | ||
|
|
c1e3d65b68 | ||
|
|
a8ecbbaef4 | ||
|
|
24575aab23 | ||
|
|
c04b24a2f5 | ||
|
|
013fbbf22c | ||
|
|
5350bc8501 | ||
|
|
92a1a00624 | ||
|
|
8afbf6c92a | ||
|
|
9976155aa9 | ||
|
|
c2cb9ab8b0 | ||
|
|
758b7e39ce | ||
|
|
4ba7bb5a8a | ||
|
|
0b62ba1530 | ||
|
|
fe92f3dffe | ||
|
|
b8e82a7775 | ||
|
|
3dc873b7d0 | ||
|
|
1254534ed4 | ||
|
|
7a5fb7d672 | ||
|
|
5b7ee5bc1a | ||
|
|
815d1583e9 | ||
|
|
6c183a7df8 | ||
|
|
8d5f821b16 | ||
|
|
0611744171 | ||
|
|
c25e0c6ead | ||
|
|
45762d20f1 | ||
|
|
67434aa7b4 | ||
|
|
cb23f91c98 | ||
|
|
37c4d0b11d | ||
|
|
626d13467e | ||
|
|
02eac18d00 | ||
|
|
4064b34eff | ||
|
|
22708a0af6 | ||
|
|
12da0ebd6d | ||
|
|
d13a3e3e16 | ||
|
|
e8b28a0466 | ||
|
|
081d6eaf76 | ||
|
|
ac361c7692 | ||
|
|
d7de3983d9 | ||
|
|
1aca1f64fe | ||
|
|
5a1f44d166 | ||
|
|
130654316b | ||
|
|
5cd139a578 | ||
|
|
dd2ed47703 | ||
|
|
d0238ba82f | ||
|
|
cf5d431d77 | ||
|
|
4ec264b6a1 | ||
|
|
ef92ee223f | ||
|
|
8b6bcf411a | ||
|
|
aa66b0651d | ||
|
|
7c5d1e02de | ||
|
|
6d27a1013d | ||
|
|
50711f4758 | ||
|
|
6998be94f6 | ||
|
|
862952bdcc | ||
|
|
f0b0b736e3 | ||
|
|
546870d83a | ||
|
|
581e58585a | ||
|
|
36b905670c | ||
|
|
42459fe457 | ||
|
|
fc571e5150 | ||
|
|
5480458dfc | ||
|
|
9950fca3c2 | ||
|
|
63e838f831 | ||
|
|
94f28b4fa1 | ||
|
|
680784c7b7 | ||
|
|
5d517127ae | ||
|
|
be9b5cc93a | ||
|
|
847eec23cf | ||
|
|
9dfb3fe452 | ||
|
|
a013c7c4c2 | ||
|
|
62ec63b039 | ||
|
|
52d3122e9e | ||
|
|
24f220224d | ||
|
|
a1ac23530e | ||
|
|
e99910dd74 | ||
|
|
88ad2843f4 | ||
|
|
a0b8683656 | ||
|
|
085a0ef298 | ||
|
|
363c471a2d | ||
|
|
7f8efec4d4 | ||
|
|
dcc0fe553c | ||
|
|
8bdddaabce | ||
|
|
2cb89b85c9 | ||
|
|
e6e9ba6094 | ||
|
|
7b854c5434 | ||
|
|
1529af7a12 | ||
|
|
e475e31e0d | ||
|
|
ea55c8b5c1 | ||
|
|
bc8b60ea46 | ||
|
|
83ffc87598 | ||
|
|
d97292c593 | ||
|
|
060a50ab11 | ||
|
|
6966be4292 | ||
|
|
f38749aacf | ||
|
|
b4b16fd7e0 | ||
|
|
b1c67fc5dc | ||
|
|
90f1f03163 | ||
|
|
101a330d55 | ||
|
|
59088096e6 | ||
|
|
d1ceffd1f0 | ||
|
|
eb6542bc8b | ||
|
|
bccd57a470 | ||
|
|
a6701ddcda | ||
|
|
31d1f11969 | ||
|
|
f9e558eef5 | ||
|
|
fcdf7c2ae2 | ||
|
|
8c424badad | ||
|
|
f1b683b582 | ||
|
|
5e37f0101d | ||
|
|
b541ec8b68 | ||
|
|
1fa677e2ef | ||
|
|
463e8f313a | ||
|
|
8130b30397 | ||
|
|
6b836c6ba7 | ||
|
|
f5a3f2b6c1 | ||
|
|
4b8e39e8f0 | ||
|
|
905bf89e66 | ||
|
|
67cbf2c0a8 | ||
|
|
3f84621d58 | ||
|
|
083eadec60 | ||
|
|
f180445be5 | ||
|
|
cbc279b899 | ||
|
|
e1bcb101f0 | ||
|
|
14f3b6c928 | ||
|
|
b6361ddde5 | ||
|
|
66dd0b8a6f | ||
|
|
94b7828990 | ||
|
|
fa4289e044 | ||
|
|
3612c2cc24 | ||
|
|
477aba9465 | ||
|
|
a640074a21 | ||
|
|
25ffbcb4b9 | ||
|
|
e44de9f37f | ||
|
|
2b7d6fef47 | ||
|
|
ae633105d6 | ||
|
|
6978131d22 | ||
|
|
83a0bc3710 | ||
|
|
6477132301 | ||
|
|
559c6b28c9 | ||
|
|
e9f70a59b2 | ||
|
|
f34d73d319 | ||
|
|
ef3ce1e38a | ||
|
|
8724184d56 | ||
|
|
61dabe61a7 | ||
|
|
de0fc83066 | ||
|
|
c3807e59cd | ||
|
|
b592f78238 | ||
|
|
84e3582332 | ||
|
|
ce1c03faa3 | ||
|
|
94c941928a | ||
|
|
8c6fe45453 | ||
|
|
c4fe65787c | ||
|
|
29b0568dec | ||
|
|
7d0a651f3e | ||
|
|
224f133d65 | ||
|
|
f956c89141 | ||
|
|
6dc8d701a8 | ||
|
|
779f904940 | ||
|
|
4161746d89 | ||
|
|
6d5654028a | ||
|
|
93d55dafd6 | ||
|
|
e1e1b6c60c | ||
|
|
1cc3e4cc57 | ||
|
|
f310b1810d | ||
|
|
e10684312e | ||
|
|
a3564d8c68 | ||
|
|
95ef0e7882 | ||
|
|
13a258d9ed | ||
|
|
72a807def1 | ||
|
|
539d951277 | ||
|
|
21788f4a26 | ||
|
|
74cb6f3fc7 | ||
|
|
e0f1151c5c | ||
|
|
10e1fcd686 | ||
|
|
3bce77a359 | ||
|
|
0ffd2e8ab9 | ||
|
|
7e85d9b202 | ||
|
|
c6443dabc6 | ||
|
|
c5db39fbf4 | ||
|
|
b6469bda1a | ||
|
|
978a5f0a27 | ||
|
|
8203ccf04c | ||
|
|
47860c20f4 | ||
|
|
6e6e11f21a | ||
|
|
288e97ad44 | ||
|
|
07b26ab3e4 | ||
|
|
0f00dfc778 | ||
|
|
b01fecd280 | ||
|
|
69921b8090 | ||
|
|
1caaa8a020 | ||
|
|
4f3844114a | ||
|
|
76de4234a1 | ||
|
|
4bac99e3da | ||
|
|
6b7a2c4cf7 | ||
|
|
1a942627bf | ||
|
|
3128f425b8 | ||
|
|
3f968933f4 | ||
|
|
67f44071cd | ||
|
|
11fbf394a3 | ||
|
|
a25e1e9f90 | ||
|
|
091ab9dee0 | ||
|
|
c353a71ce5 | ||
|
|
762581fe3f | ||
|
|
8f147034d6 | ||
|
|
6775cef651 | ||
|
|
86c7e905ba | ||
|
|
2184aa476f | ||
|
|
3e8eff16b8 | ||
|
|
41a44f4ab2 | ||
|
|
70e471f1d1 | ||
|
|
f836cb4dea | ||
|
|
47bd01ecda | ||
|
|
90ad17b4c5 | ||
|
|
f4c1beb161 | ||
|
|
c963d3ba04 | ||
|
|
c4a02a1799 | ||
|
|
59a1b7a491 | ||
|
|
62bb3c95c8 | ||
|
|
d3351a0012 | ||
|
|
0c14bd0ed0 | ||
|
|
7bbf5a2e79 | ||
|
|
ba5137bf2d | ||
|
|
b3cea6cca7 | ||
|
|
a533b9c665 | ||
|
|
ec8e611909 | ||
|
|
c66e6b27d9 | ||
|
|
1d379fdb2f | ||
|
|
47813c5a7f | ||
|
|
94ff9124c7 | ||
|
|
9f697fce8e | ||
|
|
320d0f5ad7 | ||
|
|
77087cf865 | ||
|
|
8b4a27beb4 | ||
|
|
998ebf4ce6 | ||
|
|
ccaf650214 | ||
|
|
9fad0b28a5 | ||
|
|
153820bf12 | ||
|
|
29be00dc59 | ||
|
|
ab30ec3a6f | ||
|
|
0a90687805 | ||
|
|
e750cb491d | ||
|
|
1f7715a5da | ||
|
|
8e7504a66f | ||
|
|
9ad5bb1d62 | ||
|
|
5ca4cd2f52 | ||
|
|
70f8fb1bae | ||
|
|
030e177640 | ||
|
|
3bb0b11bff | ||
|
|
60558d6a4a | ||
|
|
12f59d774e | ||
|
|
e2466a72f8 | ||
|
|
9666b64f28 | ||
|
|
15a1505880 | ||
|
|
fbd3b71690 | ||
|
|
6a76f491f8 | ||
|
|
6d925da75e | ||
|
|
e178875b40 | ||
|
|
bf45cfd84a | ||
|
|
b49e3e5620 | ||
|
|
fa6f33e552 | ||
|
|
021c539002 | ||
|
|
aedfeba66b | ||
|
|
29ed265281 | ||
|
|
b6adb1f108 | ||
|
|
c7231c58a3 | ||
|
|
c5cf9b93bc | ||
|
|
1bb06b1f7a | ||
|
|
35aa6c4e84 | ||
|
|
54cea3edc3 | ||
|
|
0178d47c85 | ||
|
|
71bc17b5e6 | ||
|
|
ac38f4ade1 | ||
|
|
b9f898698d | ||
|
|
bd564546f4 | ||
|
|
d90617959b | ||
|
|
bc106ad81e | ||
|
|
a23f808627 | ||
|
|
e4cd963b12 | ||
|
|
eff92fed98 | ||
|
|
80441ff754 | ||
|
|
35d5ba4eff | ||
|
|
edbafdbd88 | ||
|
|
98ac8ba569 | ||
|
|
173c58cf49 | ||
|
|
ec3d050adc | ||
|
|
19a7e45997 | ||
|
|
49563a9cd0 | ||
|
|
1abe61640a | ||
|
|
65a6572299 | ||
|
|
46e74c9391 | ||
|
|
7c17d32b3a | ||
|
|
bfe4ec5df5 | ||
|
|
81b7d0046e | ||
|
|
5990c72a01 | ||
|
|
018e969026 | ||
|
|
04ec7eae25 | ||
|
|
b6e7bb2c84 | ||
|
|
f112d538ea | ||
|
|
ac5ef0291c | ||
|
|
8fa86162c7 | ||
|
|
fa17b4da6b | ||
|
|
076e845912 | ||
|
|
f830177b3b | ||
|
|
0650dc77a1 | ||
|
|
c0f537a155 | ||
|
|
44d9f2bf25 | ||
|
|
89e6ad96a4 | ||
|
|
aa1dd0278d | ||
|
|
13dab99af0 | ||
|
|
03ec83ecca | ||
|
|
ce02910c6b | ||
|
|
f25e30d33f | ||
|
|
4dcccfbd6a | ||
|
|
7ca1b0c4d3 | ||
|
|
11f4e4e1ca | ||
|
|
9759bd2dd7 | ||
|
|
b28784538a | ||
|
|
fdea096c8d | ||
|
|
a9d030544a | ||
|
|
8344c4d7f8 | ||
|
|
8b1e872f81 | ||
|
|
6a778751ee | ||
|
|
4328a830f9 | ||
|
|
ae99cbe64e | ||
|
|
692ce897cb | ||
|
|
0ed1c29acc | ||
|
|
3c59b7858a | ||
|
|
2c9f63d9b0 | ||
|
|
0b43b7136d | ||
|
|
1e752a1e8f | ||
|
|
29a31859ff | ||
|
|
0b01461d50 | ||
|
|
a77abedf63 | ||
|
|
21412e2ba2 | ||
|
|
dbf9644c8d | ||
|
|
592e14f950 | ||
|
|
1fb3727c4c | ||
|
|
0501fbdd72 | ||
|
|
5235abc498 | ||
|
|
d944009904 | ||
|
|
f729f863cd | ||
|
|
c715ec09e4 | ||
|
|
4f621140ce | ||
|
|
bd251aed37 | ||
|
|
34ab65aa57 | ||
|
|
7f0c547ba6 | ||
|
|
0ff38ed52b | ||
|
|
2b1116cec1 | ||
|
|
88947e7a48 | ||
|
|
5ad69a81dc | ||
|
|
c9963af848 | ||
|
|
f38e32163e | ||
|
|
c69364d831 | ||
|
|
3142330bee | ||
|
|
9b849c5da8 | ||
|
|
a197f45ca8 | ||
|
|
6dcecf0e71 | ||
|
|
47800d1000 | ||
|
|
cc8c3cd1fd | ||
|
|
58994e330e | ||
|
|
572afd437d | ||
|
|
1eef4177e7 | ||
|
|
64fac517ed | ||
|
|
f79c766ba4 | ||
|
|
6f944b0291 | ||
|
|
04bee3faec | ||
|
|
e8c1a11730 | ||
|
|
d8e8ec69fe | ||
|
|
6d0712c8df | ||
|
|
00b2f6a386 | ||
|
|
a3dccdc7a3 | ||
|
|
b698c73690 | ||
|
|
801c9db810 | ||
|
|
409b135dd5 | ||
|
|
4dd14d4d73 | ||
|
|
44e6419644 | ||
|
|
467658e463 | ||
|
|
3fcb21642c | ||
|
|
472ce9f42c | ||
|
|
be33340383 | ||
|
|
91ed3548f1 | ||
|
|
b56f4c4372 | ||
|
|
2155b4fd50 | ||
|
|
e605e96835 | ||
|
|
dfee7020da | ||
|
|
e90b5e5f2f | ||
|
|
642baca4ed | ||
|
|
da01fa5745 | ||
|
|
78c70cf5bb | ||
|
|
8f6af3f020 | ||
|
|
22f250eaa4 | ||
|
|
eb73612a2b | ||
|
|
9c8e36d3be | ||
|
|
ed25204d75 | ||
|
|
03fd0a3ead | ||
|
|
aede4ac851 | ||
|
|
3823afcc87 | ||
|
|
4dba88fa31 | ||
|
|
f3d8100c7b | ||
|
|
b2c79bc084 | ||
|
|
f82be353b4 | ||
|
|
9ec6d28239 | ||
|
|
3a1c784eec | ||
|
|
4a014c8113 | ||
|
|
5d77553759 | ||
|
|
da14f67e2c | ||
|
|
9226fa76b3 | ||
|
|
ff36648e92 | ||
|
|
447a4739c5 | ||
|
|
f15fe869a5 | ||
|
|
125d887b85 | ||
|
|
9a8e41b9d6 | ||
|
|
68ef160dbc | ||
|
|
6d71bdf8b7 | ||
|
|
b37eaace15 | ||
|
|
c1c70edb76 | ||
|
|
75159d5d8a | ||
|
|
46862bf537 | ||
|
|
2b260fd2e8 | ||
|
|
b1a35a8b31 | ||
|
|
88ef556d4e | ||
|
|
00c6e7ea79 | ||
|
|
0c0f123a08 | ||
|
|
48311cca14 | ||
|
|
db90f4b358 | ||
|
|
b5d65edab7 | ||
|
|
a06d55ae6f | ||
|
|
c5f2e9342d | ||
|
|
2d778d5a0a | ||
|
|
0aa75b2e8d | ||
|
|
618a939c56 | ||
|
|
2337fbcfc6 | ||
|
|
9da20a8a52 | ||
|
|
4129b919c1 | ||
|
|
6d6f544010 | ||
|
|
93da3ba6d9 | ||
|
|
e064b88049 | ||
|
|
3094540a4b | ||
|
|
8e21dcc3d4 | ||
|
|
0ea22348cb | ||
|
|
48ca54de1f | ||
|
|
54de66f275 | ||
|
|
d4b0ba2a7a | ||
|
|
abc5743222 | ||
|
|
525396d9c2 | ||
|
|
bb9679b51a | ||
|
|
e44cba56b5 | ||
|
|
e4cf012522 | ||
|
|
a37d423ea8 | ||
|
|
88a4cf34b8 | ||
|
|
81261dd128 | ||
|
|
3bea9f6bee | ||
|
|
9cc2d7cfee | ||
|
|
fe1bf015bb | ||
|
|
287078566f | ||
|
|
60966518e2 | ||
|
|
2268efb49e | ||
|
|
29f535dc8d | ||
|
|
592522d34c | ||
|
|
6b9e4e4403 | ||
|
|
089e44f3db | ||
|
|
bd3c4dc1b2 | ||
|
|
bba562b557 | ||
|
|
451d23ca37 | ||
|
|
d248f77ab9 | ||
|
|
727a11573a | ||
|
|
34da0c0dab | ||
|
|
7316cd331d | ||
|
|
3a977c55c4 | ||
|
|
a094ce7dd2 | ||
|
|
941efcba18 | ||
|
|
ffb543acbb | ||
|
|
fc24b3d22b | ||
|
|
a9e064c58e | ||
|
|
1a5d89d29e | ||
|
|
650b581f9e | ||
|
|
0739dd232a | ||
|
|
41a250a15d | ||
|
|
8da0013482 | ||
|
|
0288059495 | ||
|
|
6e40361c0e | ||
|
|
c2570fe115 | ||
|
|
bd91da55ba | ||
|
|
3a3a6a6fda | ||
|
|
64c43b0d3f | ||
|
|
db2dfa9515 | ||
|
|
0069debf0c | ||
|
|
69563d83a0 | ||
|
|
60775306ea | ||
|
|
4f77420248 | ||
|
|
b021ed44e9 | ||
|
|
1f99c93a61 | ||
|
|
a924ca0db7 | ||
|
|
b5c0ab58ba | ||
|
|
e43c1a3c09 | ||
|
|
1e9529380d | ||
|
|
ff8443f33a | ||
|
|
62ab41fa07 | ||
|
|
00e3504264 | ||
|
|
852da79899 | ||
|
|
ab047f6054 | ||
|
|
5bad35c4cb | ||
|
|
8d708c03e1 | ||
|
|
f9a0b50830 | ||
|
|
8491266b12 | ||
|
|
066b5aa57e | ||
|
|
5f0a7da481 | ||
|
|
d33d684fab | ||
|
|
4c4be0a263 | ||
|
|
f476cf2b45 | ||
|
|
68c0b0591b | ||
|
|
8fccaf30d2 | ||
|
|
b69973bcf2 | ||
|
|
30eac7569f | ||
|
|
f6081ed32e | ||
|
|
0839a23902 | ||
|
|
f35a279dd1 | ||
|
|
e463d14f2e | ||
|
|
829ae2112a | ||
|
|
65664e6855 | ||
|
|
2193d6dd84 | ||
|
|
ef786188ad | ||
|
|
2b5ee8deb9 | ||
|
|
ed09c9cf87 | ||
|
|
9d91610fc0 | ||
|
|
aed689c145 | ||
|
|
c6a63a3a2f | ||
|
|
2b0a540228 | ||
|
|
2685ec1842 | ||
|
|
fdea6dcd40 | ||
|
|
ed253d3691 | ||
|
|
57729b834a | ||
|
|
caa53e8de5 | ||
|
|
31982660c8 | ||
|
|
bd7b8cc36a | ||
|
|
bc9bbfe3fa | ||
|
|
7adea3df22 | ||
|
|
5ebb2ece80 | ||
|
|
0d2e01df61 | ||
|
|
695fb5ec16 | ||
|
|
3170d47ec3 | ||
|
|
79b7c14129 | ||
|
|
977fb2aeb0 | ||
|
|
3b8c7d4e3a | ||
|
|
be33389884 | ||
|
|
59ae61de6e | ||
|
|
16ac2cd240 | ||
|
|
61543432c0 | ||
|
|
4a9614bfc3 | ||
|
|
1a917a38cd | ||
|
|
735e17f504 | ||
|
|
0731bc7b2e | ||
|
|
fb1c744daf | ||
|
|
1bc5acd7a7 | ||
|
|
f30f5ccce5 | ||
|
|
421eb5e97e | ||
|
|
5e60ec48e9 | ||
|
|
68eca440c9 | ||
|
|
7b03403791 | ||
|
|
d80c4c96c3 | ||
|
|
73b9b9b58d | ||
|
|
072851869a | ||
|
|
269e78f1a0 | ||
|
|
71acf3b6f7 | ||
|
|
e41dc68a6b | ||
|
|
cfad951442 | ||
|
|
23ffb24472 | ||
|
|
a8bfcd81be | ||
|
|
bd7419f0bd | ||
|
|
71c1ce375f | ||
|
|
cff8d22818 | ||
|
|
0051351ebb | ||
|
|
82315276db | ||
|
|
8ea07be47e | ||
|
|
eae4605e6e | ||
|
|
b6289c64dd | ||
|
|
e6d7dd08f2 | ||
|
|
ee283789b7 | ||
|
|
205569c8d2 | ||
|
|
4b67beffc8 | ||
|
|
b7a514886d | ||
|
|
0e6d8617ae | ||
|
|
dfbaee4103 | ||
|
|
f29c5f45f8 | ||
|
|
b1a8205561 | ||
|
|
2971891c69 | ||
|
|
d5e3d6ea7c | ||
|
|
20ce66f98b | ||
|
|
aff3351a25 | ||
|
|
387890d956 | ||
|
|
8765d40c2c | ||
|
|
9bb0eb7ee9 | ||
|
|
d34c922c75 | ||
|
|
3c727e091e | ||
|
|
a055933e10 | ||
|
|
20c0aac309 | ||
|
|
929829b2b5 | ||
|
|
1944588595 | ||
|
|
31a74927e4 | ||
|
|
972aa1feef | ||
|
|
c23ee5331f | ||
|
|
0f605be198 | ||
|
|
590cb29f99 | ||
|
|
286fd2abec | ||
|
|
f76d76728d | ||
|
|
80f5436370 | ||
|
|
043d2bbef5 | ||
|
|
cf59d51d93 | ||
|
|
9569b99e4c | ||
|
|
590405d878 | ||
|
|
c03b91aed3 | ||
|
|
ee2908a6cf | ||
|
|
e29f1d39e7 | ||
|
|
b509106031 | ||
|
|
47cd6fad4f | ||
|
|
fb24dfe7cf | ||
|
|
f42263e04a | ||
|
|
43872f362d | ||
|
|
73d3436570 | ||
|
|
1e50b8dc25 | ||
|
|
4a585c5258 | ||
|
|
5f738a9819 | ||
|
|
d7995b807f | ||
|
|
3e60e339a4 | ||
|
|
5feabe0988 | ||
|
|
29a5129096 | ||
|
|
fadb57e4cd | ||
|
|
653b631aea | ||
|
|
cb3abf15ad | ||
|
|
29377fc8f1 | ||
|
|
663f10d51d | ||
|
|
a192db07c0 | ||
|
|
459adc69ad | ||
|
|
500b3e630b | ||
|
|
5c5c89e8d9 | ||
|
|
140fb2b3f6 | ||
|
|
98ed58f910 | ||
|
|
7d99b8276a | ||
|
|
17841e89ae | ||
|
|
c5f9258462 | ||
|
|
3a1040d55b | ||
|
|
0864634ea6 | ||
|
|
f27e0aaecf | ||
|
|
ecc768d459 | ||
|
|
fabc411cb9 | ||
|
|
2d528d844a | ||
|
|
c65753c766 | ||
|
|
7e10b13962 | ||
|
|
05e26119f7 | ||
|
|
95a59c416b | ||
|
|
3f557be608 | ||
|
|
9ca7877587 | ||
|
|
a93ee10134 | ||
|
|
1723e9b18a | ||
|
|
16b8688c1a | ||
|
|
3b60af2c0b | ||
|
|
eba99c3a70 | ||
|
|
6bcd422afa | ||
|
|
c357d293a1 | ||
|
|
cfb43223dc | ||
|
|
72cd379f30 | ||
|
|
a3d724af27 | ||
|
|
05e0081414 | ||
|
|
b658f95e06 | ||
|
|
2758aed34d | ||
|
|
12b25868b7 | ||
|
|
1deff52d95 | ||
|
|
69eaa75f64 | ||
|
|
3e0c73bdbc | ||
|
|
9e94f8bb6c | ||
|
|
aa49723b48 | ||
|
|
167513ae57 | ||
|
|
d0f3e9b186 | ||
|
|
4fc020e0d3 | ||
|
|
db147e672e | ||
|
|
c45a0694f9 | ||
|
|
584bbc7c78 | ||
|
|
10b804e0ae | ||
|
|
4f8ab4b63f | ||
|
|
5593c251c8 | ||
|
|
7d7a7be115 | ||
|
|
f733077917 | ||
|
|
670493f97f | ||
|
|
d4adc22922 | ||
|
|
34f8cd8a90 | ||
|
|
4a7d31c87b | ||
|
|
3bf30af66c | ||
|
|
ee9720a997 | ||
|
|
69e58b5f3f | ||
|
|
de17843d8c | ||
|
|
944143a91b | ||
|
|
1ce5385e30 | ||
|
|
5ed2d88f78 | ||
|
|
cbd6f73431 | ||
|
|
65006952f3 | ||
|
|
4815dbba68 | ||
|
|
bcad792e75 | ||
|
|
fcfd9f59fe | ||
|
|
4be1279430 | ||
|
|
08ae0e8c63 |
18
.astylerc
Normal file
18
.astylerc
Normal file
@@ -0,0 +1,18 @@
|
||||
--suffix=none
|
||||
--style=kr
|
||||
--indent=spaces=2
|
||||
--indent-col1-comments
|
||||
--min-conditional-indent=0
|
||||
--attach-extern-c
|
||||
--attach-namespaces
|
||||
--indent-namespaces
|
||||
--pad-oper
|
||||
--unpad-paren
|
||||
--align-pointer=name
|
||||
--align-reference=name
|
||||
--max-instatement-indent=40
|
||||
--break-closing-brackets
|
||||
--remove-brackets
|
||||
--convert-tabs
|
||||
--max-code-length=100
|
||||
--break-after-logical
|
||||
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Non-specified newlines with a newline ending every file
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
|
||||
# 2 space indentation
|
||||
[*.{h,cpp,tcc,cmake,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Trim traling whitespaces
|
||||
[*.{h,cpp,tcc,cmake,yml}]
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# UTF-8 without BOM
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -42,3 +42,5 @@ CMakeFiles/
|
||||
/taglib/tag.dir/Release
|
||||
/ALL_BUILD.dir
|
||||
/ZERO_CHECK.dir
|
||||
taglib.xcodeproj
|
||||
CMakeScripts
|
||||
|
||||
25
.travis.yml
25
.travis.yml
@@ -1,7 +1,28 @@
|
||||
language: cpp
|
||||
|
||||
sudo: false
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
install: sudo apt-get install libcppunit-dev zlib1g-dev
|
||||
script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcppunit-dev
|
||||
- zlib1g-dev
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
|
||||
install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install cppunit; fi
|
||||
|
||||
script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON -DBUILD_BINDINGS=ON . && make && make check
|
||||
|
||||
|
||||
4
AUTHORS
4
AUTHORS
@@ -2,6 +2,8 @@ Scott Wheeler <wheeler@kde.org>
|
||||
Author, maintainer
|
||||
Lukas Lalinsky <lalinsky@gmail.com>
|
||||
Implementation of multiple new file formats, many bug fixes, maintainer
|
||||
Tsuda Kageyu <tsuda.kageyu@gmail.com>
|
||||
A lot of bug fixes and performance improvements, maintainer.
|
||||
Ismael Orenstein <orenstein@kde.org>
|
||||
Xing header implementation
|
||||
Allan Sandfeld Jensen <kde@carewolf.org>
|
||||
@@ -10,8 +12,6 @@ Teemu Tervo <teemu.tervo@gmx.net>
|
||||
Numerous bug reports and fixes
|
||||
Mathias Panzenböck <grosser.meister.morti@gmx.net>
|
||||
Mod, S3M, IT and XM metadata implementations
|
||||
Tsuda Kageyu <tsuda.kageyu@gmail.com>
|
||||
A lot of fixes and improvements, i.e. memory copy reduction etc.
|
||||
|
||||
Please send all patches and questions to taglib-devel@kde.org rather than to
|
||||
individual developers!
|
||||
|
||||
144
CMakeLists.txt
144
CMakeLists.txt
@@ -1,64 +1,98 @@
|
||||
cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR)
|
||||
|
||||
project(taglib)
|
||||
|
||||
cmake_minimum_required(VERSION 2.6.0 FATAL_ERROR)
|
||||
if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12)
|
||||
cmake_policy(SET CMP0022 OLD)
|
||||
endif()
|
||||
|
||||
option(ENABLE_STATIC "Make static version of libtag" OFF)
|
||||
if(ENABLE_STATIC)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
if(DEFINED ENABLE_STATIC)
|
||||
message(FATAL_ERROR "This option is no longer available, use BUILD_SHARED_LIBS instead")
|
||||
endif()
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
add_definitions(-DTAGLIB_STATIC)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
else()
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
endif()
|
||||
OPTION(ENABLE_STATIC_RUNTIME "Visual Studio, link with runtime statically" OFF)
|
||||
option(ENABLE_STATIC_RUNTIME "Visual Studio, link with runtime statically" OFF)
|
||||
|
||||
option(VISIBILITY_HIDDEN "Build with -fvisibility=hidden" OFF)
|
||||
option(ENABLE_CCACHE "Use ccache when building libtag" OFF)
|
||||
if(ENABLE_CCACHE)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(VISIBILITY_HIDDEN "Build with -fvisibility=hidden" OFF)
|
||||
if(VISIBILITY_HIDDEN)
|
||||
add_definitions (-fvisibility=hidden)
|
||||
add_definitions(-fvisibility=hidden)
|
||||
endif()
|
||||
|
||||
option(BUILD_TESTS "Build the test suite" OFF)
|
||||
option(BUILD_EXAMPLES "Build the examples" OFF)
|
||||
option(BUILD_TESTS "Build the test suite" OFF)
|
||||
option(BUILD_EXAMPLES "Build the examples" OFF)
|
||||
option(BUILD_BINDINGS "Build the bindings" ON)
|
||||
|
||||
option(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF)
|
||||
option(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF)
|
||||
|
||||
add_definitions(-DHAVE_CONFIG_H)
|
||||
set(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/")
|
||||
|
||||
## the following are directories where stuff will be installed to
|
||||
set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)")
|
||||
set(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE PATH "Base directory for executables and libraries" FORCE)
|
||||
set(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" CACHE PATH "The subdirectory to the binaries prefix (default prefix/bin)" FORCE)
|
||||
set(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is /lib${LIB_SUFFIX})" FORCE)
|
||||
set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The subdirectory to the header prefix" FORCE)
|
||||
set(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE PATH "Base directory for executables and libraries")
|
||||
set(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" CACHE PATH "The subdirectory to the binaries prefix (default prefix/bin)")
|
||||
set(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is /lib${LIB_SUFFIX})")
|
||||
set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The subdirectory to the header prefix")
|
||||
|
||||
if(APPLE)
|
||||
option(BUILD_FRAMEWORK "Build an OS X framework" OFF)
|
||||
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
|
||||
option(BUILD_FRAMEWORK "Build an OS X framework" OFF)
|
||||
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
endif()
|
||||
if (MSVC AND ENABLE_STATIC_RUNTIME)
|
||||
|
||||
if(MSVC AND ENABLE_STATIC_RUNTIME)
|
||||
foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
|
||||
endforeach(flag_var)
|
||||
endif()
|
||||
|
||||
set(TAGLIB_LIB_MAJOR_VERSION "1")
|
||||
set(TAGLIB_LIB_MINOR_VERSION "9")
|
||||
set(TAGLIB_LIB_PATCH_VERSION "1")
|
||||
# Read version information from file taglib/toolkit/taglib.h into variables
|
||||
# TAGLIB_LIB_MAJOR_VERSION, TAGLIB_LIB_MINOR_VERSION, TAGLIB_LIB_PATCH_VERSION.
|
||||
foreach(version_part MAJOR MINOR PATCH)
|
||||
set(version_var_name "TAGLIB_${version_part}_VERSION")
|
||||
file(STRINGS taglib/toolkit/taglib.h version_line
|
||||
REGEX "^#define +${version_var_name}")
|
||||
if(NOT version_line)
|
||||
message(FATAL_ERROR "${version_var_name} not found in taglib.h")
|
||||
endif()
|
||||
string(REGEX MATCH "${version_var_name} +([^ ]+)" result ${version_line})
|
||||
set(TAGLIB_LIB_${version_part}_VERSION ${CMAKE_MATCH_1})
|
||||
endforeach(version_part)
|
||||
|
||||
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
|
||||
# Only used to force cmake rerun when taglib.h changes.
|
||||
configure_file(taglib/toolkit/taglib.h ${CMAKE_CURRENT_BINARY_DIR}/taglib.h.stamp)
|
||||
|
||||
if("${TAGLIB_LIB_PATCH_VERSION}" EQUAL "0")
|
||||
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}")
|
||||
else()
|
||||
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
|
||||
endif()
|
||||
|
||||
# 1. If the library source code has changed at all since the last update, then increment revision.
|
||||
# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0.
|
||||
# 3. If any interfaces have been added since the last public release, then increment age.
|
||||
# 4. If any interfaces have been removed since the last public release, then set age to 0.
|
||||
set(TAGLIB_SOVERSION_CURRENT 15)
|
||||
# 4. If any interfaces have been removed since the last public release, then set age to 0.
|
||||
set(TAGLIB_SOVERSION_CURRENT 18)
|
||||
set(TAGLIB_SOVERSION_REVISION 0)
|
||||
set(TAGLIB_SOVERSION_AGE 14)
|
||||
set(TAGLIB_SOVERSION_AGE 17)
|
||||
|
||||
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
|
||||
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
|
||||
@@ -67,22 +101,27 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
|
||||
include(ConfigureChecks.cmake)
|
||||
|
||||
if(NOT WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config )
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR})
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config")
|
||||
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd )
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR})
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd")
|
||||
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}")
|
||||
endif()
|
||||
|
||||
if(NOT WIN32 AND NOT BUILD_FRAMEWORK)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc )
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
|
||||
if(NOT BUILD_FRAMEWORK)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" @ONLY)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig")
|
||||
endif()
|
||||
|
||||
if(NOT HAVE_ZLIB AND ZLIB_SOURCE)
|
||||
set(HAVE_ZLIB 1)
|
||||
set(HAVE_ZLIB_SOURCE 1)
|
||||
endif()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
configure_file(config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||
|
||||
if(WITH_ASF)
|
||||
set(TAGLIB_WITH_ASF TRUE)
|
||||
@@ -91,30 +130,35 @@ if(WITH_MP4)
|
||||
set(TAGLIB_WITH_MP4 TRUE)
|
||||
endif()
|
||||
|
||||
option(TRACE_IN_RELEASE "Output debug messages even in release mode" OFF)
|
||||
option(TRACE_IN_RELEASE "Output debug messages even in release mode" OFF)
|
||||
if(TRACE_IN_RELEASE)
|
||||
set(TRACE_IN_RELEASE TRUE)
|
||||
endif()
|
||||
|
||||
configure_file(taglib/taglib_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h)
|
||||
configure_file(taglib/taglib_config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h")
|
||||
|
||||
add_subdirectory(taglib)
|
||||
add_subdirectory(bindings)
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif(BUILD_TESTS)
|
||||
add_subdirectory(examples)
|
||||
add_subdirectory(taglib)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
if(BUILD_BINDINGS)
|
||||
add_subdirectory(bindings)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTS AND NOT BUILD_SHARED_LIBS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
|
||||
file(COPY doc/taglib.png DESTINATION doc)
|
||||
add_custom_target(docs doxygen)
|
||||
|
||||
# uninstall target
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
|
||||
IMMEDIATE @ONLY)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
|
||||
|
||||
add_custom_target(uninstall
|
||||
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
if(NOT TARGET uninstall)
|
||||
add_custom_target(uninstall COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
endif()
|
||||
|
||||
@@ -1,107 +1,106 @@
|
||||
include(CheckIncludeFile)
|
||||
include(CheckIncludeFiles)
|
||||
include(CheckSymbolExists)
|
||||
include(CheckFunctionExists)
|
||||
include(CheckLibraryExists)
|
||||
include(CheckTypeSize)
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(TestBigEndian)
|
||||
|
||||
# Check if the size of integral types are suitable.
|
||||
# Check if the size of numeric types are suitable.
|
||||
|
||||
check_type_size("short" SIZEOF_SHORT)
|
||||
if(NOT ${SIZEOF_SHORT} EQUAL 2)
|
||||
MESSAGE(FATAL_ERROR "TagLib requires that short is 16-bit wide.")
|
||||
message(FATAL_ERROR "TagLib requires that short is 16-bit wide.")
|
||||
endif()
|
||||
|
||||
check_type_size("int" SIZEOF_INT)
|
||||
if(NOT ${SIZEOF_INT} EQUAL 4)
|
||||
MESSAGE(FATAL_ERROR "TagLib requires that int is 32-bit wide.")
|
||||
message(FATAL_ERROR "TagLib requires that int is 32-bit wide.")
|
||||
endif()
|
||||
|
||||
check_type_size("long long" SIZEOF_LONGLONG)
|
||||
if(NOT ${SIZEOF_LONGLONG} EQUAL 8)
|
||||
MESSAGE(FATAL_ERROR "TagLib requires that long long is 64-bit wide.")
|
||||
message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.")
|
||||
endif()
|
||||
|
||||
check_type_size("wchar_t" SIZEOF_WCHAR_T)
|
||||
if(${SIZEOF_WCHAR_T} LESS 2)
|
||||
MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
|
||||
message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
|
||||
endif()
|
||||
|
||||
# Determine the CPU byte order.
|
||||
check_type_size("float" SIZEOF_FLOAT)
|
||||
if(NOT ${SIZEOF_FLOAT} EQUAL 4)
|
||||
message(FATAL_ERROR "TagLib requires that float is 32-bit wide.")
|
||||
endif()
|
||||
|
||||
test_big_endian(IS_BIG_ENDIAN)
|
||||
check_type_size("double" SIZEOF_DOUBLE)
|
||||
if(NOT ${SIZEOF_DOUBLE} EQUAL 8)
|
||||
message(FATAL_ERROR "TagLib requires that double is 64-bit wide.")
|
||||
endif()
|
||||
|
||||
if(NOT IS_BIG_ENDIAN)
|
||||
set(SYSTEM_BYTEORDER 1)
|
||||
else()
|
||||
set(SYSTEM_BYTEORDER 2)
|
||||
# Enable check_cxx_source_compiles() to work with Boost "header-only" libraries.
|
||||
|
||||
find_package(Boost)
|
||||
if(Boost_FOUND)
|
||||
set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${Boost_INCLUDE_DIRS}")
|
||||
endif()
|
||||
|
||||
# Determine which kind of atomic operations your compiler supports.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <atomic>
|
||||
int main() {
|
||||
int main() {
|
||||
std::atomic<unsigned int> x;
|
||||
x.fetch_add(1);
|
||||
x.fetch_sub(1);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_STD_ATOMIC)
|
||||
|
||||
if(NOT HAVE_STD_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <boost/atomic.hpp>
|
||||
int main() {
|
||||
boost::atomic<unsigned int> x(1);
|
||||
x.fetch_add(1);
|
||||
x.fetch_sub(1);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_BOOST_ATOMIC)
|
||||
find_package(Boost COMPONENTS atomic)
|
||||
if(Boost_ATOMIC_FOUND)
|
||||
set(HAVE_BOOST_ATOMIC 1)
|
||||
else()
|
||||
set(HAVE_BOOST_ATOMIC 0)
|
||||
endif()
|
||||
|
||||
if(NOT HAVE_BOOST_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
int main() {
|
||||
int main() {
|
||||
volatile int x;
|
||||
__sync_add_and_fetch(&x, 1);
|
||||
int y = __sync_sub_and_fetch(&x, 1);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GCC_ATOMIC)
|
||||
|
||||
if(NOT HAVE_GCC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <libkern/OSAtomic.h>
|
||||
int main() {
|
||||
int main() {
|
||||
volatile int32_t x;
|
||||
OSAtomicIncrement32Barrier(&x);
|
||||
int32_t y = OSAtomicDecrement32Barrier(&x);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_MAC_ATOMIC)
|
||||
|
||||
if(NOT HAVE_MAC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <windows.h>
|
||||
int main() {
|
||||
int main() {
|
||||
volatile LONG x;
|
||||
InterlockedIncrement(&x);
|
||||
LONG y = InterlockedDecrement(&x);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_WIN_ATOMIC)
|
||||
|
||||
if(NOT HAVE_WIN_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <ia64intrin.h>
|
||||
int main() {
|
||||
int main() {
|
||||
volatile int x;
|
||||
__sync_add_and_fetch(&x, 1);
|
||||
int y = __sync_sub_and_fetch(&x, 1);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_IA64_ATOMIC)
|
||||
endif()
|
||||
@@ -112,115 +111,138 @@ endif()
|
||||
|
||||
# Determine which kind of byte swap functions your compiler supports.
|
||||
|
||||
# GCC's __builtin_bswap* should be checked individually
|
||||
# because some of them can be missing depends on the GCC version.
|
||||
check_cxx_source_compiles("
|
||||
#include <boost/endian/conversion.hpp>
|
||||
int main() {
|
||||
__builtin_bswap16(0);
|
||||
return 0;
|
||||
boost::endian::endian_reverse(static_cast<uint16_t>(1));
|
||||
boost::endian::endian_reverse(static_cast<uint32_t>(1));
|
||||
boost::endian::endian_reverse(static_cast<uint64_t>(1));
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GCC_BYTESWAP_16)
|
||||
" HAVE_BOOST_BYTESWAP)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
int main() {
|
||||
__builtin_bswap32(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GCC_BYTESWAP_32)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
int main() {
|
||||
__builtin_bswap64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GCC_BYTESWAP_64)
|
||||
|
||||
if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP_64)
|
||||
if(NOT HAVE_BOOST_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <byteswap.h>
|
||||
int main() {
|
||||
__bswap_16(0);
|
||||
__bswap_32(0);
|
||||
__bswap_64(0);
|
||||
return 0;
|
||||
__builtin_bswap16(0);
|
||||
__builtin_bswap32(0);
|
||||
__builtin_bswap64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GLIBC_BYTESWAP)
|
||||
" HAVE_GCC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_GLIBC_BYTESWAP)
|
||||
if(NOT HAVE_GCC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <stdlib.h>
|
||||
#include <byteswap.h>
|
||||
int main() {
|
||||
_byteswap_ushort(0);
|
||||
_byteswap_ulong(0);
|
||||
_byteswap_uint64(0);
|
||||
return 0;
|
||||
__bswap_16(0);
|
||||
__bswap_32(0);
|
||||
__bswap_64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_MSC_BYTESWAP)
|
||||
" HAVE_GLIBC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MSC_BYTESWAP)
|
||||
if(NOT HAVE_GLIBC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <stdlib.h>
|
||||
int main() {
|
||||
OSSwapInt16(0);
|
||||
OSSwapInt32(0);
|
||||
OSSwapInt64(0);
|
||||
return 0;
|
||||
_byteswap_ushort(0);
|
||||
_byteswap_ulong(0);
|
||||
_byteswap_uint64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_MAC_BYTESWAP)
|
||||
" HAVE_MSC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MAC_BYTESWAP)
|
||||
if(NOT HAVE_MSC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <sys/endian.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
int main() {
|
||||
swap16(0);
|
||||
swap32(0);
|
||||
swap64(0);
|
||||
return 0;
|
||||
OSSwapInt16(0);
|
||||
OSSwapInt32(0);
|
||||
OSSwapInt64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_OPENBSD_BYTESWAP)
|
||||
" HAVE_MAC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MAC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <sys/endian.h>
|
||||
int main() {
|
||||
swap16(0);
|
||||
swap32(0);
|
||||
swap64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_OPENBSD_BYTESWAP)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Determine whether your compiler supports some safer version of sprintf.
|
||||
# Determine whether your compiler supports some safer version of vsprintf.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <cstdio>
|
||||
int main() { char buf[20]; snprintf(buf, 20, \"%d\", 1); return 0; }
|
||||
" HAVE_SNPRINTF)
|
||||
#include <cstdarg>
|
||||
int main() {
|
||||
char buf[20];
|
||||
va_list args;
|
||||
vsnprintf(buf, 20, \"%d\", args);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_VSNPRINTF)
|
||||
|
||||
if(NOT HAVE_SNPRINTF)
|
||||
if(NOT HAVE_VSNPRINTF)
|
||||
check_cxx_source_compiles("
|
||||
#include <cstdio>
|
||||
int main() { char buf[20]; sprintf_s(buf, \"%d\", 1); return 0; }
|
||||
" HAVE_SPRINTF_S)
|
||||
#include <cstdarg>
|
||||
int main() {
|
||||
char buf[20];
|
||||
va_list args;
|
||||
vsprintf_s(buf, \"%d\", args);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_VSPRINTF_S)
|
||||
endif()
|
||||
|
||||
# Determine whether your compiler supports codecvt.
|
||||
# Determine whether your compiler supports ISO _strdup.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <codecvt>
|
||||
int main() {
|
||||
std::codecvt_utf8_utf16<wchar_t> x;
|
||||
return 0;
|
||||
#include <cstring>
|
||||
int main() {
|
||||
_strdup(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_STD_CODECVT)
|
||||
" HAVE_ISO_STRDUP)
|
||||
|
||||
# Check for libz using the cmake supplied FindZLIB.cmake
|
||||
# Determine whether zlib is installed.
|
||||
|
||||
find_package(ZLIB)
|
||||
if(ZLIB_FOUND)
|
||||
set(HAVE_ZLIB 1)
|
||||
else()
|
||||
set(HAVE_ZLIB 0)
|
||||
if(NOT ZLIB_SOURCE)
|
||||
find_package(ZLIB)
|
||||
if(ZLIB_FOUND)
|
||||
set(HAVE_ZLIB 1)
|
||||
else()
|
||||
set(HAVE_ZLIB 0)
|
||||
endif()
|
||||
|
||||
if(NOT HAVE_ZLIB)
|
||||
find_package(Boost COMPONENTS iostreams zlib)
|
||||
if(Boost_IOSTREAMS_FOUND AND Boost_ZLIB_FOUND)
|
||||
set(HAVE_BOOST_ZLIB 1)
|
||||
else()
|
||||
set(HAVE_BOOST_ZLIB 0)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Determine whether CppUnit is installed.
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||
|
||||
find_package(CppUnit)
|
||||
if(NOT CppUnit_FOUND AND BUILD_TESTS)
|
||||
message(STATUS "CppUnit not found, disabling tests.")
|
||||
set(BUILD_TESTS OFF)
|
||||
if(BUILD_TESTS AND NOT BUILD_SHARED_LIBS)
|
||||
find_package(CppUnit)
|
||||
if(NOT CppUnit_FOUND)
|
||||
message(STATUS "CppUnit not found, disabling tests.")
|
||||
set(BUILD_TESTS OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
12
INSTALL
12
INSTALL
@@ -5,7 +5,7 @@ TagLib uses the CMake build system. As a user, you will most likely want to
|
||||
build TagLib in release mode and install it into a system-wide location.
|
||||
This can be done using the following commands:
|
||||
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_RELEASE_TYPE=Release .
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release .
|
||||
make
|
||||
sudo make install
|
||||
|
||||
@@ -37,7 +37,7 @@ For a 10.6 Snow Leopard static library with both 32-bit and 64-bit code, use:
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.6 \
|
||||
-DCMAKE_OSX_ARCHITECTURES="i386;x86_64" \
|
||||
-DENABLE_STATIC=ON \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_INSTALL_PREFIX="<folder you want to build to>"
|
||||
|
||||
After 'make', and 'make install', add libtag.a to your XCode project, and add
|
||||
@@ -72,6 +72,8 @@ Useful configuration options used with CMake (GUI and/or Command line):
|
||||
Assumes parent of: \include and \lib.
|
||||
ZLIB_INCLUDE_DIR= Where to find ZLib's Include directory.
|
||||
ZLIB_LIBRARY= Where to find ZLib's Library.
|
||||
ZLIB_SOURCE= Where to find ZLib's Source Code.
|
||||
Alternative to ZLIB_INCLUDE_DIR and ZLIB_LIBRARY.
|
||||
CMAKE_INSTALL_PREFIX= Where to install Taglib.
|
||||
CMAKE_BUILD_TYPE= Release, Debug, etc ... (Not available in MSVC)
|
||||
|
||||
@@ -137,9 +139,9 @@ The easiest way is at the Command Prompt.
|
||||
4. Select: Project Only
|
||||
5. Select: Build Only INSTALL
|
||||
|
||||
To build a static library enable the following two options with CMake.
|
||||
-DENABLE_STATIC=ON -DENABLE_STATIC_RUNTIME=ON
|
||||
|
||||
To build a static library, set the following two options with CMake.
|
||||
-DBUILD_SHARED_LIBS=OFF -DENABLE_STATIC_RUNTIME=ON
|
||||
|
||||
Including ENABLE_STATIC_RUNTIME=ON indicates you want TagLib built using the
|
||||
static runtime library, rather than the DLL form of the runtime.
|
||||
|
||||
|
||||
113
NEWS
113
NEWS
@@ -1,3 +1,114 @@
|
||||
TagLib 1.11.1 (Oct 24, 2016)
|
||||
============================
|
||||
|
||||
* Fixed binary incompatible change in TagLib::String.
|
||||
* Fixed reading ID3v2 CTOC frames with a lot of entries.
|
||||
* Fixed seeking ByteVectorStream from the end.
|
||||
|
||||
TagLib 1.11 (Apr 29, 2016)
|
||||
==========================
|
||||
|
||||
1.11:
|
||||
|
||||
* Fixed reading APE items with long keys.
|
||||
* Fixed reading ID3v2 SYLT frames when description is empty.
|
||||
|
||||
1.11 BETA 2:
|
||||
|
||||
* Better handling of PCM WAV files with a 'fact' chunk.
|
||||
* Better handling of corrupted APE tags.
|
||||
* Efficient decoding of unsynchronized ID3v2 frames.
|
||||
* Fixed text encoding when saving certain frames in ID3v2.3 tags.
|
||||
* Fixed updating the size of RIFF files when removing chunks.
|
||||
* Several smaller bug fixes and performance improvements.
|
||||
|
||||
1.11 BETA:
|
||||
|
||||
* New API for creating FileRef from IOStream.
|
||||
* Added support for ID3v2 PCST and WFED frames.
|
||||
* Added support for pictures in XiphComment.
|
||||
* Added String::clear().
|
||||
* Added FLAC::File::strip() for removing non-standard tags.
|
||||
* Added alternative functions to XiphComment::removeField().
|
||||
* Added BUILD_BINDINGS build option.
|
||||
* Added ENABLE_CCACHE build option.
|
||||
* Replaced ENABLE_STATIC build option with BUILD_SHARED_LIBS.
|
||||
* Better handling of duplicate ID3v2 tags in all kinds of files.
|
||||
* Better handling of duplicate tag chunks in WAV files.
|
||||
* Better handling of duplicate tag chunks in AIFF files.
|
||||
* Better handling of duplicate Vorbis comment blocks in FLAC files.
|
||||
* Better handling of broken MPEG audio frames.
|
||||
* Fixed crash when calling File::properties() after strip().
|
||||
* Fixed crash when parsing certain MPEG files.
|
||||
* Fixed crash when saving Ogg files.
|
||||
* Fixed possible file corruptions when saving ASF files.
|
||||
* Fixed possible file corruptions when saving FLAC files.
|
||||
* Fixed possible file corruptions when saving MP4 files.
|
||||
* Fixed possible file corruptions when saving MPEG files.
|
||||
* Fixed possible file corruptions when saving APE files.
|
||||
* Fixed possible file corruptions when saving Musepack files.
|
||||
* Fixed possible file corruptions when saving WavPack files.
|
||||
* Fixed updating the comment field of Vorbis comments.
|
||||
* Fixed reading date and time in ID3v2.3 tags.
|
||||
* Marked ByteVector::null and ByteVector::isNull() deprecated.
|
||||
* Marked String::null and String::isNull() deprecated.
|
||||
* Marked XiphComment::removeField() deprecated.
|
||||
* Marked Ogg::Page::getCopyWithNewPageSequenceNumber() deprecated. It returns null.
|
||||
* Marked custom integer types deprecated.
|
||||
* Many smaller bug fixes and performance improvements.
|
||||
|
||||
TagLib 1.10 (Nov 11, 2015)
|
||||
==========================
|
||||
|
||||
1.10:
|
||||
|
||||
* Added new options to the tagwriter example.
|
||||
* Fixed self-assignment operator in some types.
|
||||
* Fixed extraction of MP4 tag keys with an empty list.
|
||||
|
||||
1.10 BETA:
|
||||
|
||||
* New API for the audio length in milliseconds.
|
||||
* Added support for ID3v2 ETCO and SYLT frames.
|
||||
* Added support for album artist in PropertyMap API of MP4 files.
|
||||
* Added support for embedded frames in ID3v2 CHAP and CTOC frames.
|
||||
* Added support for AIFF-C files.
|
||||
* Better handling of duplicate ID3v2 tags in MPEG files.
|
||||
* Allowed generating taglib.pc on Windows.
|
||||
* Added ZLIB_SOURCE build option.
|
||||
* Fixed backwards-incompatible change in TagLib::String when constructing UTF16 strings.
|
||||
* Fixed crash when parsing certain FLAC files.
|
||||
* Fixed crash when encoding empty strings.
|
||||
* Fixed saving of certain XM files on OS X.
|
||||
* Changed Xiph and APE generic getters to return space-concatenated values.
|
||||
* Fixed possible file corruptions when removing tags from WAV files.
|
||||
* Added support for MP4 files with 64-bit atoms in certain 64-bit environments.
|
||||
* Prevented ID3v2 padding from being too large.
|
||||
* Fixed crash when parsing corrupted APE files.
|
||||
* Fixed crash when parsing corrupted WAV files.
|
||||
* Fixed crash when parsing corrupted Ogg FLAC files.
|
||||
* Fixed crash when parsing corrupted MPEG files.
|
||||
* Fixed saving empty tags in WAV files.
|
||||
* Fixed crash when parsing corrupted Musepack files.
|
||||
* Fixed possible memory leaks when parsing AIFF and WAV files.
|
||||
* Fixed crash when parsing corrupted MP4 files.
|
||||
* Stopped writing empty ID3v2 frames.
|
||||
* Fixed possible file corruptions when saving WMA files.
|
||||
* Added TagLib::MP4::Tag::isEmpty().
|
||||
* Added accessors to manipulate MP4 tags.
|
||||
* Fixed crash when parsing corrupted WavPack files.
|
||||
* Fixed seeking MPEG frames.
|
||||
* Fixed reading FLAC files with zero-sized padding blocks.
|
||||
* Added support for reading the encoder information of WMA files.
|
||||
* Added support for reading the codec of WAV files.
|
||||
* Added support for multi channel WavPack files.
|
||||
* Added support for reading the nominal bitrate of Ogg Speex files.
|
||||
* Added support for VBR headers in MPEG files.
|
||||
* Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector.
|
||||
* Marked FLAC::File::streamLength() deprecated. It returns zero.
|
||||
* Fixed possible file corruptions when adding an ID3v1 tag to FLAC files.
|
||||
* Many smaller bug fixes and performance improvements.
|
||||
|
||||
TagLib 1.9.1 (Oct 8, 2013)
|
||||
==========================
|
||||
|
||||
@@ -5,7 +116,7 @@ TagLib 1.9.1 (Oct 8, 2013)
|
||||
* Fixed constructing String from ByteVector.
|
||||
* Fixed compilation on MSVC with the /Zc:wchar_t- option.
|
||||
* Fixed detecting of RIFF files with invalid chunk sizes.
|
||||
* Added TagLib::MP4::PropertyMap::codec().
|
||||
* Added TagLib::MP4::Properties::codec().
|
||||
|
||||
TagLib 1.9 (Oct 6, 2013)
|
||||
========================
|
||||
|
||||
@@ -61,7 +61,7 @@ install(TARGETS tag_c
|
||||
PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib
|
||||
)
|
||||
|
||||
if(NOT WIN32 AND NOT BUILD_FRAMEWORK)
|
||||
if(NOT BUILD_FRAMEWORK)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib_c.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib_c.pc)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib_c.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
|
||||
endif()
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
* USA *
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fileref.h>
|
||||
#include <tfile.h>
|
||||
@@ -40,18 +44,41 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
static List<char *> strings;
|
||||
static bool unicodeStrings = true;
|
||||
static bool stringManagementEnabled = true;
|
||||
namespace
|
||||
{
|
||||
List<char *> strings;
|
||||
bool unicodeStrings = true;
|
||||
bool stringManagementEnabled = true;
|
||||
|
||||
char *stringToCharArray(const String &s)
|
||||
{
|
||||
const std::string str = s.to8Bit(unicodeStrings);
|
||||
|
||||
#ifdef HAVE_ISO_STRDUP
|
||||
|
||||
return ::_strdup(str.c_str());
|
||||
|
||||
#else
|
||||
|
||||
return ::strdup(str.c_str());
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
String charArrayToString(const char *s)
|
||||
{
|
||||
return String(s, unicodeStrings ? String::UTF8 : String::Latin1);
|
||||
}
|
||||
}
|
||||
|
||||
void taglib_set_strings_unicode(BOOL unicode)
|
||||
{
|
||||
unicodeStrings = bool(unicode);
|
||||
unicodeStrings = (unicode != 0);
|
||||
}
|
||||
|
||||
void taglib_set_string_management_enabled(BOOL management)
|
||||
{
|
||||
stringManagementEnabled = bool(management);
|
||||
stringManagementEnabled = (management != 0);
|
||||
}
|
||||
|
||||
void taglib_free(void* pointer)
|
||||
@@ -94,8 +121,6 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type)
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void taglib_file_free(TagLib_File *file)
|
||||
@@ -132,7 +157,7 @@ BOOL taglib_file_save(TagLib_File *file)
|
||||
char *taglib_tag_title(const TagLib_Tag *tag)
|
||||
{
|
||||
const Tag *t = reinterpret_cast<const Tag *>(tag);
|
||||
char *s = ::strdup(t->title().toCString(unicodeStrings));
|
||||
char *s = stringToCharArray(t->title());
|
||||
if(stringManagementEnabled)
|
||||
strings.append(s);
|
||||
return s;
|
||||
@@ -141,7 +166,7 @@ char *taglib_tag_title(const TagLib_Tag *tag)
|
||||
char *taglib_tag_artist(const TagLib_Tag *tag)
|
||||
{
|
||||
const Tag *t = reinterpret_cast<const Tag *>(tag);
|
||||
char *s = ::strdup(t->artist().toCString(unicodeStrings));
|
||||
char *s = stringToCharArray(t->artist());
|
||||
if(stringManagementEnabled)
|
||||
strings.append(s);
|
||||
return s;
|
||||
@@ -150,7 +175,7 @@ char *taglib_tag_artist(const TagLib_Tag *tag)
|
||||
char *taglib_tag_album(const TagLib_Tag *tag)
|
||||
{
|
||||
const Tag *t = reinterpret_cast<const Tag *>(tag);
|
||||
char *s = ::strdup(t->album().toCString(unicodeStrings));
|
||||
char *s = stringToCharArray(t->album());
|
||||
if(stringManagementEnabled)
|
||||
strings.append(s);
|
||||
return s;
|
||||
@@ -159,7 +184,7 @@ char *taglib_tag_album(const TagLib_Tag *tag)
|
||||
char *taglib_tag_comment(const TagLib_Tag *tag)
|
||||
{
|
||||
const Tag *t = reinterpret_cast<const Tag *>(tag);
|
||||
char *s = ::strdup(t->comment().toCString(unicodeStrings));
|
||||
char *s = stringToCharArray(t->comment());
|
||||
if(stringManagementEnabled)
|
||||
strings.append(s);
|
||||
return s;
|
||||
@@ -168,7 +193,7 @@ char *taglib_tag_comment(const TagLib_Tag *tag)
|
||||
char *taglib_tag_genre(const TagLib_Tag *tag)
|
||||
{
|
||||
const Tag *t = reinterpret_cast<const Tag *>(tag);
|
||||
char *s = ::strdup(t->genre().toCString(unicodeStrings));
|
||||
char *s = stringToCharArray(t->genre());
|
||||
if(stringManagementEnabled)
|
||||
strings.append(s);
|
||||
return s;
|
||||
@@ -189,31 +214,31 @@ unsigned int taglib_tag_track(const TagLib_Tag *tag)
|
||||
void taglib_tag_set_title(TagLib_Tag *tag, const char *title)
|
||||
{
|
||||
Tag *t = reinterpret_cast<Tag *>(tag);
|
||||
t->setTitle(String(title, unicodeStrings ? String::UTF8 : String::Latin1));
|
||||
t->setTitle(charArrayToString(title));
|
||||
}
|
||||
|
||||
void taglib_tag_set_artist(TagLib_Tag *tag, const char *artist)
|
||||
{
|
||||
Tag *t = reinterpret_cast<Tag *>(tag);
|
||||
t->setArtist(String(artist, unicodeStrings ? String::UTF8 : String::Latin1));
|
||||
t->setArtist(charArrayToString(artist));
|
||||
}
|
||||
|
||||
void taglib_tag_set_album(TagLib_Tag *tag, const char *album)
|
||||
{
|
||||
Tag *t = reinterpret_cast<Tag *>(tag);
|
||||
t->setAlbum(String(album, unicodeStrings ? String::UTF8 : String::Latin1));
|
||||
t->setAlbum(charArrayToString(album));
|
||||
}
|
||||
|
||||
void taglib_tag_set_comment(TagLib_Tag *tag, const char *comment)
|
||||
{
|
||||
Tag *t = reinterpret_cast<Tag *>(tag);
|
||||
t->setComment(String(comment, unicodeStrings ? String::UTF8 : String::Latin1));
|
||||
t->setComment(charArrayToString(comment));
|
||||
}
|
||||
|
||||
void taglib_tag_set_genre(TagLib_Tag *tag, const char *genre)
|
||||
{
|
||||
Tag *t = reinterpret_cast<Tag *>(tag);
|
||||
t->setGenre(String(genre, unicodeStrings ? String::UTF8 : String::Latin1));
|
||||
t->setGenre(charArrayToString(genre));
|
||||
}
|
||||
|
||||
void taglib_tag_set_year(TagLib_Tag *tag, unsigned int year)
|
||||
@@ -233,7 +258,7 @@ void taglib_tag_free_strings()
|
||||
if(!stringManagementEnabled)
|
||||
return;
|
||||
|
||||
for(List<char *>::Iterator it = strings.begin(); it != strings.end(); ++it)
|
||||
for(List<char *>::ConstIterator it = strings.begin(); it != strings.end(); ++it)
|
||||
free(*it);
|
||||
strings.clear();
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ TAGLIB_C_EXPORT TagLib_File *taglib_file_new_type(const char *filename, TagLib_F
|
||||
TAGLIB_C_EXPORT void taglib_file_free(TagLib_File *file);
|
||||
|
||||
/*!
|
||||
* Returns true if the file is open and readble and valid information for
|
||||
* Returns true if the file is open and readable and valid information for
|
||||
* the Tag and / or AudioProperties was found.
|
||||
*/
|
||||
|
||||
@@ -137,7 +137,7 @@ TAGLIB_C_EXPORT BOOL taglib_file_is_valid(const TagLib_File *file);
|
||||
TAGLIB_C_EXPORT TagLib_Tag *taglib_file_tag(const TagLib_File *file);
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the the audio properties associated with this file. This
|
||||
* Returns a pointer to the audio properties associated with this file. This
|
||||
* will be freed automatically when the file is freed.
|
||||
*/
|
||||
TAGLIB_C_EXPORT const TagLib_AudioProperties *taglib_file_audioproperties(const TagLib_File *file);
|
||||
|
||||
@@ -7,6 +7,6 @@ includedir=${INCLUDE_INSTALL_DIR}
|
||||
Name: TagLib C Bindings
|
||||
Description: Audio meta-data library (C bindings)
|
||||
Requires: taglib
|
||||
Version: ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
Version: ${TAGLIB_LIB_VERSION_STRING}
|
||||
Libs: -L${LIB_INSTALL_DIR} -ltag_c
|
||||
Cflags: -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
/* config.h. Generated by cmake from config.h.cmake */
|
||||
|
||||
/* Indicates the byte order of your target system */
|
||||
/* 1 if little-endian, 2 if big-endian. */
|
||||
#cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER}
|
||||
|
||||
/* Defined if your compiler supports some byte swap functions */
|
||||
#cmakedefine HAVE_GCC_BYTESWAP_16 1
|
||||
#cmakedefine HAVE_GCC_BYTESWAP_32 1
|
||||
#cmakedefine HAVE_GCC_BYTESWAP_64 1
|
||||
#cmakedefine HAVE_BOOST_BYTESWAP 1
|
||||
#cmakedefine HAVE_GCC_BYTESWAP 1
|
||||
#cmakedefine HAVE_GLIBC_BYTESWAP 1
|
||||
#cmakedefine HAVE_MSC_BYTESWAP 1
|
||||
#cmakedefine HAVE_MAC_BYTESWAP 1
|
||||
#cmakedefine HAVE_OPENBSD_BYTESWAP 1
|
||||
|
||||
/* Defined if your compiler supports codecvt */
|
||||
#cmakedefine HAVE_STD_CODECVT 1
|
||||
|
||||
/* Defined if your compiler supports some atomic operations */
|
||||
#cmakedefine HAVE_STD_ATOMIC 1
|
||||
#cmakedefine HAVE_BOOST_ATOMIC 1
|
||||
@@ -24,12 +16,16 @@
|
||||
#cmakedefine HAVE_WIN_ATOMIC 1
|
||||
#cmakedefine HAVE_IA64_ATOMIC 1
|
||||
|
||||
/* Defined if your compiler supports some safer version of sprintf */
|
||||
#cmakedefine HAVE_SNPRINTF 1
|
||||
#cmakedefine HAVE_SPRINTF_S 1
|
||||
/* Defined if your compiler supports some safer version of vsprintf */
|
||||
#cmakedefine HAVE_VSNPRINTF 1
|
||||
#cmakedefine HAVE_VSPRINTF_S 1
|
||||
|
||||
/* Defined if you have libz */
|
||||
/* Defined if your compiler supports ISO _strdup */
|
||||
#cmakedefine HAVE_ISO_STRDUP 1
|
||||
|
||||
/* Defined if zlib is installed */
|
||||
#cmakedefine HAVE_ZLIB 1
|
||||
#cmakedefine HAVE_BOOST_ZLIB 1
|
||||
|
||||
/* Indicates whether debug messages are shown even in release mode */
|
||||
#cmakedefine TRACE_IN_RELEASE 1
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
if(BUILD_EXAMPLES)
|
||||
INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../bindings/c/ )
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../bindings/c/
|
||||
)
|
||||
|
||||
if(ENABLE_STATIC)
|
||||
add_definitions(-DTAGLIB_STATIC)
|
||||
endif(ENABLE_STATIC)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
add_definitions(-DTAGLIB_STATIC)
|
||||
endif()
|
||||
|
||||
########### next target ###############
|
||||
|
||||
ADD_EXECUTABLE(tagreader tagreader.cpp)
|
||||
|
||||
TARGET_LINK_LIBRARIES(tagreader tag )
|
||||
|
||||
add_executable(tagreader tagreader.cpp)
|
||||
target_link_libraries(tagreader tag)
|
||||
|
||||
########### next target ###############
|
||||
|
||||
ADD_EXECUTABLE(tagreader_c tagreader_c.c)
|
||||
|
||||
TARGET_LINK_LIBRARIES(tagreader_c tag_c )
|
||||
|
||||
add_executable(tagreader_c tagreader_c.c)
|
||||
target_link_libraries(tagreader_c tag_c)
|
||||
|
||||
########### next target ###############
|
||||
|
||||
ADD_EXECUTABLE(tagwriter tagwriter.cpp)
|
||||
|
||||
TARGET_LINK_LIBRARIES(tagwriter tag )
|
||||
|
||||
add_executable(tagwriter tagwriter.cpp)
|
||||
target_link_libraries(tagwriter tag)
|
||||
|
||||
########### next target ###############
|
||||
|
||||
ADD_EXECUTABLE(framelist framelist.cpp)
|
||||
|
||||
TARGET_LINK_LIBRARIES(framelist tag )
|
||||
|
||||
add_executable(framelist framelist.cpp)
|
||||
target_link_libraries(framelist tag)
|
||||
|
||||
########### next target ###############
|
||||
|
||||
ADD_EXECUTABLE(strip-id3v1 strip-id3v1.cpp)
|
||||
|
||||
TARGET_LINK_LIBRARIES(strip-id3v1 tag )
|
||||
|
||||
|
||||
endif(BUILD_EXAMPLES)
|
||||
add_executable(strip-id3v1 strip-id3v1.cpp)
|
||||
target_link_libraries(strip-id3v1 tag)
|
||||
|
||||
|
||||
@@ -32,13 +32,6 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
TagLib::String formatSeconds(int seconds)
|
||||
{
|
||||
char secondsString[3];
|
||||
sprintf(secondsString, "%02i", seconds);
|
||||
return secondsString;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
for(int i = 1; i < argc; i++) {
|
||||
@@ -89,7 +82,7 @@ int main(int argc, char *argv[])
|
||||
cout << "bitrate - " << properties->bitrate() << endl;
|
||||
cout << "sample rate - " << properties->sampleRate() << endl;
|
||||
cout << "channels - " << properties->channels() << endl;
|
||||
cout << "length - " << minutes << ":" << formatSeconds(seconds) << endl;
|
||||
cout << "length - " << minutes << ":" << setfill('0') << setw(2) << seconds << endl;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <fileref.h>
|
||||
#include <tfile.h>
|
||||
#include <tag.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -65,11 +67,32 @@ void usage()
|
||||
cout << " -g <genre>" << endl;
|
||||
cout << " -y <year>" << endl;
|
||||
cout << " -T <track>" << endl;
|
||||
cout << " -R <tagname> <tagvalue>" << endl;
|
||||
cout << " -I <tagname> <tagvalue>" << endl;
|
||||
cout << " -D <tagname>" << endl;
|
||||
cout << endl;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void checkForRejectedProperties(const TagLib::PropertyMap &tags)
|
||||
{ // stolen from tagreader.cpp
|
||||
if(tags.size() > 0) {
|
||||
unsigned int longest = 0;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
if(i->first.size() > longest) {
|
||||
longest = i->first.size();
|
||||
}
|
||||
}
|
||||
cout << "-- rejected TAGs (properties) --" << endl;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
TagLib::List<TagLib::FileRef> fileList;
|
||||
@@ -94,7 +117,7 @@ int main(int argc, char *argv[])
|
||||
char field = argv[i][1];
|
||||
TagLib::String value = argv[i + 1];
|
||||
|
||||
TagLib::List<TagLib::FileRef>::Iterator it;
|
||||
TagLib::List<TagLib::FileRef>::ConstIterator it;
|
||||
for(it = fileList.begin(); it != fileList.end(); ++it) {
|
||||
|
||||
TagLib::Tag *t = (*it).tag();
|
||||
@@ -121,6 +144,29 @@ int main(int argc, char *argv[])
|
||||
case 'T':
|
||||
t->setTrack(value.toInt());
|
||||
break;
|
||||
case 'R':
|
||||
case 'I':
|
||||
if(i + 2 < argc) {
|
||||
TagLib::PropertyMap map = (*it).file()->properties ();
|
||||
if(field == 'R') {
|
||||
map.replace(value, TagLib::String(argv[i + 2]));
|
||||
}
|
||||
else {
|
||||
map.insert(value, TagLib::String(argv[i + 2]));
|
||||
}
|
||||
++i;
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
}
|
||||
else {
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
case 'D': {
|
||||
TagLib::PropertyMap map = (*it).file()->properties();
|
||||
map.erase(value);
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
@@ -131,7 +177,7 @@ int main(int argc, char *argv[])
|
||||
usage();
|
||||
}
|
||||
|
||||
TagLib::List<TagLib::FileRef>::Iterator it;
|
||||
TagLib::List<TagLib::FileRef>::ConstIterator it;
|
||||
for(it = fileList.begin(); it != fileList.end(); ++it)
|
||||
(*it).file()->save();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ do
|
||||
flags="$flags -I$includedir/taglib"
|
||||
;;
|
||||
--version)
|
||||
echo ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
echo ${TAGLIB_LIB_VERSION_STRING}
|
||||
;;
|
||||
--prefix)
|
||||
echo $prefix
|
||||
|
||||
@@ -29,7 +29,7 @@ goto theend
|
||||
:doit
|
||||
if /i "%1#" == "--libs#" echo -L${LIB_INSTALL_DIR} -llibtag
|
||||
if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_VERSION_STRING}
|
||||
if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX}
|
||||
|
||||
:theend
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
prefix=${CMAKE_INSTALL_PREFIX}
|
||||
exec_prefix=${CMAKE_INSTALL_PREFIX}
|
||||
libdir=${LIB_INSTALL_DIR}
|
||||
includedir=${INCLUDE_INSTALL_DIR}
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=@LIB_INSTALL_DIR@
|
||||
includedir=@INCLUDE_INSTALL_DIR@
|
||||
|
||||
Name: TagLib
|
||||
Description: Audio meta-data library
|
||||
Requires:
|
||||
Version: ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
Libs: -L${LIB_INSTALL_DIR} -ltag
|
||||
Cflags: -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
Version: @TAGLIB_LIB_VERSION_STRING@
|
||||
Libs: -L${libdir} -ltag
|
||||
Cflags: -I${includedir}/taglib
|
||||
|
||||
@@ -28,6 +28,12 @@ include_directories(
|
||||
|
||||
if(ZLIB_FOUND)
|
||||
include_directories(${ZLIB_INCLUDE_DIR})
|
||||
elseif(HAVE_ZLIB_SOURCE)
|
||||
include_directories(${ZLIB_SOURCE})
|
||||
endif()
|
||||
|
||||
if(HAVE_BOOST_BYTESWAP OR HAVE_BOOST_ATOMIC OR HAVE_BOOST_ZLIB)
|
||||
include_directories(${Boost_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
set(tag_HDRS
|
||||
@@ -35,7 +41,7 @@ set(tag_HDRS
|
||||
fileref.h
|
||||
audioproperties.h
|
||||
taglib_export.h
|
||||
${CMAKE_BINARY_DIR}/taglib_config.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../taglib_config.h
|
||||
toolkit/taglib.h
|
||||
toolkit/tstring.h
|
||||
toolkit/tlist.h
|
||||
@@ -67,16 +73,21 @@ set(tag_HDRS
|
||||
mpeg/id3v2/id3v2tag.h
|
||||
mpeg/id3v2/frames/attachedpictureframe.h
|
||||
mpeg/id3v2/frames/commentsframe.h
|
||||
mpeg/id3v2/frames/eventtimingcodesframe.h
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.h
|
||||
mpeg/id3v2/frames/ownershipframe.h
|
||||
mpeg/id3v2/frames/popularimeterframe.h
|
||||
mpeg/id3v2/frames/privateframe.h
|
||||
mpeg/id3v2/frames/relativevolumeframe.h
|
||||
mpeg/id3v2/frames/synchronizedlyricsframe.h
|
||||
mpeg/id3v2/frames/textidentificationframe.h
|
||||
mpeg/id3v2/frames/uniquefileidentifierframe.h
|
||||
mpeg/id3v2/frames/unknownframe.h
|
||||
mpeg/id3v2/frames/unsynchronizedlyricsframe.h
|
||||
mpeg/id3v2/frames/urllinkframe.h
|
||||
mpeg/id3v2/frames/chapterframe.h
|
||||
mpeg/id3v2/frames/tableofcontentsframe.h
|
||||
mpeg/id3v2/frames/podcastframe.h
|
||||
ogg/oggfile.h
|
||||
ogg/oggpage.h
|
||||
ogg/oggpageheader.h
|
||||
@@ -157,16 +168,21 @@ set(id3v2_SRCS
|
||||
set(frames_SRCS
|
||||
mpeg/id3v2/frames/attachedpictureframe.cpp
|
||||
mpeg/id3v2/frames/commentsframe.cpp
|
||||
mpeg/id3v2/frames/eventtimingcodesframe.cpp
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp
|
||||
mpeg/id3v2/frames/ownershipframe.cpp
|
||||
mpeg/id3v2/frames/popularimeterframe.cpp
|
||||
mpeg/id3v2/frames/privateframe.cpp
|
||||
mpeg/id3v2/frames/relativevolumeframe.cpp
|
||||
mpeg/id3v2/frames/synchronizedlyricsframe.cpp
|
||||
mpeg/id3v2/frames/textidentificationframe.cpp
|
||||
mpeg/id3v2/frames/uniquefileidentifierframe.cpp
|
||||
mpeg/id3v2/frames/unknownframe.cpp
|
||||
mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp
|
||||
mpeg/id3v2/frames/urllinkframe.cpp
|
||||
mpeg/id3v2/frames/chapterframe.cpp
|
||||
mpeg/id3v2/frames/tableofcontentsframe.cpp
|
||||
mpeg/id3v2/frames/podcastframe.cpp
|
||||
)
|
||||
|
||||
set(ogg_SRCS
|
||||
@@ -293,24 +309,51 @@ set(toolkit_SRCS
|
||||
toolkit/tpropertymap.cpp
|
||||
toolkit/trefcounter.cpp
|
||||
toolkit/tdebuglistener.cpp
|
||||
toolkit/unicode.cpp
|
||||
toolkit/tzlib.cpp
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
set(unicode_SRCS
|
||||
toolkit/unicode.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HAVE_ZLIB_SOURCE)
|
||||
set(zlib_SRCS
|
||||
${ZLIB_SOURCE}/adler32.c
|
||||
${ZLIB_SOURCE}/crc32.c
|
||||
${ZLIB_SOURCE}/inffast.c
|
||||
${ZLIB_SOURCE}/inflate.c
|
||||
${ZLIB_SOURCE}/inftrees.c
|
||||
${ZLIB_SOURCE}/zutil.c
|
||||
)
|
||||
endif()
|
||||
|
||||
set(tag_LIB_SRCS
|
||||
${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS}
|
||||
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
|
||||
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
|
||||
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
|
||||
${unicode_SRCS} ${zlib_SRCS}
|
||||
tag.cpp
|
||||
tagunion.cpp
|
||||
fileref.cpp
|
||||
audioproperties.cpp
|
||||
tagutils.cpp
|
||||
)
|
||||
|
||||
add_library(tag ${tag_LIB_SRCS} ${tag_HDRS})
|
||||
|
||||
if(ZLIB_FOUND)
|
||||
target_link_libraries(tag ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(tag ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_BOOST_ATOMIC)
|
||||
target_link_libraries(tag ${Boost_ATOMIC_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(HAVE_BOOST_ZLIB)
|
||||
target_link_libraries(tag ${Boost_IOSTREAMS_LIBRARY} ${Boost_ZLIB_LIBRARY})
|
||||
endif()
|
||||
|
||||
set_target_properties(tag PROPERTIES
|
||||
@@ -326,10 +369,9 @@ if(BUILD_FRAMEWORK)
|
||||
endif()
|
||||
|
||||
install(TARGETS tag
|
||||
FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR}
|
||||
LIBRARY DESTINATION ${LIB_INSTALL_DIR}
|
||||
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
|
||||
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
|
||||
PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib
|
||||
FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR}
|
||||
LIBRARY DESTINATION ${LIB_INSTALL_DIR}
|
||||
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
|
||||
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
|
||||
PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib
|
||||
)
|
||||
|
||||
|
||||
@@ -36,10 +36,11 @@
|
||||
#include <tdebug.h>
|
||||
#include <tagunion.h>
|
||||
#include <id3v1tag.h>
|
||||
#include <id3v2header.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tagutils.h>
|
||||
|
||||
#include "apefile.h"
|
||||
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
|
||||
@@ -57,49 +58,49 @@ public:
|
||||
APELocation(-1),
|
||||
APESize(0),
|
||||
ID3v1Location(-1),
|
||||
properties(0),
|
||||
hasAPE(false),
|
||||
hasID3v1(false) {}
|
||||
ID3v2Header(0),
|
||||
ID3v2Location(-1),
|
||||
ID3v2Size(0),
|
||||
properties(0) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete ID3v2Header;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
long APELocation;
|
||||
uint APESize;
|
||||
long APESize;
|
||||
|
||||
long ID3v1Location;
|
||||
|
||||
ID3v2::Header *ID3v2Header;
|
||||
long ID3v2Location;
|
||||
long ID3v2Size;
|
||||
|
||||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
|
||||
// These indicate whether the file *on disk* has these tags, not if
|
||||
// this data structure does. This is used in computing offsets.
|
||||
|
||||
bool hasAPE;
|
||||
bool hasID3v1;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
APE::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
APE::File::~File()
|
||||
@@ -114,26 +115,20 @@ TagLib::Tag *APE::File::tag() const
|
||||
|
||||
PropertyMap APE::File::properties() const
|
||||
{
|
||||
if(d->hasAPE)
|
||||
return d->tag.access<APE::Tag>(ApeAPEIndex, false)->properties();
|
||||
if(d->hasID3v1)
|
||||
return d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->properties();
|
||||
return PropertyMap();
|
||||
return d->tag.properties();
|
||||
}
|
||||
|
||||
void APE::File::removeUnsupportedProperties(const StringList &properties)
|
||||
{
|
||||
if(d->hasAPE)
|
||||
d->tag.access<APE::Tag>(ApeAPEIndex, false)->removeUnsupportedProperties(properties);
|
||||
if(d->hasID3v1)
|
||||
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->removeUnsupportedProperties(properties);
|
||||
d->tag.removeUnsupportedProperties(properties);
|
||||
}
|
||||
|
||||
PropertyMap APE::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
if(d->hasID3v1)
|
||||
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->setProperties(properties);
|
||||
return d->tag.access<APE::Tag>(ApeAPEIndex, true)->setProperties(properties);
|
||||
if(ID3v1Tag())
|
||||
ID3v1Tag()->setProperties(properties);
|
||||
|
||||
return APETag(true)->setProperties(properties);
|
||||
}
|
||||
|
||||
APE::Properties *APE::File::audioProperties() const
|
||||
@@ -150,64 +145,67 @@ bool APE::File::save()
|
||||
|
||||
// Update ID3v1 tag
|
||||
|
||||
if(ID3v1Tag()) {
|
||||
if(d->hasID3v1) {
|
||||
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
|
||||
|
||||
// ID3v1 tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
seek(d->ID3v1Location);
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->ID3v1Location = tell();
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
}
|
||||
else {
|
||||
if(d->hasID3v1) {
|
||||
removeBlock(d->ID3v1Location, 128);
|
||||
d->hasID3v1 = false;
|
||||
if(d->hasAPE) {
|
||||
if(d->APELocation > d->ID3v1Location)
|
||||
d->APELocation -= 128;
|
||||
}
|
||||
|
||||
// ID3v1 tag is empty. Remove the old one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
truncate(d->ID3v1Location);
|
||||
d->ID3v1Location = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update APE tag
|
||||
|
||||
if(APETag()) {
|
||||
if(d->hasAPE)
|
||||
insert(APETag()->render(), d->APELocation, d->APESize);
|
||||
else {
|
||||
if(d->hasID3v1) {
|
||||
insert(APETag()->render(), d->ID3v1Location, 0);
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->hasAPE = true;
|
||||
if(APETag() && !APETag()->isEmpty()) {
|
||||
|
||||
// APE tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->APELocation < 0) {
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->APELocation = d->ID3v1Location;
|
||||
d->ID3v1Location += d->APESize;
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->APELocation = tell();
|
||||
writeBlock(APETag()->render());
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->hasAPE = true;
|
||||
}
|
||||
else
|
||||
d->APELocation = length();
|
||||
}
|
||||
|
||||
const ByteVector data = APETag()->render();
|
||||
insert(data, d->APELocation, d->APESize);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
|
||||
|
||||
d->APESize = data.size();
|
||||
}
|
||||
else {
|
||||
if(d->hasAPE) {
|
||||
|
||||
// APE tag is empty. Remove the old one.
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
removeBlock(d->APELocation, d->APESize);
|
||||
d->hasAPE = false;
|
||||
if(d->hasID3v1) {
|
||||
if(d->ID3v1Location > d->APELocation) {
|
||||
d->ID3v1Location -= d->APESize;
|
||||
}
|
||||
}
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location -= d->APESize;
|
||||
|
||||
d->APELocation = -1;
|
||||
d->APESize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ID3v1::Tag *APE::File::ID3v1Tag(bool create)
|
||||
@@ -222,93 +220,83 @@ APE::Tag *APE::File::APETag(bool create)
|
||||
|
||||
void APE::File::strip(int tags)
|
||||
{
|
||||
if(tags & ID3v1) {
|
||||
if(tags & ID3v1)
|
||||
d->tag.set(ApeID3v1Index, 0);
|
||||
APETag(true);
|
||||
}
|
||||
|
||||
if(tags & APE) {
|
||||
if(tags & APE)
|
||||
d->tag.set(ApeAPEIndex, 0);
|
||||
|
||||
if(!ID3v1Tag())
|
||||
APETag(true);
|
||||
}
|
||||
if(!ID3v1Tag())
|
||||
APETag(true);
|
||||
}
|
||||
|
||||
bool APE::File::hasAPETag() const
|
||||
{
|
||||
return d->hasAPE;
|
||||
return (d->APELocation >= 0);
|
||||
}
|
||||
|
||||
bool APE::File::hasID3v1Tag() const
|
||||
{
|
||||
return d->hasID3v1;
|
||||
return (d->ID3v1Location >= 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */)
|
||||
void APE::File::read(bool readProperties)
|
||||
{
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
d->ID3v2Location = Utils::findID3v2(this);
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
seek(d->ID3v2Location);
|
||||
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
|
||||
d->ID3v2Size = d->ID3v2Header->completeTagSize();
|
||||
}
|
||||
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
d->ID3v1Location = findID3v1();
|
||||
d->ID3v1Location = Utils::findID3v1(this);
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
// Look for an APE tag
|
||||
|
||||
d->APELocation = findAPE();
|
||||
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation));
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize;
|
||||
d->hasAPE = true;
|
||||
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
|
||||
}
|
||||
|
||||
if(!d->hasID3v1)
|
||||
if(d->ID3v1Location < 0)
|
||||
APETag(true);
|
||||
|
||||
// Look for APE audio properties
|
||||
|
||||
if(readProperties) {
|
||||
d->properties = new Properties(this);
|
||||
|
||||
long streamLength;
|
||||
|
||||
if(d->APELocation >= 0)
|
||||
streamLength = d->APELocation;
|
||||
else if(d->ID3v1Location >= 0)
|
||||
streamLength = d->ID3v1Location;
|
||||
else
|
||||
streamLength = length();
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
seek(d->ID3v2Location + d->ID3v2Size);
|
||||
streamLength -= (d->ID3v2Location + d->ID3v2Size);
|
||||
}
|
||||
else {
|
||||
seek(0);
|
||||
}
|
||||
|
||||
d->properties = new Properties(this, streamLength);
|
||||
}
|
||||
}
|
||||
|
||||
long APE::File::findAPE()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
if(d->hasID3v1)
|
||||
seek(-160, End);
|
||||
else
|
||||
seek(-32, End);
|
||||
|
||||
long p = tell();
|
||||
|
||||
if(readBlock(8) == APE::Tag::fileIdentifier())
|
||||
return p;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
long APE::File::findID3v1()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(-128, End);
|
||||
long p = tell();
|
||||
|
||||
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
|
||||
return p;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- import function.
|
||||
* Creates an APEv2 tag if necessary. A pontentially existing ID3v1
|
||||
* Creates an APEv2 tag if necessary. A potentially existing ID3v1
|
||||
* tag will be updated as well.
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &);
|
||||
@@ -156,8 +156,8 @@ namespace TagLib {
|
||||
* if there is no valid ID3v1 tag. If \a create is true it will create
|
||||
* an ID3v1 tag if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -175,8 +175,8 @@ namespace TagLib {
|
||||
* if there is no valid APE tag. If \a create is true it will create
|
||||
* an APE tag if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an APE tag. Use hasAPETag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an APE tag. Use hasAPETag() to check if the file
|
||||
* on disk actually has an APE tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -215,10 +215,7 @@ namespace TagLib {
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void scan();
|
||||
long findID3v1();
|
||||
long findAPE();
|
||||
void read(bool readProperties);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
||||
@@ -38,54 +38,51 @@ using namespace APE;
|
||||
class APE::Footer::FooterPrivate
|
||||
{
|
||||
public:
|
||||
FooterPrivate() : version(0),
|
||||
footerPresent(true),
|
||||
headerPresent(false),
|
||||
isHeader(false),
|
||||
itemCount(0),
|
||||
tagSize(0) {}
|
||||
FooterPrivate() :
|
||||
version(0),
|
||||
footerPresent(true),
|
||||
headerPresent(false),
|
||||
isHeader(false),
|
||||
itemCount(0),
|
||||
tagSize(0) {}
|
||||
|
||||
~FooterPrivate() {}
|
||||
|
||||
uint version;
|
||||
unsigned int version;
|
||||
|
||||
bool footerPresent;
|
||||
bool headerPresent;
|
||||
|
||||
bool isHeader;
|
||||
|
||||
uint itemCount;
|
||||
uint tagSize;
|
||||
|
||||
static const uint size = 32;
|
||||
unsigned int itemCount;
|
||||
unsigned int tagSize;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// static members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TagLib::uint APE::Footer::size()
|
||||
unsigned int APE::Footer::size()
|
||||
{
|
||||
return FooterPrivate::size;
|
||||
return 32;
|
||||
}
|
||||
|
||||
ByteVector APE::Footer::fileIdentifier()
|
||||
{
|
||||
return ByteVector::fromCString("APETAGEX");
|
||||
return ByteVector("APETAGEX");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::Footer::Footer()
|
||||
APE::Footer::Footer() :
|
||||
d(new FooterPrivate())
|
||||
{
|
||||
d = new FooterPrivate;
|
||||
}
|
||||
|
||||
APE::Footer::Footer(const ByteVector &data)
|
||||
APE::Footer::Footer(const ByteVector &data) :
|
||||
d(new FooterPrivate())
|
||||
{
|
||||
d = new FooterPrivate;
|
||||
parse(data);
|
||||
}
|
||||
|
||||
@@ -94,7 +91,7 @@ APE::Footer::~Footer()
|
||||
delete d;
|
||||
}
|
||||
|
||||
TagLib::uint APE::Footer::version() const
|
||||
unsigned int APE::Footer::version() const
|
||||
{
|
||||
return d->version;
|
||||
}
|
||||
@@ -119,30 +116,30 @@ void APE::Footer::setHeaderPresent(bool b) const
|
||||
d->headerPresent = b;
|
||||
}
|
||||
|
||||
TagLib::uint APE::Footer::itemCount() const
|
||||
unsigned int APE::Footer::itemCount() const
|
||||
{
|
||||
return d->itemCount;
|
||||
}
|
||||
|
||||
void APE::Footer::setItemCount(uint s)
|
||||
void APE::Footer::setItemCount(unsigned int s)
|
||||
{
|
||||
d->itemCount = s;
|
||||
}
|
||||
|
||||
TagLib::uint APE::Footer::tagSize() const
|
||||
unsigned int APE::Footer::tagSize() const
|
||||
{
|
||||
return d->tagSize;
|
||||
}
|
||||
|
||||
TagLib::uint APE::Footer::completeTagSize() const
|
||||
unsigned int APE::Footer::completeTagSize() const
|
||||
{
|
||||
if(d->headerPresent)
|
||||
return d->tagSize + d->size;
|
||||
return d->tagSize + size();
|
||||
else
|
||||
return d->tagSize;
|
||||
}
|
||||
|
||||
void APE::Footer::setTagSize(uint s)
|
||||
void APE::Footer::setTagSize(unsigned int s)
|
||||
{
|
||||
d->tagSize = s;
|
||||
}
|
||||
@@ -154,13 +151,14 @@ void APE::Footer::setData(const ByteVector &data)
|
||||
|
||||
ByteVector APE::Footer::renderFooter() const
|
||||
{
|
||||
return render(false);
|
||||
return render(false);
|
||||
}
|
||||
|
||||
ByteVector APE::Footer::renderHeader() const
|
||||
{
|
||||
if (!d->headerPresent) return ByteVector();
|
||||
|
||||
if(!d->headerPresent)
|
||||
return ByteVector();
|
||||
else
|
||||
return render(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* This class implements APE footers (and headers). It attempts to follow, both
|
||||
* semantically and programatically, the structure specified in
|
||||
* semantically and programmatically, the structure specified in
|
||||
* the APE v2.0 standard. The API is based on the properties of APE footer and
|
||||
* headers specified there.
|
||||
*/
|
||||
@@ -64,7 +64,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns the version number. (Note: This is the 1000 or 2000.)
|
||||
*/
|
||||
uint version() const;
|
||||
unsigned int version() const;
|
||||
|
||||
/*!
|
||||
* Returns true if a header is present in the tag.
|
||||
@@ -89,13 +89,13 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns the number of items in the tag.
|
||||
*/
|
||||
uint itemCount() const;
|
||||
unsigned int itemCount() const;
|
||||
|
||||
/*!
|
||||
* Set the item count to \a s.
|
||||
* \see itemCount()
|
||||
*/
|
||||
void setItemCount(uint s);
|
||||
void setItemCount(unsigned int s);
|
||||
|
||||
/*!
|
||||
* Returns the tag size in bytes. This is the size of the frame content and footer.
|
||||
@@ -103,7 +103,7 @@ namespace TagLib {
|
||||
*
|
||||
* \see completeTagSize()
|
||||
*/
|
||||
uint tagSize() const;
|
||||
unsigned int tagSize() const;
|
||||
|
||||
/*!
|
||||
* Returns the tag size, including if present, the header
|
||||
@@ -111,18 +111,18 @@ namespace TagLib {
|
||||
*
|
||||
* \see tagSize()
|
||||
*/
|
||||
uint completeTagSize() const;
|
||||
unsigned int completeTagSize() const;
|
||||
|
||||
/*!
|
||||
* Set the tag size to \a s.
|
||||
* \see tagSize()
|
||||
*/
|
||||
void setTagSize(uint s);
|
||||
void setTagSize(unsigned int s);
|
||||
|
||||
/*!
|
||||
* Returns the size of the footer. Presently this is always 32 bytes.
|
||||
*/
|
||||
static uint size();
|
||||
static unsigned int size();
|
||||
|
||||
/*!
|
||||
* Returns the string used to identify an APE tag inside of a file.
|
||||
|
||||
@@ -34,7 +34,9 @@ using namespace APE;
|
||||
class APE::Item::ItemPrivate
|
||||
{
|
||||
public:
|
||||
ItemPrivate() : type(Text), readOnly(false) {}
|
||||
ItemPrivate() :
|
||||
type(Text),
|
||||
readOnly(false) {}
|
||||
|
||||
Item::ItemTypes type;
|
||||
String key;
|
||||
@@ -43,40 +45,45 @@ public:
|
||||
bool readOnly;
|
||||
};
|
||||
|
||||
APE::Item::Item()
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::Item::Item() :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
}
|
||||
|
||||
APE::Item::Item(const String &key, const String &value)
|
||||
APE::Item::Item(const String &key, const String &value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->key = key;
|
||||
d->text.append(value);
|
||||
}
|
||||
|
||||
APE::Item::Item(const String &key, const StringList &values)
|
||||
APE::Item::Item(const String &key, const StringList &values) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->key = key;
|
||||
d->text = values;
|
||||
}
|
||||
|
||||
APE::Item::Item(const String &key, const ByteVector &value, bool binary)
|
||||
APE::Item::Item(const String &key, const ByteVector &value, bool binary) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->key = key;
|
||||
if(binary) {
|
||||
d->type = Binary;
|
||||
d->value = value;
|
||||
}
|
||||
else
|
||||
else {
|
||||
d->text.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
APE::Item::Item(const Item &item)
|
||||
APE::Item::Item(const Item &item) :
|
||||
d(new ItemPrivate(*item.d))
|
||||
{
|
||||
d = new ItemPrivate(*item.d);
|
||||
}
|
||||
|
||||
APE::Item::~Item()
|
||||
@@ -86,11 +93,17 @@ APE::Item::~Item()
|
||||
|
||||
Item &APE::Item::operator=(const Item &item)
|
||||
{
|
||||
delete d;
|
||||
d = new ItemPrivate(*item.d);
|
||||
Item(item).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void APE::Item::swap(Item &item)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, item.d);
|
||||
}
|
||||
|
||||
void APE::Item::setReadOnly(bool readOnly)
|
||||
{
|
||||
d->readOnly = readOnly;
|
||||
@@ -171,11 +184,10 @@ void APE::Item::appendValues(const StringList &values)
|
||||
|
||||
int APE::Item::size() const
|
||||
{
|
||||
// SFB: Why is d->key.size() used when size() returns the length in UniChars and not UTF-8?
|
||||
int result = 8 + d->key.size() /* d->key.data(String::UTF8).size() */ + 1;
|
||||
switch (d->type) {
|
||||
int result = 8 + d->key.size() + 1;
|
||||
switch(d->type) {
|
||||
case Text:
|
||||
if(d->text.size()) {
|
||||
if(!d->text.isEmpty()) {
|
||||
StringList::ConstIterator it = d->text.begin();
|
||||
|
||||
result += it->data(String::UTF8).size();
|
||||
@@ -208,7 +220,7 @@ String APE::Item::toString() const
|
||||
if(d->type == Text && !isEmpty())
|
||||
return d->text.front();
|
||||
else
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
bool APE::Item::isEmpty() const
|
||||
@@ -237,17 +249,20 @@ void APE::Item::parse(const ByteVector &data)
|
||||
return;
|
||||
}
|
||||
|
||||
const uint valueLength = data.toUInt(0, false);
|
||||
const uint flags = data.toUInt(4, false);
|
||||
const unsigned int valueLength = data.toUInt(0, false);
|
||||
const unsigned int flags = data.toUInt(4, false);
|
||||
|
||||
d->key = String(data.mid(8), String::UTF8);
|
||||
// An item key can contain ASCII characters from 0x20 up to 0x7E, not UTF-8.
|
||||
// We assume that the validity of the given key has been checked.
|
||||
|
||||
d->key = String(&data[8], String::Latin1);
|
||||
|
||||
const ByteVector value = data.mid(8 + d->key.size() + 1, valueLength);
|
||||
|
||||
setReadOnly(flags & 1);
|
||||
setType(ItemTypes((flags >> 1) & 3));
|
||||
|
||||
if(Text == d->type)
|
||||
if(Text == d->type)
|
||||
d->text = StringList(ByteVectorList::split(value, '\0'), String::UTF8);
|
||||
else
|
||||
d->value = value;
|
||||
@@ -256,7 +271,7 @@ void APE::Item::parse(const ByteVector &data)
|
||||
ByteVector APE::Item::render() const
|
||||
{
|
||||
ByteVector data;
|
||||
TagLib::uint flags = ((d->readOnly) ? 1 : 0) | (d->type << 1);
|
||||
unsigned int flags = ((d->readOnly) ? 1 : 0) | (d->type << 1);
|
||||
ByteVector value;
|
||||
|
||||
if(isEmpty())
|
||||
@@ -278,7 +293,7 @@ ByteVector APE::Item::render() const
|
||||
|
||||
data.append(ByteVector::fromUInt(value.size(), false));
|
||||
data.append(ByteVector::fromUInt(flags, false));
|
||||
data.append(d->key.data(String::UTF8));
|
||||
data.append(d->key.data(String::Latin1));
|
||||
data.append(ByteVector('\0'));
|
||||
data.append(value);
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ namespace TagLib {
|
||||
*/
|
||||
Item &operator=(const Item &item);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of this item by the content of \a item.
|
||||
*/
|
||||
void swap(Item &item);
|
||||
|
||||
/*!
|
||||
* Returns the key.
|
||||
*/
|
||||
@@ -153,7 +158,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns the value as a single string. In case of multiple strings,
|
||||
* the first is returned. If the data type is not \a Text, always returns
|
||||
* the first is returned. If the data type is not \a Text, always returns
|
||||
* an empty String.
|
||||
*/
|
||||
String toString() const;
|
||||
@@ -164,7 +169,7 @@ namespace TagLib {
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Returns the list of text values. If the data type is not \a Text, always
|
||||
* Returns the list of text values. If the data type is not \a Text, always
|
||||
* returns an empty StringList.
|
||||
*/
|
||||
StringList values() const;
|
||||
|
||||
@@ -33,22 +33,22 @@
|
||||
#include "id3v2tag.h"
|
||||
#include "apeproperties.h"
|
||||
#include "apefile.h"
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class APE::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *file, long streamLength) :
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
version(0),
|
||||
bitsPerSample(0),
|
||||
sampleFrames(0),
|
||||
file(file),
|
||||
streamLength(streamLength) {}
|
||||
sampleFrames(0) {}
|
||||
|
||||
int length;
|
||||
int bitrate;
|
||||
@@ -56,19 +56,25 @@ public:
|
||||
int channels;
|
||||
int version;
|
||||
int bitsPerSample;
|
||||
uint sampleFrames;
|
||||
File *file;
|
||||
long streamLength;
|
||||
unsigned int sampleFrames;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style)
|
||||
APE::Properties::Properties(File *, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(file, file->length());
|
||||
read();
|
||||
debug("APE::Properties::Properties() -- This constructor is no longer used.");
|
||||
}
|
||||
|
||||
APE::Properties::Properties(File *file, long streamLength, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
read(file, streamLength);
|
||||
}
|
||||
|
||||
APE::Properties::~Properties()
|
||||
@@ -77,6 +83,16 @@ APE::Properties::~Properties()
|
||||
}
|
||||
|
||||
int APE::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int APE::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int APE::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@@ -106,7 +122,7 @@ int APE::Properties::bitsPerSample() const
|
||||
return d->bitsPerSample;
|
||||
}
|
||||
|
||||
TagLib::uint APE::Properties::sampleFrames() const
|
||||
unsigned int APE::Properties::sampleFrames() const
|
||||
{
|
||||
return d->sampleFrames;
|
||||
}
|
||||
@@ -115,118 +131,122 @@ TagLib::uint APE::Properties::sampleFrames() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void APE::Properties::read()
|
||||
namespace
|
||||
{
|
||||
// First we are searching the descriptor
|
||||
long offset = findDescriptor();
|
||||
if(offset < 0)
|
||||
return;
|
||||
int headerVersion(const ByteVector &header)
|
||||
{
|
||||
if(header.size() < 6 || !header.startsWith("MAC "))
|
||||
return -1;
|
||||
|
||||
// Then we read the header common for all versions of APE
|
||||
d->file->seek(offset);
|
||||
ByteVector commonHeader = d->file->readBlock(6);
|
||||
if(!commonHeader.startsWith("MAC "))
|
||||
return;
|
||||
d->version = commonHeader.toUShort(4, false);
|
||||
|
||||
if(d->version >= 3980) {
|
||||
analyzeCurrent();
|
||||
}
|
||||
else {
|
||||
analyzeOld();
|
||||
return header.toUShort(4, false);
|
||||
}
|
||||
}
|
||||
|
||||
long APE::Properties::findDescriptor()
|
||||
void APE::Properties::read(File *file, long streamLength)
|
||||
{
|
||||
long ID3v2Location = findID3v2();
|
||||
long ID3v2OriginalSize = 0;
|
||||
bool hasID3v2 = false;
|
||||
if(ID3v2Location >= 0) {
|
||||
ID3v2::Tag tag(d->file, ID3v2Location);
|
||||
ID3v2OriginalSize = tag.header()->completeTagSize();
|
||||
if(tag.header()->tagSize() > 0)
|
||||
hasID3v2 = true;
|
||||
// First, we assume that the file pointer is set at the first descriptor.
|
||||
long offset = file->tell();
|
||||
int version = headerVersion(file->readBlock(6));
|
||||
|
||||
// Next, we look for the descriptor.
|
||||
if(version < 0) {
|
||||
offset = file->find("MAC ", offset);
|
||||
file->seek(offset);
|
||||
version = headerVersion(file->readBlock(6));
|
||||
}
|
||||
|
||||
long offset = 0;
|
||||
if(hasID3v2)
|
||||
offset = d->file->find("MAC ", ID3v2Location + ID3v2OriginalSize);
|
||||
if(version < 0) {
|
||||
debug("APE::Properties::read() -- APE descriptor not found");
|
||||
return;
|
||||
}
|
||||
|
||||
d->version = version;
|
||||
|
||||
if(d->version >= 3980)
|
||||
analyzeCurrent(file);
|
||||
else
|
||||
offset = d->file->find("MAC ");
|
||||
analyzeOld(file);
|
||||
|
||||
if(offset < 0) {
|
||||
debug("APE::Properties::findDescriptor() -- APE descriptor not found");
|
||||
return -1;
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
long APE::Properties::findID3v2()
|
||||
{
|
||||
if(!d->file->isValid())
|
||||
return -1;
|
||||
|
||||
d->file->seek(0);
|
||||
|
||||
if(d->file->readBlock(3) == ID3v2::Header::fileIdentifier())
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void APE::Properties::analyzeCurrent()
|
||||
void APE::Properties::analyzeCurrent(File *file)
|
||||
{
|
||||
// Read the descriptor
|
||||
d->file->seek(2, File::Current);
|
||||
ByteVector descriptor = d->file->readBlock(44);
|
||||
const uint descriptorBytes = descriptor.toUInt(0, false);
|
||||
file->seek(2, File::Current);
|
||||
const ByteVector descriptor = file->readBlock(44);
|
||||
if(descriptor.size() < 44) {
|
||||
debug("APE::Properties::analyzeCurrent() -- descriptor is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((descriptorBytes - 52) > 0)
|
||||
d->file->seek(descriptorBytes - 52, File::Current);
|
||||
const unsigned int descriptorBytes = descriptor.toUInt(0, false);
|
||||
|
||||
if((descriptorBytes - 52) > 0)
|
||||
file->seek(descriptorBytes - 52, File::Current);
|
||||
|
||||
// Read the header
|
||||
ByteVector header = d->file->readBlock(24);
|
||||
const ByteVector header = file->readBlock(24);
|
||||
if(header.size() < 24) {
|
||||
debug("APE::Properties::analyzeCurrent() -- MAC header is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the APE info
|
||||
d->channels = header.toShort(18, false);
|
||||
d->sampleRate = header.toUInt(20, false);
|
||||
d->bitsPerSample = header.toShort(16, false);
|
||||
//d->compressionLevel =
|
||||
|
||||
const uint totalFrames = header.toUInt(12, false);
|
||||
const uint blocksPerFrame = header.toUInt(4, false);
|
||||
const uint finalFrameBlocks = header.toUInt(8, false);
|
||||
d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
|
||||
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
const unsigned int totalFrames = header.toUInt(12, false);
|
||||
if(totalFrames == 0)
|
||||
return;
|
||||
|
||||
const unsigned int blocksPerFrame = header.toUInt(4, false);
|
||||
const unsigned int finalFrameBlocks = header.toUInt(8, false);
|
||||
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
|
||||
}
|
||||
|
||||
void APE::Properties::analyzeOld()
|
||||
void APE::Properties::analyzeOld(File *file)
|
||||
{
|
||||
ByteVector header = d->file->readBlock(26);
|
||||
const uint totalFrames = header.toUInt(18, false);
|
||||
const ByteVector header = file->readBlock(26);
|
||||
if(header.size() < 26) {
|
||||
debug("APE::Properties::analyzeOld() -- MAC header is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned int totalFrames = header.toUInt(18, false);
|
||||
|
||||
// Fail on 0 length APE files (catches non-finalized APE files)
|
||||
if(totalFrames == 0)
|
||||
return;
|
||||
|
||||
const short compressionLevel = header.toShort(0, false);
|
||||
uint blocksPerFrame;
|
||||
unsigned int blocksPerFrame;
|
||||
if(d->version >= 3950)
|
||||
blocksPerFrame = 73728 * 4;
|
||||
else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000))
|
||||
blocksPerFrame = 73728;
|
||||
else
|
||||
blocksPerFrame = 9216;
|
||||
|
||||
// Get the APE info
|
||||
d->channels = header.toShort(4, false);
|
||||
d->sampleRate = header.toUInt(6, false);
|
||||
const uint finalFrameBlocks = header.toUInt(22, false);
|
||||
const uint totalBlocks
|
||||
= totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
|
||||
d->length = totalBlocks / d->sampleRate;
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
}
|
||||
|
||||
const unsigned int finalFrameBlocks = header.toUInt(22, false);
|
||||
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
|
||||
|
||||
// Get the bit depth from the RIFF-fmt chunk.
|
||||
file->seek(16, File::Current);
|
||||
const ByteVector fmt = file->readBlock(28);
|
||||
if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) {
|
||||
debug("APE::Properties::analyzeOld() -- fmt header is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->bitsPerSample = fmt.toShort(26, false);
|
||||
}
|
||||
|
||||
@@ -51,27 +51,74 @@ namespace TagLib {
|
||||
public:
|
||||
/*!
|
||||
* Create an instance of APE::Properties with the data read from the
|
||||
* ByteVector \a data.
|
||||
* APE::File \a file.
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
Properties(File *f, ReadStyle style = Average);
|
||||
Properties(File *file, ReadStyle style = Average);
|
||||
|
||||
/*!
|
||||
* Create an instance of APE::Properties with the data read from the
|
||||
* APE::File \a file.
|
||||
*/
|
||||
Properties(File *file, long streamLength, ReadStyle style = Average);
|
||||
|
||||
/*!
|
||||
* Destroys this APE::Properties instance.
|
||||
*/
|
||||
virtual ~Properties();
|
||||
|
||||
// Reimplementations.
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \note This method is just an alias of lengthInSeconds().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
virtual int length() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the average bit rate of the file in kb/s.
|
||||
*/
|
||||
virtual int bitrate() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns number of bits per sample.
|
||||
* Returns the number of bits per audio sample.
|
||||
*/
|
||||
int bitsPerSample() const;
|
||||
uint sampleFrames() const;
|
||||
|
||||
/*!
|
||||
* Returns the total number of audio samples in file.
|
||||
*/
|
||||
unsigned int sampleFrames() const;
|
||||
|
||||
/*!
|
||||
* Returns APE version.
|
||||
@@ -82,13 +129,10 @@ namespace TagLib {
|
||||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(File *file, long streamLength);
|
||||
|
||||
long findDescriptor();
|
||||
long findID3v2();
|
||||
|
||||
void analyzeCurrent();
|
||||
void analyzeOld();
|
||||
void analyzeCurrent(File *file);
|
||||
void analyzeOld(File *file);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef __SUNPRO_CC
|
||||
#if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130)
|
||||
// Sun Studio finds multiple specializations of Map because
|
||||
// it considers specializations with and without class types
|
||||
// to be different; this define forces Map to use only the
|
||||
@@ -35,6 +35,8 @@
|
||||
#include <tstring.h>
|
||||
#include <tmap.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
#include <tutils.h>
|
||||
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
@@ -43,17 +45,43 @@
|
||||
using namespace TagLib;
|
||||
using namespace APE;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool isKeyValid(const char *key, size_t length)
|
||||
{
|
||||
const char *invalidKeys[] = { "ID3", "TAG", "OGGS", "MP+", 0 };
|
||||
|
||||
if(length < 2 || length > 255)
|
||||
return false;
|
||||
|
||||
// only allow printable ASCII including space (32..126)
|
||||
|
||||
for(const char *p = key; p < key + length; ++p) {
|
||||
const int c = static_cast<unsigned char>(*p);
|
||||
if(c < 32 || c > 126)
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; invalidKeys[i] != 0; ++i) {
|
||||
if(Utils::equalsIgnoreCase(key, invalidKeys[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class APE::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate() : file(0), footerLocation(-1), tagLength(0) {}
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
footerLocation(0) {}
|
||||
|
||||
TagLib::File *file;
|
||||
File *file;
|
||||
long footerLocation;
|
||||
long tagLength;
|
||||
|
||||
Footer footer;
|
||||
|
||||
ItemListMap itemListMap;
|
||||
};
|
||||
|
||||
@@ -61,14 +89,16 @@ public:
|
||||
// public methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::Tag::Tag() : TagLib::Tag()
|
||||
APE::Tag::Tag() :
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
}
|
||||
|
||||
APE::Tag::Tag(TagLib::File *file, long footerLocation) : TagLib::Tag()
|
||||
APE::Tag::Tag(TagLib::File *file, long footerLocation) :
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
d->file = file;
|
||||
d->footerLocation = footerLocation;
|
||||
|
||||
@@ -88,46 +118,46 @@ ByteVector APE::Tag::fileIdentifier()
|
||||
String APE::Tag::title() const
|
||||
{
|
||||
if(d->itemListMap["TITLE"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["TITLE"].toString();
|
||||
return String();
|
||||
return d->itemListMap["TITLE"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::artist() const
|
||||
{
|
||||
if(d->itemListMap["ARTIST"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["ARTIST"].toString();
|
||||
return String();
|
||||
return d->itemListMap["ARTIST"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::album() const
|
||||
{
|
||||
if(d->itemListMap["ALBUM"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["ALBUM"].toString();
|
||||
return String();
|
||||
return d->itemListMap["ALBUM"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::comment() const
|
||||
{
|
||||
if(d->itemListMap["COMMENT"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["COMMENT"].toString();
|
||||
return String();
|
||||
return d->itemListMap["COMMENT"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::genre() const
|
||||
{
|
||||
if(d->itemListMap["GENRE"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["GENRE"].toString();
|
||||
return String();
|
||||
return d->itemListMap["GENRE"].values().toString();
|
||||
}
|
||||
|
||||
TagLib::uint APE::Tag::year() const
|
||||
unsigned int APE::Tag::year() const
|
||||
{
|
||||
if(d->itemListMap["YEAR"].isEmpty())
|
||||
return 0;
|
||||
return d->itemListMap["YEAR"].toString().toInt();
|
||||
}
|
||||
|
||||
TagLib::uint APE::Tag::track() const
|
||||
unsigned int APE::Tag::track() const
|
||||
{
|
||||
if(d->itemListMap["TRACK"].isEmpty())
|
||||
return 0;
|
||||
@@ -159,7 +189,7 @@ void APE::Tag::setGenre(const String &s)
|
||||
addValue("GENRE", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setYear(uint i)
|
||||
void APE::Tag::setYear(unsigned int i)
|
||||
{
|
||||
if(i <= 0)
|
||||
removeItem("YEAR");
|
||||
@@ -167,7 +197,7 @@ void APE::Tag::setYear(uint i)
|
||||
addValue("YEAR", String::number(i), true);
|
||||
}
|
||||
|
||||
void APE::Tag::setTrack(uint i)
|
||||
void APE::Tag::setTrack(unsigned int i)
|
||||
{
|
||||
if(i <= 0)
|
||||
removeItem("TRACK");
|
||||
@@ -175,14 +205,18 @@ void APE::Tag::setTrack(uint i)
|
||||
addValue("TRACK", String::number(i), true);
|
||||
}
|
||||
|
||||
// conversions of tag keys between what we use in PropertyMap and what's usual
|
||||
// for APE tags
|
||||
static const TagLib::uint keyConversionsSize = 5; //usual, APE
|
||||
static const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" },
|
||||
{"DATE", "YEAR" },
|
||||
{"ALBUMARTIST", "ALBUM ARTIST"},
|
||||
{"DISCNUMBER", "DISC" },
|
||||
{"REMIXER", "MIXARTIST" }};
|
||||
namespace
|
||||
{
|
||||
// conversions of tag keys between what we use in PropertyMap and what's usual
|
||||
// for APE tags
|
||||
// usual, APE
|
||||
const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" },
|
||||
{"DATE", "YEAR" },
|
||||
{"ALBUMARTIST", "ALBUM ARTIST"},
|
||||
{"DISCNUMBER", "DISC" },
|
||||
{"REMIXER", "MIXARTIST" }};
|
||||
const size_t keyConversionsSize = sizeof(keyConversions) / sizeof(keyConversions[0]);
|
||||
}
|
||||
|
||||
PropertyMap APE::Tag::properties() const
|
||||
{
|
||||
@@ -192,14 +226,16 @@ PropertyMap APE::Tag::properties() const
|
||||
String tagName = it->first.upper();
|
||||
// if the item is Binary or Locator, or if the key is an invalid string,
|
||||
// add to unsupportedData
|
||||
if(it->second.type() != Item::Text || tagName.isNull())
|
||||
if(it->second.type() != Item::Text || tagName.isEmpty()) {
|
||||
properties.unsupportedData().append(it->first);
|
||||
}
|
||||
else {
|
||||
// Some tags need to be handled specially
|
||||
for(uint i = 0; i < keyConversionsSize; ++i)
|
||||
for(size_t i = 0; i < keyConversionsSize; ++i) {
|
||||
if(tagName == keyConversions[i][1])
|
||||
tagName = keyConversions[i][0];
|
||||
properties[tagName].append(it->second.toStringList());
|
||||
}
|
||||
properties[tagName].append(it->second.toStringList());
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
@@ -217,7 +253,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
PropertyMap properties(origProps); // make a local copy that can be modified
|
||||
|
||||
// see comment in properties()
|
||||
for(uint i = 0; i < keyConversionsSize; ++i)
|
||||
for(size_t i = 0; i < keyConversionsSize; ++i)
|
||||
if(properties.contains(keyConversions[i][0])) {
|
||||
properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]);
|
||||
properties.erase(keyConversions[i][0]);
|
||||
@@ -229,11 +265,11 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
for(; remIt != itemListMap().end(); ++remIt) {
|
||||
String key = remIt->first.upper();
|
||||
// only remove if a) key is valid, b) type is text, c) key not contained in new properties
|
||||
if(!key.isNull() && remIt->second.type() == APE::Item::Text && !properties.contains(key))
|
||||
if(!key.isEmpty() && remIt->second.type() == APE::Item::Text && !properties.contains(key))
|
||||
toRemove.append(remIt->first);
|
||||
}
|
||||
|
||||
for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++)
|
||||
for(StringList::ConstIterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++)
|
||||
removeItem(*removeIt);
|
||||
|
||||
// now sync in the "forward direction"
|
||||
@@ -244,7 +280,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
if(!checkKey(tagName))
|
||||
invalid.insert(it->first, it->second);
|
||||
else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
|
||||
if(it->second.size() == 0)
|
||||
if(it->second.isEmpty())
|
||||
removeItem(tagName);
|
||||
else {
|
||||
StringList::ConstIterator valueIt = it->second.begin();
|
||||
@@ -260,16 +296,11 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
|
||||
bool APE::Tag::checkKey(const String &key)
|
||||
{
|
||||
if(key.size() < 2 || key.size() > 16)
|
||||
return false;
|
||||
for(String::ConstIterator it = key.begin(); it != key.end(); it++)
|
||||
// only allow printable ASCII including space (32..127)
|
||||
if (*it < 32 || *it >= 128)
|
||||
return false;
|
||||
String upperKey = key.upper();
|
||||
if (upperKey=="ID3" || upperKey=="TAG" || upperKey=="OGGS" || upperKey=="MP+")
|
||||
return false;
|
||||
return true;
|
||||
if(!key.isLatin1())
|
||||
return false;
|
||||
|
||||
const std::string data = key.to8Bit(false);
|
||||
return isKeyValid(data.c_str(), data.size());
|
||||
}
|
||||
|
||||
APE::Footer *APE::Tag::footer() const
|
||||
@@ -284,40 +315,46 @@ const APE::ItemListMap& APE::Tag::itemListMap() const
|
||||
|
||||
void APE::Tag::removeItem(const String &key)
|
||||
{
|
||||
Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
|
||||
if(it != d->itemListMap.end())
|
||||
d->itemListMap.erase(it);
|
||||
d->itemListMap.erase(key.upper());
|
||||
}
|
||||
|
||||
void APE::Tag::addValue(const String &key, const String &value, bool replace)
|
||||
{
|
||||
if(replace)
|
||||
removeItem(key);
|
||||
if(!key.isEmpty() && !value.isEmpty()) {
|
||||
if(!replace && d->itemListMap.contains(key)) {
|
||||
// Text items may contain more than one value
|
||||
if(APE::Item::Text == d->itemListMap.begin()->second.type())
|
||||
d->itemListMap[key.upper()].appendValue(value);
|
||||
// Binary or locator items may have only one value
|
||||
else
|
||||
setItem(key, Item(key, value));
|
||||
}
|
||||
else
|
||||
setItem(key, Item(key, value));
|
||||
}
|
||||
|
||||
if(value.isEmpty())
|
||||
return;
|
||||
|
||||
// Text items may contain more than one value.
|
||||
// Binary or locator items may have only one value, hence always replaced.
|
||||
|
||||
ItemListMap::Iterator it = d->itemListMap.find(key.upper());
|
||||
|
||||
if(it != d->itemListMap.end() && it->second.type() == Item::Text)
|
||||
it->second.appendValue(value);
|
||||
else
|
||||
setItem(key, Item(key, value));
|
||||
}
|
||||
|
||||
void APE::Tag::setData(const String &key, const ByteVector &value)
|
||||
{
|
||||
removeItem(key);
|
||||
if(!key.isEmpty() && !value.isEmpty())
|
||||
setItem(key, Item(key, value, true));
|
||||
|
||||
if(value.isEmpty())
|
||||
return;
|
||||
|
||||
setItem(key, Item(key, value, true));
|
||||
}
|
||||
|
||||
void APE::Tag::setItem(const String &key, const Item &item)
|
||||
{
|
||||
if(!key.isEmpty())
|
||||
d->itemListMap.insert(key.upper(), item);
|
||||
if(!checkKey(key)) {
|
||||
debug("APE::Tag::setItem() - Couldn't set an item due to an invalid key.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->itemListMap[key.upper()] = item;
|
||||
}
|
||||
|
||||
bool APE::Tag::isEmpty() const
|
||||
@@ -337,7 +374,7 @@ void APE::Tag::read()
|
||||
d->footer.setData(d->file->readBlock(Footer::size()));
|
||||
|
||||
if(d->footer.tagSize() <= Footer::size() ||
|
||||
d->footer.tagSize() > uint(d->file->length()))
|
||||
d->footer.tagSize() > static_cast<unsigned long>(d->file->length()))
|
||||
return;
|
||||
|
||||
d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize());
|
||||
@@ -348,15 +385,11 @@ void APE::Tag::read()
|
||||
ByteVector APE::Tag::render() const
|
||||
{
|
||||
ByteVector data;
|
||||
uint itemCount = 0;
|
||||
unsigned int itemCount = 0;
|
||||
|
||||
{
|
||||
for(Map<const String, Item>::ConstIterator it = d->itemListMap.begin();
|
||||
it != d->itemListMap.end(); ++it)
|
||||
{
|
||||
data.append(it->second.render());
|
||||
itemCount++;
|
||||
}
|
||||
for(ItemListMap::ConstIterator it = d->itemListMap.begin(); it != d->itemListMap.end(); ++it) {
|
||||
data.append(it->second.render());
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
d->footer.setItemCount(itemCount);
|
||||
@@ -368,16 +401,34 @@ ByteVector APE::Tag::render() const
|
||||
|
||||
void APE::Tag::parse(const ByteVector &data)
|
||||
{
|
||||
uint pos = 0;
|
||||
|
||||
// 11 bytes is the minimum size for an APE item
|
||||
|
||||
for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) {
|
||||
APE::Item item;
|
||||
item.parse(data.mid(pos));
|
||||
if(data.size() < 11)
|
||||
return;
|
||||
|
||||
d->itemListMap.insert(item.key().upper(), item);
|
||||
unsigned int pos = 0;
|
||||
|
||||
pos += item.size();
|
||||
for(unsigned int i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) {
|
||||
|
||||
const int nullPos = data.find('\0', pos + 8);
|
||||
if(nullPos < 0) {
|
||||
debug("APE::Tag::parse() - Couldn't find a key/value separator. Stopped parsing.");
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned int keyLength = nullPos - pos - 8;
|
||||
const unsigned int valLegnth = data.toUInt(pos, false);
|
||||
|
||||
if(isKeyValid(&data[pos + 8], keyLength)){
|
||||
APE::Item item;
|
||||
item.parse(data.mid(pos));
|
||||
|
||||
d->itemListMap.insert(item.key().upper(), item);
|
||||
}
|
||||
else {
|
||||
debug("APE::Tag::parse() - Skipped an item due to an invalid key.");
|
||||
}
|
||||
|
||||
pos += keyLength + valLegnth + 9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +92,16 @@ namespace TagLib {
|
||||
virtual String album() const;
|
||||
virtual String comment() const;
|
||||
virtual String genre() const;
|
||||
virtual uint year() const;
|
||||
virtual uint track() const;
|
||||
virtual unsigned int year() const;
|
||||
virtual unsigned int track() const;
|
||||
|
||||
virtual void setTitle(const String &s);
|
||||
virtual void setArtist(const String &s);
|
||||
virtual void setAlbum(const String &s);
|
||||
virtual void setComment(const String &s);
|
||||
virtual void setGenre(const String &s);
|
||||
virtual void setYear(uint i);
|
||||
virtual void setTrack(uint i);
|
||||
virtual void setYear(unsigned int i);
|
||||
virtual void setTrack(unsigned int i);
|
||||
|
||||
/*!
|
||||
* Implements the unified tag dictionary interface -- export function.
|
||||
@@ -143,7 +143,7 @@ namespace TagLib {
|
||||
* Returns a reference to the item list map. This is an ItemListMap of
|
||||
* all of the items in the tag.
|
||||
*
|
||||
* This is the most powerfull structure for accessing the items of the tag.
|
||||
* This is the most powerful structure for accessing the items of the tag.
|
||||
*
|
||||
* APE tags are case-insensitive, all keys in this map have been converted
|
||||
* to upper case.
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
|
||||
#include <taglib.h>
|
||||
#include <tdebug.h>
|
||||
#include "trefcounter.h"
|
||||
#include <trefcounter.h>
|
||||
|
||||
#include "asfattribute.h"
|
||||
#include "asffile.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -56,82 +58,86 @@ public:
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::Attribute::Attribute()
|
||||
ASF::Attribute::Attribute() :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = UnicodeType;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const ASF::Attribute &other)
|
||||
: d(other.d)
|
||||
ASF::Attribute::Attribute(const ASF::Attribute &other) :
|
||||
d(other.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const String &value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = UnicodeType;
|
||||
d->stringValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const ByteVector &value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = BytesType;
|
||||
d->byteVectorValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const ASF::Picture &value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = BytesType;
|
||||
d->pictureValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned int value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = DWordType;
|
||||
d->intValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned long long value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = QWordType;
|
||||
d->longLongValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned short value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = WordType;
|
||||
d->shortValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(bool value) :
|
||||
d(new AttributePrivate())
|
||||
{
|
||||
d->type = BoolType;
|
||||
d->boolValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other)
|
||||
{
|
||||
if(d->deref())
|
||||
delete d;
|
||||
d = other.d;
|
||||
d->ref();
|
||||
Attribute(other).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ASF::Attribute::swap(Attribute &other)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, other.d);
|
||||
}
|
||||
|
||||
ASF::Attribute::~Attribute()
|
||||
{
|
||||
if(d->deref())
|
||||
delete d;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const String &value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = UnicodeType;
|
||||
d->stringValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const ByteVector &value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = BytesType;
|
||||
d->byteVectorValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(const ASF::Picture &value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = BytesType;
|
||||
d->pictureValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned int value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = DWordType;
|
||||
d->intValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned long long value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = QWordType;
|
||||
d->longLongValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(unsigned short value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = WordType;
|
||||
d->shortValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::Attribute(bool value)
|
||||
{
|
||||
d = new AttributePrivate;
|
||||
d->type = BoolType;
|
||||
d->boolValue = value;
|
||||
}
|
||||
|
||||
ASF::Attribute::AttributeTypes ASF::Attribute::type() const
|
||||
{
|
||||
return d->type;
|
||||
@@ -176,28 +182,28 @@ ASF::Picture ASF::Attribute::toPicture() const
|
||||
|
||||
String ASF::Attribute::parse(ASF::File &f, int kind)
|
||||
{
|
||||
uint size, nameLength;
|
||||
unsigned int size, nameLength;
|
||||
String name;
|
||||
d->pictureValue = Picture::fromInvalid();
|
||||
// extended content descriptor
|
||||
if(kind == 0) {
|
||||
nameLength = f.readWORD();
|
||||
name = f.readString(nameLength);
|
||||
d->type = ASF::Attribute::AttributeTypes(f.readWORD());
|
||||
size = f.readWORD();
|
||||
nameLength = readWORD(&f);
|
||||
name = readString(&f, nameLength);
|
||||
d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
|
||||
size = readWORD(&f);
|
||||
}
|
||||
// metadata & metadata library
|
||||
else {
|
||||
int temp = f.readWORD();
|
||||
int temp = readWORD(&f);
|
||||
// metadata library
|
||||
if(kind == 2) {
|
||||
d->language = temp;
|
||||
}
|
||||
d->stream = f.readWORD();
|
||||
nameLength = f.readWORD();
|
||||
d->type = ASF::Attribute::AttributeTypes(f.readWORD());
|
||||
size = f.readDWORD();
|
||||
name = f.readString(nameLength);
|
||||
d->stream = readWORD(&f);
|
||||
nameLength = readWORD(&f);
|
||||
d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
|
||||
size = readDWORD(&f);
|
||||
name = readString(&f, nameLength);
|
||||
}
|
||||
|
||||
if(kind != 2 && size > 65535) {
|
||||
@@ -206,28 +212,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
|
||||
|
||||
switch(d->type) {
|
||||
case WordType:
|
||||
d->shortValue = f.readWORD();
|
||||
d->shortValue = readWORD(&f);
|
||||
break;
|
||||
|
||||
case BoolType:
|
||||
if(kind == 0) {
|
||||
d->boolValue = f.readDWORD() == 1;
|
||||
d->boolValue = (readDWORD(&f) == 1);
|
||||
}
|
||||
else {
|
||||
d->boolValue = f.readWORD() == 1;
|
||||
d->boolValue = (readWORD(&f) == 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case DWordType:
|
||||
d->intValue = f.readDWORD();
|
||||
d->intValue = readDWORD(&f);
|
||||
break;
|
||||
|
||||
case QWordType:
|
||||
d->longLongValue = f.readQWORD();
|
||||
d->longLongValue = readQWORD(&f);
|
||||
break;
|
||||
|
||||
case UnicodeType:
|
||||
d->stringValue = f.readString(size);
|
||||
d->stringValue = readString(&f, size);
|
||||
break;
|
||||
|
||||
case BytesType:
|
||||
@@ -295,7 +301,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
|
||||
break;
|
||||
|
||||
case UnicodeType:
|
||||
data.append(File::renderString(d->stringValue));
|
||||
data.append(renderString(d->stringValue));
|
||||
break;
|
||||
|
||||
case BytesType:
|
||||
@@ -309,13 +315,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
|
||||
}
|
||||
|
||||
if(kind == 0) {
|
||||
data = File::renderString(name, true) +
|
||||
data = renderString(name, true) +
|
||||
ByteVector::fromShort((int)d->type, false) +
|
||||
ByteVector::fromShort(data.size(), false) +
|
||||
data;
|
||||
}
|
||||
else {
|
||||
ByteVector nameData = File::renderString(name);
|
||||
ByteVector nameData = renderString(name);
|
||||
data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) +
|
||||
ByteVector::fromShort(d->stream, false) +
|
||||
ByteVector::fromShort(nameData.size(), false) +
|
||||
@@ -347,4 +353,3 @@ void ASF::Attribute::setStream(int value)
|
||||
{
|
||||
d->stream = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,11 @@ namespace TagLib
|
||||
*/
|
||||
ASF::Attribute &operator=(const Attribute &other);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the Attribute by the content of \a other.
|
||||
*/
|
||||
void swap(Attribute &other);
|
||||
|
||||
/*!
|
||||
* Destroys the attribute.
|
||||
*/
|
||||
|
||||
@@ -27,206 +27,257 @@
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tstring.h>
|
||||
|
||||
#include "asffile.h"
|
||||
#include "asftag.h"
|
||||
#include "asfproperties.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class ASF::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
class BaseObject;
|
||||
class UnknownObject;
|
||||
class FilePropertiesObject;
|
||||
class StreamPropertiesObject;
|
||||
class ContentDescriptionObject;
|
||||
class ExtendedContentDescriptionObject;
|
||||
class HeaderExtensionObject;
|
||||
class CodecListObject;
|
||||
class MetadataObject;
|
||||
class MetadataLibraryObject;
|
||||
|
||||
FilePrivate():
|
||||
size(0),
|
||||
headerSize(0),
|
||||
tag(0),
|
||||
properties(0),
|
||||
contentDescriptionObject(0),
|
||||
extendedContentDescriptionObject(0),
|
||||
headerExtensionObject(0),
|
||||
metadataObject(0),
|
||||
metadataLibraryObject(0) {}
|
||||
unsigned long long size;
|
||||
metadataLibraryObject(0)
|
||||
{
|
||||
objects.setAutoDelete(true);
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete tag;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
unsigned long long headerSize;
|
||||
|
||||
ASF::Tag *tag;
|
||||
ASF::Properties *properties;
|
||||
List<ASF::File::BaseObject *> objects;
|
||||
ASF::File::ContentDescriptionObject *contentDescriptionObject;
|
||||
ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject;
|
||||
ASF::File::HeaderExtensionObject *headerExtensionObject;
|
||||
ASF::File::MetadataObject *metadataObject;
|
||||
ASF::File::MetadataLibraryObject *metadataLibraryObject;
|
||||
|
||||
List<BaseObject *> objects;
|
||||
|
||||
ContentDescriptionObject *contentDescriptionObject;
|
||||
ExtendedContentDescriptionObject *extendedContentDescriptionObject;
|
||||
HeaderExtensionObject *headerExtensionObject;
|
||||
MetadataObject *metadataObject;
|
||||
MetadataLibraryObject *metadataLibraryObject;
|
||||
};
|
||||
|
||||
static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16);
|
||||
static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
|
||||
static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
|
||||
static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16);
|
||||
static ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16);
|
||||
static ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16);
|
||||
static ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16);
|
||||
namespace
|
||||
{
|
||||
const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16);
|
||||
const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
|
||||
const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
|
||||
const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16);
|
||||
const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16);
|
||||
const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16);
|
||||
const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16);
|
||||
const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16);
|
||||
}
|
||||
|
||||
class ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVector data;
|
||||
virtual ~BaseObject() {}
|
||||
virtual ByteVector guid() = 0;
|
||||
virtual ByteVector guid() const = 0;
|
||||
virtual void parse(ASF::File *file, unsigned int size);
|
||||
virtual ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::UnknownObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
ByteVector myGuid;
|
||||
public:
|
||||
UnknownObject(const ByteVector &guid);
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
};
|
||||
|
||||
class ASF::File::FilePropertiesObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
};
|
||||
|
||||
class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
};
|
||||
|
||||
class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::MetadataObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
List<ASF::File::BaseObject *> objects;
|
||||
~HeaderExtensionObject();
|
||||
ByteVector guid();
|
||||
void parse(ASF::File *file, uint size);
|
||||
List<ASF::File::FilePrivate::BaseObject *> objects;
|
||||
HeaderExtensionObject();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
ASF::File::HeaderExtensionObject::~HeaderExtensionObject()
|
||||
class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
for(unsigned int i = 0; i < objects.size(); i++) {
|
||||
delete objects[i];
|
||||
}
|
||||
}
|
||||
public:
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, unsigned int size);
|
||||
|
||||
void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size)
|
||||
private:
|
||||
enum CodecType
|
||||
{
|
||||
Video = 0x0001,
|
||||
Audio = 0x0002,
|
||||
Unknown = 0xFFFF
|
||||
};
|
||||
};
|
||||
|
||||
void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size)
|
||||
{
|
||||
data.clear();
|
||||
if (size > 24 && size <= (unsigned int)(file->length()))
|
||||
if(size > 24 && size <= (unsigned int)(file->length()))
|
||||
data = file->readBlock(size - 24);
|
||||
else
|
||||
data = ByteVector::null;
|
||||
data = ByteVector();
|
||||
}
|
||||
|
||||
ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/)
|
||||
ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/)
|
||||
{
|
||||
return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data;
|
||||
}
|
||||
|
||||
ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid)
|
||||
ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid)
|
||||
{
|
||||
}
|
||||
|
||||
ByteVector ASF::File::UnknownObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::UnknownObject::guid() const
|
||||
{
|
||||
return myGuid;
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePropertiesObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const
|
||||
{
|
||||
return filePropertiesGuid;
|
||||
}
|
||||
|
||||
void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size)
|
||||
void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, unsigned int size)
|
||||
{
|
||||
BaseObject::parse(file, size);
|
||||
file->d->properties->setLength(
|
||||
(int)(data.toLongLong(40, false) / 10000000L - data.toLongLong(56, false) / 1000L));
|
||||
if(data.size() < 64) {
|
||||
debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
const long long duration = data.toLongLong(40, false);
|
||||
const long long preroll = data.toLongLong(56, false);
|
||||
file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5));
|
||||
}
|
||||
|
||||
ByteVector ASF::File::StreamPropertiesObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
|
||||
{
|
||||
return streamPropertiesGuid;
|
||||
}
|
||||
|
||||
void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size)
|
||||
void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, unsigned int size)
|
||||
{
|
||||
BaseObject::parse(file, size);
|
||||
file->d->properties->setChannels(data.toShort(56, false));
|
||||
if(data.size() < 70) {
|
||||
debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
file->d->properties->setCodec(data.toUShort(54, false));
|
||||
file->d->properties->setChannels(data.toUShort(56, false));
|
||||
file->d->properties->setSampleRate(data.toUInt(58, false));
|
||||
file->d->properties->setBitrate(data.toUInt(62, false) * 8 / 1000);
|
||||
file->d->properties->setBitrate(static_cast<int>(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5));
|
||||
file->d->properties->setBitsPerSample(data.toUShort(68, false));
|
||||
}
|
||||
|
||||
ByteVector ASF::File::ContentDescriptionObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const
|
||||
{
|
||||
return contentDescriptionGuid;
|
||||
}
|
||||
|
||||
void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
|
||||
void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/)
|
||||
{
|
||||
file->d->contentDescriptionObject = this;
|
||||
int titleLength = file->readWORD();
|
||||
int artistLength = file->readWORD();
|
||||
int copyrightLength = file->readWORD();
|
||||
int commentLength = file->readWORD();
|
||||
int ratingLength = file->readWORD();
|
||||
file->d->tag->setTitle(file->readString(titleLength));
|
||||
file->d->tag->setArtist(file->readString(artistLength));
|
||||
file->d->tag->setCopyright(file->readString(copyrightLength));
|
||||
file->d->tag->setComment(file->readString(commentLength));
|
||||
file->d->tag->setRating(file->readString(ratingLength));
|
||||
const int titleLength = readWORD(file);
|
||||
const int artistLength = readWORD(file);
|
||||
const int copyrightLength = readWORD(file);
|
||||
const int commentLength = readWORD(file);
|
||||
const int ratingLength = readWORD(file);
|
||||
file->d->tag->setTitle(readString(file,titleLength));
|
||||
file->d->tag->setArtist(readString(file,artistLength));
|
||||
file->d->tag->setCopyright(readString(file,copyrightLength));
|
||||
file->d->tag->setComment(readString(file,commentLength));
|
||||
file->d->tag->setRating(readString(file,ratingLength));
|
||||
}
|
||||
|
||||
ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file)
|
||||
ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file)
|
||||
{
|
||||
ByteVector v1 = file->renderString(file->d->tag->title());
|
||||
ByteVector v2 = file->renderString(file->d->tag->artist());
|
||||
ByteVector v3 = file->renderString(file->d->tag->copyright());
|
||||
ByteVector v4 = file->renderString(file->d->tag->comment());
|
||||
ByteVector v5 = file->renderString(file->d->tag->rating());
|
||||
const ByteVector v1 = renderString(file->d->tag->title());
|
||||
const ByteVector v2 = renderString(file->d->tag->artist());
|
||||
const ByteVector v3 = renderString(file->d->tag->copyright());
|
||||
const ByteVector v4 = renderString(file->d->tag->comment());
|
||||
const ByteVector v5 = renderString(file->d->tag->rating());
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(v1.size(), false));
|
||||
data.append(ByteVector::fromShort(v2.size(), false));
|
||||
@@ -241,15 +292,15 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file)
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::ExtendedContentDescriptionObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const
|
||||
{
|
||||
return extendedContentDescriptionGuid;
|
||||
}
|
||||
|
||||
void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
|
||||
void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/)
|
||||
{
|
||||
file->d->extendedContentDescriptionObject = this;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
String name = attribute.parse(*file);
|
||||
@@ -257,23 +308,23 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file)
|
||||
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::MetadataObject::guid() const
|
||||
{
|
||||
return metadataGuid;
|
||||
}
|
||||
|
||||
void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/)
|
||||
void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, unsigned int /*size*/)
|
||||
{
|
||||
file->d->metadataObject = this;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
String name = attribute.parse(*file, 1);
|
||||
@@ -281,23 +332,23 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/)
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataObject::render(ASF::File *file)
|
||||
ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataLibraryObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const
|
||||
{
|
||||
return metadataLibraryGuid;
|
||||
}
|
||||
|
||||
void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
|
||||
void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, unsigned int /*size*/)
|
||||
{
|
||||
file->d->metadataLibraryObject = this;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
String name = attribute.parse(*file, 2);
|
||||
@@ -305,24 +356,29 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file)
|
||||
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::HeaderExtensionObject::guid()
|
||||
ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject()
|
||||
{
|
||||
objects.setAutoDelete(true);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const
|
||||
{
|
||||
return headerExtensionGuid;
|
||||
}
|
||||
|
||||
void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
||||
void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, unsigned int /*size*/)
|
||||
{
|
||||
file->d->headerExtensionObject = this;
|
||||
file->seek(18, File::Current);
|
||||
long long dataSize = file->readDWORD();
|
||||
long long dataSize = readDWORD(file);
|
||||
long long dataPos = 0;
|
||||
while(dataPos < dataSize) {
|
||||
ByteVector guid = file->readBlock(16);
|
||||
@@ -331,7 +387,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
||||
break;
|
||||
}
|
||||
bool ok;
|
||||
long long size = file->readQWORD(&ok);
|
||||
long long size = readQWORD(file, &ok);
|
||||
if(!ok) {
|
||||
file->setValid(false);
|
||||
break;
|
||||
@@ -352,47 +408,93 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file)
|
||||
ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
for(unsigned int i = 0; i < objects.size(); i++) {
|
||||
data.append(objects[i]->render(file));
|
||||
for(List<BaseObject *>::ConstIterator it = objects.begin(); it != objects.end(); ++it) {
|
||||
data.append((*it)->render(file));
|
||||
}
|
||||
data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data;
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePrivate::CodecListObject::guid() const
|
||||
{
|
||||
return codecListGuid;
|
||||
}
|
||||
|
||||
void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, unsigned int size)
|
||||
{
|
||||
BaseObject::parse(file, size);
|
||||
if(data.size() <= 20) {
|
||||
debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int pos = 16;
|
||||
|
||||
const int count = data.toUInt(pos, false);
|
||||
pos += 4;
|
||||
|
||||
for(int i = 0; i < count; ++i) {
|
||||
|
||||
if(pos >= data.size())
|
||||
break;
|
||||
|
||||
const CodecType type = static_cast<CodecType>(data.toUShort(pos, false));
|
||||
pos += 2;
|
||||
|
||||
int nameLength = data.toUShort(pos, false);
|
||||
pos += 2;
|
||||
|
||||
const unsigned int namePos = pos;
|
||||
pos += nameLength * 2;
|
||||
|
||||
const int descLength = data.toUShort(pos, false);
|
||||
pos += 2;
|
||||
|
||||
const unsigned int descPos = pos;
|
||||
pos += descLength * 2;
|
||||
|
||||
const int infoLength = data.toUShort(pos, false);
|
||||
pos += 2 + infoLength * 2;
|
||||
|
||||
if(type == CodecListObject::Audio) {
|
||||
// First audio codec found.
|
||||
|
||||
const String name(data.mid(namePos, nameLength * 2), String::UTF16LE);
|
||||
file->d->properties->setCodecName(name.stripWhiteSpace());
|
||||
|
||||
const String desc(data.mid(descPos, descLength * 2), String::UTF16LE);
|
||||
file->d->properties->setCodecDescription(desc.stripWhiteSpace());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
: TagLib::File(file)
|
||||
ASF::File::File(FileName file, bool, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read();
|
||||
}
|
||||
|
||||
ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
ASF::File::File(IOStream *stream, bool, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read();
|
||||
}
|
||||
|
||||
ASF::File::~File()
|
||||
{
|
||||
for(unsigned int i = 0; i < d->objects.size(); i++) {
|
||||
delete d->objects[i];
|
||||
}
|
||||
if(d->tag) {
|
||||
delete d->tag;
|
||||
}
|
||||
if(d->properties) {
|
||||
delete d->properties;
|
||||
}
|
||||
delete d;
|
||||
}
|
||||
|
||||
@@ -421,7 +523,95 @@ ASF::Properties *ASF::File::audioProperties() const
|
||||
return d->properties;
|
||||
}
|
||||
|
||||
void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/)
|
||||
bool ASF::File::save()
|
||||
{
|
||||
if(readOnly()) {
|
||||
debug("ASF::File::save() -- File is read only.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isValid()) {
|
||||
debug("ASF::File::save() -- Trying to save invalid file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!d->contentDescriptionObject) {
|
||||
d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject();
|
||||
d->objects.append(d->contentDescriptionObject);
|
||||
}
|
||||
if(!d->extendedContentDescriptionObject) {
|
||||
d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject();
|
||||
d->objects.append(d->extendedContentDescriptionObject);
|
||||
}
|
||||
if(!d->headerExtensionObject) {
|
||||
d->headerExtensionObject = new FilePrivate::HeaderExtensionObject();
|
||||
d->objects.append(d->headerExtensionObject);
|
||||
}
|
||||
if(!d->metadataObject) {
|
||||
d->metadataObject = new FilePrivate::MetadataObject();
|
||||
d->headerExtensionObject->objects.append(d->metadataObject);
|
||||
}
|
||||
if(!d->metadataLibraryObject) {
|
||||
d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject();
|
||||
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
|
||||
}
|
||||
|
||||
d->extendedContentDescriptionObject->attributeData.clear();
|
||||
d->metadataObject->attributeData.clear();
|
||||
d->metadataLibraryObject->attributeData.clear();
|
||||
|
||||
const AttributeListMap allAttributes = d->tag->attributeListMap();
|
||||
|
||||
for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) {
|
||||
|
||||
const String &name = it->first;
|
||||
const AttributeList &attributes = it->second;
|
||||
|
||||
bool inExtendedContentDescriptionObject = false;
|
||||
bool inMetadataObject = false;
|
||||
|
||||
for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) {
|
||||
|
||||
const Attribute &attribute = *jt;
|
||||
const bool largeValue = (attribute.dataSize() > 65535);
|
||||
const bool guid = (attribute.type() == Attribute::GuidType);
|
||||
|
||||
if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
|
||||
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
|
||||
inExtendedContentDescriptionObject = true;
|
||||
}
|
||||
else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
|
||||
d->metadataObject->attributeData.append(attribute.render(name, 1));
|
||||
inMetadataObject = true;
|
||||
}
|
||||
else {
|
||||
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector data;
|
||||
for(List<FilePrivate::BaseObject *>::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) {
|
||||
data.append((*it)->render(this));
|
||||
}
|
||||
|
||||
seek(16);
|
||||
writeBlock(ByteVector::fromLongLong(data.size() + 30, false));
|
||||
writeBlock(ByteVector::fromUInt(d->objects.size(), false));
|
||||
writeBlock(ByteVector("\x01\x02", 2));
|
||||
|
||||
insert(data, 30, static_cast<unsigned long>(d->headerSize - 30));
|
||||
|
||||
d->headerSize = data.size() + 30;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ASF::File::read()
|
||||
{
|
||||
if(!isValid())
|
||||
return;
|
||||
@@ -437,12 +627,12 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
||||
d->properties = new ASF::Properties();
|
||||
|
||||
bool ok;
|
||||
d->size = readQWORD(&ok);
|
||||
d->headerSize = readQWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
int numObjects = readDWORD(&ok);
|
||||
int numObjects = readDWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
@@ -450,31 +640,34 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
||||
seek(2, Current);
|
||||
|
||||
for(int i = 0; i < numObjects; i++) {
|
||||
ByteVector guid = readBlock(16);
|
||||
guid = readBlock(16);
|
||||
if(guid.size() != 16) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
long size = (long)readQWORD(&ok);
|
||||
long size = (long)readQWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
BaseObject *obj;
|
||||
FilePrivate::BaseObject *obj;
|
||||
if(guid == filePropertiesGuid) {
|
||||
obj = new FilePropertiesObject();
|
||||
obj = new FilePrivate::FilePropertiesObject();
|
||||
}
|
||||
else if(guid == streamPropertiesGuid) {
|
||||
obj = new StreamPropertiesObject();
|
||||
obj = new FilePrivate::StreamPropertiesObject();
|
||||
}
|
||||
else if(guid == contentDescriptionGuid) {
|
||||
obj = new ContentDescriptionObject();
|
||||
obj = new FilePrivate::ContentDescriptionObject();
|
||||
}
|
||||
else if(guid == extendedContentDescriptionGuid) {
|
||||
obj = new ExtendedContentDescriptionObject();
|
||||
obj = new FilePrivate::ExtendedContentDescriptionObject();
|
||||
}
|
||||
else if(guid == headerExtensionGuid) {
|
||||
obj = new HeaderExtensionObject();
|
||||
obj = new FilePrivate::HeaderExtensionObject();
|
||||
}
|
||||
else if(guid == codecListGuid) {
|
||||
obj = new FilePrivate::CodecListObject();
|
||||
}
|
||||
else {
|
||||
if(guid == contentEncryptionGuid ||
|
||||
@@ -482,149 +675,9 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
||||
guid == advancedContentEncryptionGuid) {
|
||||
d->properties->setEncrypted(true);
|
||||
}
|
||||
obj = new UnknownObject(guid);
|
||||
obj = new FilePrivate::UnknownObject(guid);
|
||||
}
|
||||
obj->parse(this, size);
|
||||
d->objects.append(obj);
|
||||
}
|
||||
}
|
||||
|
||||
bool ASF::File::save()
|
||||
{
|
||||
if(readOnly()) {
|
||||
debug("ASF::File::save() -- File is read only.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isValid()) {
|
||||
debug("ASF::File::save() -- Trying to save invalid file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!d->contentDescriptionObject) {
|
||||
d->contentDescriptionObject = new ContentDescriptionObject();
|
||||
d->objects.append(d->contentDescriptionObject);
|
||||
}
|
||||
if(!d->extendedContentDescriptionObject) {
|
||||
d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject();
|
||||
d->objects.append(d->extendedContentDescriptionObject);
|
||||
}
|
||||
if(!d->headerExtensionObject) {
|
||||
d->headerExtensionObject = new HeaderExtensionObject();
|
||||
d->objects.append(d->headerExtensionObject);
|
||||
}
|
||||
if(!d->metadataObject) {
|
||||
d->metadataObject = new MetadataObject();
|
||||
d->headerExtensionObject->objects.append(d->metadataObject);
|
||||
}
|
||||
if(!d->metadataLibraryObject) {
|
||||
d->metadataLibraryObject = new MetadataLibraryObject();
|
||||
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
|
||||
}
|
||||
|
||||
ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin();
|
||||
for(; it != d->tag->attributeListMap().end(); it++) {
|
||||
const String &name = it->first;
|
||||
const AttributeList &attributes = it->second;
|
||||
bool inExtendedContentDescriptionObject = false;
|
||||
bool inMetadataObject = false;
|
||||
for(unsigned int j = 0; j < attributes.size(); j++) {
|
||||
const Attribute &attribute = attributes[j];
|
||||
bool largeValue = attribute.dataSize() > 65535;
|
||||
if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
|
||||
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
|
||||
inExtendedContentDescriptionObject = true;
|
||||
}
|
||||
else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
|
||||
d->metadataObject->attributeData.append(attribute.render(name, 1));
|
||||
inMetadataObject = true;
|
||||
}
|
||||
else {
|
||||
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector data;
|
||||
for(unsigned int i = 0; i < d->objects.size(); i++) {
|
||||
data.append(d->objects[i]->render(this));
|
||||
}
|
||||
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data;
|
||||
insert(data, 0, (TagLib::ulong)d->size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int ASF::File::readBYTE(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(1);
|
||||
if(v.size() != 1) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v[0];
|
||||
}
|
||||
|
||||
int ASF::File::readWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(2);
|
||||
if(v.size() != 2) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUShort(false);
|
||||
}
|
||||
|
||||
unsigned int ASF::File::readDWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(4);
|
||||
if(v.size() != 4) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUInt(false);
|
||||
}
|
||||
|
||||
long long ASF::File::readQWORD(bool *ok)
|
||||
{
|
||||
ByteVector v = readBlock(8);
|
||||
if(v.size() != 8) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toLongLong(false);
|
||||
}
|
||||
|
||||
String ASF::File::readString(int length)
|
||||
{
|
||||
ByteVector data = readBlock(length);
|
||||
unsigned int size = data.size();
|
||||
while (size >= 2) {
|
||||
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
|
||||
break;
|
||||
}
|
||||
size -= 2;
|
||||
}
|
||||
if(size != data.size()) {
|
||||
data.resize(size);
|
||||
}
|
||||
return String(data, String::UTF16LE);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::renderString(const String &str, bool includeLength)
|
||||
{
|
||||
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
|
||||
if(includeLength) {
|
||||
data = ByteVector::fromShort(data.size(), false) + data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace TagLib {
|
||||
* \a propertiesStyle are ignored. The audio properties are always
|
||||
* read.
|
||||
*/
|
||||
File(FileName file, bool readProperties = true,
|
||||
File(FileName file, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
@@ -67,7 +67,7 @@ namespace TagLib {
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
@@ -116,26 +116,7 @@ namespace TagLib {
|
||||
virtual bool save();
|
||||
|
||||
private:
|
||||
int readBYTE(bool *ok = 0);
|
||||
int readWORD(bool *ok = 0);
|
||||
unsigned int readDWORD(bool *ok = 0);
|
||||
long long readQWORD(bool *ok = 0);
|
||||
static ByteVector renderString(const String &str, bool includeLength = false);
|
||||
String readString(int len);
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
|
||||
friend class Attribute;
|
||||
friend class Picture;
|
||||
|
||||
class BaseObject;
|
||||
class UnknownObject;
|
||||
class FilePropertiesObject;
|
||||
class StreamPropertiesObject;
|
||||
class ContentDescriptionObject;
|
||||
class ExtendedContentDescriptionObject;
|
||||
class HeaderExtensionObject;
|
||||
class MetadataObject;
|
||||
class MetadataLibraryObject;
|
||||
void read();
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
|
||||
#include <taglib.h>
|
||||
#include <tdebug.h>
|
||||
#include "trefcounter.h"
|
||||
#include <trefcounter.h>
|
||||
|
||||
#include "asfattribute.h"
|
||||
#include "asffile.h"
|
||||
#include "asfpicture.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -46,14 +48,14 @@ public:
|
||||
// Picture class members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::Picture::Picture()
|
||||
ASF::Picture::Picture() :
|
||||
d(new PicturePrivate())
|
||||
{
|
||||
d = new PicturePrivate();
|
||||
d->valid = true;
|
||||
}
|
||||
|
||||
ASF::Picture::Picture(const Picture& other)
|
||||
: d(other.d)
|
||||
ASF::Picture::Picture(const Picture& other) :
|
||||
d(other.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
@@ -118,24 +120,27 @@ int ASF::Picture::dataSize() const
|
||||
|
||||
ASF::Picture& ASF::Picture::operator=(const ASF::Picture& other)
|
||||
{
|
||||
if(other.d != d) {
|
||||
if(d->deref())
|
||||
delete d;
|
||||
d = other.d;
|
||||
d->ref();
|
||||
}
|
||||
Picture(other).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ASF::Picture::swap(Picture &other)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, other.d);
|
||||
}
|
||||
|
||||
ByteVector ASF::Picture::render() const
|
||||
{
|
||||
if(!isValid())
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
return
|
||||
ByteVector((char)d->type) +
|
||||
ByteVector::fromUInt(d->picture.size(), false) +
|
||||
ASF::File::renderString(d->mimeType) +
|
||||
ASF::File::renderString(d->description) +
|
||||
renderString(d->mimeType) +
|
||||
renderString(d->description) +
|
||||
d->picture;
|
||||
}
|
||||
|
||||
@@ -146,7 +151,7 @@ void ASF::Picture::parse(const ByteVector& bytes)
|
||||
return;
|
||||
int pos = 0;
|
||||
d->type = (Type)bytes[0]; ++pos;
|
||||
const uint dataLen = bytes.toUInt(pos, false); pos+=4;
|
||||
const unsigned int dataLen = bytes.toUInt(pos, false); pos+=4;
|
||||
|
||||
const ByteVector nullStringTerminator(2, 0);
|
||||
|
||||
@@ -176,4 +181,3 @@ ASF::Picture ASF::Picture::fromInvalid()
|
||||
ret.d->valid = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ namespace TagLib
|
||||
*/
|
||||
Picture& operator=(const Picture& other);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the Picture by the content of \a other.
|
||||
*/
|
||||
void swap(Picture &other);
|
||||
|
||||
/*!
|
||||
* Returns true if Picture stores valid picture
|
||||
*/
|
||||
@@ -205,8 +210,8 @@ namespace TagLib
|
||||
/* THIS IS PRIVATE, DON'T TOUCH IT! */
|
||||
void parse(const ByteVector& );
|
||||
static Picture fromInvalid();
|
||||
friend class Attribute;
|
||||
#endif
|
||||
|
||||
private:
|
||||
class PicturePrivate;
|
||||
PicturePrivate *d;
|
||||
|
||||
@@ -32,11 +32,23 @@ using namespace TagLib;
|
||||
class ASF::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0), encrypted(false) {}
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
bitsPerSample(0),
|
||||
codec(ASF::Properties::Unknown),
|
||||
encrypted(false) {}
|
||||
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
int bitsPerSample;
|
||||
ASF::Properties::Codec codec;
|
||||
String codecName;
|
||||
String codecDescription;
|
||||
bool encrypted;
|
||||
};
|
||||
|
||||
@@ -44,18 +56,28 @@ public:
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::Properties::Properties() : AudioProperties(AudioProperties::Average)
|
||||
ASF::Properties::Properties() :
|
||||
AudioProperties(AudioProperties::Average),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate;
|
||||
}
|
||||
|
||||
ASF::Properties::~Properties()
|
||||
{
|
||||
if(d)
|
||||
delete d;
|
||||
delete d;
|
||||
}
|
||||
|
||||
int ASF::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int ASF::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int ASF::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@@ -75,6 +97,26 @@ int ASF::Properties::channels() const
|
||||
return d->channels;
|
||||
}
|
||||
|
||||
int ASF::Properties::bitsPerSample() const
|
||||
{
|
||||
return d->bitsPerSample;
|
||||
}
|
||||
|
||||
ASF::Properties::Codec ASF::Properties::codec() const
|
||||
{
|
||||
return d->codec;
|
||||
}
|
||||
|
||||
String ASF::Properties::codecName() const
|
||||
{
|
||||
return d->codecName;
|
||||
}
|
||||
|
||||
String ASF::Properties::codecDescription() const
|
||||
{
|
||||
return d->codecDescription;
|
||||
}
|
||||
|
||||
bool ASF::Properties::isEncrypted() const
|
||||
{
|
||||
return d->encrypted;
|
||||
@@ -84,28 +126,69 @@ bool ASF::Properties::isEncrypted() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ASF::Properties::setLength(int length)
|
||||
void ASF::Properties::setLength(int /*length*/)
|
||||
{
|
||||
d->length = length;
|
||||
debug("ASF::Properties::setLength() -- This method is deprecated. Do not use.");
|
||||
}
|
||||
|
||||
void ASF::Properties::setBitrate(int length)
|
||||
void ASF::Properties::setLengthInMilliseconds(int value)
|
||||
{
|
||||
d->bitrate = length;
|
||||
d->length = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setSampleRate(int length)
|
||||
void ASF::Properties::setBitrate(int value)
|
||||
{
|
||||
d->sampleRate = length;
|
||||
d->bitrate = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setChannels(int length)
|
||||
void ASF::Properties::setSampleRate(int value)
|
||||
{
|
||||
d->channels = length;
|
||||
d->sampleRate = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setEncrypted(bool encrypted)
|
||||
void ASF::Properties::setChannels(int value)
|
||||
{
|
||||
d->encrypted = encrypted;
|
||||
d->channels = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setBitsPerSample(int value)
|
||||
{
|
||||
d->bitsPerSample = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setCodec(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 0x0160:
|
||||
d->codec = WMA1;
|
||||
break;
|
||||
case 0x0161:
|
||||
d->codec = WMA2;
|
||||
break;
|
||||
case 0x0162:
|
||||
d->codec = WMA9Pro;
|
||||
break;
|
||||
case 0x0163:
|
||||
d->codec = WMA9Lossless;
|
||||
break;
|
||||
default:
|
||||
d->codec = Unknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ASF::Properties::setCodecName(const String &value)
|
||||
{
|
||||
d->codecName = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setCodecDescription(const String &value)
|
||||
{
|
||||
d->codecDescription = value;
|
||||
}
|
||||
|
||||
void ASF::Properties::setEncrypted(bool value)
|
||||
{
|
||||
d->encrypted = value;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,38 @@ namespace TagLib {
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Create an instance of ASF::Properties.
|
||||
* Audio codec types can be used in ASF file.
|
||||
*/
|
||||
enum Codec
|
||||
{
|
||||
/*!
|
||||
* Couldn't detect the codec.
|
||||
*/
|
||||
Unknown = 0,
|
||||
|
||||
/*!
|
||||
* Windows Media Audio 1
|
||||
*/
|
||||
WMA1,
|
||||
|
||||
/*!
|
||||
* Windows Media Audio 2 or above
|
||||
*/
|
||||
WMA2,
|
||||
|
||||
/*!
|
||||
* Windows Media Audio 9 Professional
|
||||
*/
|
||||
WMA9Pro,
|
||||
|
||||
/*!
|
||||
* Windows Media Audio 9 Lossless
|
||||
*/
|
||||
WMA9Lossless,
|
||||
};
|
||||
|
||||
/*!
|
||||
* Creates an instance of ASF::Properties.
|
||||
*/
|
||||
Properties();
|
||||
|
||||
@@ -49,18 +80,97 @@ namespace TagLib {
|
||||
*/
|
||||
virtual ~Properties();
|
||||
|
||||
// Reimplementations.
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \note This method is just an alias of lengthInSeconds().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
virtual int length() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the average bit rate of the file in kb/s.
|
||||
*/
|
||||
virtual int bitrate() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of bits per audio sample.
|
||||
*/
|
||||
int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Returns the codec used in the file.
|
||||
*
|
||||
* \see codecName()
|
||||
* \see codecDescription()
|
||||
*/
|
||||
Codec codec() const;
|
||||
|
||||
/*!
|
||||
* Returns the concrete codec name, for example "Windows Media Audio 9.1"
|
||||
* used in the file if available, otherwise an empty string.
|
||||
*
|
||||
* \see codec()
|
||||
* \see codecDescription()
|
||||
*/
|
||||
String codecName() const;
|
||||
|
||||
/*!
|
||||
* Returns the codec description, typically contains the encoder settings,
|
||||
* for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available,
|
||||
* otherwise an empty string.
|
||||
*
|
||||
* \see codec()
|
||||
* \see codecName()
|
||||
*/
|
||||
String codecDescription() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file is encrypted.
|
||||
*/
|
||||
bool isEncrypted() const;
|
||||
|
||||
#ifndef DO_NOT_DOCUMENT
|
||||
// deprecated
|
||||
void setLength(int value);
|
||||
|
||||
void setLengthInMilliseconds(int value);
|
||||
void setBitrate(int value);
|
||||
void setSampleRate(int value);
|
||||
void setChannels(int value);
|
||||
void setBitsPerSample(int value);
|
||||
void setCodec(int value);
|
||||
void setCodecName(const String &value);
|
||||
void setCodecDescription(const String &value);
|
||||
void setEncrypted(bool value);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ ASF::Tag::Tag()
|
||||
|
||||
ASF::Tag::~Tag()
|
||||
{
|
||||
if(d)
|
||||
delete d;
|
||||
delete d;
|
||||
}
|
||||
|
||||
String ASF::Tag::title() const
|
||||
@@ -65,7 +64,7 @@ String ASF::Tag::album() const
|
||||
{
|
||||
if(d->attributeListMap.contains("WM/AlbumTitle"))
|
||||
return d->attributeListMap["WM/AlbumTitle"][0].toString();
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String ASF::Tag::copyright() const
|
||||
@@ -108,7 +107,7 @@ String ASF::Tag::genre() const
|
||||
{
|
||||
if(d->attributeListMap.contains("WM/Genre"))
|
||||
return d->attributeListMap["WM/Genre"][0].toString();
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
void ASF::Tag::setTitle(const String &value)
|
||||
@@ -146,12 +145,12 @@ void ASF::Tag::setGenre(const String &value)
|
||||
setAttribute("WM/Genre", value);
|
||||
}
|
||||
|
||||
void ASF::Tag::setYear(uint value)
|
||||
void ASF::Tag::setYear(unsigned int value)
|
||||
{
|
||||
setAttribute("WM/Year", String::number(value));
|
||||
}
|
||||
|
||||
void ASF::Tag::setTrack(uint value)
|
||||
void ASF::Tag::setTrack(unsigned int value)
|
||||
{
|
||||
setAttribute("WM/TrackNumber", String::number(value));
|
||||
}
|
||||
@@ -161,11 +160,24 @@ ASF::AttributeListMap& ASF::Tag::attributeListMap()
|
||||
return d->attributeListMap;
|
||||
}
|
||||
|
||||
const ASF::AttributeListMap &ASF::Tag::attributeListMap() const
|
||||
{
|
||||
return d->attributeListMap;
|
||||
}
|
||||
|
||||
bool ASF::Tag::contains(const String &key) const
|
||||
{
|
||||
return d->attributeListMap.contains(key);
|
||||
}
|
||||
|
||||
void ASF::Tag::removeItem(const String &key)
|
||||
{
|
||||
AttributeListMap::Iterator it = d->attributeListMap.find(key);
|
||||
if(it != d->attributeListMap.end())
|
||||
d->attributeListMap.erase(it);
|
||||
d->attributeListMap.erase(key);
|
||||
}
|
||||
|
||||
ASF::AttributeList ASF::Tag::attribute(const String &name) const
|
||||
{
|
||||
return d->attributeListMap[name];
|
||||
}
|
||||
|
||||
void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
|
||||
@@ -175,6 +187,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
|
||||
d->attributeListMap.insert(name, value);
|
||||
}
|
||||
|
||||
void ASF::Tag::setAttribute(const String &name, const AttributeList &values)
|
||||
{
|
||||
d->attributeListMap.insert(name, values);
|
||||
}
|
||||
|
||||
void ASF::Tag::addAttribute(const String &name, const Attribute &attribute)
|
||||
{
|
||||
if(d->attributeListMap.contains(name)) {
|
||||
@@ -193,57 +210,64 @@ bool ASF::Tag::isEmpty() const
|
||||
d->attributeListMap.isEmpty();
|
||||
}
|
||||
|
||||
static const char *keyTranslation[][2] = {
|
||||
{ "WM/AlbumTitle", "ALBUM" },
|
||||
{ "WM/Composer", "COMPOSER" },
|
||||
{ "WM/Writer", "WRITER" },
|
||||
{ "WM/Conductor", "CONDUCTOR" },
|
||||
{ "WM/ModifiedBy", "REMIXER" },
|
||||
{ "WM/Year", "DATE" },
|
||||
{ "WM/OriginalReleaseYear", "ORIGINALDATE" },
|
||||
{ "WM/Producer", "PRODUCER" },
|
||||
{ "WM/ContentGroupDescription", "GROUPING" },
|
||||
{ "WM/SubTitle", "SUBTITLE" },
|
||||
{ "WM/SetSubTitle", "DISCSUBTITLE" },
|
||||
{ "WM/TrackNumber", "TRACKNUMBER" },
|
||||
{ "WM/PartOfSet", "DISCNUMBER" },
|
||||
{ "WM/Genre", "GENRE" },
|
||||
{ "WM/BeatsPerMinute", "BPM" },
|
||||
{ "WM/Mood", "MOOD" },
|
||||
{ "WM/ISRC", "ISRC" },
|
||||
{ "WM/Lyrics", "LYRICS" },
|
||||
{ "WM/Media", "MEDIA" },
|
||||
{ "WM/Publisher", "LABEL" },
|
||||
{ "WM/CatalogNo", "CATALOGNUMBER" },
|
||||
{ "WM/Barcode", "BARCODE" },
|
||||
{ "WM/EncodedBy", "ENCODEDBY" },
|
||||
{ "WM/AlbumSortOrder", "ALBUMSORT" },
|
||||
{ "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" },
|
||||
{ "WM/ArtistSortOrder", "ARTISTSORT" },
|
||||
{ "WM/TitleSortOrder", "TITLESORT" },
|
||||
{ "WM/Script", "SCRIPT" },
|
||||
{ "WM/Language", "LANGUAGE" },
|
||||
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "MusicIP/PUID", "MUSICIP_PUID" },
|
||||
{ "Acoustid/Id", "ACOUSTID_ID" },
|
||||
{ "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" },
|
||||
};
|
||||
namespace
|
||||
{
|
||||
const char *keyTranslation[][2] = {
|
||||
{ "WM/AlbumTitle", "ALBUM" },
|
||||
{ "WM/AlbumArtist", "ALBUMARTIST" },
|
||||
{ "WM/Composer", "COMPOSER" },
|
||||
{ "WM/Writer", "WRITER" },
|
||||
{ "WM/Conductor", "CONDUCTOR" },
|
||||
{ "WM/ModifiedBy", "REMIXER" },
|
||||
{ "WM/Year", "DATE" },
|
||||
{ "WM/OriginalReleaseYear", "ORIGINALDATE" },
|
||||
{ "WM/Producer", "PRODUCER" },
|
||||
{ "WM/ContentGroupDescription", "GROUPING" },
|
||||
{ "WM/SubTitle", "SUBTITLE" },
|
||||
{ "WM/SetSubTitle", "DISCSUBTITLE" },
|
||||
{ "WM/TrackNumber", "TRACKNUMBER" },
|
||||
{ "WM/PartOfSet", "DISCNUMBER" },
|
||||
{ "WM/Genre", "GENRE" },
|
||||
{ "WM/BeatsPerMinute", "BPM" },
|
||||
{ "WM/Mood", "MOOD" },
|
||||
{ "WM/ISRC", "ISRC" },
|
||||
{ "WM/Lyrics", "LYRICS" },
|
||||
{ "WM/Media", "MEDIA" },
|
||||
{ "WM/Publisher", "LABEL" },
|
||||
{ "WM/CatalogNo", "CATALOGNUMBER" },
|
||||
{ "WM/Barcode", "BARCODE" },
|
||||
{ "WM/EncodedBy", "ENCODEDBY" },
|
||||
{ "WM/AlbumSortOrder", "ALBUMSORT" },
|
||||
{ "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" },
|
||||
{ "WM/ArtistSortOrder", "ARTISTSORT" },
|
||||
{ "WM/TitleSortOrder", "TITLESORT" },
|
||||
{ "WM/Script", "SCRIPT" },
|
||||
{ "WM/Language", "LANGUAGE" },
|
||||
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "MusicIP/PUID", "MUSICIP_PUID" },
|
||||
{ "Acoustid/Id", "ACOUSTID_ID" },
|
||||
{ "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" },
|
||||
};
|
||||
const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
|
||||
String translateKey(const String &key)
|
||||
{
|
||||
for(size_t i = 0; i < keyTranslationSize; ++i) {
|
||||
if(key == keyTranslation[i][0])
|
||||
return keyTranslation[i][1];
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap ASF::Tag::properties() const
|
||||
{
|
||||
static Map<String, String> keyMap;
|
||||
if(keyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap props;
|
||||
|
||||
if(!d->title.isEmpty()) {
|
||||
@@ -261,8 +285,8 @@ PropertyMap ASF::Tag::properties() const
|
||||
|
||||
ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin();
|
||||
for(; it != d->attributeListMap.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
const String key = translateKey(it->first);
|
||||
if(!key.isEmpty()) {
|
||||
AttributeList::ConstIterator it2 = it->second.begin();
|
||||
for(; it2 != it->second.end(); ++it2) {
|
||||
if(key == "TRACKNUMBER") {
|
||||
@@ -305,16 +329,16 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
|
||||
for(; it != origProps.end(); ++it) {
|
||||
if(!props.contains(it->first) || props[it->first].isEmpty()) {
|
||||
if(it->first == "TITLE") {
|
||||
d->title = String::null;
|
||||
d->title.clear();
|
||||
}
|
||||
else if(it->first == "ARTIST") {
|
||||
d->artist = String::null;
|
||||
d->artist.clear();
|
||||
}
|
||||
else if(it->first == "COMMENT") {
|
||||
d->comment = String::null;
|
||||
d->comment.clear();
|
||||
}
|
||||
else if(it->first == "COPYRIGHT") {
|
||||
d->copyright = String::null;
|
||||
d->copyright.clear();
|
||||
}
|
||||
else {
|
||||
d->attributeListMap.erase(reverseKeyMap[it->first]);
|
||||
|
||||
@@ -90,13 +90,13 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns the year; if there is no year set, this will return 0.
|
||||
*/
|
||||
virtual uint year() const;
|
||||
virtual unsigned int year() const;
|
||||
|
||||
/*!
|
||||
* Returns the track number; if there is no track number set, this will
|
||||
* return 0.
|
||||
*/
|
||||
virtual uint track() const;
|
||||
virtual unsigned int track() const;
|
||||
|
||||
/*!
|
||||
* Sets the title to \a s.
|
||||
@@ -137,12 +137,12 @@ namespace TagLib {
|
||||
/*!
|
||||
* Sets the year to \a i. If \a s is 0 then this value will be cleared.
|
||||
*/
|
||||
virtual void setYear(uint i);
|
||||
virtual void setYear(unsigned int i);
|
||||
|
||||
/*!
|
||||
* Sets the track to \a i. If \a s is 0 then this value will be cleared.
|
||||
*/
|
||||
virtual void setTrack(uint i);
|
||||
virtual void setTrack(unsigned int i);
|
||||
|
||||
/*!
|
||||
* Returns true if the tag does not contain any data. This should be
|
||||
@@ -152,24 +152,43 @@ namespace TagLib {
|
||||
virtual bool isEmpty() const;
|
||||
|
||||
/*!
|
||||
* Returns a reference to the item list map. This is an AttributeListMap of
|
||||
* all of the items in the tag.
|
||||
*
|
||||
* This is the most powerfull structure for accessing the items of the tag.
|
||||
* \deprecated
|
||||
*/
|
||||
AttributeListMap &attributeListMap();
|
||||
|
||||
/*!
|
||||
* Returns a reference to the item list map. This is an AttributeListMap of
|
||||
* all of the items in the tag.
|
||||
*/
|
||||
const AttributeListMap &attributeListMap() const;
|
||||
|
||||
/*!
|
||||
* \return True if a value for \a attribute is currently set.
|
||||
*/
|
||||
bool contains(const String &name) const;
|
||||
|
||||
/*!
|
||||
* Removes the \a key attribute from the tag
|
||||
*/
|
||||
void removeItem(const String &name);
|
||||
|
||||
/*!
|
||||
* \return The list of values for the key \a name, or an empty list if no
|
||||
* values have been set.
|
||||
*/
|
||||
AttributeList attribute(const String &name) const;
|
||||
|
||||
/*!
|
||||
* Sets the \a key attribute to the value of \a attribute. If an attribute
|
||||
* with the \a key is already present, it will be replaced.
|
||||
*/
|
||||
void setAttribute(const String &name, const Attribute &attribute);
|
||||
|
||||
/*!
|
||||
* Sets multiple \a values to the key \a name.
|
||||
*/
|
||||
void setAttribute(const String &name, const AttributeList &values);
|
||||
|
||||
/*!
|
||||
* Sets the \a key attribute to the value of \a attribute. If an attribute
|
||||
* with the \a key is already present, it will be added to the list.
|
||||
|
||||
104
taglib/asf/asfutils.h
Normal file
104
taglib/asf/asfutils.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Tsuda Kageyu
|
||||
email : tsuda.kageyu@gmail.com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_ASFUTILS_H
|
||||
#define TAGLIB_ASFUTILS_H
|
||||
|
||||
// THIS FILE IS NOT A PART OF THE TAGLIB API
|
||||
|
||||
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
|
||||
|
||||
namespace TagLib
|
||||
{
|
||||
namespace ASF
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
inline unsigned short readWORD(File *file, bool *ok = 0)
|
||||
{
|
||||
const ByteVector v = file->readBlock(2);
|
||||
if(v.size() != 2) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUShort(false);
|
||||
}
|
||||
|
||||
inline unsigned int readDWORD(File *file, bool *ok = 0)
|
||||
{
|
||||
const ByteVector v = file->readBlock(4);
|
||||
if(v.size() != 4) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toUInt(false);
|
||||
}
|
||||
|
||||
inline long long readQWORD(File *file, bool *ok = 0)
|
||||
{
|
||||
const ByteVector v = file->readBlock(8);
|
||||
if(v.size() != 8) {
|
||||
if(ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
if(ok) *ok = true;
|
||||
return v.toLongLong(false);
|
||||
}
|
||||
|
||||
inline String readString(File *file, int length)
|
||||
{
|
||||
ByteVector data = file->readBlock(length);
|
||||
unsigned int size = data.size();
|
||||
while (size >= 2) {
|
||||
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
|
||||
break;
|
||||
}
|
||||
size -= 2;
|
||||
}
|
||||
if(size != data.size()) {
|
||||
data.resize(size);
|
||||
}
|
||||
return String(data, String::UTF16LE);
|
||||
}
|
||||
|
||||
inline ByteVector renderString(const String &str, bool includeLength = false)
|
||||
{
|
||||
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
|
||||
if(includeLength) {
|
||||
data = ByteVector::fromShort(data.size(), false) + data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,22 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <tbytevector.h>
|
||||
|
||||
#include "aiffproperties.h"
|
||||
#include "apeproperties.h"
|
||||
#include "asfproperties.h"
|
||||
#include "flacproperties.h"
|
||||
#include "mp4properties.h"
|
||||
#include "mpcproperties.h"
|
||||
#include "mpegproperties.h"
|
||||
#include "opusproperties.h"
|
||||
#include "speexproperties.h"
|
||||
#include "trueaudioproperties.h"
|
||||
#include "vorbisproperties.h"
|
||||
#include "wavproperties.h"
|
||||
#include "wavpackproperties.h"
|
||||
|
||||
#include "audioproperties.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -41,11 +57,108 @@ AudioProperties::~AudioProperties()
|
||||
|
||||
}
|
||||
|
||||
int AudioProperties::lengthInSeconds() const
|
||||
{
|
||||
// This is an ugly workaround but we can't add a virtual function.
|
||||
// Should be virtual in taglib2.
|
||||
|
||||
if(dynamic_cast<const APE::Properties*>(this))
|
||||
return dynamic_cast<const APE::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const ASF::Properties*>(this))
|
||||
return dynamic_cast<const ASF::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const FLAC::Properties*>(this))
|
||||
return dynamic_cast<const FLAC::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const MP4::Properties*>(this))
|
||||
return dynamic_cast<const MP4::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const MPC::Properties*>(this))
|
||||
return dynamic_cast<const MPC::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const MPEG::Properties*>(this))
|
||||
return dynamic_cast<const MPEG::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const Ogg::Opus::Properties*>(this))
|
||||
return dynamic_cast<const Ogg::Opus::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const Ogg::Speex::Properties*>(this))
|
||||
return dynamic_cast<const Ogg::Speex::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const TrueAudio::Properties*>(this))
|
||||
return dynamic_cast<const TrueAudio::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if (dynamic_cast<const RIFF::AIFF::Properties*>(this))
|
||||
return dynamic_cast<const RIFF::AIFF::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const RIFF::WAV::Properties*>(this))
|
||||
return dynamic_cast<const RIFF::WAV::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const Vorbis::Properties*>(this))
|
||||
return dynamic_cast<const Vorbis::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else if(dynamic_cast<const WavPack::Properties*>(this))
|
||||
return dynamic_cast<const WavPack::Properties*>(this)->lengthInSeconds();
|
||||
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AudioProperties::lengthInMilliseconds() const
|
||||
{
|
||||
// This is an ugly workaround but we can't add a virtual function.
|
||||
// Should be virtual in taglib2.
|
||||
|
||||
if(dynamic_cast<const APE::Properties*>(this))
|
||||
return dynamic_cast<const APE::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const ASF::Properties*>(this))
|
||||
return dynamic_cast<const ASF::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const FLAC::Properties*>(this))
|
||||
return dynamic_cast<const FLAC::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const MP4::Properties*>(this))
|
||||
return dynamic_cast<const MP4::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const MPC::Properties*>(this))
|
||||
return dynamic_cast<const MPC::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const MPEG::Properties*>(this))
|
||||
return dynamic_cast<const MPEG::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const Ogg::Opus::Properties*>(this))
|
||||
return dynamic_cast<const Ogg::Opus::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const Ogg::Speex::Properties*>(this))
|
||||
return dynamic_cast<const Ogg::Speex::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const TrueAudio::Properties*>(this))
|
||||
return dynamic_cast<const TrueAudio::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const RIFF::AIFF::Properties*>(this))
|
||||
return dynamic_cast<const RIFF::AIFF::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const RIFF::WAV::Properties*>(this))
|
||||
return dynamic_cast<const RIFF::WAV::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const Vorbis::Properties*>(this))
|
||||
return dynamic_cast<const Vorbis::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else if(dynamic_cast<const WavPack::Properties*>(this))
|
||||
return dynamic_cast<const WavPack::Properties*>(this)->lengthInMilliseconds();
|
||||
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AudioProperties::AudioProperties(ReadStyle)
|
||||
AudioProperties::AudioProperties(ReadStyle) :
|
||||
d(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* The values here are common to most audio formats. For more specific, codec
|
||||
* dependant values, please see see the subclasses APIs. This is meant to
|
||||
* dependent values, please see see the subclasses APIs. This is meant to
|
||||
* compliment the TagLib::File and TagLib::Tag APIs in providing a simple
|
||||
* interface that is sufficient for most applications.
|
||||
*/
|
||||
@@ -69,6 +69,23 @@ namespace TagLib {
|
||||
*/
|
||||
virtual int length() const = 0;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the most appropriate bit rate for the file in kb/s. For constant
|
||||
* bitrate formats this is simply the bitrate of the file. For variable
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include <tfile.h>
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
#include "trefcounter.h"
|
||||
#include <trefcounter.h>
|
||||
|
||||
#include "fileref.h"
|
||||
#include "asffile.h"
|
||||
@@ -54,41 +54,176 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef List<const FileRef::FileTypeResolver *> ResolverList;
|
||||
ResolverList fileTypeResolvers;
|
||||
|
||||
// Templatized internal functions. T should be String or IOStream*.
|
||||
|
||||
template <typename T>
|
||||
FileName toFileName(T arg)
|
||||
{
|
||||
debug("FileRef::toFileName<T>(): This version should never be called.");
|
||||
return FileName(L"");
|
||||
}
|
||||
|
||||
template <>
|
||||
FileName toFileName<IOStream *>(IOStream *arg)
|
||||
{
|
||||
return arg->name();
|
||||
}
|
||||
|
||||
template <>
|
||||
FileName toFileName<FileName>(FileName arg)
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
File *resolveFileType(T arg, bool readProperties,
|
||||
AudioProperties::ReadStyle style)
|
||||
{
|
||||
debug("FileRef::resolveFileType<T>(): This version should never be called.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
File *resolveFileType<IOStream *>(IOStream *arg, bool readProperties,
|
||||
AudioProperties::ReadStyle style)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
File *resolveFileType<FileName>(FileName arg, bool readProperties,
|
||||
AudioProperties::ReadStyle style)
|
||||
{
|
||||
ResolverList::ConstIterator it = fileTypeResolvers.begin();
|
||||
for(; it != fileTypeResolvers.end(); ++it) {
|
||||
File *file = (*it)->createFile(arg, readProperties, style);
|
||||
if(file)
|
||||
return file;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
File* createInternal(T arg, bool readAudioProperties,
|
||||
AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
{
|
||||
File *file = resolveFileType(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(file)
|
||||
return file;
|
||||
|
||||
#ifdef _WIN32
|
||||
const String s = toFileName(arg).toString();
|
||||
#else
|
||||
const String s(toFileName(arg));
|
||||
#endif
|
||||
|
||||
String ext;
|
||||
const int pos = s.rfind(".");
|
||||
if(pos != -1)
|
||||
ext = s.substr(pos + 1).upper();
|
||||
|
||||
// If this list is updated, the method defaultFileExtensions() should also be
|
||||
// updated. However at some point that list should be created at the same time
|
||||
// that a default file type resolver is created.
|
||||
|
||||
if(ext.isEmpty())
|
||||
return 0;
|
||||
|
||||
if(ext == "MP3")
|
||||
return new MPEG::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OGG")
|
||||
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OGA") {
|
||||
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
|
||||
File *file = new Ogg::FLAC::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(file->isValid())
|
||||
return file;
|
||||
delete file;
|
||||
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
}
|
||||
if(ext == "FLAC")
|
||||
return new FLAC::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "MPC")
|
||||
return new MPC::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WV")
|
||||
return new WavPack::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "SPX")
|
||||
return new Ogg::Speex::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OPUS")
|
||||
return new Ogg::Opus::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "TTA")
|
||||
return new TrueAudio::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
|
||||
return new MP4::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WMA" || ext == "ASF")
|
||||
return new ASF::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
|
||||
return new RIFF::AIFF::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WAV")
|
||||
return new RIFF::WAV::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "APE")
|
||||
return new APE::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
// module, nst and wow are possible but uncommon extensions
|
||||
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
|
||||
return new Mod::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "S3M")
|
||||
return new S3M::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "IT")
|
||||
return new IT::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "XM")
|
||||
return new XM::File(arg, readAudioProperties, audioPropertiesStyle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class FileRef::FileRefPrivate : public RefCounter
|
||||
{
|
||||
public:
|
||||
FileRefPrivate(File *f) : RefCounter(), file(f) {}
|
||||
FileRefPrivate(File *f) :
|
||||
RefCounter(),
|
||||
file(f) {}
|
||||
|
||||
~FileRefPrivate() {
|
||||
delete file;
|
||||
}
|
||||
|
||||
File *file;
|
||||
static List<const FileTypeResolver *> fileTypeResolvers;
|
||||
};
|
||||
|
||||
List<const FileRef::FileTypeResolver *> FileRef::FileRefPrivate::fileTypeResolvers;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FileRef::FileRef()
|
||||
FileRef::FileRef() :
|
||||
d(new FileRefPrivate(0))
|
||||
{
|
||||
d = new FileRefPrivate(0);
|
||||
}
|
||||
|
||||
FileRef::FileRef(FileName fileName, bool readAudioProperties,
|
||||
AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
AudioProperties::ReadStyle audioPropertiesStyle) :
|
||||
d(new FileRefPrivate(createInternal(fileName, readAudioProperties, audioPropertiesStyle)))
|
||||
{
|
||||
d = new FileRefPrivate(create(fileName, readAudioProperties, audioPropertiesStyle));
|
||||
}
|
||||
|
||||
FileRef::FileRef(File *file)
|
||||
FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) :
|
||||
d(new FileRefPrivate(createInternal(stream, readAudioProperties, audioPropertiesStyle)))
|
||||
{
|
||||
d = new FileRefPrivate(file);
|
||||
}
|
||||
|
||||
FileRef::FileRef(const FileRef &ref) : d(ref.d)
|
||||
FileRef::FileRef(File *file) :
|
||||
d(new FileRefPrivate(file))
|
||||
{
|
||||
}
|
||||
|
||||
FileRef::FileRef(const FileRef &ref) :
|
||||
d(ref.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
@@ -133,7 +268,7 @@ bool FileRef::save()
|
||||
|
||||
const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static
|
||||
{
|
||||
FileRefPrivate::fileTypeResolvers.prepend(resolver);
|
||||
fileTypeResolvers.prepend(resolver);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@@ -155,6 +290,7 @@ StringList FileRef::defaultFileExtensions()
|
||||
l.append("m4p");
|
||||
l.append("3g2");
|
||||
l.append("mp4");
|
||||
l.append("m4v");
|
||||
l.append("wma");
|
||||
l.append("asf");
|
||||
l.append("aif");
|
||||
@@ -174,113 +310,34 @@ StringList FileRef::defaultFileExtensions()
|
||||
|
||||
bool FileRef::isNull() const
|
||||
{
|
||||
return !d->file || !d->file->isValid();
|
||||
return (!d->file || !d->file->isValid());
|
||||
}
|
||||
|
||||
FileRef &FileRef::operator=(const FileRef &ref)
|
||||
{
|
||||
if(&ref == this)
|
||||
return *this;
|
||||
|
||||
if(d->deref())
|
||||
delete d;
|
||||
|
||||
d = ref.d;
|
||||
d->ref();
|
||||
|
||||
FileRef(ref).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FileRef::swap(FileRef &ref)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, ref.d);
|
||||
}
|
||||
|
||||
bool FileRef::operator==(const FileRef &ref) const
|
||||
{
|
||||
return ref.d->file == d->file;
|
||||
return (ref.d->file == d->file);
|
||||
}
|
||||
|
||||
bool FileRef::operator!=(const FileRef &ref) const
|
||||
{
|
||||
return ref.d->file != d->file;
|
||||
return (ref.d->file != d->file);
|
||||
}
|
||||
|
||||
File *FileRef::create(FileName fileName, bool readAudioProperties,
|
||||
AudioProperties::ReadStyle audioPropertiesStyle) // static
|
||||
{
|
||||
|
||||
List<const FileTypeResolver *>::ConstIterator it = FileRefPrivate::fileTypeResolvers.begin();
|
||||
|
||||
for(; it != FileRefPrivate::fileTypeResolvers.end(); ++it) {
|
||||
File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(file)
|
||||
return file;
|
||||
}
|
||||
|
||||
// Ok, this is really dumb for now, but it works for testing.
|
||||
|
||||
String ext;
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
String s = fileName.toString();
|
||||
|
||||
#else
|
||||
|
||||
String s = fileName;
|
||||
|
||||
#endif
|
||||
|
||||
const int pos = s.rfind(".");
|
||||
if(pos != -1)
|
||||
ext = s.substr(pos + 1).upper();
|
||||
}
|
||||
|
||||
// If this list is updated, the method defaultFileExtensions() should also be
|
||||
// updated. However at some point that list should be created at the same time
|
||||
// that a default file type resolver is created.
|
||||
|
||||
if(!ext.isEmpty()) {
|
||||
if(ext == "MP3")
|
||||
return new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OGG")
|
||||
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OGA") {
|
||||
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
|
||||
File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if (file->isValid())
|
||||
return file;
|
||||
delete file;
|
||||
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
}
|
||||
if(ext == "FLAC")
|
||||
return new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "MPC")
|
||||
return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WV")
|
||||
return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "SPX")
|
||||
return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OPUS")
|
||||
return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "TTA")
|
||||
return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2")
|
||||
return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WMA" || ext == "ASF")
|
||||
return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "AIF" || ext == "AIFF")
|
||||
return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WAV")
|
||||
return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "APE")
|
||||
return new APE::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
// module, nst and wow are possible but uncommon extensions
|
||||
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
|
||||
return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "S3M")
|
||||
return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "IT")
|
||||
return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "XM")
|
||||
return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace TagLib {
|
||||
*
|
||||
* class MyFileTypeResolver : FileTypeResolver
|
||||
* {
|
||||
* TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle)
|
||||
* TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const
|
||||
* {
|
||||
* if(someCheckForAnMP3File(fileName))
|
||||
* return new TagLib::MPEG::File(fileName);
|
||||
@@ -128,7 +128,24 @@ namespace TagLib {
|
||||
audioPropertiesStyle = AudioProperties::Average);
|
||||
|
||||
/*!
|
||||
* Contruct a FileRef using \a file. The FileRef now takes ownership of the
|
||||
* Construct a FileRef from an opened \a IOStream. If \a readAudioProperties
|
||||
* is true then the audio properties will be read using \a audioPropertiesStyle.
|
||||
* If \a readAudioProperties is false then \a audioPropertiesStyle will be
|
||||
* ignored.
|
||||
*
|
||||
* Also see the note in the class documentation about why you may not want to
|
||||
* use this method in your application.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
explicit FileRef(IOStream* stream,
|
||||
bool readAudioProperties = true,
|
||||
AudioProperties::ReadStyle
|
||||
audioPropertiesStyle = AudioProperties::Average);
|
||||
|
||||
/*!
|
||||
* Construct a FileRef using \a file. The FileRef now takes ownership of the
|
||||
* pointer and will delete the File when it passes out of scope.
|
||||
*/
|
||||
explicit FileRef(File *file);
|
||||
@@ -191,7 +208,7 @@ namespace TagLib {
|
||||
* is tried.
|
||||
*
|
||||
* Returns a pointer to the added resolver (the same one that's passed in --
|
||||
* this is mostly so that static inialializers have something to use for
|
||||
* this is mostly so that static initializers have something to use for
|
||||
* assignment).
|
||||
*
|
||||
* \see FileTypeResolver
|
||||
@@ -209,7 +226,7 @@ namespace TagLib {
|
||||
* by TagLib for resolution is case-insensitive.
|
||||
*
|
||||
* \note This does not account for any additional file type resolvers that
|
||||
* are plugged in. Also note that this is not intended to replace a propper
|
||||
* are plugged in. Also note that this is not intended to replace a proper
|
||||
* mime-type resolution system, but is just here for reference.
|
||||
*
|
||||
* \see FileTypeResolver
|
||||
@@ -226,6 +243,11 @@ namespace TagLib {
|
||||
*/
|
||||
FileRef &operator=(const FileRef &ref);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the FileRef by the content of \a ref.
|
||||
*/
|
||||
void swap(FileRef &ref);
|
||||
|
||||
/*!
|
||||
* Returns true if this FileRef and \a ref point to the same File object.
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <tdebug.h>
|
||||
#include <tagunion.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tagutils.h>
|
||||
|
||||
#include <id3v2header.h>
|
||||
#include <id3v2tag.h>
|
||||
@@ -44,93 +45,84 @@ using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef List<FLAC::MetadataBlock *> BlockList;
|
||||
typedef BlockList::Iterator BlockIterator;
|
||||
typedef BlockList::Iterator BlockConstIterator;
|
||||
|
||||
enum { FlacXiphIndex = 0, FlacID3v2Index = 1, FlacID3v1Index = 2 };
|
||||
enum { MinPaddingLength = 4096 };
|
||||
enum { LastBlockFlag = 0x80 };
|
||||
|
||||
const long MinPaddingLength = 4096;
|
||||
const long MaxPaddingLegnth = 1024 * 1024;
|
||||
|
||||
const char LastBlockFlag = '\x80';
|
||||
}
|
||||
|
||||
class FLAC::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate() :
|
||||
ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
|
||||
FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
|
||||
ID3v2FrameFactory(frameFactory),
|
||||
ID3v2Location(-1),
|
||||
ID3v2OriginalSize(0),
|
||||
ID3v1Location(-1),
|
||||
properties(0),
|
||||
flacStart(0),
|
||||
streamStart(0),
|
||||
streamLength(0),
|
||||
scanned(false),
|
||||
hasXiphComment(false),
|
||||
hasID3v2(false),
|
||||
hasID3v1(false)
|
||||
scanned(false)
|
||||
{
|
||||
blocks.setAutoDelete(true);
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
uint size = blocks.size();
|
||||
for(uint i = 0; i < size; i++) {
|
||||
delete blocks[i];
|
||||
}
|
||||
delete properties;
|
||||
}
|
||||
|
||||
const ID3v2::FrameFactory *ID3v2FrameFactory;
|
||||
long ID3v2Location;
|
||||
uint ID3v2OriginalSize;
|
||||
long ID3v2OriginalSize;
|
||||
|
||||
long ID3v1Location;
|
||||
|
||||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
ByteVector streamInfoData;
|
||||
ByteVector xiphCommentData;
|
||||
List<MetadataBlock *> blocks;
|
||||
BlockList blocks;
|
||||
|
||||
long flacStart;
|
||||
long streamStart;
|
||||
long streamLength;
|
||||
bool scanned;
|
||||
|
||||
bool hasXiphComment;
|
||||
bool hasID3v2;
|
||||
bool hasID3v1;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FLAC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file)
|
||||
FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate(frameFactory))
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(stream)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate(frameFactory))
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::~File()
|
||||
@@ -145,30 +137,17 @@ TagLib::Tag *FLAC::File::tag() const
|
||||
|
||||
PropertyMap FLAC::File::properties() const
|
||||
{
|
||||
// once Tag::properties() is virtual, this case distinction could actually be done
|
||||
// within TagUnion.
|
||||
if(d->hasXiphComment)
|
||||
return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, false)->properties();
|
||||
if(d->hasID3v2)
|
||||
return d->tag.access<ID3v2::Tag>(FlacID3v2Index, false)->properties();
|
||||
if(d->hasID3v1)
|
||||
return d->tag.access<ID3v1::Tag>(FlacID3v1Index, false)->properties();
|
||||
return PropertyMap();
|
||||
return d->tag.properties();
|
||||
}
|
||||
|
||||
void FLAC::File::removeUnsupportedProperties(const StringList &unsupported)
|
||||
{
|
||||
if(d->hasXiphComment)
|
||||
d->tag.access<Ogg::XiphComment>(FlacXiphIndex, false)->removeUnsupportedProperties(unsupported);
|
||||
if(d->hasID3v2)
|
||||
d->tag.access<ID3v2::Tag>(FlacID3v2Index, false)->removeUnsupportedProperties(unsupported);
|
||||
if(d->hasID3v1)
|
||||
d->tag.access<ID3v1::Tag>(FlacID3v1Index, false)->removeUnsupportedProperties(unsupported);
|
||||
d->tag.removeUnsupportedProperties(unsupported);
|
||||
}
|
||||
|
||||
PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, true)->setProperties(properties);
|
||||
return xiphComment(true)->setProperties(properties);
|
||||
}
|
||||
|
||||
FLAC::Properties *FLAC::File::audioProperties() const
|
||||
@@ -176,7 +155,6 @@ FLAC::Properties *FLAC::File::audioProperties() const
|
||||
return d->properties;
|
||||
}
|
||||
|
||||
|
||||
bool FLAC::File::save()
|
||||
{
|
||||
if(readOnly()) {
|
||||
@@ -197,75 +175,123 @@ bool FLAC::File::save()
|
||||
|
||||
// Replace metadata blocks
|
||||
|
||||
bool foundVorbisCommentBlock = false;
|
||||
List<MetadataBlock *> newBlocks;
|
||||
for(uint i = 0; i < d->blocks.size(); i++) {
|
||||
MetadataBlock *block = d->blocks[i];
|
||||
if(block->code() == MetadataBlock::VorbisComment) {
|
||||
for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
|
||||
if((*it)->code() == MetadataBlock::VorbisComment) {
|
||||
// Set the new Vorbis Comment block
|
||||
delete block;
|
||||
block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData);
|
||||
foundVorbisCommentBlock = true;
|
||||
delete *it;
|
||||
d->blocks.erase(it);
|
||||
break;
|
||||
}
|
||||
if(block->code() == MetadataBlock::Padding) {
|
||||
delete block;
|
||||
continue;
|
||||
}
|
||||
newBlocks.append(block);
|
||||
}
|
||||
if(!foundVorbisCommentBlock) {
|
||||
newBlocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
|
||||
foundVorbisCommentBlock = true;
|
||||
}
|
||||
d->blocks = newBlocks;
|
||||
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
|
||||
|
||||
// Render data for the metadata blocks
|
||||
|
||||
ByteVector data;
|
||||
for(uint i = 0; i < newBlocks.size(); i++) {
|
||||
FLAC::MetadataBlock *block = newBlocks[i];
|
||||
ByteVector blockData = block->render();
|
||||
for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
|
||||
ByteVector blockData = (*it)->render();
|
||||
ByteVector blockHeader = ByteVector::fromUInt(blockData.size());
|
||||
blockHeader[0] = block->code();
|
||||
blockHeader[0] = (*it)->code();
|
||||
data.append(blockHeader);
|
||||
data.append(blockData);
|
||||
}
|
||||
|
||||
// Adjust the padding block(s)
|
||||
// Compute the amount of padding, and append that to data.
|
||||
// TODO: Should be calculated in offset_t in taglib2.
|
||||
|
||||
long originalLength = d->streamStart - d->flacStart;
|
||||
int paddingLength = originalLength - data.size() - 4;
|
||||
if (paddingLength < 0) {
|
||||
long paddingLength = originalLength - data.size() - 4;
|
||||
|
||||
if(paddingLength <= 0) {
|
||||
paddingLength = MinPaddingLength;
|
||||
}
|
||||
ByteVector padding = ByteVector::fromUInt(paddingLength);
|
||||
padding.resize(paddingLength + 4);
|
||||
padding[0] = (char)(FLAC::MetadataBlock::Padding | LastBlockFlag);
|
||||
data.append(padding);
|
||||
else {
|
||||
// Padding won't increase beyond 1% of the file size or 1MB.
|
||||
|
||||
long threshold = length() / 100;
|
||||
threshold = std::max(threshold, MinPaddingLength);
|
||||
threshold = std::min(threshold, MaxPaddingLegnth);
|
||||
|
||||
if(paddingLength > threshold)
|
||||
paddingLength = MinPaddingLength;
|
||||
}
|
||||
|
||||
ByteVector paddingHeader = ByteVector::fromUInt(paddingLength);
|
||||
paddingHeader[0] = static_cast<char>(MetadataBlock::Padding | LastBlockFlag);
|
||||
data.append(paddingHeader);
|
||||
data.resize(static_cast<unsigned int>(data.size() + paddingLength));
|
||||
|
||||
// Write the data to the file
|
||||
|
||||
insert(data, d->flacStart, originalLength);
|
||||
d->hasXiphComment = true;
|
||||
|
||||
d->streamStart += (static_cast<long>(data.size()) - originalLength);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location += (static_cast<long>(data.size()) - originalLength);
|
||||
|
||||
// Update ID3 tags
|
||||
|
||||
if(ID3v2Tag()) {
|
||||
if(d->hasID3v2) {
|
||||
if(d->ID3v2Location < d->flacStart)
|
||||
debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
|
||||
"start of the FLAC bytestream? Not writing the ID3v2 tag.");
|
||||
else
|
||||
insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
|
||||
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
|
||||
|
||||
// ID3v2 tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->ID3v2Location < 0)
|
||||
d->ID3v2Location = 0;
|
||||
|
||||
data = ID3v2Tag()->render();
|
||||
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
|
||||
|
||||
d->flacStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
|
||||
d->streamStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
|
||||
|
||||
d->ID3v2OriginalSize = data.size();
|
||||
}
|
||||
else {
|
||||
|
||||
// ID3v2 tag is empty. Remove the old one.
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
|
||||
|
||||
d->flacStart -= d->ID3v2OriginalSize;
|
||||
d->streamStart -= d->ID3v2OriginalSize;
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location -= d->ID3v2OriginalSize;
|
||||
|
||||
d->ID3v2Location = -1;
|
||||
d->ID3v2OriginalSize = 0;
|
||||
}
|
||||
else
|
||||
insert(ID3v2Tag()->render(), 0, 0);
|
||||
}
|
||||
|
||||
if(ID3v1Tag()) {
|
||||
seek(-128, End);
|
||||
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
|
||||
|
||||
// ID3v1 tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
seek(d->ID3v1Location);
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->ID3v1Location = tell();
|
||||
}
|
||||
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
}
|
||||
else {
|
||||
|
||||
// ID3v1 tag is empty. Remove the old one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
truncate(d->ID3v1Location);
|
||||
d->ID3v1Location = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -294,226 +320,23 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
|
||||
d->ID3v2FrameFactory = factory;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
{
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
d->ID3v2Location = findID3v2();
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
|
||||
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
|
||||
|
||||
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
|
||||
|
||||
if(ID3v2Tag()->header()->tagSize() <= 0)
|
||||
d->tag.set(FlacID3v2Index, 0);
|
||||
else
|
||||
d->hasID3v2 = true;
|
||||
}
|
||||
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
d->ID3v1Location = findID3v1();
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
// Look for FLAC metadata, including vorbis comments
|
||||
|
||||
scan();
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
if(d->hasXiphComment)
|
||||
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(xiphCommentData()));
|
||||
else
|
||||
d->tag.set(FlacXiphIndex, new Ogg::XiphComment);
|
||||
|
||||
if(readProperties)
|
||||
d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
|
||||
}
|
||||
|
||||
ByteVector FLAC::File::streamInfoData()
|
||||
{
|
||||
return isValid() ? d->streamInfoData : ByteVector();
|
||||
}
|
||||
|
||||
ByteVector FLAC::File::xiphCommentData() const
|
||||
{
|
||||
return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
|
||||
debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector.");
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
long FLAC::File::streamLength()
|
||||
{
|
||||
return d->streamLength;
|
||||
}
|
||||
|
||||
void FLAC::File::scan()
|
||||
{
|
||||
// Scan the metadata pages
|
||||
|
||||
if(d->scanned)
|
||||
return;
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
long nextBlockOffset;
|
||||
|
||||
if(d->hasID3v2)
|
||||
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
|
||||
else
|
||||
nextBlockOffset = find("fLaC");
|
||||
|
||||
if(nextBlockOffset < 0) {
|
||||
debug("FLAC::File::scan() -- FLAC stream not found");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
nextBlockOffset += 4;
|
||||
d->flacStart = nextBlockOffset;
|
||||
|
||||
seek(nextBlockOffset);
|
||||
|
||||
ByteVector header = readBlock(4);
|
||||
|
||||
// Header format (from spec):
|
||||
// <1> Last-metadata-block flag
|
||||
// <7> BLOCK_TYPE
|
||||
// 0 : STREAMINFO
|
||||
// 1 : PADDING
|
||||
// ..
|
||||
// 4 : VORBIS_COMMENT
|
||||
// ..
|
||||
// <24> Length of metadata to follow
|
||||
|
||||
char blockType = header[0] & 0x7f;
|
||||
bool isLastBlock = (header[0] & 0x80) != 0;
|
||||
uint length = header.toUInt(1U, 3U);
|
||||
|
||||
// First block should be the stream_info metadata
|
||||
|
||||
if(blockType != MetadataBlock::StreamInfo) {
|
||||
debug("FLAC::File::scan() -- invalid FLAC stream");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->streamInfoData = readBlock(length);
|
||||
d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData));
|
||||
nextBlockOffset += length + 4;
|
||||
|
||||
// Search through the remaining metadata
|
||||
while(!isLastBlock) {
|
||||
|
||||
header = readBlock(4);
|
||||
blockType = header[0] & 0x7f;
|
||||
isLastBlock = (header[0] & 0x80) != 0;
|
||||
length = header.toUInt(1U, 3U);
|
||||
|
||||
ByteVector data = readBlock(length);
|
||||
if(data.size() != length || length == 0) {
|
||||
debug("FLAC::File::scan() -- FLAC stream corrupted");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
MetadataBlock *block = 0;
|
||||
|
||||
// Found the vorbis-comment
|
||||
if(blockType == MetadataBlock::VorbisComment) {
|
||||
if(!d->hasXiphComment) {
|
||||
d->xiphCommentData = data;
|
||||
d->hasXiphComment = true;
|
||||
}
|
||||
else {
|
||||
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
|
||||
}
|
||||
}
|
||||
else if(blockType == MetadataBlock::Picture) {
|
||||
FLAC::Picture *picture = new FLAC::Picture();
|
||||
if(picture->parse(data)) {
|
||||
block = picture;
|
||||
}
|
||||
else {
|
||||
debug("FLAC::File::scan() -- invalid picture found, discarting");
|
||||
delete picture;
|
||||
}
|
||||
}
|
||||
|
||||
if(!block) {
|
||||
block = new UnknownMetadataBlock(blockType, data);
|
||||
}
|
||||
if(block->code() != MetadataBlock::Padding) {
|
||||
d->blocks.append(block);
|
||||
}
|
||||
else {
|
||||
delete block;
|
||||
}
|
||||
|
||||
nextBlockOffset += length + 4;
|
||||
|
||||
if(nextBlockOffset >= File::length()) {
|
||||
debug("FLAC::File::scan() -- FLAC stream corrupted");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
seek(nextBlockOffset);
|
||||
}
|
||||
|
||||
// End of metadata, now comes the datastream
|
||||
|
||||
d->streamStart = nextBlockOffset;
|
||||
d->streamLength = File::length() - d->streamStart;
|
||||
|
||||
if(d->hasID3v1)
|
||||
d->streamLength -= 128;
|
||||
|
||||
d->scanned = true;
|
||||
}
|
||||
|
||||
long FLAC::File::findID3v1()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(-128, End);
|
||||
long p = tell();
|
||||
|
||||
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
|
||||
return p;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
long FLAC::File::findID3v2()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(0);
|
||||
|
||||
if(readBlock(3) == ID3v2::Header::fileIdentifier())
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<FLAC::Picture *> FLAC::File::pictureList()
|
||||
{
|
||||
List<Picture *> pictures;
|
||||
for(uint i = 0; i < d->blocks.size(); i++) {
|
||||
Picture *picture = dynamic_cast<Picture *>(d->blocks[i]);
|
||||
for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
|
||||
Picture *picture = dynamic_cast<Picture *>(*it);
|
||||
if(picture) {
|
||||
pictures.append(picture);
|
||||
}
|
||||
@@ -528,8 +351,7 @@ void FLAC::File::addPicture(Picture *picture)
|
||||
|
||||
void FLAC::File::removePicture(Picture *picture, bool del)
|
||||
{
|
||||
MetadataBlock *block = picture;
|
||||
List<MetadataBlock *>::Iterator it = d->blocks.find(block);
|
||||
BlockIterator it = d->blocks.find(picture);
|
||||
if(it != d->blocks.end())
|
||||
d->blocks.erase(it);
|
||||
|
||||
@@ -539,30 +361,206 @@ void FLAC::File::removePicture(Picture *picture, bool del)
|
||||
|
||||
void FLAC::File::removePictures()
|
||||
{
|
||||
List<MetadataBlock *> newBlocks;
|
||||
for(uint i = 0; i < d->blocks.size(); i++) {
|
||||
Picture *picture = dynamic_cast<Picture *>(d->blocks[i]);
|
||||
if(picture) {
|
||||
delete picture;
|
||||
for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ) {
|
||||
if(dynamic_cast<Picture *>(*it)) {
|
||||
delete *it;
|
||||
it = d->blocks.erase(it);
|
||||
}
|
||||
else {
|
||||
newBlocks.append(d->blocks[i]);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
d->blocks = newBlocks;
|
||||
}
|
||||
|
||||
void FLAC::File::strip(int tags)
|
||||
{
|
||||
if(tags & ID3v1)
|
||||
d->tag.set(FlacID3v1Index, 0);
|
||||
|
||||
if(tags & ID3v2)
|
||||
d->tag.set(FlacID3v2Index, 0);
|
||||
|
||||
if(tags & XiphComment) {
|
||||
xiphComment()->removeAllFields();
|
||||
xiphComment()->removeAllPictures();
|
||||
}
|
||||
}
|
||||
|
||||
bool FLAC::File::hasXiphComment() const
|
||||
{
|
||||
return d->hasXiphComment;
|
||||
return !d->xiphCommentData.isEmpty();
|
||||
}
|
||||
|
||||
bool FLAC::File::hasID3v1Tag() const
|
||||
{
|
||||
return d->hasID3v1;
|
||||
return (d->ID3v1Location >= 0);
|
||||
}
|
||||
|
||||
bool FLAC::File::hasID3v2Tag() const
|
||||
{
|
||||
return d->hasID3v2;
|
||||
return (d->ID3v2Location >= 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FLAC::File::read(bool readProperties)
|
||||
{
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
d->ID3v2Location = Utils::findID3v2(this);
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
|
||||
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
|
||||
}
|
||||
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
d->ID3v1Location = Utils::findID3v1(this);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
|
||||
|
||||
// Look for FLAC metadata, including vorbis comments
|
||||
|
||||
scan();
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
if(!d->xiphCommentData.isEmpty())
|
||||
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData));
|
||||
else
|
||||
d->tag.set(FlacXiphIndex, new Ogg::XiphComment());
|
||||
|
||||
if(readProperties) {
|
||||
|
||||
// First block should be the stream_info metadata
|
||||
|
||||
const ByteVector infoData = d->blocks.front()->render();
|
||||
|
||||
long streamLength;
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
streamLength = d->ID3v1Location - d->streamStart;
|
||||
else
|
||||
streamLength = length() - d->streamStart;
|
||||
|
||||
d->properties = new Properties(infoData, streamLength);
|
||||
}
|
||||
}
|
||||
|
||||
void FLAC::File::scan()
|
||||
{
|
||||
// Scan the metadata pages
|
||||
|
||||
if(d->scanned)
|
||||
return;
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
long nextBlockOffset;
|
||||
|
||||
if(d->ID3v2Location >= 0)
|
||||
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
|
||||
else
|
||||
nextBlockOffset = find("fLaC");
|
||||
|
||||
if(nextBlockOffset < 0) {
|
||||
debug("FLAC::File::scan() -- FLAC stream not found");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
nextBlockOffset += 4;
|
||||
d->flacStart = nextBlockOffset;
|
||||
|
||||
while(true) {
|
||||
|
||||
seek(nextBlockOffset);
|
||||
const ByteVector header = readBlock(4);
|
||||
|
||||
// Header format (from spec):
|
||||
// <1> Last-metadata-block flag
|
||||
// <7> BLOCK_TYPE
|
||||
// 0 : STREAMINFO
|
||||
// 1 : PADDING
|
||||
// ..
|
||||
// 4 : VORBIS_COMMENT
|
||||
// ..
|
||||
// 6 : PICTURE
|
||||
// ..
|
||||
// <24> Length of metadata to follow
|
||||
|
||||
const char blockType = header[0] & ~LastBlockFlag;
|
||||
const bool isLastBlock = (header[0] & LastBlockFlag) != 0;
|
||||
const unsigned int blockLength = header.toUInt(1U, 3U);
|
||||
|
||||
// First block should be the stream_info metadata
|
||||
|
||||
if(d->blocks.isEmpty() && blockType != MetadataBlock::StreamInfo) {
|
||||
debug("FLAC::File::scan() -- First block should be the stream_info metadata");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(blockLength == 0 && blockType != MetadataBlock::Padding) {
|
||||
debug("FLAC::File::scan() -- Zero-sized metadata block found");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const ByteVector data = readBlock(blockLength);
|
||||
if(data.size() != blockLength) {
|
||||
debug("FLAC::File::scan() -- Failed to read a metadata block");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
MetadataBlock *block = 0;
|
||||
|
||||
// Found the vorbis-comment
|
||||
if(blockType == MetadataBlock::VorbisComment) {
|
||||
if(d->xiphCommentData.isEmpty()) {
|
||||
d->xiphCommentData = data;
|
||||
block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, data);
|
||||
}
|
||||
else {
|
||||
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, discarding");
|
||||
}
|
||||
}
|
||||
else if(blockType == MetadataBlock::Picture) {
|
||||
FLAC::Picture *picture = new FLAC::Picture();
|
||||
if(picture->parse(data)) {
|
||||
block = picture;
|
||||
}
|
||||
else {
|
||||
debug("FLAC::File::scan() -- invalid picture found, discarding");
|
||||
delete picture;
|
||||
}
|
||||
}
|
||||
else if(blockType == MetadataBlock::Padding) {
|
||||
// Skip all padding blocks.
|
||||
}
|
||||
else {
|
||||
block = new UnknownMetadataBlock(blockType, data);
|
||||
}
|
||||
|
||||
if(block)
|
||||
d->blocks.append(block);
|
||||
|
||||
nextBlockOffset += blockLength + 4;
|
||||
|
||||
if(isLastBlock)
|
||||
break;
|
||||
}
|
||||
|
||||
// End of metadata, now comes the datastream
|
||||
|
||||
d->streamStart = nextBlockOffset;
|
||||
|
||||
d->scanned = true;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* This is implementation of FLAC metadata for non-Ogg FLAC files. At some
|
||||
* point when Ogg / FLAC is more common there will be a similar implementation
|
||||
* under the Ogg hiearchy.
|
||||
* under the Ogg hierarchy.
|
||||
*
|
||||
* This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream
|
||||
* properties from the file.
|
||||
@@ -66,6 +66,23 @@ namespace TagLib {
|
||||
class TAGLIB_EXPORT File : public TagLib::File
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* This set of flags is used for various operations and is suitable for
|
||||
* being OR-ed together.
|
||||
*/
|
||||
enum TagTypes {
|
||||
//! Empty set. Matches no tag types.
|
||||
NoTags = 0x0000,
|
||||
//! Matches Vorbis comments.
|
||||
XiphComment = 0x0001,
|
||||
//! Matches ID3v1 tags.
|
||||
ID3v1 = 0x0002,
|
||||
//! Matches ID3v2 tags.
|
||||
ID3v2 = 0x0004,
|
||||
//! Matches all tag types.
|
||||
AllTags = 0xffff
|
||||
};
|
||||
|
||||
/*!
|
||||
* Constructs a FLAC file from \a file. If \a readProperties is true the
|
||||
* file's audio properties will also be read.
|
||||
@@ -79,7 +96,7 @@ namespace TagLib {
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
* Constructs an APE file from \a file. If \a readProperties is true the
|
||||
* Constructs an FLAC file from \a file. If \a readProperties is true the
|
||||
* file's audio properties will also be read.
|
||||
*
|
||||
* If this file contains and ID3v2 tag the frames will be created using
|
||||
@@ -165,8 +182,8 @@ namespace TagLib {
|
||||
* if there is no valid ID3v2 tag. If \a create is true it will create
|
||||
* an ID3v2 tag if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
|
||||
* on disk actually has an ID3v2 tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -184,8 +201,8 @@ namespace TagLib {
|
||||
* if there is no valid APE tag. If \a create is true it will create
|
||||
* an APE tag if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -203,10 +220,10 @@ namespace TagLib {
|
||||
* if there is no valid XiphComment. If \a create is true it will create
|
||||
* a XiphComment if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has a XiphComment. Use hasXiphComment() to check if the
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has a XiphComment. Use hasXiphComment() to check if the
|
||||
* file on disk actually has a XiphComment.
|
||||
*
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the FLAC::File and should not be
|
||||
* deleted by the user. It will be deleted when the file (object) is
|
||||
* destroyed.
|
||||
@@ -221,6 +238,7 @@ namespace TagLib {
|
||||
* when
|
||||
*
|
||||
* \see ID3v2FrameFactory
|
||||
* \deprecated This value should be passed in via the constructor
|
||||
*/
|
||||
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
|
||||
|
||||
@@ -228,7 +246,7 @@ namespace TagLib {
|
||||
* Returns the block of data used by FLAC::Properties for parsing the
|
||||
* stream properties.
|
||||
*
|
||||
* \deprecated This method will not be public in a future release.
|
||||
* \deprecated Always returns an empty vector.
|
||||
*/
|
||||
ByteVector streamInfoData(); // BIC: remove
|
||||
|
||||
@@ -236,7 +254,7 @@ namespace TagLib {
|
||||
* Returns the length of the audio-stream, used by FLAC::Properties for
|
||||
* calculating the bitrate.
|
||||
*
|
||||
* \deprecated This method will not be public in a future release.
|
||||
* \deprecated Always returns zero.
|
||||
*/
|
||||
long streamLength(); // BIC: remove
|
||||
|
||||
@@ -264,6 +282,21 @@ namespace TagLib {
|
||||
*/
|
||||
void addPicture(Picture *picture);
|
||||
|
||||
/*!
|
||||
* This will remove the tags that match the OR-ed together TagTypes from
|
||||
* the file. By default it removes all tags.
|
||||
*
|
||||
* \warning This will also invalidate pointers to the tags as their memory
|
||||
* will be freed.
|
||||
*
|
||||
* \note In order to make the removal permanent save() still needs to be
|
||||
* called.
|
||||
*
|
||||
* \note This won't remove the Vorbis comment block completely. The
|
||||
* vendor ID will be preserved.
|
||||
*/
|
||||
void strip(int tags = AllTags);
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk actually has a XiphComment.
|
||||
*
|
||||
@@ -289,12 +322,8 @@ namespace TagLib {
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void read(bool readProperties);
|
||||
void scan();
|
||||
long findID3v2();
|
||||
long findID3v1();
|
||||
ByteVector xiphCommentData() const;
|
||||
long findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
||||
@@ -78,10 +78,10 @@ bool FLAC::Picture::parse(const ByteVector &data)
|
||||
return false;
|
||||
}
|
||||
|
||||
uint pos = 0;
|
||||
unsigned int pos = 0;
|
||||
d->type = FLAC::Picture::Type(data.toUInt(pos));
|
||||
pos += 4;
|
||||
uint mimeTypeLength = data.toUInt(pos);
|
||||
unsigned int mimeTypeLength = data.toUInt(pos);
|
||||
pos += 4;
|
||||
if(pos + mimeTypeLength + 24 > data.size()) {
|
||||
debug("Invalid picture block.");
|
||||
@@ -89,7 +89,7 @@ bool FLAC::Picture::parse(const ByteVector &data)
|
||||
}
|
||||
d->mimeType = String(data.mid(pos, mimeTypeLength), String::UTF8);
|
||||
pos += mimeTypeLength;
|
||||
uint descriptionLength = data.toUInt(pos);
|
||||
unsigned int descriptionLength = data.toUInt(pos);
|
||||
pos += 4;
|
||||
if(pos + descriptionLength + 20 > data.size()) {
|
||||
debug("Invalid picture block.");
|
||||
@@ -105,7 +105,7 @@ bool FLAC::Picture::parse(const ByteVector &data)
|
||||
pos += 4;
|
||||
d->numColors = data.toUInt(pos);
|
||||
pos += 4;
|
||||
uint dataLength = data.toUInt(pos);
|
||||
unsigned int dataLength = data.toUInt(pos);
|
||||
pos += 4;
|
||||
if(pos + dataLength > data.size()) {
|
||||
debug("Invalid picture block.");
|
||||
|
||||
@@ -34,24 +34,18 @@ using namespace TagLib;
|
||||
class FLAC::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(ByteVector d, long st, ReadStyle s) :
|
||||
data(d),
|
||||
streamLength(st),
|
||||
style(s),
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
sampleWidth(0),
|
||||
bitsPerSample(0),
|
||||
channels(0),
|
||||
sampleFrames(0) {}
|
||||
|
||||
ByteVector data;
|
||||
long streamLength;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int sampleWidth;
|
||||
int bitsPerSample;
|
||||
int channels;
|
||||
unsigned long long sampleFrames;
|
||||
ByteVector signature;
|
||||
@@ -61,16 +55,18 @@ public:
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : AudioProperties(style)
|
||||
FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(data, streamLength, style);
|
||||
read();
|
||||
read(data, streamLength);
|
||||
}
|
||||
|
||||
FLAC::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style)
|
||||
FLAC::Properties::Properties(File *, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(file->streamInfoData(), file->streamLength(), style);
|
||||
read();
|
||||
debug("FLAC::Properties::Properties() - This constructor is no longer used.");
|
||||
}
|
||||
|
||||
FLAC::Properties::~Properties()
|
||||
@@ -79,6 +75,16 @@ FLAC::Properties::~Properties()
|
||||
}
|
||||
|
||||
int FLAC::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int FLAC::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int FLAC::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@@ -93,9 +99,14 @@ int FLAC::Properties::sampleRate() const
|
||||
return d->sampleRate;
|
||||
}
|
||||
|
||||
int FLAC::Properties::bitsPerSample() const
|
||||
{
|
||||
return d->bitsPerSample;
|
||||
}
|
||||
|
||||
int FLAC::Properties::sampleWidth() const
|
||||
{
|
||||
return d->sampleWidth;
|
||||
return bitsPerSample();
|
||||
}
|
||||
|
||||
int FLAC::Properties::channels() const
|
||||
@@ -117,14 +128,14 @@ ByteVector FLAC::Properties::signature() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FLAC::Properties::read()
|
||||
void FLAC::Properties::read(const ByteVector &data, long streamLength)
|
||||
{
|
||||
if(d->data.size() < 18) {
|
||||
if(data.size() < 18) {
|
||||
debug("FLAC::Properties::read() - FLAC properties must contain at least 18 bytes.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint pos = 0;
|
||||
unsigned int pos = 0;
|
||||
|
||||
// Minimum block size (in samples)
|
||||
pos += 2;
|
||||
@@ -138,32 +149,28 @@ void FLAC::Properties::read()
|
||||
// Maximum frame size (in bytes)
|
||||
pos += 3;
|
||||
|
||||
uint flags = d->data.toUInt(pos, true);
|
||||
const unsigned int flags = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
|
||||
d->sampleRate = flags >> 12;
|
||||
d->channels = ((flags >> 9) & 7) + 1;
|
||||
d->sampleWidth = ((flags >> 4) & 31) + 1;
|
||||
d->sampleRate = flags >> 12;
|
||||
d->channels = ((flags >> 9) & 7) + 1;
|
||||
d->bitsPerSample = ((flags >> 4) & 31) + 1;
|
||||
|
||||
// The last 4 bits are the most significant 4 bits for the 36 bit
|
||||
// stream length in samples. (Audio files measured in days)
|
||||
|
||||
unsigned long long hi = flags & 0xf;
|
||||
unsigned long long lo = d->data.toUInt(pos, true);
|
||||
const unsigned long long hi = flags & 0xf;
|
||||
const unsigned long long lo = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
|
||||
d->sampleFrames = (hi << 32) | lo;
|
||||
|
||||
if(d->sampleRate > 0)
|
||||
d->length = int(d->sampleFrames / d->sampleRate);
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
|
||||
// Uncompressed bitrate:
|
||||
|
||||
//d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth;
|
||||
|
||||
// Real bitrate:
|
||||
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8UL) / d->length) / 1000 : 0;
|
||||
|
||||
d->signature = d->data.mid(pos, 32);
|
||||
if(data.size() >= pos + 16)
|
||||
d->signature = data.mid(pos, 16);
|
||||
}
|
||||
|
||||
@@ -64,27 +64,72 @@ namespace TagLib {
|
||||
*/
|
||||
virtual ~Properties();
|
||||
|
||||
// Reimplementations.
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \note This method is just an alias of lengthInSeconds().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
virtual int length() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the average bit rate of the file in kb/s.
|
||||
*/
|
||||
virtual int bitrate() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of bits per audio sample as read from the FLAC
|
||||
* identification header.
|
||||
*/
|
||||
int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample width as read from the FLAC identification
|
||||
* header.
|
||||
*
|
||||
* \note This method is just an alias of bitsPerSample().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
int sampleWidth() const;
|
||||
|
||||
/*!
|
||||
* Return the number of sample frames
|
||||
* Return the number of sample frames.
|
||||
*/
|
||||
unsigned long long sampleFrames() const;
|
||||
|
||||
/*!
|
||||
* Returns the MD5 signature of the uncompressed audio stream as read
|
||||
* from the stream info header header.
|
||||
* from the stream info header.
|
||||
*/
|
||||
ByteVector signature() const;
|
||||
|
||||
@@ -92,7 +137,7 @@ namespace TagLib {
|
||||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(const ByteVector &data, long streamLength);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "tstringlist.h"
|
||||
#include "itfile.h"
|
||||
#include "tdebug.h"
|
||||
@@ -96,9 +101,9 @@ bool IT::File::save()
|
||||
|
||||
seek(2, Current);
|
||||
|
||||
ushort length = 0;
|
||||
ushort instrumentCount = 0;
|
||||
ushort sampleCount = 0;
|
||||
unsigned short length = 0;
|
||||
unsigned short instrumentCount = 0;
|
||||
unsigned short sampleCount = 0;
|
||||
|
||||
if(!readU16L(length) || !readU16L(instrumentCount) || !readU16L(sampleCount))
|
||||
return false;
|
||||
@@ -107,9 +112,9 @@ bool IT::File::save()
|
||||
|
||||
// write comment as instrument and sample names:
|
||||
StringList lines = d->tag.comment().split("\n");
|
||||
for(ushort i = 0; i < instrumentCount; ++ i) {
|
||||
for(unsigned short i = 0; i < instrumentCount; ++ i) {
|
||||
seek(192L + length + ((long)i << 2));
|
||||
ulong instrumentOffset = 0;
|
||||
unsigned long instrumentOffset = 0;
|
||||
if(!readU32L(instrumentOffset))
|
||||
return false;
|
||||
|
||||
@@ -118,28 +123,28 @@ bool IT::File::save()
|
||||
if(i < lines.size())
|
||||
writeString(lines[i], 25);
|
||||
else
|
||||
writeString(String::null, 25);
|
||||
writeString(String(), 25);
|
||||
writeByte(0);
|
||||
}
|
||||
|
||||
for(ushort i = 0; i < sampleCount; ++ i) {
|
||||
for(unsigned short i = 0; i < sampleCount; ++ i) {
|
||||
seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2));
|
||||
ulong sampleOffset = 0;
|
||||
unsigned long sampleOffset = 0;
|
||||
if(!readU32L(sampleOffset))
|
||||
return false;
|
||||
|
||||
seek(sampleOffset + 20);
|
||||
|
||||
if((TagLib::uint)(i + instrumentCount) < lines.size())
|
||||
if((unsigned int)(i + instrumentCount) < lines.size())
|
||||
writeString(lines[i + instrumentCount], 25);
|
||||
else
|
||||
writeString(String::null, 25);
|
||||
writeString(String(), 25);
|
||||
writeByte(0);
|
||||
}
|
||||
|
||||
// write rest as message:
|
||||
StringList messageLines;
|
||||
for(uint i = instrumentCount + sampleCount; i < lines.size(); ++ i)
|
||||
for(unsigned int i = instrumentCount + sampleCount; i < lines.size(); ++ i)
|
||||
messageLines.append(lines[i]);
|
||||
ByteVector message = messageLines.toString("\r").data(String::Latin1);
|
||||
|
||||
@@ -149,15 +154,15 @@ bool IT::File::save()
|
||||
message.resize(7999);
|
||||
message.append((char)0);
|
||||
|
||||
ushort special = 0;
|
||||
ushort messageLength = 0;
|
||||
ulong messageOffset = 0;
|
||||
unsigned short special = 0;
|
||||
unsigned short messageLength = 0;
|
||||
unsigned long messageOffset = 0;
|
||||
|
||||
seek(46);
|
||||
if(!readU16L(special))
|
||||
return false;
|
||||
|
||||
ulong fileSize = File::length();
|
||||
unsigned long fileSize = File::length();
|
||||
if(special & Properties::MessageAttached) {
|
||||
seek(54);
|
||||
if(!readU16L(messageLength) || !readU32L(messageOffset))
|
||||
@@ -259,8 +264,8 @@ void IT::File::read(bool)
|
||||
d->properties.setChannels(channels);
|
||||
|
||||
// real length might be shorter because of skips and terminator
|
||||
ushort realLength = 0;
|
||||
for(ushort i = 0; i < length; ++ i) {
|
||||
unsigned short realLength = 0;
|
||||
for(unsigned short i = 0; i < length; ++ i) {
|
||||
READ_BYTE_AS(order);
|
||||
if(order == 255) break;
|
||||
if(order != 254) ++ realLength;
|
||||
@@ -274,7 +279,7 @@ void IT::File::read(bool)
|
||||
// Currently I just discard anything after a nil, but
|
||||
// e.g. VLC seems to interprete a nil as a space. I
|
||||
// don't know what is the proper behaviour.
|
||||
for(ushort i = 0; i < instrumentCount; ++ i) {
|
||||
for(unsigned short i = 0; i < instrumentCount; ++ i) {
|
||||
seek(192L + length + ((long)i << 2));
|
||||
READ_U32L_AS(instrumentOffset);
|
||||
seek(instrumentOffset);
|
||||
@@ -290,7 +295,7 @@ void IT::File::read(bool)
|
||||
comment.append(instrumentName);
|
||||
}
|
||||
|
||||
for(ushort i = 0; i < sampleCount; ++ i) {
|
||||
for(unsigned short i = 0; i < sampleCount; ++ i) {
|
||||
seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2));
|
||||
READ_U32L_AS(sampleOffset);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "itproperties.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -46,21 +51,21 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
int channels;
|
||||
ushort lengthInPatterns;
|
||||
ushort instrumentCount;
|
||||
ushort sampleCount;
|
||||
ushort patternCount;
|
||||
ushort version;
|
||||
ushort compatibleVersion;
|
||||
ushort flags;
|
||||
ushort special;
|
||||
uchar globalVolume;
|
||||
uchar mixVolume;
|
||||
uchar tempo;
|
||||
uchar bpmSpeed;
|
||||
uchar panningSeparation;
|
||||
uchar pitchWheelDepth;
|
||||
int channels;
|
||||
unsigned short lengthInPatterns;
|
||||
unsigned short instrumentCount;
|
||||
unsigned short sampleCount;
|
||||
unsigned short patternCount;
|
||||
unsigned short version;
|
||||
unsigned short compatibleVersion;
|
||||
unsigned short flags;
|
||||
unsigned short special;
|
||||
unsigned char globalVolume;
|
||||
unsigned char mixVolume;
|
||||
unsigned char tempo;
|
||||
unsigned char bpmSpeed;
|
||||
unsigned char panningSeparation;
|
||||
unsigned char pitchWheelDepth;
|
||||
};
|
||||
|
||||
IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
|
||||
@@ -79,6 +84,16 @@ int IT::Properties::length() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::lengthInSeconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
@@ -94,7 +109,7 @@ int IT::Properties::channels() const
|
||||
return d->channels;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::lengthInPatterns() const
|
||||
unsigned short IT::Properties::lengthInPatterns() const
|
||||
{
|
||||
return d->lengthInPatterns;
|
||||
}
|
||||
@@ -104,67 +119,67 @@ bool IT::Properties::stereo() const
|
||||
return d->flags & Stereo;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::instrumentCount() const
|
||||
unsigned short IT::Properties::instrumentCount() const
|
||||
{
|
||||
return d->instrumentCount;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::sampleCount() const
|
||||
unsigned short IT::Properties::sampleCount() const
|
||||
{
|
||||
return d->sampleCount;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::patternCount() const
|
||||
unsigned short IT::Properties::patternCount() const
|
||||
{
|
||||
return d->patternCount;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::version() const
|
||||
unsigned short IT::Properties::version() const
|
||||
{
|
||||
return d->version;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::compatibleVersion() const
|
||||
unsigned short IT::Properties::compatibleVersion() const
|
||||
{
|
||||
return d->compatibleVersion;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::flags() const
|
||||
unsigned short IT::Properties::flags() const
|
||||
{
|
||||
return d->flags;
|
||||
}
|
||||
|
||||
TagLib::ushort IT::Properties::special() const
|
||||
unsigned short IT::Properties::special() const
|
||||
{
|
||||
return d->special;
|
||||
}
|
||||
|
||||
uchar IT::Properties::globalVolume() const
|
||||
unsigned char IT::Properties::globalVolume() const
|
||||
{
|
||||
return d->globalVolume;
|
||||
}
|
||||
|
||||
uchar IT::Properties::mixVolume() const
|
||||
unsigned char IT::Properties::mixVolume() const
|
||||
{
|
||||
return d->mixVolume;
|
||||
}
|
||||
|
||||
uchar IT::Properties::tempo() const
|
||||
unsigned char IT::Properties::tempo() const
|
||||
{
|
||||
return d->tempo;
|
||||
}
|
||||
|
||||
uchar IT::Properties::bpmSpeed() const
|
||||
unsigned char IT::Properties::bpmSpeed() const
|
||||
{
|
||||
return d->bpmSpeed;
|
||||
}
|
||||
|
||||
uchar IT::Properties::panningSeparation() const
|
||||
unsigned char IT::Properties::panningSeparation() const
|
||||
{
|
||||
return d->panningSeparation;
|
||||
}
|
||||
|
||||
uchar IT::Properties::pitchWheelDepth() const
|
||||
unsigned char IT::Properties::pitchWheelDepth() const
|
||||
{
|
||||
return d->pitchWheelDepth;
|
||||
}
|
||||
@@ -174,72 +189,72 @@ void IT::Properties::setChannels(int channels)
|
||||
d->channels = channels;
|
||||
}
|
||||
|
||||
void IT::Properties::setLengthInPatterns(ushort lengthInPatterns)
|
||||
void IT::Properties::setLengthInPatterns(unsigned short lengthInPatterns)
|
||||
{
|
||||
d->lengthInPatterns = lengthInPatterns;
|
||||
}
|
||||
|
||||
void IT::Properties::setInstrumentCount(ushort instrumentCount)
|
||||
void IT::Properties::setInstrumentCount(unsigned short instrumentCount)
|
||||
{
|
||||
d->instrumentCount = instrumentCount;
|
||||
}
|
||||
|
||||
void IT::Properties::setSampleCount(ushort sampleCount)
|
||||
void IT::Properties::setSampleCount(unsigned short sampleCount)
|
||||
{
|
||||
d->sampleCount = sampleCount;
|
||||
}
|
||||
|
||||
void IT::Properties::setPatternCount(ushort patternCount)
|
||||
void IT::Properties::setPatternCount(unsigned short patternCount)
|
||||
{
|
||||
d->patternCount = patternCount;
|
||||
}
|
||||
|
||||
void IT::Properties::setFlags(ushort flags)
|
||||
void IT::Properties::setFlags(unsigned short flags)
|
||||
{
|
||||
d->flags = flags;
|
||||
}
|
||||
|
||||
void IT::Properties::setSpecial(ushort special)
|
||||
void IT::Properties::setSpecial(unsigned short special)
|
||||
{
|
||||
d->special = special;
|
||||
}
|
||||
|
||||
void IT::Properties::setCompatibleVersion(ushort compatibleVersion)
|
||||
void IT::Properties::setCompatibleVersion(unsigned short compatibleVersion)
|
||||
{
|
||||
d->compatibleVersion = compatibleVersion;
|
||||
}
|
||||
|
||||
void IT::Properties::setVersion(ushort version)
|
||||
void IT::Properties::setVersion(unsigned short version)
|
||||
{
|
||||
d->version = version;
|
||||
}
|
||||
|
||||
void IT::Properties::setGlobalVolume(uchar globalVolume)
|
||||
void IT::Properties::setGlobalVolume(unsigned char globalVolume)
|
||||
{
|
||||
d->globalVolume = globalVolume;
|
||||
}
|
||||
|
||||
void IT::Properties::setMixVolume(uchar mixVolume)
|
||||
void IT::Properties::setMixVolume(unsigned char mixVolume)
|
||||
{
|
||||
d->mixVolume = mixVolume;
|
||||
}
|
||||
|
||||
void IT::Properties::setTempo(uchar tempo)
|
||||
void IT::Properties::setTempo(unsigned char tempo)
|
||||
{
|
||||
d->tempo = tempo;
|
||||
}
|
||||
|
||||
void IT::Properties::setBpmSpeed(uchar bpmSpeed)
|
||||
void IT::Properties::setBpmSpeed(unsigned char bpmSpeed)
|
||||
{
|
||||
d->bpmSpeed = bpmSpeed;
|
||||
}
|
||||
|
||||
void IT::Properties::setPanningSeparation(uchar panningSeparation)
|
||||
void IT::Properties::setPanningSeparation(unsigned char panningSeparation)
|
||||
{
|
||||
d->panningSeparation = panningSeparation;
|
||||
}
|
||||
|
||||
void IT::Properties::setPitchWheelDepth(uchar pitchWheelDepth)
|
||||
void IT::Properties::setPitchWheelDepth(unsigned char pitchWheelDepth)
|
||||
{
|
||||
d->pitchWheelDepth = pitchWheelDepth;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,8 +15,12 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_ITPROPERTIES_H
|
||||
@@ -51,42 +55,44 @@ namespace TagLib {
|
||||
Properties(AudioProperties::ReadStyle propertiesStyle);
|
||||
virtual ~Properties();
|
||||
|
||||
int length() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
int length() const;
|
||||
int lengthInSeconds() const;
|
||||
int lengthInMilliseconds() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
|
||||
ushort lengthInPatterns() const;
|
||||
bool stereo() const;
|
||||
ushort instrumentCount() const;
|
||||
ushort sampleCount() const;
|
||||
ushort patternCount() const;
|
||||
ushort version() const;
|
||||
ushort compatibleVersion() const;
|
||||
ushort flags() const;
|
||||
ushort special() const;
|
||||
uchar globalVolume() const;
|
||||
uchar mixVolume() const;
|
||||
uchar tempo() const;
|
||||
uchar bpmSpeed() const;
|
||||
uchar panningSeparation() const;
|
||||
uchar pitchWheelDepth() const;
|
||||
unsigned short lengthInPatterns() const;
|
||||
bool stereo() const;
|
||||
unsigned short instrumentCount() const;
|
||||
unsigned short sampleCount() const;
|
||||
unsigned short patternCount() const;
|
||||
unsigned short version() const;
|
||||
unsigned short compatibleVersion() const;
|
||||
unsigned short flags() const;
|
||||
unsigned short special() const;
|
||||
unsigned char globalVolume() const;
|
||||
unsigned char mixVolume() const;
|
||||
unsigned char tempo() const;
|
||||
unsigned char bpmSpeed() const;
|
||||
unsigned char panningSeparation() const;
|
||||
unsigned char pitchWheelDepth() const;
|
||||
|
||||
void setChannels(int channels);
|
||||
void setLengthInPatterns(ushort lengthInPatterns);
|
||||
void setInstrumentCount(ushort instrumentCount);
|
||||
void setSampleCount (ushort sampleCount);
|
||||
void setPatternCount(ushort patternCount);
|
||||
void setVersion (ushort version);
|
||||
void setCompatibleVersion(ushort compatibleVersion);
|
||||
void setFlags (ushort flags);
|
||||
void setSpecial (ushort special);
|
||||
void setGlobalVolume(uchar globalVolume);
|
||||
void setMixVolume (uchar mixVolume);
|
||||
void setTempo (uchar tempo);
|
||||
void setBpmSpeed (uchar bpmSpeed);
|
||||
void setPanningSeparation(uchar panningSeparation);
|
||||
void setPitchWheelDepth (uchar pitchWheelDepth);
|
||||
void setLengthInPatterns(unsigned short lengthInPatterns);
|
||||
void setInstrumentCount(unsigned short instrumentCount);
|
||||
void setSampleCount (unsigned short sampleCount);
|
||||
void setPatternCount(unsigned short patternCount);
|
||||
void setVersion (unsigned short version);
|
||||
void setCompatibleVersion(unsigned short compatibleVersion);
|
||||
void setFlags (unsigned short flags);
|
||||
void setSpecial (unsigned short special);
|
||||
void setGlobalVolume(unsigned char globalVolume);
|
||||
void setMixVolume (unsigned char mixVolume);
|
||||
void setTempo (unsigned char tempo);
|
||||
void setBpmSpeed (unsigned char bpmSpeed);
|
||||
void setPanningSeparation(unsigned char panningSeparation);
|
||||
void setPitchWheelDepth (unsigned char pitchWheelDepth);
|
||||
|
||||
private:
|
||||
Properties(const Properties&);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "modfile.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tdebug.h"
|
||||
@@ -92,14 +97,14 @@ bool Mod::File::save()
|
||||
seek(0);
|
||||
writeString(d->tag.title(), 20);
|
||||
StringList lines = d->tag.comment().split("\n");
|
||||
uint n = std::min(lines.size(), d->properties.instrumentCount());
|
||||
for(uint i = 0; i < n; ++ i) {
|
||||
unsigned int n = std::min(lines.size(), d->properties.instrumentCount());
|
||||
for(unsigned int i = 0; i < n; ++ i) {
|
||||
writeString(lines[i], 22);
|
||||
seek(8, Current);
|
||||
}
|
||||
|
||||
for(uint i = n; i < d->properties.instrumentCount(); ++ i) {
|
||||
writeString(String::null, 22);
|
||||
for(unsigned int i = n; i < d->properties.instrumentCount(); ++ i) {
|
||||
writeString(String(), 22);
|
||||
seek(8, Current);
|
||||
}
|
||||
return true;
|
||||
@@ -114,8 +119,8 @@ void Mod::File::read(bool)
|
||||
ByteVector modId = readBlock(4);
|
||||
READ_ASSERT(modId.size() == 4);
|
||||
|
||||
int channels = 4;
|
||||
uint instruments = 31;
|
||||
int channels = 4;
|
||||
unsigned int instruments = 31;
|
||||
if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") {
|
||||
d->tag.setTrackerName("ProTracker");
|
||||
channels = 4;
|
||||
@@ -159,7 +164,7 @@ void Mod::File::read(bool)
|
||||
READ_STRING(d->tag.setTitle, 20);
|
||||
|
||||
StringList comment;
|
||||
for(uint i = 0; i < instruments; ++ i) {
|
||||
for(unsigned int i = 0; i < instruments; ++ i) {
|
||||
READ_STRING_AS(instrumentName, 22);
|
||||
// value in words, * 2 (<< 1) for bytes:
|
||||
READ_U16B_AS(sampleLength);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,8 +15,12 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_MODFILE_H
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "tdebug.h"
|
||||
#include "modfilebase.h"
|
||||
|
||||
@@ -33,14 +38,14 @@ Mod::FileBase::FileBase(IOStream *stream) : TagLib::File(stream)
|
||||
{
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeString(const String &s, ulong size, char padding)
|
||||
void Mod::FileBase::writeString(const String &s, unsigned long size, char padding)
|
||||
{
|
||||
ByteVector data(s.data(String::Latin1));
|
||||
data.resize(size, padding);
|
||||
writeBlock(data);
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readString(String &s, ulong size)
|
||||
bool Mod::FileBase::readString(String &s, unsigned long size)
|
||||
{
|
||||
ByteVector data(readBlock(size));
|
||||
if(data.size() < size) return false;
|
||||
@@ -49,39 +54,39 @@ bool Mod::FileBase::readString(String &s, ulong size)
|
||||
{
|
||||
data.resize(index);
|
||||
}
|
||||
data.replace((char) 0xff, ' ');
|
||||
data.replace('\xff', ' ');
|
||||
|
||||
s = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeByte(uchar byte)
|
||||
void Mod::FileBase::writeByte(unsigned char byte)
|
||||
{
|
||||
ByteVector data(1, byte);
|
||||
writeBlock(data);
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeU16L(ushort number)
|
||||
void Mod::FileBase::writeU16L(unsigned short number)
|
||||
{
|
||||
writeBlock(ByteVector::fromShort(number, false));
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeU32L(ulong number)
|
||||
void Mod::FileBase::writeU32L(unsigned long number)
|
||||
{
|
||||
writeBlock(ByteVector::fromUInt(number, false));
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeU16B(ushort number)
|
||||
void Mod::FileBase::writeU16B(unsigned short number)
|
||||
{
|
||||
writeBlock(ByteVector::fromShort(number, true));
|
||||
}
|
||||
|
||||
void Mod::FileBase::writeU32B(ulong number)
|
||||
void Mod::FileBase::writeU32B(unsigned long number)
|
||||
{
|
||||
writeBlock(ByteVector::fromUInt(number, true));
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readByte(uchar &byte)
|
||||
bool Mod::FileBase::readByte(unsigned char &byte)
|
||||
{
|
||||
ByteVector data(readBlock(1));
|
||||
if(data.size() < 1) return false;
|
||||
@@ -89,7 +94,7 @@ bool Mod::FileBase::readByte(uchar &byte)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readU16L(ushort &number)
|
||||
bool Mod::FileBase::readU16L(unsigned short &number)
|
||||
{
|
||||
ByteVector data(readBlock(2));
|
||||
if(data.size() < 2) return false;
|
||||
@@ -97,14 +102,14 @@ bool Mod::FileBase::readU16L(ushort &number)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readU32L(ulong &number) {
|
||||
bool Mod::FileBase::readU32L(unsigned long &number) {
|
||||
ByteVector data(readBlock(4));
|
||||
if(data.size() < 4) return false;
|
||||
number = data.toUInt(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readU16B(ushort &number)
|
||||
bool Mod::FileBase::readU16B(unsigned short &number)
|
||||
{
|
||||
ByteVector data(readBlock(2));
|
||||
if(data.size() < 2) return false;
|
||||
@@ -112,7 +117,7 @@ bool Mod::FileBase::readU16B(ushort &number)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mod::FileBase::readU32B(ulong &number) {
|
||||
bool Mod::FileBase::readU32B(unsigned long &number) {
|
||||
ByteVector data(readBlock(4));
|
||||
if(data.size() < 4) return false;
|
||||
number = data.toUInt(true);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,8 +15,12 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_MODFILEBASE_H
|
||||
@@ -40,19 +44,19 @@ namespace TagLib {
|
||||
FileBase(FileName file);
|
||||
FileBase(IOStream *stream);
|
||||
|
||||
void writeString(const String &s, ulong size, char padding = 0);
|
||||
void writeByte(uchar byte);
|
||||
void writeU16L(ushort number);
|
||||
void writeU32L(ulong number);
|
||||
void writeU16B(ushort number);
|
||||
void writeU32B(ulong number);
|
||||
void writeString(const String &s, unsigned long size, char padding = 0);
|
||||
void writeByte(unsigned char byte);
|
||||
void writeU16L(unsigned short number);
|
||||
void writeU32L(unsigned long number);
|
||||
void writeU16B(unsigned short number);
|
||||
void writeU32B(unsigned long number);
|
||||
|
||||
bool readString(String &s, ulong size);
|
||||
bool readByte(uchar &byte);
|
||||
bool readU16L(ushort &number);
|
||||
bool readU32L(ulong &number);
|
||||
bool readU16B(ushort &number);
|
||||
bool readU32B(ulong &number);
|
||||
bool readString(String &s, unsigned long size);
|
||||
bool readByte(unsigned char &byte);
|
||||
bool readU16L(unsigned short &number);
|
||||
bool readU32L(unsigned long &number);
|
||||
bool readU16B(unsigned short &number);
|
||||
bool readU32B(unsigned long &number);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
setter(number); \
|
||||
}
|
||||
|
||||
#define READ_BYTE(setter) READ(setter,uchar,readByte)
|
||||
#define READ_U16L(setter) READ(setter,ushort,readU16L)
|
||||
#define READ_U32L(setter) READ(setter,ulong,readU32L)
|
||||
#define READ_U16B(setter) READ(setter,ushort,readU16B)
|
||||
#define READ_U32B(setter) READ(setter,ulong,readU32B)
|
||||
#define READ_BYTE(setter) READ(setter,unsigned char,readByte)
|
||||
#define READ_U16L(setter) READ(setter,unsigned short,readU16L)
|
||||
#define READ_U32L(setter) READ(setter,unsigned long,readU32L)
|
||||
#define READ_U16B(setter) READ(setter,unsigned short,readU16B)
|
||||
#define READ_U32B(setter) READ(setter,unsigned long,readU32B)
|
||||
|
||||
#define READ_STRING(setter,size) \
|
||||
{ \
|
||||
@@ -54,11 +54,11 @@
|
||||
type name = 0; \
|
||||
READ_ASSERT(read(name));
|
||||
|
||||
#define READ_BYTE_AS(name) READ_AS(uchar,name,readByte)
|
||||
#define READ_U16L_AS(name) READ_AS(ushort,name,readU16L)
|
||||
#define READ_U32L_AS(name) READ_AS(ulong,name,readU32L)
|
||||
#define READ_U16B_AS(name) READ_AS(ushort,name,readU16B)
|
||||
#define READ_U32B_AS(name) READ_AS(ulong,name,readU32B)
|
||||
#define READ_BYTE_AS(name) READ_AS(unsigned char,name,readByte)
|
||||
#define READ_U16L_AS(name) READ_AS(unsigned short,name,readU16L)
|
||||
#define READ_U32L_AS(name) READ_AS(unsigned long,name,readU32L)
|
||||
#define READ_U16B_AS(name) READ_AS(unsigned short,name,readU16B)
|
||||
#define READ_U32B_AS(name) READ_AS(unsigned long,name,readU32B)
|
||||
|
||||
#define READ_STRING_AS(name,size) \
|
||||
String name; \
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "modproperties.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -34,9 +39,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
int channels;
|
||||
uint instrumentCount;
|
||||
uchar lengthInPatterns;
|
||||
int channels;
|
||||
unsigned int instrumentCount;
|
||||
unsigned char lengthInPatterns;
|
||||
};
|
||||
|
||||
Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
|
||||
@@ -55,6 +60,16 @@ int Mod::Properties::length() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::lengthInSeconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
@@ -70,12 +85,12 @@ int Mod::Properties::channels() const
|
||||
return d->channels;
|
||||
}
|
||||
|
||||
TagLib::uint Mod::Properties::instrumentCount() const
|
||||
unsigned int Mod::Properties::instrumentCount() const
|
||||
{
|
||||
return d->instrumentCount;
|
||||
}
|
||||
|
||||
uchar Mod::Properties::lengthInPatterns() const
|
||||
unsigned char Mod::Properties::lengthInPatterns() const
|
||||
{
|
||||
return d->lengthInPatterns;
|
||||
}
|
||||
@@ -85,12 +100,12 @@ void Mod::Properties::setChannels(int channels)
|
||||
d->channels = channels;
|
||||
}
|
||||
|
||||
void Mod::Properties::setInstrumentCount(uint instrumentCount)
|
||||
void Mod::Properties::setInstrumentCount(unsigned int instrumentCount)
|
||||
{
|
||||
d->instrumentCount = instrumentCount;
|
||||
}
|
||||
|
||||
void Mod::Properties::setLengthInPatterns(uchar lengthInPatterns)
|
||||
void Mod::Properties::setLengthInPatterns(unsigned char lengthInPatterns)
|
||||
{
|
||||
d->lengthInPatterns = lengthInPatterns;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,8 +15,12 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_MODPROPERTIES_H
|
||||
@@ -35,18 +39,20 @@ namespace TagLib {
|
||||
Properties(AudioProperties::ReadStyle propertiesStyle);
|
||||
virtual ~Properties();
|
||||
|
||||
int length() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
int length() const;
|
||||
int lengthInSeconds() const;
|
||||
int lengthInMilliseconds() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
|
||||
uint instrumentCount() const;
|
||||
uchar lengthInPatterns() const;
|
||||
unsigned int instrumentCount() const;
|
||||
unsigned char lengthInPatterns() const;
|
||||
|
||||
void setChannels(int channels);
|
||||
|
||||
void setInstrumentCount(uint sampleCount);
|
||||
void setLengthInPatterns(uchar lengthInPatterns);
|
||||
void setInstrumentCount(unsigned int sampleCount);
|
||||
void setLengthInPatterns(unsigned char lengthInPatterns);
|
||||
|
||||
private:
|
||||
friend class File;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,10 +15,15 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "modtag.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tpropertymap.h"
|
||||
@@ -55,12 +60,12 @@ String Mod::Tag::title() const
|
||||
|
||||
String Mod::Tag::artist() const
|
||||
{
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String Mod::Tag::album() const
|
||||
{
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String Mod::Tag::comment() const
|
||||
@@ -70,15 +75,15 @@ String Mod::Tag::comment() const
|
||||
|
||||
String Mod::Tag::genre() const
|
||||
{
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
TagLib::uint Mod::Tag::year() const
|
||||
unsigned int Mod::Tag::year() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
TagLib::uint Mod::Tag::track() const
|
||||
unsigned int Mod::Tag::track() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -110,11 +115,11 @@ void Mod::Tag::setGenre(const String &)
|
||||
{
|
||||
}
|
||||
|
||||
void Mod::Tag::setYear(uint)
|
||||
void Mod::Tag::setYear(unsigned int)
|
||||
{
|
||||
}
|
||||
|
||||
void Mod::Tag::setTrack(uint)
|
||||
void Mod::Tag::setTrack(unsigned int)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -128,7 +133,7 @@ PropertyMap Mod::Tag::properties() const
|
||||
PropertyMap properties;
|
||||
properties["TITLE"] = d->title;
|
||||
properties["COMMENT"] = d->comment;
|
||||
if(!(d->trackerName.isNull()))
|
||||
if(!(d->trackerName.isEmpty()))
|
||||
properties["TRACKERNAME"] = d->trackerName;
|
||||
return properties;
|
||||
}
|
||||
@@ -142,23 +147,23 @@ PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps)
|
||||
d->title = properties["TITLE"].front();
|
||||
oneValueSet.append("TITLE");
|
||||
} else
|
||||
d->title = String::null;
|
||||
d->title.clear();
|
||||
|
||||
if(properties.contains("COMMENT")) {
|
||||
d->comment = properties["COMMENT"].front();
|
||||
oneValueSet.append("COMMENT");
|
||||
} else
|
||||
d->comment = String::null;
|
||||
d->comment.clear();
|
||||
|
||||
if(properties.contains("TRACKERNAME")) {
|
||||
d->trackerName = properties["TRACKERNAME"].front();
|
||||
oneValueSet.append("TRACKERNAME");
|
||||
} else
|
||||
d->trackerName = String::null;
|
||||
d->trackerName.clear();
|
||||
|
||||
// for each tag that has been set above, remove the first entry in the corresponding
|
||||
// value list. The others will be returned as unsupported by this format.
|
||||
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
|
||||
for(StringList::ConstIterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
|
||||
if(properties[*it].size() == 1)
|
||||
properties.erase(*it);
|
||||
else
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
@@ -15,8 +15,12 @@
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301 USA *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_MODTAG_H
|
||||
@@ -50,39 +54,39 @@ namespace TagLib {
|
||||
* Returns the track name; if no track name is present in the tag
|
||||
* String::null will be returned.
|
||||
*/
|
||||
String title() const;
|
||||
virtual String title() const;
|
||||
|
||||
/*!
|
||||
* Not supported by module files. Therefore always returns String::null.
|
||||
*/
|
||||
String artist() const;
|
||||
virtual String artist() const;
|
||||
|
||||
/*!
|
||||
* Not supported by module files. Therefore always returns String::null.
|
||||
*/
|
||||
String album() const;
|
||||
virtual String album() const;
|
||||
|
||||
/*!
|
||||
* Returns the track comment derived from the instrument/sample/pattern
|
||||
* names; if no comment is present in the tag String::null will be
|
||||
* returned.
|
||||
*/
|
||||
String comment() const;
|
||||
virtual String comment() const;
|
||||
|
||||
/*!
|
||||
* Not supported by module files. Therefore always returns String::null.
|
||||
*/
|
||||
String genre() const;
|
||||
virtual String genre() const;
|
||||
|
||||
/*!
|
||||
* Not supported by module files. Therefore always returns 0.
|
||||
*/
|
||||
uint year() const;
|
||||
virtual unsigned int year() const;
|
||||
|
||||
/*!
|
||||
* Not supported by module files. Therefore always returns 0.
|
||||
*/
|
||||
uint track() const;
|
||||
virtual unsigned int track() const;
|
||||
|
||||
/*!
|
||||
* Returns the name of the tracker used to create/edit the module file.
|
||||
@@ -97,21 +101,21 @@ namespace TagLib {
|
||||
* Sets the title to \a title. If \a title is String::null then this
|
||||
* value will be cleared.
|
||||
*
|
||||
* The length limits per file type are (1 characetr = 1 byte):
|
||||
* The length limits per file type are (1 character = 1 byte):
|
||||
* Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20
|
||||
* characters.
|
||||
*/
|
||||
void setTitle(const String &title);
|
||||
virtual void setTitle(const String &title);
|
||||
|
||||
/*!
|
||||
* Not supported by module files and therefore ignored.
|
||||
*/
|
||||
void setArtist(const String &artist);
|
||||
virtual void setArtist(const String &artist);
|
||||
|
||||
/*!
|
||||
* Not supported by module files and therefore ignored.
|
||||
*/
|
||||
void setAlbum(const String &album);
|
||||
virtual void setAlbum(const String &album);
|
||||
|
||||
/*!
|
||||
* Sets the comment to \a comment. If \a comment is String::null then
|
||||
@@ -126,26 +130,26 @@ namespace TagLib {
|
||||
* an thus the line length in comments are limited. Too big comments
|
||||
* will be truncated.
|
||||
*
|
||||
* The line length limits per file type are (1 characetr = 1 byte):
|
||||
* The line length limits per file type are (1 character = 1 byte):
|
||||
* Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22
|
||||
* characters.
|
||||
*/
|
||||
void setComment(const String &comment);
|
||||
virtual void setComment(const String &comment);
|
||||
|
||||
/*!
|
||||
* Not supported by module files and therefore ignored.
|
||||
*/
|
||||
void setGenre(const String &genre);
|
||||
virtual void setGenre(const String &genre);
|
||||
|
||||
/*!
|
||||
* Not supported by module files and therefore ignored.
|
||||
*/
|
||||
void setYear(uint year);
|
||||
virtual void setYear(unsigned int year);
|
||||
|
||||
/*!
|
||||
* Not supported by module files and therefore ignored.
|
||||
*/
|
||||
void setTrack(uint track);
|
||||
virtual void setTrack(unsigned int track);
|
||||
|
||||
/*!
|
||||
* Sets the tracker name to \a trackerName. If \a trackerName is
|
||||
@@ -169,7 +173,7 @@ namespace TagLib {
|
||||
* Implements the unified property interface -- import function.
|
||||
* Because of the limitations of the module file tag, any tags besides
|
||||
* COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be
|
||||
* returened. Additionally, if the map contains tags with multiple values,
|
||||
* returned. Additionally, if the map contains tags with multiple values,
|
||||
* all but the first will be contained in the returned map of unsupported
|
||||
* properties.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <climits>
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstring.h>
|
||||
#include "mp4atom.h"
|
||||
@@ -37,9 +39,11 @@ const char *MP4::Atom::containers[11] = {
|
||||
|
||||
MP4::Atom::Atom(File *file)
|
||||
{
|
||||
children.setAutoDelete(true);
|
||||
|
||||
offset = file->tell();
|
||||
ByteVector header = file->readBlock(8);
|
||||
if (header.size() != 8) {
|
||||
if(header.size() != 8) {
|
||||
// The atom header must be 8 bytes long, otherwise there is either
|
||||
// trailing garbage or the file is truncated
|
||||
debug("MP4: Couldn't read 8 bytes of data for atom header");
|
||||
@@ -50,20 +54,25 @@ MP4::Atom::Atom(File *file)
|
||||
|
||||
length = header.toUInt();
|
||||
|
||||
if (length == 1) {
|
||||
if(length == 1) {
|
||||
const long long longLength = file->readBlock(8).toLongLong();
|
||||
if (longLength >= 8 && longLength <= 0xFFFFFFFF) {
|
||||
// The atom has a 64-bit length, but it's actually a 32-bit value
|
||||
length = (long)longLength;
|
||||
if(sizeof(long) == sizeof(long long)) {
|
||||
length = static_cast<long>(longLength);
|
||||
}
|
||||
else {
|
||||
if(longLength <= LONG_MAX) {
|
||||
// The atom has a 64-bit length, but it's actually a 31-bit value
|
||||
length = static_cast<long>(longLength);
|
||||
}
|
||||
else {
|
||||
debug("MP4: 64-bit atoms are not supported");
|
||||
length = 0;
|
||||
file->seek(0, File::End);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length < 8) {
|
||||
if(length < 8) {
|
||||
debug("MP4: Invalid atom size");
|
||||
length = 0;
|
||||
file->seek(0, File::End);
|
||||
@@ -83,7 +92,7 @@ MP4::Atom::Atom(File *file)
|
||||
while(file->tell() < offset + length) {
|
||||
MP4::Atom *child = new MP4::Atom(file);
|
||||
children.append(child);
|
||||
if (child->length == 0)
|
||||
if(child->length == 0)
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@@ -95,10 +104,6 @@ MP4::Atom::Atom(File *file)
|
||||
|
||||
MP4::Atom::~Atom()
|
||||
{
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
delete children[i];
|
||||
}
|
||||
children.clear();
|
||||
}
|
||||
|
||||
MP4::Atom *
|
||||
@@ -107,9 +112,9 @@ MP4::Atom::find(const char *name1, const char *name2, const char *name3, const c
|
||||
if(name1 == 0) {
|
||||
return this;
|
||||
}
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name1) {
|
||||
return children[i]->find(name2, name3, name4);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->find(name2, name3, name4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -119,12 +124,12 @@ MP4::AtomList
|
||||
MP4::Atom::findall(const char *name, bool recursive)
|
||||
{
|
||||
MP4::AtomList result;
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name) {
|
||||
result.append(children[i]);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name) {
|
||||
result.append(*it);
|
||||
}
|
||||
if(recursive) {
|
||||
result.append(children[i]->findall(name, recursive));
|
||||
result.append((*it)->findall(name, recursive));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -137,9 +142,9 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
|
||||
if(name1 == 0) {
|
||||
return true;
|
||||
}
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name1) {
|
||||
return children[i]->path(path, name2, name3);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->path(path, name2, name3);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -147,6 +152,8 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
|
||||
|
||||
MP4::Atoms::Atoms(File *file)
|
||||
{
|
||||
atoms.setAutoDelete(true);
|
||||
|
||||
file->seek(0, File::End);
|
||||
long end = file->tell();
|
||||
file->seek(0);
|
||||
@@ -160,18 +167,14 @@ MP4::Atoms::Atoms(File *file)
|
||||
|
||||
MP4::Atoms::~Atoms()
|
||||
{
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
delete atoms[i];
|
||||
}
|
||||
atoms.clear();
|
||||
}
|
||||
|
||||
MP4::Atom *
|
||||
MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4)
|
||||
{
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
if(atoms[i]->name == name1) {
|
||||
return atoms[i]->find(name2, name3, name4);
|
||||
for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->find(name2, name3, name4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -181,9 +184,9 @@ MP4::AtomList
|
||||
MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4)
|
||||
{
|
||||
MP4::AtomList path;
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
if(atoms[i]->name == name1) {
|
||||
if(!atoms[i]->path(path, name2, name3, name4)) {
|
||||
for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
if(!(*it)->path(path, name2, name3, name4)) {
|
||||
path.clear();
|
||||
}
|
||||
return path;
|
||||
@@ -191,4 +194,3 @@ MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace TagLib {
|
||||
TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
|
||||
TypeGenred = 18, // a list of enumerated values
|
||||
TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes
|
||||
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger
|
||||
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer
|
||||
TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID)
|
||||
TypeBMP = 27, // Windows bitmap image
|
||||
TypeUndefined = 255 // undefined
|
||||
@@ -77,29 +77,29 @@ namespace TagLib {
|
||||
class Atom
|
||||
{
|
||||
public:
|
||||
Atom(File *file);
|
||||
~Atom();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
|
||||
AtomList findall(const char *name, bool recursive = false);
|
||||
long offset;
|
||||
long length;
|
||||
TagLib::ByteVector name;
|
||||
AtomList children;
|
||||
Atom(File *file);
|
||||
~Atom();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
|
||||
AtomList findall(const char *name, bool recursive = false);
|
||||
long offset;
|
||||
long length;
|
||||
TagLib::ByteVector name;
|
||||
AtomList children;
|
||||
private:
|
||||
static const int numContainers = 11;
|
||||
static const char *containers[11];
|
||||
static const int numContainers = 11;
|
||||
static const char *containers[11];
|
||||
};
|
||||
|
||||
//! Root-level atoms
|
||||
class Atoms
|
||||
{
|
||||
public:
|
||||
Atoms(File *file);
|
||||
~Atoms();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList atoms;
|
||||
Atoms(File *file);
|
||||
~Atoms();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList atoms;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -33,20 +33,27 @@ using namespace TagLib;
|
||||
class MP4::CoverArt::CoverArtPrivate : public RefCounter
|
||||
{
|
||||
public:
|
||||
CoverArtPrivate() : RefCounter(), format(MP4::CoverArt::JPEG) {}
|
||||
CoverArtPrivate() :
|
||||
RefCounter(),
|
||||
format(MP4::CoverArt::JPEG) {}
|
||||
|
||||
Format format;
|
||||
ByteVector data;
|
||||
};
|
||||
|
||||
MP4::CoverArt::CoverArt(Format format, const ByteVector &data)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MP4::CoverArt::CoverArt(Format format, const ByteVector &data) :
|
||||
d(new CoverArtPrivate())
|
||||
{
|
||||
d = new CoverArtPrivate;
|
||||
d->format = format;
|
||||
d->data = data;
|
||||
}
|
||||
|
||||
MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
|
||||
MP4::CoverArt::CoverArt(const CoverArt &item) :
|
||||
d(item.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
@@ -54,14 +61,18 @@ MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
|
||||
MP4::CoverArt &
|
||||
MP4::CoverArt::operator=(const CoverArt &item)
|
||||
{
|
||||
if(d->deref()) {
|
||||
delete d;
|
||||
}
|
||||
d = item.d;
|
||||
d->ref();
|
||||
CoverArt(item).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
MP4::CoverArt::swap(CoverArt &item)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, item.d);
|
||||
}
|
||||
|
||||
MP4::CoverArt::~CoverArt()
|
||||
{
|
||||
if(d->deref()) {
|
||||
@@ -80,4 +91,3 @@ MP4::CoverArt::data() const
|
||||
{
|
||||
return d->data;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,17 @@ namespace TagLib {
|
||||
~CoverArt();
|
||||
|
||||
CoverArt(const CoverArt &item);
|
||||
|
||||
/*!
|
||||
* Copies the contents of \a item into this CoverArt.
|
||||
*/
|
||||
CoverArt &operator=(const CoverArt &item);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the CoverArt by the content of \a item.
|
||||
*/
|
||||
void swap(CoverArt &item);
|
||||
|
||||
//! Format of the image
|
||||
Format format() const;
|
||||
|
||||
|
||||
@@ -32,48 +32,57 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool checkValid(const MP4::AtomList &list)
|
||||
{
|
||||
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
|
||||
|
||||
if((*it)->length == 0)
|
||||
return false;
|
||||
|
||||
if(!checkValid((*it)->children))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MP4::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate() : tag(0), atoms(0), properties(0)
|
||||
{
|
||||
}
|
||||
FilePrivate() :
|
||||
tag(0),
|
||||
atoms(0),
|
||||
properties(0) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
if(atoms) {
|
||||
delete atoms;
|
||||
atoms = 0;
|
||||
}
|
||||
if(tag) {
|
||||
delete tag;
|
||||
tag = 0;
|
||||
}
|
||||
if(properties) {
|
||||
delete properties;
|
||||
properties = 0;
|
||||
}
|
||||
delete atoms;
|
||||
delete tag;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
MP4::Tag *tag;
|
||||
MP4::Atoms *atoms;
|
||||
MP4::Tag *tag;
|
||||
MP4::Atoms *atoms;
|
||||
MP4::Properties *properties;
|
||||
};
|
||||
|
||||
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
: TagLib::File(file)
|
||||
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MP4::File::~File()
|
||||
@@ -108,40 +117,27 @@ MP4::File::audioProperties() const
|
||||
return d->properties;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::File::checkValid(const MP4::AtomList &list)
|
||||
{
|
||||
for(uint i = 0; i < list.size(); i++) {
|
||||
if(list[i]->length == 0)
|
||||
return false;
|
||||
if(!checkValid(list[i]->children))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle)
|
||||
MP4::File::read(bool readProperties)
|
||||
{
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
d->atoms = new Atoms(this);
|
||||
if (!checkValid(d->atoms->atoms)) {
|
||||
if(!checkValid(d->atoms->atoms)) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// must have a moov atom, otherwise consider it invalid
|
||||
MP4::Atom *moov = d->atoms->find("moov");
|
||||
if(!moov) {
|
||||
if(!d->atoms->find("moov")) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->tag = new Tag(this, d->atoms);
|
||||
if(readProperties) {
|
||||
d->properties = new Properties(this, d->atoms, audioPropertiesStyle);
|
||||
d->properties = new Properties(this, d->atoms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,3 +157,8 @@ MP4::File::save()
|
||||
return d->tag->save();
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::File::hasMP4Tag() const
|
||||
{
|
||||
return (d->atoms->find("moov", "udta", "meta", "ilst") != 0);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace TagLib {
|
||||
*
|
||||
* \note In the current implementation, \a propertiesStyle is ignored.
|
||||
*/
|
||||
File(FileName file, bool readProperties = true,
|
||||
File(FileName file, bool readProperties = true,
|
||||
Properties::ReadStyle audioPropertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
@@ -66,7 +66,7 @@ namespace TagLib {
|
||||
*
|
||||
* \note In the current implementation, \a propertiesStyle is ignored.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle audioPropertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
@@ -114,10 +114,14 @@ namespace TagLib {
|
||||
*/
|
||||
bool save();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Returns whether or not the file on disk actually has an MP4 tag, or the
|
||||
* file has a Metadata Item List (ilst) atom.
|
||||
*/
|
||||
bool hasMP4Tag() const;
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle);
|
||||
bool checkValid(const MP4::AtomList &list);
|
||||
private:
|
||||
void read(bool readProperties);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
||||
@@ -33,7 +33,10 @@ using namespace TagLib;
|
||||
class MP4::Item::ItemPrivate : public RefCounter
|
||||
{
|
||||
public:
|
||||
ItemPrivate() : RefCounter(), valid(true), atomDataType(TypeUndefined) {}
|
||||
ItemPrivate() :
|
||||
RefCounter(),
|
||||
valid(true),
|
||||
atomDataType(TypeUndefined) {}
|
||||
|
||||
bool valid;
|
||||
AtomDataType atomDataType;
|
||||
@@ -41,8 +44,8 @@ public:
|
||||
bool m_bool;
|
||||
int m_int;
|
||||
IntPair m_intPair;
|
||||
uchar m_byte;
|
||||
uint m_uint;
|
||||
unsigned char m_byte;
|
||||
unsigned int m_uint;
|
||||
long long m_longlong;
|
||||
};
|
||||
StringList m_stringList;
|
||||
@@ -50,13 +53,14 @@ public:
|
||||
MP4::CoverArtList m_coverArtList;
|
||||
};
|
||||
|
||||
MP4::Item::Item()
|
||||
MP4::Item::Item() :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->valid = false;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const Item &item) : d(item.d)
|
||||
MP4::Item::Item(const Item &item) :
|
||||
d(item.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
@@ -64,73 +68,76 @@ MP4::Item::Item(const Item &item) : d(item.d)
|
||||
MP4::Item &
|
||||
MP4::Item::operator=(const Item &item)
|
||||
{
|
||||
if(d->deref()) {
|
||||
delete d;
|
||||
}
|
||||
d = item.d;
|
||||
d->ref();
|
||||
Item(item).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Item::swap(Item &item)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, item.d);
|
||||
}
|
||||
|
||||
MP4::Item::~Item()
|
||||
{
|
||||
if(d->deref()) {
|
||||
if(d->deref())
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
MP4::Item::Item(bool value)
|
||||
MP4::Item::Item(bool value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_bool = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(int value)
|
||||
MP4::Item::Item(int value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_int = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(uchar value)
|
||||
MP4::Item::Item(unsigned char value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_byte = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(uint value)
|
||||
MP4::Item::Item(unsigned int value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_uint = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(long long value)
|
||||
MP4::Item::Item(long long value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_longlong = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(int value1, int value2)
|
||||
MP4::Item::Item(int value1, int value2) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_intPair.first = value1;
|
||||
d->m_intPair.second = value2;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const ByteVectorList &value)
|
||||
MP4::Item::Item(const ByteVectorList &value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_byteVectorList = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const StringList &value)
|
||||
MP4::Item::Item(const StringList &value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_stringList = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const MP4::CoverArtList &value)
|
||||
MP4::Item::Item(const MP4::CoverArtList &value) :
|
||||
d(new ItemPrivate())
|
||||
{
|
||||
d = new ItemPrivate;
|
||||
d->m_coverArtList = value;
|
||||
}
|
||||
|
||||
@@ -156,13 +163,13 @@ MP4::Item::toInt() const
|
||||
return d->m_int;
|
||||
}
|
||||
|
||||
uchar
|
||||
unsigned char
|
||||
MP4::Item::toByte() const
|
||||
{
|
||||
return d->m_byte;
|
||||
}
|
||||
|
||||
TagLib::uint
|
||||
unsigned int
|
||||
MP4::Item::toUInt() const
|
||||
{
|
||||
return d->m_uint;
|
||||
@@ -203,4 +210,3 @@ MP4::Item::isValid() const
|
||||
{
|
||||
return d->valid;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,22 @@ namespace TagLib {
|
||||
|
||||
Item();
|
||||
Item(const Item &item);
|
||||
|
||||
/*!
|
||||
* Copies the contents of \a item into this Item.
|
||||
*/
|
||||
Item &operator=(const Item &item);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the Item by the content of \a item.
|
||||
*/
|
||||
void swap(Item &item);
|
||||
|
||||
~Item();
|
||||
|
||||
Item(int value);
|
||||
Item(uchar value);
|
||||
Item(uint value);
|
||||
Item(unsigned char value);
|
||||
Item(unsigned int value);
|
||||
Item(long long value);
|
||||
Item(bool value);
|
||||
Item(int first, int second);
|
||||
@@ -60,8 +70,8 @@ namespace TagLib {
|
||||
AtomDataType atomDataType() const;
|
||||
|
||||
int toInt() const;
|
||||
uchar toByte() const;
|
||||
uint toUInt() const;
|
||||
unsigned char toByte() const;
|
||||
unsigned int toUInt() const;
|
||||
long long toLongLong() const;
|
||||
bool toBool() const;
|
||||
IntPair toIntPair() const;
|
||||
|
||||
@@ -34,7 +34,14 @@ using namespace TagLib;
|
||||
class MP4::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false), codec(MP4::Properties::Unknown) {}
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
bitsPerSample(0),
|
||||
encrypted(false),
|
||||
codec(MP4::Properties::Unknown) {}
|
||||
|
||||
int length;
|
||||
int bitrate;
|
||||
@@ -45,110 +52,15 @@ public:
|
||||
Codec codec;
|
||||
};
|
||||
|
||||
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style)
|
||||
: AudioProperties(style)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate;
|
||||
|
||||
MP4::Atom *moov = atoms->find("moov");
|
||||
if(!moov) {
|
||||
debug("MP4: Atom 'moov' not found");
|
||||
return;
|
||||
}
|
||||
|
||||
MP4::Atom *trak = 0;
|
||||
ByteVector data;
|
||||
|
||||
MP4::AtomList trakList = moov->findall("trak");
|
||||
for (unsigned int i = 0; i < trakList.size(); i++) {
|
||||
trak = trakList[i];
|
||||
MP4::Atom *hdlr = trak->find("mdia", "hdlr");
|
||||
if(!hdlr) {
|
||||
debug("MP4: Atom 'trak.mdia.hdlr' not found");
|
||||
return;
|
||||
}
|
||||
file->seek(hdlr->offset);
|
||||
data = file->readBlock(hdlr->length);
|
||||
if(data.mid(16, 4) == "soun") {
|
||||
break;
|
||||
}
|
||||
trak = 0;
|
||||
}
|
||||
if (!trak) {
|
||||
debug("MP4: No audio tracks");
|
||||
return;
|
||||
}
|
||||
|
||||
MP4::Atom *mdhd = trak->find("mdia", "mdhd");
|
||||
if(!mdhd) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' not found");
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(mdhd->offset);
|
||||
data = file->readBlock(mdhd->length);
|
||||
uint version = data[8];
|
||||
if(version == 1) {
|
||||
if (data.size() < 36 + 8) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
|
||||
return;
|
||||
}
|
||||
const long long unit = data.toLongLong(28U);
|
||||
const long long length = data.toLongLong(36U);
|
||||
d->length = unit ? int(length / unit) : 0;
|
||||
}
|
||||
else {
|
||||
if (data.size() < 24 + 4) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
|
||||
return;
|
||||
}
|
||||
const unsigned int unit = data.toUInt(20U);
|
||||
const unsigned int length = data.toUInt(24U);
|
||||
d->length = unit ? length / unit : 0;
|
||||
}
|
||||
|
||||
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
|
||||
if(!atom) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(atom->offset);
|
||||
data = file->readBlock(atom->length);
|
||||
if(data.mid(20, 4) == "mp4a") {
|
||||
d->codec = AAC;
|
||||
d->channels = data.toShort(40U);
|
||||
d->bitsPerSample = data.toShort(42U);
|
||||
d->sampleRate = data.toUInt(46U);
|
||||
if(data.mid(56, 4) == "esds" && data[64] == 0x03) {
|
||||
uint pos = 65;
|
||||
if(data.mid(pos, 3) == "\x80\x80\x80") {
|
||||
pos += 3;
|
||||
}
|
||||
pos += 4;
|
||||
if(data[pos] == 0x04) {
|
||||
pos += 1;
|
||||
if(data.mid(pos, 3) == "\x80\x80\x80") {
|
||||
pos += 3;
|
||||
}
|
||||
pos += 10;
|
||||
d->bitrate = (data.toUInt(pos) + 500) / 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.mid(20, 4) == "alac") {
|
||||
if (atom->length == 88 && data.mid(56, 4) == "alac") {
|
||||
d->codec = ALAC;
|
||||
d->bitsPerSample = data.at(69);
|
||||
d->channels = data.at(73);
|
||||
d->bitrate = data.toUInt(80U) / 1000;
|
||||
d->sampleRate = data.toUInt(84U);
|
||||
}
|
||||
}
|
||||
|
||||
MP4::Atom *drms = atom->find("drms");
|
||||
if(drms) {
|
||||
d->encrypted = true;
|
||||
}
|
||||
read(file, atoms);
|
||||
}
|
||||
|
||||
MP4::Properties::~Properties()
|
||||
@@ -170,6 +82,18 @@ MP4::Properties::sampleRate() const
|
||||
|
||||
int
|
||||
MP4::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int
|
||||
MP4::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int
|
||||
MP4::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@@ -192,8 +116,119 @@ MP4::Properties::isEncrypted() const
|
||||
return d->encrypted;
|
||||
}
|
||||
|
||||
MP4::Properties::Codec MP4::Properties::codec() const
|
||||
MP4::Properties::Codec
|
||||
MP4::Properties::codec() const
|
||||
{
|
||||
return d->codec;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
MP4::Properties::read(File *file, Atoms *atoms)
|
||||
{
|
||||
MP4::Atom *moov = atoms->find("moov");
|
||||
if(!moov) {
|
||||
debug("MP4: Atom 'moov' not found");
|
||||
return;
|
||||
}
|
||||
|
||||
MP4::Atom *trak = 0;
|
||||
ByteVector data;
|
||||
|
||||
const MP4::AtomList trakList = moov->findall("trak");
|
||||
for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) {
|
||||
trak = *it;
|
||||
MP4::Atom *hdlr = trak->find("mdia", "hdlr");
|
||||
if(!hdlr) {
|
||||
debug("MP4: Atom 'trak.mdia.hdlr' not found");
|
||||
return;
|
||||
}
|
||||
file->seek(hdlr->offset);
|
||||
data = file->readBlock(hdlr->length);
|
||||
if(data.containsAt("soun", 16)) {
|
||||
break;
|
||||
}
|
||||
trak = 0;
|
||||
}
|
||||
if(!trak) {
|
||||
debug("MP4: No audio tracks");
|
||||
return;
|
||||
}
|
||||
|
||||
MP4::Atom *mdhd = trak->find("mdia", "mdhd");
|
||||
if(!mdhd) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' not found");
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(mdhd->offset);
|
||||
data = file->readBlock(mdhd->length);
|
||||
|
||||
const unsigned int version = data[8];
|
||||
long long unit;
|
||||
long long length;
|
||||
if(version == 1) {
|
||||
if(data.size() < 36 + 8) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
|
||||
return;
|
||||
}
|
||||
unit = data.toLongLong(28U);
|
||||
length = data.toLongLong(36U);
|
||||
}
|
||||
else {
|
||||
if(data.size() < 24 + 4) {
|
||||
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
|
||||
return;
|
||||
}
|
||||
unit = data.toUInt(20U);
|
||||
length = data.toUInt(24U);
|
||||
}
|
||||
if(unit > 0 && length > 0)
|
||||
d->length = static_cast<int>(length * 1000.0 / unit + 0.5);
|
||||
|
||||
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
|
||||
if(!atom) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(atom->offset);
|
||||
data = file->readBlock(atom->length);
|
||||
if(data.containsAt("mp4a", 20)) {
|
||||
d->codec = AAC;
|
||||
d->channels = data.toShort(40U);
|
||||
d->bitsPerSample = data.toShort(42U);
|
||||
d->sampleRate = data.toUInt(46U);
|
||||
if(data.containsAt("esds", 56) && data[64] == 0x03) {
|
||||
unsigned int pos = 65;
|
||||
if(data.containsAt("\x80\x80\x80", pos)) {
|
||||
pos += 3;
|
||||
}
|
||||
pos += 4;
|
||||
if(data[pos] == 0x04) {
|
||||
pos += 1;
|
||||
if(data.containsAt("\x80\x80\x80", pos)) {
|
||||
pos += 3;
|
||||
}
|
||||
pos += 10;
|
||||
d->bitrate = static_cast<int>((data.toUInt(pos) + 500) / 1000.0 + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(data.containsAt("alac", 20)) {
|
||||
if(atom->length == 88 && data.containsAt("alac", 56)) {
|
||||
d->codec = ALAC;
|
||||
d->bitsPerSample = data.at(69);
|
||||
d->channels = data.at(73);
|
||||
d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5);
|
||||
d->sampleRate = data.toUInt(84U);
|
||||
}
|
||||
}
|
||||
|
||||
MP4::Atom *drms = atom->find("drms");
|
||||
if(drms) {
|
||||
d->encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,17 +49,66 @@ namespace TagLib {
|
||||
Properties(File *file, Atoms *atoms, ReadStyle style = Average);
|
||||
virtual ~Properties();
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \note This method is just an alias of lengthInSeconds().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
virtual int length() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the average bit rate of the file in kb/s.
|
||||
*/
|
||||
virtual int bitrate() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of bits per audio sample.
|
||||
*/
|
||||
virtual int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file is encrypted.
|
||||
*/
|
||||
bool isEncrypted() const;
|
||||
|
||||
//! Audio codec used in the MP4 file
|
||||
/*!
|
||||
* Returns the codec used in the file.
|
||||
*/
|
||||
Codec codec() const;
|
||||
|
||||
private:
|
||||
void read(File *file, Atoms *atoms);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
};
|
||||
|
||||
@@ -35,21 +35,23 @@ using namespace TagLib;
|
||||
class MP4::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate() : file(0), atoms(0) {}
|
||||
~TagPrivate() {}
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
atoms(0) {}
|
||||
|
||||
TagLib::File *file;
|
||||
Atoms *atoms;
|
||||
ItemListMap items;
|
||||
ItemMap items;
|
||||
};
|
||||
|
||||
MP4::Tag::Tag()
|
||||
MP4::Tag::Tag() :
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
}
|
||||
|
||||
MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
|
||||
MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) :
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
d->file = file;
|
||||
d->atoms = atoms;
|
||||
|
||||
@@ -59,40 +61,40 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
|
||||
return;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < ilst->children.size(); i++) {
|
||||
MP4::Atom *atom = ilst->children[i];
|
||||
for(AtomList::ConstIterator it = ilst->children.begin(); it != ilst->children.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
file->seek(atom->offset + 8);
|
||||
if(atom->name == "----") {
|
||||
parseFreeForm(atom, file);
|
||||
parseFreeForm(atom);
|
||||
}
|
||||
else if(atom->name == "trkn" || atom->name == "disk") {
|
||||
parseIntPair(atom, file);
|
||||
parseIntPair(atom);
|
||||
}
|
||||
else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" ||
|
||||
atom->name == "hdvd") {
|
||||
parseBool(atom, file);
|
||||
parseBool(atom);
|
||||
}
|
||||
else if(atom->name == "tmpo") {
|
||||
parseInt(atom, file);
|
||||
parseInt(atom);
|
||||
}
|
||||
else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" ||
|
||||
atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") {
|
||||
parseUInt(atom, file);
|
||||
parseUInt(atom);
|
||||
}
|
||||
else if(atom->name == "plID") {
|
||||
parseLongLong(atom, file);
|
||||
parseLongLong(atom);
|
||||
}
|
||||
else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") {
|
||||
parseByte(atom, file);
|
||||
parseByte(atom);
|
||||
}
|
||||
else if(atom->name == "gnre") {
|
||||
parseGnre(atom, file);
|
||||
parseGnre(atom);
|
||||
}
|
||||
else if(atom->name == "covr") {
|
||||
parseCovr(atom, file);
|
||||
parseCovr(atom);
|
||||
}
|
||||
else {
|
||||
parseText(atom, file);
|
||||
parseText(atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,15 +105,20 @@ MP4::Tag::~Tag()
|
||||
}
|
||||
|
||||
MP4::AtomDataList
|
||||
MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm)
|
||||
MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm)
|
||||
{
|
||||
AtomDataList result;
|
||||
ByteVector data = file->readBlock(atom->length - 8);
|
||||
ByteVector data = d->file->readBlock(atom->length - 8);
|
||||
int i = 0;
|
||||
unsigned int pos = 0;
|
||||
while(pos < data.size()) {
|
||||
const int length = static_cast<int>(data.toUInt(pos));
|
||||
ByteVector name = data.mid(pos + 4, 4);
|
||||
if(length < 12) {
|
||||
debug("MP4: Too short atom");
|
||||
return result;
|
||||
}
|
||||
|
||||
const ByteVector name = data.mid(pos + 4, 4);
|
||||
const int flags = static_cast<int>(data.toUInt(pos + 8));
|
||||
if(freeForm && i < 2) {
|
||||
if(i == 0 && name != "mean") {
|
||||
@@ -140,57 +147,57 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo
|
||||
}
|
||||
|
||||
ByteVectorList
|
||||
MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm)
|
||||
MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
|
||||
{
|
||||
AtomDataList data = parseData2(atom, file, expectedFlags, freeForm);
|
||||
AtomDataList data = parseData2(atom, expectedFlags, freeForm);
|
||||
ByteVectorList result;
|
||||
for(uint i = 0; i < data.size(); i++) {
|
||||
result.append(data[i].data);
|
||||
for(AtomDataList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
result.append(it->data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseInt(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, (int)data[0].toShort());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseUInt(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, data[0].toUInt());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseLongLong(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, data[0].toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseByte(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
addItem(atom->name, (uchar)data[0].at(0));
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, static_cast<unsigned char>(data[0].at(0)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseGnre(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
int idx = (int)data[0].toShort();
|
||||
if(idx > 0) {
|
||||
addItem("\251gen", StringList(ID3v1::genre(idx - 1)));
|
||||
@@ -199,10 +206,10 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseIntPair(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
const int a = data[0].toShort(2U);
|
||||
const int b = data[0].toShort(4U);
|
||||
addItem(atom->name, MP4::Item(a, b));
|
||||
@@ -210,45 +217,52 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseBool(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(!data.isEmpty()) {
|
||||
bool value = data[0].size() ? data[0][0] != '\0' : false;
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags)
|
||||
MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, file, expectedFlags);
|
||||
if(data.size()) {
|
||||
ByteVectorList data = parseData(atom, expectedFlags);
|
||||
if(!data.isEmpty()) {
|
||||
StringList value;
|
||||
for(unsigned int i = 0; i < data.size(); i++) {
|
||||
value.append(String(data[i], String::UTF8));
|
||||
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
value.append(String(*it, String::UTF8));
|
||||
}
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseFreeForm(const MP4::Atom *atom)
|
||||
{
|
||||
AtomDataList data = parseData2(atom, file, -1, true);
|
||||
AtomDataList data = parseData2(atom, -1, true);
|
||||
if(data.size() > 2) {
|
||||
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8);
|
||||
AtomDataType type = data[2].type;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
if(data[i].type != type) {
|
||||
AtomDataList::ConstIterator itBegin = data.begin();
|
||||
|
||||
String name = "----:";
|
||||
name += String((itBegin++)->data, String::UTF8); // data[0].data
|
||||
name += ':';
|
||||
name += String((itBegin++)->data, String::UTF8); // data[1].data
|
||||
|
||||
AtomDataType type = itBegin->type; // data[2].type
|
||||
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
if(it->type != type) {
|
||||
debug("MP4: We currently don't support values with multiple types");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(type == TypeUTF8) {
|
||||
StringList value;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
value.append(String(data[i].data, String::UTF8));
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
value.append(String(it->data, String::UTF8));
|
||||
}
|
||||
Item item(value);
|
||||
item.setAtomDataType(type);
|
||||
@@ -256,8 +270,8 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
|
||||
}
|
||||
else {
|
||||
ByteVectorList value;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
value.append(data[i].data);
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
value.append(it->data);
|
||||
}
|
||||
Item item(value);
|
||||
item.setAtomDataType(type);
|
||||
@@ -267,20 +281,26 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseCovr(const MP4::Atom *atom)
|
||||
{
|
||||
MP4::CoverArtList value;
|
||||
ByteVector data = file->readBlock(atom->length - 8);
|
||||
ByteVector data = d->file->readBlock(atom->length - 8);
|
||||
unsigned int pos = 0;
|
||||
while(pos < data.size()) {
|
||||
const int length = static_cast<int>(data.toUInt(pos));
|
||||
ByteVector name = data.mid(pos + 4, 4);
|
||||
if(length < 12) {
|
||||
debug("MP4: Too short atom");
|
||||
break;;
|
||||
}
|
||||
|
||||
const ByteVector name = data.mid(pos + 4, 4);
|
||||
const int flags = static_cast<int>(data.toUInt(pos + 8));
|
||||
if(name != "data") {
|
||||
debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
|
||||
break;
|
||||
}
|
||||
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) {
|
||||
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP ||
|
||||
flags == TypeGIF || flags == TypeImplicit) {
|
||||
value.append(MP4::CoverArt(MP4::CoverArt::Format(flags),
|
||||
data.mid(pos + 16, length - 16)));
|
||||
}
|
||||
@@ -289,37 +309,37 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
|
||||
}
|
||||
pos += length;
|
||||
}
|
||||
if(value.size() > 0)
|
||||
if(!value.isEmpty())
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::padIlst(const ByteVector &data, int length)
|
||||
MP4::Tag::padIlst(const ByteVector &data, int length) const
|
||||
{
|
||||
if (length == -1) {
|
||||
if(length == -1) {
|
||||
length = ((data.size() + 1023) & ~1023) - data.size();
|
||||
}
|
||||
return renderAtom("free", ByteVector(length, '\1'));
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data)
|
||||
MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const
|
||||
{
|
||||
return ByteVector::fromUInt(data.size() + 8) + name + data;
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data)
|
||||
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
|
||||
{
|
||||
ByteVector result;
|
||||
for(unsigned int i = 0; i < data.size(); i++) {
|
||||
result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + data[i]));
|
||||
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + *it));
|
||||
}
|
||||
return renderAtom(name, result);
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(1, item.toBool() ? '\1' : '\0'));
|
||||
@@ -327,7 +347,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromShort(item.toInt()));
|
||||
@@ -335,7 +355,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromUInt(item.toUInt()));
|
||||
@@ -343,7 +363,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromLongLong(item.toLongLong()));
|
||||
@@ -351,7 +371,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(1, item.toByte()));
|
||||
@@ -359,7 +379,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
@@ -370,7 +390,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
@@ -380,35 +400,35 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
|
||||
MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
StringList value = item.toStringList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(value[i].data(String::UTF8));
|
||||
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(it->data(String::UTF8));
|
||||
}
|
||||
return renderData(name, flags, data);
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVector data;
|
||||
MP4::CoverArtList value = item.toCoverArtList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) +
|
||||
ByteVector(4, '\0') + value[i].data()));
|
||||
for(MP4::CoverArtList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(it->format()) +
|
||||
ByteVector(4, '\0') + it->data()));
|
||||
}
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderFreeForm(const String &name, MP4::Item &item)
|
||||
MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
|
||||
{
|
||||
StringList header = StringList::split(name, ":");
|
||||
if (header.size() != 3) {
|
||||
if(header.size() != 3) {
|
||||
debug("MP4: Invalid free-form item name \"" + name + "\"");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
ByteVector data;
|
||||
data.append(renderAtom("mean", ByteVector::fromUInt(0) + header[1].data(String::UTF8)));
|
||||
@@ -424,14 +444,14 @@ MP4::Tag::renderFreeForm(const String &name, MP4::Item &item)
|
||||
}
|
||||
if(type == TypeUTF8) {
|
||||
StringList value = item.toStringList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i].data(String::UTF8)));
|
||||
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + it->data(String::UTF8)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
ByteVectorList value = item.toByteVectorList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i]));
|
||||
for(ByteVectorList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + *it));
|
||||
}
|
||||
}
|
||||
return renderAtom("----", data);
|
||||
@@ -441,38 +461,38 @@ bool
|
||||
MP4::Tag::save()
|
||||
{
|
||||
ByteVector data;
|
||||
for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) {
|
||||
const String name = i->first;
|
||||
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
|
||||
const String name = it->first;
|
||||
if(name.startsWith("----")) {
|
||||
data.append(renderFreeForm(name, i->second));
|
||||
data.append(renderFreeForm(name, it->second));
|
||||
}
|
||||
else if(name == "trkn") {
|
||||
data.append(renderIntPair(name.data(String::Latin1), i->second));
|
||||
data.append(renderIntPair(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "disk") {
|
||||
data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second));
|
||||
data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") {
|
||||
data.append(renderBool(name.data(String::Latin1), i->second));
|
||||
data.append(renderBool(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "tmpo") {
|
||||
data.append(renderInt(name.data(String::Latin1), i->second));
|
||||
data.append(renderInt(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "tvsn" || name == "tves" || name == "cnID" ||
|
||||
name == "sfID" || name == "atID" || name == "geID") {
|
||||
data.append(renderUInt(name.data(String::Latin1), i->second));
|
||||
data.append(renderUInt(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "plID") {
|
||||
data.append(renderLongLong(name.data(String::Latin1), i->second));
|
||||
data.append(renderLongLong(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "stik" || name == "rtng" || name == "akID") {
|
||||
data.append(renderByte(name.data(String::Latin1), i->second));
|
||||
data.append(renderByte(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name == "covr") {
|
||||
data.append(renderCovr(name.data(String::Latin1), i->second));
|
||||
data.append(renderCovr(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else if(name.size() == 4){
|
||||
data.append(renderText(name.data(String::Latin1), i->second));
|
||||
data.append(renderText(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else {
|
||||
debug("MP4: Unknown item name \"" + name + "\"");
|
||||
@@ -492,22 +512,28 @@ MP4::Tag::save()
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::updateParents(AtomList &path, long delta, int ignore)
|
||||
MP4::Tag::updateParents(const AtomList &path, long delta, int ignore)
|
||||
{
|
||||
for(unsigned int i = 0; i < path.size() - ignore; i++) {
|
||||
d->file->seek(path[i]->offset);
|
||||
if(static_cast<int>(path.size()) <= ignore)
|
||||
return;
|
||||
|
||||
AtomList::ConstIterator itEnd = path.end();
|
||||
std::advance(itEnd, 0 - ignore);
|
||||
|
||||
for(AtomList::ConstIterator it = path.begin(); it != itEnd; ++it) {
|
||||
d->file->seek((*it)->offset);
|
||||
long size = d->file->readBlock(4).toUInt();
|
||||
// 64-bit
|
||||
if (size == 1) {
|
||||
d->file->seek(4, File::Current); // Skip name
|
||||
long long longSize = d->file->readBlock(8).toLongLong();
|
||||
// Seek the offset of the 64-bit size
|
||||
d->file->seek(path[i]->offset + 8);
|
||||
d->file->seek((*it)->offset + 8);
|
||||
d->file->writeBlock(ByteVector::fromLongLong(longSize + delta));
|
||||
}
|
||||
// 32-bit
|
||||
else {
|
||||
d->file->seek(path[i]->offset);
|
||||
d->file->seek((*it)->offset);
|
||||
d->file->writeBlock(ByteVector::fromUInt(size + delta));
|
||||
}
|
||||
}
|
||||
@@ -519,8 +545,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
MP4::Atom *moov = d->atoms->find("moov");
|
||||
if(moov) {
|
||||
MP4::AtomList stco = moov->findall("stco", true);
|
||||
for(unsigned int i = 0; i < stco.size(); i++) {
|
||||
MP4::Atom *atom = stco[i];
|
||||
for(MP4::AtomList::ConstIterator it = stco.begin(); it != stco.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@@ -528,7 +554,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
ByteVector data = d->file->readBlock(atom->length - 12);
|
||||
unsigned int count = data.toUInt();
|
||||
d->file->seek(atom->offset + 16);
|
||||
uint pos = 4;
|
||||
unsigned int pos = 4;
|
||||
while(count--) {
|
||||
long o = static_cast<long>(data.toUInt(pos));
|
||||
if(o > offset) {
|
||||
@@ -540,8 +566,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
}
|
||||
|
||||
MP4::AtomList co64 = moov->findall("co64", true);
|
||||
for(unsigned int i = 0; i < co64.size(); i++) {
|
||||
MP4::Atom *atom = co64[i];
|
||||
for(MP4::AtomList::ConstIterator it = co64.begin(); it != co64.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@@ -549,7 +575,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
ByteVector data = d->file->readBlock(atom->length - 12);
|
||||
unsigned int count = data.toUInt();
|
||||
d->file->seek(atom->offset + 16);
|
||||
uint pos = 4;
|
||||
unsigned int pos = 4;
|
||||
while(count--) {
|
||||
long long o = data.toLongLong(pos);
|
||||
if(o > offset) {
|
||||
@@ -564,8 +590,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
MP4::Atom *moof = d->atoms->find("moof");
|
||||
if(moof) {
|
||||
MP4::AtomList tfhd = moof->findall("tfhd", true);
|
||||
for(unsigned int i = 0; i < tfhd.size(); i++) {
|
||||
MP4::Atom *atom = tfhd[i];
|
||||
for(MP4::AtomList::ConstIterator it = tfhd.begin(); it != tfhd.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@@ -585,10 +611,11 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::saveNew(ByteVector &data)
|
||||
MP4::Tag::saveNew(ByteVector data)
|
||||
{
|
||||
data = renderAtom("meta", TagLib::ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) +
|
||||
data = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
data + padIlst(data));
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta");
|
||||
@@ -597,26 +624,33 @@ MP4::Tag::saveNew(ByteVector &data)
|
||||
data = renderAtom("udta", data);
|
||||
}
|
||||
|
||||
long offset = path[path.size() - 1]->offset + 8;
|
||||
long offset = path.back()->offset + 8;
|
||||
d->file->insert(data, offset, 0);
|
||||
|
||||
updateParents(path, data.size());
|
||||
updateOffsets(data.size(), offset);
|
||||
|
||||
// Insert the newly created atoms into the tree to keep it up-to-date.
|
||||
|
||||
d->file->seek(offset);
|
||||
path.back()->children.prepend(new Atom(d->file));
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
|
||||
MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
|
||||
{
|
||||
MP4::Atom *ilst = path[path.size() - 1];
|
||||
AtomList::ConstIterator it = path.end();
|
||||
|
||||
MP4::Atom *ilst = *(--it);
|
||||
long offset = ilst->offset;
|
||||
long length = ilst->length;
|
||||
|
||||
MP4::Atom *meta = path[path.size() - 2];
|
||||
AtomList::Iterator index = meta->children.find(ilst);
|
||||
MP4::Atom *meta = *(--it);
|
||||
AtomList::ConstIterator index = meta->children.find(ilst);
|
||||
|
||||
// check if there is an atom before 'ilst', and possibly use it as padding
|
||||
if(index != meta->children.begin()) {
|
||||
AtomList::Iterator prevIndex = index;
|
||||
AtomList::ConstIterator prevIndex = index;
|
||||
prevIndex--;
|
||||
MP4::Atom *prev = *prevIndex;
|
||||
if(prev->name == "free") {
|
||||
@@ -625,7 +659,7 @@ MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
|
||||
}
|
||||
}
|
||||
// check if there is an atom after 'ilst', and possibly use it as padding
|
||||
AtomList::Iterator nextIndex = index;
|
||||
AtomList::ConstIterator nextIndex = index;
|
||||
nextIndex++;
|
||||
if(nextIndex != meta->children.end()) {
|
||||
MP4::Atom *next = *nextIndex;
|
||||
@@ -657,7 +691,7 @@ MP4::Tag::title() const
|
||||
{
|
||||
if(d->items.contains("\251nam"))
|
||||
return d->items["\251nam"].toStringList().toString(", ");
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String
|
||||
@@ -665,7 +699,7 @@ MP4::Tag::artist() const
|
||||
{
|
||||
if(d->items.contains("\251ART"))
|
||||
return d->items["\251ART"].toStringList().toString(", ");
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String
|
||||
@@ -673,7 +707,7 @@ MP4::Tag::album() const
|
||||
{
|
||||
if(d->items.contains("\251alb"))
|
||||
return d->items["\251alb"].toStringList().toString(", ");
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String
|
||||
@@ -681,7 +715,7 @@ MP4::Tag::comment() const
|
||||
{
|
||||
if(d->items.contains("\251cmt"))
|
||||
return d->items["\251cmt"].toStringList().toString(", ");
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
String
|
||||
@@ -689,7 +723,7 @@ MP4::Tag::genre() const
|
||||
{
|
||||
if(d->items.contains("\251gen"))
|
||||
return d->items["\251gen"].toStringList().toString(", ");
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
unsigned int
|
||||
@@ -739,87 +773,122 @@ MP4::Tag::setGenre(const String &value)
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setYear(uint value)
|
||||
MP4::Tag::setYear(unsigned int value)
|
||||
{
|
||||
d->items["\251day"] = StringList(String::number(value));
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setTrack(uint value)
|
||||
MP4::Tag::setTrack(unsigned int value)
|
||||
{
|
||||
d->items["trkn"] = MP4::Item(value, 0);
|
||||
}
|
||||
|
||||
MP4::ItemListMap &
|
||||
MP4::Tag::itemListMap()
|
||||
bool MP4::Tag::isEmpty() const
|
||||
{
|
||||
return d->items.isEmpty();
|
||||
}
|
||||
|
||||
MP4::ItemMap &MP4::Tag::itemListMap()
|
||||
{
|
||||
return d->items;
|
||||
}
|
||||
|
||||
static const char *keyTranslation[][2] = {
|
||||
{ "\251nam", "TITLE" },
|
||||
{ "\251ART", "ARTIST" },
|
||||
{ "\251alb", "ALBUM" },
|
||||
{ "\251cmt", "COMMENT" },
|
||||
{ "\251gen", "GENRE" },
|
||||
{ "\251day", "DATE" },
|
||||
{ "\251wrt", "COMPOSER" },
|
||||
{ "\251grp", "GROUPING" },
|
||||
{ "trkn", "TRACKNUMBER" },
|
||||
{ "disk", "DISCNUMBER" },
|
||||
{ "cpil", "COMPILATION" },
|
||||
{ "tmpo", "BPM" },
|
||||
{ "cprt", "COPYRIGHT" },
|
||||
{ "\251lyr", "LYRICS" },
|
||||
{ "\251too", "ENCODEDBY" },
|
||||
{ "soal", "ALBUMSORT" },
|
||||
{ "soaa", "ALBUMARTISTSORT" },
|
||||
{ "soar", "ARTISTSORT" },
|
||||
{ "sonm", "TITLESORT" },
|
||||
{ "soco", "COMPOSERSORT" },
|
||||
{ "sosn", "SHOWSORT" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "----:com.apple.iTunes:ASIN", "ASIN" },
|
||||
{ "----:com.apple.iTunes:LABEL", "LABEL" },
|
||||
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
|
||||
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
|
||||
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
|
||||
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
|
||||
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
|
||||
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
|
||||
{ "----:com.apple.iTunes:MIXER", "MIXER" },
|
||||
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
|
||||
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
|
||||
{ "----:com.apple.iTunes:MOOD", "MOOD" },
|
||||
{ "----:com.apple.iTunes:ISRC", "ISRC" },
|
||||
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
|
||||
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
|
||||
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
|
||||
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
|
||||
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
|
||||
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
|
||||
};
|
||||
const MP4::ItemMap &MP4::Tag::itemMap() const
|
||||
{
|
||||
return d->items;
|
||||
}
|
||||
|
||||
MP4::Item MP4::Tag::item(const String &key) const
|
||||
{
|
||||
return d->items[key];
|
||||
}
|
||||
|
||||
void MP4::Tag::setItem(const String &key, const Item &value)
|
||||
{
|
||||
d->items[key] = value;
|
||||
}
|
||||
|
||||
void MP4::Tag::removeItem(const String &key)
|
||||
{
|
||||
d->items.erase(key);
|
||||
}
|
||||
|
||||
bool MP4::Tag::contains(const String &key) const
|
||||
{
|
||||
return d->items.contains(key);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
const char *keyTranslation[][2] = {
|
||||
{ "\251nam", "TITLE" },
|
||||
{ "\251ART", "ARTIST" },
|
||||
{ "\251alb", "ALBUM" },
|
||||
{ "\251cmt", "COMMENT" },
|
||||
{ "\251gen", "GENRE" },
|
||||
{ "\251day", "DATE" },
|
||||
{ "\251wrt", "COMPOSER" },
|
||||
{ "\251grp", "GROUPING" },
|
||||
{ "aART", "ALBUMARTIST" },
|
||||
{ "trkn", "TRACKNUMBER" },
|
||||
{ "disk", "DISCNUMBER" },
|
||||
{ "cpil", "COMPILATION" },
|
||||
{ "tmpo", "BPM" },
|
||||
{ "cprt", "COPYRIGHT" },
|
||||
{ "\251lyr", "LYRICS" },
|
||||
{ "\251too", "ENCODEDBY" },
|
||||
{ "soal", "ALBUMSORT" },
|
||||
{ "soaa", "ALBUMARTISTSORT" },
|
||||
{ "soar", "ARTISTSORT" },
|
||||
{ "sonm", "TITLESORT" },
|
||||
{ "soco", "COMPOSERSORT" },
|
||||
{ "sosn", "SHOWSORT" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "----:com.apple.iTunes:ASIN", "ASIN" },
|
||||
{ "----:com.apple.iTunes:LABEL", "LABEL" },
|
||||
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
|
||||
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
|
||||
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
|
||||
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
|
||||
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
|
||||
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
|
||||
{ "----:com.apple.iTunes:MIXER", "MIXER" },
|
||||
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
|
||||
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
|
||||
{ "----:com.apple.iTunes:MOOD", "MOOD" },
|
||||
{ "----:com.apple.iTunes:ISRC", "ISRC" },
|
||||
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
|
||||
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
|
||||
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
|
||||
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
|
||||
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
|
||||
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
|
||||
};
|
||||
const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
|
||||
String translateKey(const String &key)
|
||||
{
|
||||
for(size_t i = 0; i < keyTranslationSize; ++i) {
|
||||
if(key == keyTranslation[i][0])
|
||||
return keyTranslation[i][1];
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap MP4::Tag::properties() const
|
||||
{
|
||||
static Map<String, String> keyMap;
|
||||
if(keyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap props;
|
||||
MP4::ItemListMap::ConstIterator it = d->items.begin();
|
||||
for(; it != d->items.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
|
||||
const String key = translateKey(it->first);
|
||||
if(!key.isEmpty()) {
|
||||
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
|
||||
MP4::Item::IntPair ip = it->second.toIntPair();
|
||||
String value = String::number(ip.first);
|
||||
@@ -847,8 +916,7 @@ PropertyMap MP4::Tag::properties() const
|
||||
|
||||
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
|
||||
{
|
||||
StringList::ConstIterator it = props.begin();
|
||||
for(; it != props.end(); ++it)
|
||||
for(StringList::ConstIterator it = props.begin(); it != props.end(); ++it)
|
||||
d->items.erase(*it);
|
||||
}
|
||||
|
||||
@@ -863,22 +931,20 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
}
|
||||
|
||||
PropertyMap origProps = properties();
|
||||
PropertyMap::ConstIterator it = origProps.begin();
|
||||
for(; it != origProps.end(); ++it) {
|
||||
for(PropertyMap::ConstIterator it = origProps.begin(); it != origProps.end(); ++it) {
|
||||
if(!props.contains(it->first) || props[it->first].isEmpty()) {
|
||||
d->items.erase(reverseKeyMap[it->first]);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap ignoredProps;
|
||||
it = props.begin();
|
||||
for(; it != props.end(); ++it) {
|
||||
for(PropertyMap::ConstIterator it = props.begin(); it != props.end(); ++it) {
|
||||
if(reverseKeyMap.contains(it->first)) {
|
||||
String name = reverseKeyMap[it->first];
|
||||
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
|
||||
if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) {
|
||||
int first = 0, second = 0;
|
||||
StringList parts = StringList::split(it->second.front(), "/");
|
||||
if(parts.size() > 0) {
|
||||
if(!parts.isEmpty()) {
|
||||
first = parts[0].toInt();
|
||||
if(parts.size() > 1) {
|
||||
second = parts[1].toInt();
|
||||
@@ -886,11 +952,11 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
d->items[name] = MP4::Item(first, second);
|
||||
}
|
||||
}
|
||||
else if(it->first == "BPM") {
|
||||
else if(it->first == "BPM" && !it->second.isEmpty()) {
|
||||
int value = it->second.front().toInt();
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
else if(it->first == "COMPILATION") {
|
||||
else if(it->first == "COMPILATION" && !it->second.isEmpty()) {
|
||||
bool value = (it->second.front().toInt() != 0);
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
|
||||
@@ -39,71 +39,110 @@ namespace TagLib {
|
||||
|
||||
namespace MP4 {
|
||||
|
||||
/*!
|
||||
* \deprecated
|
||||
*/
|
||||
typedef TagLib::Map<String, Item> ItemListMap;
|
||||
typedef TagLib::Map<String, Item> ItemMap;
|
||||
|
||||
class TAGLIB_EXPORT Tag: public TagLib::Tag
|
||||
{
|
||||
public:
|
||||
Tag();
|
||||
Tag(TagLib::File *file, Atoms *atoms);
|
||||
~Tag();
|
||||
virtual ~Tag();
|
||||
bool save();
|
||||
|
||||
String title() const;
|
||||
String artist() const;
|
||||
String album() const;
|
||||
String comment() const;
|
||||
String genre() const;
|
||||
uint year() const;
|
||||
uint track() const;
|
||||
virtual String title() const;
|
||||
virtual String artist() const;
|
||||
virtual String album() const;
|
||||
virtual String comment() const;
|
||||
virtual String genre() const;
|
||||
virtual unsigned int year() const;
|
||||
virtual unsigned int track() const;
|
||||
|
||||
void setTitle(const String &value);
|
||||
void setArtist(const String &value);
|
||||
void setAlbum(const String &value);
|
||||
void setComment(const String &value);
|
||||
void setGenre(const String &value);
|
||||
void setYear(uint value);
|
||||
void setTrack(uint value);
|
||||
virtual void setTitle(const String &value);
|
||||
virtual void setArtist(const String &value);
|
||||
virtual void setAlbum(const String &value);
|
||||
virtual void setComment(const String &value);
|
||||
virtual void setGenre(const String &value);
|
||||
virtual void setYear(unsigned int value);
|
||||
virtual void setTrack(unsigned int value);
|
||||
|
||||
ItemListMap &itemListMap();
|
||||
virtual bool isEmpty() const;
|
||||
|
||||
/*!
|
||||
* \deprecated Use the item() and setItem() API instead
|
||||
*/
|
||||
ItemMap &itemListMap();
|
||||
|
||||
/*!
|
||||
* Returns a string-keyed map of the MP4::Items for this tag.
|
||||
*/
|
||||
const ItemMap &itemMap() const;
|
||||
|
||||
/*!
|
||||
* \return The item, if any, corresponding to \a key.
|
||||
*/
|
||||
Item item(const String &key) const;
|
||||
|
||||
/*!
|
||||
* Sets the value of \a key to \a value, overwriting any previous value.
|
||||
*/
|
||||
void setItem(const String &key, const Item &value);
|
||||
|
||||
/*!
|
||||
* Removes the entry with \a key from the tag, or does nothing if it does
|
||||
* not exist.
|
||||
*/
|
||||
void removeItem(const String &key);
|
||||
|
||||
/*!
|
||||
* \return True if the tag contains an entry for \a key.
|
||||
*/
|
||||
bool contains(const String &key) const;
|
||||
|
||||
PropertyMap properties() const;
|
||||
void removeUnsupportedProperties(const StringList& properties);
|
||||
PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
private:
|
||||
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1);
|
||||
void parseFreeForm(Atom *atom, TagLib::File *file);
|
||||
void parseInt(Atom *atom, TagLib::File *file);
|
||||
void parseByte(Atom *atom, TagLib::File *file);
|
||||
void parseUInt(Atom *atom, TagLib::File *file);
|
||||
void parseLongLong(Atom *atom, TagLib::File *file);
|
||||
void parseGnre(Atom *atom, TagLib::File *file);
|
||||
void parseIntPair(Atom *atom, TagLib::File *file);
|
||||
void parseBool(Atom *atom, TagLib::File *file);
|
||||
void parseCovr(Atom *atom, TagLib::File *file);
|
||||
AtomDataList parseData2(const Atom *atom, int expectedFlags = -1,
|
||||
bool freeForm = false);
|
||||
ByteVectorList parseData(const Atom *atom, int expectedFlags = -1,
|
||||
bool freeForm = false);
|
||||
void parseText(const Atom *atom, int expectedFlags = 1);
|
||||
void parseFreeForm(const Atom *atom);
|
||||
void parseInt(const Atom *atom);
|
||||
void parseByte(const Atom *atom);
|
||||
void parseUInt(const Atom *atom);
|
||||
void parseLongLong(const Atom *atom);
|
||||
void parseGnre(const Atom *atom);
|
||||
void parseIntPair(const Atom *atom);
|
||||
void parseBool(const Atom *atom);
|
||||
void parseCovr(const Atom *atom);
|
||||
|
||||
TagLib::ByteVector padIlst(const ByteVector &data, int length = -1);
|
||||
TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data);
|
||||
TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data);
|
||||
TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8);
|
||||
TagLib::ByteVector renderFreeForm(const String &name, Item &item);
|
||||
TagLib::ByteVector renderBool(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderInt(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderByte(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderUInt(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderCovr(const ByteVector &name, Item &item);
|
||||
ByteVector padIlst(const ByteVector &data, int length = -1) const;
|
||||
ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const;
|
||||
ByteVector renderData(const ByteVector &name, int flags,
|
||||
const ByteVectorList &data) const;
|
||||
ByteVector renderText(const ByteVector &name, const Item &item,
|
||||
int flags = TypeUTF8) const;
|
||||
ByteVector renderFreeForm(const String &name, const Item &item) const;
|
||||
ByteVector renderBool(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderInt(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderByte(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderUInt(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderLongLong(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderIntPair(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderCovr(const ByteVector &name, const Item &item) const;
|
||||
|
||||
void updateParents(AtomList &path, long delta, int ignore = 0);
|
||||
void updateParents(const AtomList &path, long delta, int ignore = 0);
|
||||
void updateOffsets(long delta, long offset);
|
||||
|
||||
void saveNew(TagLib::ByteVector &data);
|
||||
void saveExisting(TagLib::ByteVector &data, AtomList &path);
|
||||
void saveNew(ByteVector data);
|
||||
void saveExisting(ByteVector data, const AtomList &path);
|
||||
|
||||
void addItem(const String &name, const Item &value);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <tagunion.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tagutils.h>
|
||||
|
||||
#include "mpcfile.h"
|
||||
#include "id3v1tag.h"
|
||||
@@ -52,11 +53,7 @@ public:
|
||||
ID3v2Header(0),
|
||||
ID3v2Location(-1),
|
||||
ID3v2Size(0),
|
||||
properties(0),
|
||||
scanned(false),
|
||||
hasAPE(false),
|
||||
hasID3v1(false),
|
||||
hasID3v2(false) {}
|
||||
properties(0) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
@@ -65,45 +62,37 @@ public:
|
||||
}
|
||||
|
||||
long APELocation;
|
||||
uint APESize;
|
||||
long APESize;
|
||||
|
||||
long ID3v1Location;
|
||||
|
||||
ID3v2::Header *ID3v2Header;
|
||||
long ID3v2Location;
|
||||
uint ID3v2Size;
|
||||
long ID3v2Size;
|
||||
|
||||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
bool scanned;
|
||||
|
||||
// These indicate whether the file *on disk* has these tags, not if
|
||||
// this data structure does. This is used in computing offsets.
|
||||
|
||||
bool hasAPE;
|
||||
bool hasID3v1;
|
||||
bool hasID3v2;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPC::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPC::File::~File()
|
||||
@@ -118,26 +107,20 @@ TagLib::Tag *MPC::File::tag() const
|
||||
|
||||
PropertyMap MPC::File::properties() const
|
||||
{
|
||||
if(d->hasAPE)
|
||||
return d->tag.access<APE::Tag>(MPCAPEIndex, false)->properties();
|
||||
if(d->hasID3v1)
|
||||
return d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->properties();
|
||||
return PropertyMap();
|
||||
return d->tag.properties();
|
||||
}
|
||||
|
||||
void MPC::File::removeUnsupportedProperties(const StringList &properties)
|
||||
{
|
||||
if(d->hasAPE)
|
||||
d->tag.access<APE::Tag>(MPCAPEIndex, false)->removeUnsupportedProperties(properties);
|
||||
if(d->hasID3v1)
|
||||
d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->removeUnsupportedProperties(properties);
|
||||
d->tag.removeUnsupportedProperties(properties);
|
||||
}
|
||||
|
||||
PropertyMap MPC::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
if(d->hasID3v1)
|
||||
d->tag.access<APE::Tag>(MPCID3v1Index, false)->setProperties(properties);
|
||||
return d->tag.access<APE::Tag>(MPCAPEIndex, true)->setProperties(properties);
|
||||
if(ID3v1Tag())
|
||||
ID3v1Tag()->setProperties(properties);
|
||||
|
||||
return APETag(true)->setProperties(properties);
|
||||
}
|
||||
|
||||
MPC::Properties *MPC::File::audioProperties() const
|
||||
@@ -154,69 +137,80 @@ bool MPC::File::save()
|
||||
|
||||
// Possibly strip ID3v2 tag
|
||||
|
||||
if(d->hasID3v2 && !d->ID3v2Header) {
|
||||
if(!d->ID3v2Header && d->ID3v2Location >= 0) {
|
||||
removeBlock(d->ID3v2Location, d->ID3v2Size);
|
||||
d->hasID3v2 = false;
|
||||
if(d->hasID3v1)
|
||||
d->ID3v1Location -= d->ID3v2Size;
|
||||
if(d->hasAPE)
|
||||
|
||||
if(d->APELocation >= 0)
|
||||
d->APELocation -= d->ID3v2Size;
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location -= d->ID3v2Size;
|
||||
|
||||
d->ID3v2Location = -1;
|
||||
d->ID3v2Size = 0;
|
||||
}
|
||||
|
||||
// Update ID3v1 tag
|
||||
|
||||
if(ID3v1Tag()) {
|
||||
if(d->hasID3v1) {
|
||||
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
|
||||
|
||||
// ID3v1 tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
seek(d->ID3v1Location);
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->ID3v1Location = tell();
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
} else
|
||||
if(d->hasID3v1) {
|
||||
removeBlock(d->ID3v1Location, 128);
|
||||
d->hasID3v1 = false;
|
||||
if(d->hasAPE) {
|
||||
if(d->APELocation > d->ID3v1Location)
|
||||
d->APELocation -= 128;
|
||||
}
|
||||
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
}
|
||||
else {
|
||||
|
||||
// ID3v1 tag is empty. Remove the old one.
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
truncate(d->ID3v1Location);
|
||||
d->ID3v1Location = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update APE tag
|
||||
|
||||
if(APETag()) {
|
||||
if(d->hasAPE)
|
||||
insert(APETag()->render(), d->APELocation, d->APESize);
|
||||
else {
|
||||
if(d->hasID3v1) {
|
||||
insert(APETag()->render(), d->ID3v1Location, 0);
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->hasAPE = true;
|
||||
if(APETag() && !APETag()->isEmpty()) {
|
||||
|
||||
// APE tag is not empty. Update the old one or create a new one.
|
||||
|
||||
if(d->APELocation < 0) {
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->APELocation = d->ID3v1Location;
|
||||
d->ID3v1Location += d->APESize;
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->APELocation = tell();
|
||||
writeBlock(APETag()->render());
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->hasAPE = true;
|
||||
}
|
||||
else
|
||||
d->APELocation = length();
|
||||
}
|
||||
|
||||
const ByteVector data = APETag()->render();
|
||||
insert(data, d->APELocation, d->APESize);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
|
||||
|
||||
d->APESize = data.size();
|
||||
}
|
||||
else {
|
||||
|
||||
// APE tag is empty. Remove the old one.
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
removeBlock(d->APELocation, d->APESize);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->ID3v1Location -= d->APESize;
|
||||
|
||||
d->APELocation = -1;
|
||||
d->APESize = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
if(d->hasAPE) {
|
||||
removeBlock(d->APELocation, d->APESize);
|
||||
d->hasAPE = false;
|
||||
if(d->hasID3v1) {
|
||||
if(d->ID3v1Location > d->APELocation)
|
||||
d->ID3v1Location -= d->APESize;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -233,22 +227,19 @@ APE::Tag *MPC::File::APETag(bool create)
|
||||
|
||||
void MPC::File::strip(int tags)
|
||||
{
|
||||
if(tags & ID3v1) {
|
||||
if(tags & ID3v1)
|
||||
d->tag.set(MPCID3v1Index, 0);
|
||||
|
||||
if(tags & APE)
|
||||
d->tag.set(MPCAPEIndex, 0);
|
||||
|
||||
if(!ID3v1Tag())
|
||||
APETag(true);
|
||||
}
|
||||
|
||||
if(tags & ID3v2) {
|
||||
delete d->ID3v2Header;
|
||||
d->ID3v2Header = 0;
|
||||
}
|
||||
|
||||
if(tags & APE) {
|
||||
d->tag.set(MPCAPEIndex, 0);
|
||||
|
||||
if(!ID3v1Tag())
|
||||
APETag(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MPC::File::remove(int tags)
|
||||
@@ -258,110 +249,71 @@ void MPC::File::remove(int tags)
|
||||
|
||||
bool MPC::File::hasID3v1Tag() const
|
||||
{
|
||||
return d->hasID3v1;
|
||||
return (d->ID3v1Location >= 0);
|
||||
}
|
||||
|
||||
bool MPC::File::hasAPETag() const
|
||||
{
|
||||
return d->hasAPE;
|
||||
return (d->APELocation >= 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */)
|
||||
void MPC::File::read(bool readProperties)
|
||||
{
|
||||
// Look for an ID3v1 tag
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
d->ID3v1Location = findID3v1();
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
// Look for an APE tag
|
||||
|
||||
findAPE();
|
||||
|
||||
d->APELocation = findAPE();
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
|
||||
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize;
|
||||
d->hasAPE = true;
|
||||
}
|
||||
|
||||
if(!d->hasID3v1)
|
||||
APETag(true);
|
||||
|
||||
// Look for and skip an ID3v2 tag
|
||||
|
||||
d->ID3v2Location = findID3v2();
|
||||
d->ID3v2Location = Utils::findID3v2(this);
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
seek(d->ID3v2Location);
|
||||
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
|
||||
d->ID3v2Size = d->ID3v2Header->completeTagSize();
|
||||
d->hasID3v2 = true;
|
||||
}
|
||||
|
||||
if(d->hasID3v2)
|
||||
seek(d->ID3v2Location + d->ID3v2Size);
|
||||
else
|
||||
seek(0);
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
d->ID3v1Location = Utils::findID3v1(this);
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
|
||||
|
||||
// Look for an APE tag
|
||||
|
||||
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
|
||||
d->APESize = APETag()->footer()->completeTagSize();
|
||||
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
|
||||
}
|
||||
|
||||
if(d->ID3v1Location < 0)
|
||||
APETag(true);
|
||||
|
||||
// Look for MPC metadata
|
||||
|
||||
if(readProperties) {
|
||||
d->properties = new Properties(this, length() - d->ID3v2Size - d->APESize);
|
||||
|
||||
long streamLength;
|
||||
|
||||
if(d->APELocation >= 0)
|
||||
streamLength = d->APELocation;
|
||||
else if(d->ID3v1Location >= 0)
|
||||
streamLength = d->ID3v1Location;
|
||||
else
|
||||
streamLength = length();
|
||||
|
||||
if(d->ID3v2Location >= 0) {
|
||||
seek(d->ID3v2Location + d->ID3v2Size);
|
||||
streamLength -= (d->ID3v2Location + d->ID3v2Size);
|
||||
}
|
||||
else {
|
||||
seek(0);
|
||||
}
|
||||
|
||||
d->properties = new Properties(this, streamLength);
|
||||
}
|
||||
}
|
||||
|
||||
long MPC::File::findAPE()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
if(d->hasID3v1)
|
||||
seek(-160, End);
|
||||
else
|
||||
seek(-32, End);
|
||||
|
||||
long p = tell();
|
||||
|
||||
if(readBlock(8) == APE::Tag::fileIdentifier())
|
||||
return p;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
long MPC::File::findID3v1()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(-128, End);
|
||||
long p = tell();
|
||||
|
||||
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
|
||||
return p;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
long MPC::File::findID3v2()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(0);
|
||||
|
||||
if(readBlock(3) == ID3v2::Header::fileIdentifier())
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,8 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Saves the file.
|
||||
*
|
||||
* This returns true if the save was successful.
|
||||
*/
|
||||
virtual bool save();
|
||||
|
||||
@@ -149,8 +151,8 @@ namespace TagLib {
|
||||
* if there is no valid APE tag. If \a create is true it will create
|
||||
* an APE tag if one does not exist and returns a valid pointer.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -166,11 +168,11 @@ namespace TagLib {
|
||||
*
|
||||
* If \a create is false (the default) this may return a null pointer
|
||||
* if there is no valid APE tag. If \a create is true it will create
|
||||
* an APE tag if one does not exist and returns a valid pointer. If
|
||||
* an APE tag if one does not exist and returns a valid pointer. If
|
||||
* there already be an ID3v1 tag, the new APE tag will be placed before it.
|
||||
*
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an APE tag. Use hasAPETag() to check if the file
|
||||
* \note This may return a valid pointer regardless of whether or not the
|
||||
* file on disk has an APE tag. Use hasAPETag() to check if the file
|
||||
* on disk actually has an APE tag.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
|
||||
@@ -216,11 +218,7 @@ namespace TagLib {
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void scan();
|
||||
long findAPE();
|
||||
long findID3v1();
|
||||
long findID3v2();
|
||||
void read(bool readProperties);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
||||
@@ -36,9 +36,7 @@ using namespace TagLib;
|
||||
class MPC::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(long length, ReadStyle s) :
|
||||
streamLength(length),
|
||||
style(s),
|
||||
PropertiesPrivate() :
|
||||
version(0),
|
||||
length(0),
|
||||
bitrate(0),
|
||||
@@ -51,43 +49,43 @@ public:
|
||||
albumGain(0),
|
||||
albumPeak(0) {}
|
||||
|
||||
long streamLength;
|
||||
ReadStyle style;
|
||||
int version;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
uint totalFrames;
|
||||
uint sampleFrames;
|
||||
uint trackGain;
|
||||
uint trackPeak;
|
||||
uint albumGain;
|
||||
uint albumPeak;
|
||||
String flags;
|
||||
int version;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
unsigned int totalFrames;
|
||||
unsigned int sampleFrames;
|
||||
unsigned int trackGain;
|
||||
unsigned int trackPeak;
|
||||
unsigned int albumGain;
|
||||
unsigned int albumPeak;
|
||||
String flags;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style)
|
||||
MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(streamLength, style);
|
||||
readSV7(data);
|
||||
readSV7(data, streamLength);
|
||||
}
|
||||
|
||||
MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style)
|
||||
MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(streamLength, style);
|
||||
ByteVector magic = file->readBlock(4);
|
||||
if(magic == "MPCK") {
|
||||
// Musepack version 8
|
||||
readSV8(file);
|
||||
readSV8(file, streamLength);
|
||||
}
|
||||
else {
|
||||
// Musepack version 7 or older, fixed size header
|
||||
readSV7(magic + file->readBlock(MPC::HeaderSize - 4));
|
||||
readSV7(magic + file->readBlock(MPC::HeaderSize - 4), streamLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +95,16 @@ MPC::Properties::~Properties()
|
||||
}
|
||||
|
||||
int MPC::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int MPC::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int MPC::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@@ -121,12 +129,12 @@ int MPC::Properties::mpcVersion() const
|
||||
return d->version;
|
||||
}
|
||||
|
||||
TagLib::uint MPC::Properties::totalFrames() const
|
||||
unsigned int MPC::Properties::totalFrames() const
|
||||
{
|
||||
return d->totalFrames;
|
||||
}
|
||||
|
||||
TagLib::uint MPC::Properties::sampleFrames() const
|
||||
unsigned int MPC::Properties::sampleFrames() const
|
||||
{
|
||||
return d->sampleFrames;
|
||||
}
|
||||
@@ -155,78 +163,121 @@ int MPC::Properties::albumPeak() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsigned long readSize(File *file, TagLib::uint &sizelength)
|
||||
namespace
|
||||
{
|
||||
unsigned char tmp;
|
||||
unsigned long size = 0;
|
||||
unsigned long readSize(File *file, unsigned int &sizeLength, bool &eof)
|
||||
{
|
||||
sizeLength = 0;
|
||||
eof = false;
|
||||
|
||||
do {
|
||||
ByteVector b = file->readBlock(1);
|
||||
tmp = b[0];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
sizelength++;
|
||||
} while((tmp & 0x80));
|
||||
return size;
|
||||
unsigned char tmp;
|
||||
unsigned long size = 0;
|
||||
|
||||
do {
|
||||
const ByteVector b = file->readBlock(1);
|
||||
if(b.isEmpty()) {
|
||||
eof = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = b[0];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
sizeLength++;
|
||||
} while((tmp & 0x80));
|
||||
return size;
|
||||
}
|
||||
|
||||
unsigned long readSize(const ByteVector &data, unsigned int &pos)
|
||||
{
|
||||
unsigned char tmp;
|
||||
unsigned long size = 0;
|
||||
|
||||
do {
|
||||
tmp = data[pos++];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
} while((tmp & 0x80) && (pos < data.size()));
|
||||
return size;
|
||||
}
|
||||
|
||||
// This array looks weird, but the same as original MusePack code found at:
|
||||
// https://www.musepack.net/index.php?pg=src
|
||||
const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
unsigned long readSize(const ByteVector &data, TagLib::uint &sizelength)
|
||||
{
|
||||
unsigned char tmp;
|
||||
unsigned long size = 0;
|
||||
unsigned long pos = 0;
|
||||
|
||||
do {
|
||||
tmp = data[pos++];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
sizelength++;
|
||||
} while((tmp & 0x80) && (pos < data.size()));
|
||||
return size;
|
||||
}
|
||||
|
||||
static const unsigned short sftable [4] = { 44100, 48000, 37800, 32000 };
|
||||
|
||||
void MPC::Properties::readSV8(File *file)
|
||||
void MPC::Properties::readSV8(File *file, long streamLength)
|
||||
{
|
||||
bool readSH = false, readRG = false;
|
||||
|
||||
while(!readSH && !readRG) {
|
||||
ByteVector packetType = file->readBlock(2);
|
||||
uint packetSizeLength = 0;
|
||||
unsigned long packetSize = readSize(file, packetSizeLength);
|
||||
unsigned long dataSize = packetSize - 2 - packetSizeLength;
|
||||
const ByteVector packetType = file->readBlock(2);
|
||||
|
||||
unsigned int packetSizeLength;
|
||||
bool eof;
|
||||
const unsigned long packetSize = readSize(file, packetSizeLength, eof);
|
||||
if(eof) {
|
||||
debug("MPC::Properties::readSV8() - Reached to EOF.");
|
||||
break;
|
||||
}
|
||||
|
||||
const unsigned long dataSize = packetSize - 2 - packetSizeLength;
|
||||
|
||||
const ByteVector data = file->readBlock(dataSize);
|
||||
if(data.size() != dataSize) {
|
||||
debug("MPC::Properties::readSV8() - dataSize doesn't match the actual data size.");
|
||||
break;
|
||||
}
|
||||
|
||||
if(packetType == "SH") {
|
||||
// Stream Header
|
||||
// http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket
|
||||
ByteVector data = file->readBlock(dataSize);
|
||||
|
||||
if(dataSize <= 5) {
|
||||
debug("MPC::Properties::readSV8() - \"SH\" packet is too short to parse.");
|
||||
break;
|
||||
}
|
||||
|
||||
readSH = true;
|
||||
|
||||
TagLib::uint pos = 4;
|
||||
unsigned int pos = 4;
|
||||
d->version = data[pos];
|
||||
pos += 1;
|
||||
d->sampleFrames = readSize(data.mid(pos), pos);
|
||||
ulong begSilence = readSize(data.mid(pos), pos);
|
||||
d->sampleFrames = readSize(data, pos);
|
||||
if(pos > dataSize - 3) {
|
||||
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
|
||||
break;
|
||||
}
|
||||
|
||||
std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(data.toUShort(pos, true)));
|
||||
const unsigned long begSilence = readSize(data, pos);
|
||||
if(pos > dataSize - 2) {
|
||||
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
|
||||
break;
|
||||
}
|
||||
|
||||
const unsigned short flags = data.toUShort(pos, true);
|
||||
pos += 2;
|
||||
|
||||
d->sampleRate = sftable[flags[15] * 4 + flags[14] * 2 + flags[13]];
|
||||
d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1;
|
||||
d->sampleRate = sftable[(flags >> 13) & 0x07];
|
||||
d->channels = ((flags >> 4) & 0x0F) + 1;
|
||||
|
||||
if((d->sampleFrames - begSilence) != 0)
|
||||
d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence));
|
||||
d->bitrate = d->bitrate / 1000;
|
||||
|
||||
d->length = (d->sampleFrames - begSilence) / d->sampleRate;
|
||||
const unsigned int frameCount = d->sampleFrames - begSilence;
|
||||
if(frameCount > 0 && d->sampleRate > 0) {
|
||||
const double length = frameCount * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
else if (packetType == "RG") {
|
||||
// Replay Gain
|
||||
// http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket
|
||||
ByteVector data = file->readBlock(dataSize);
|
||||
|
||||
if(dataSize <= 9) {
|
||||
debug("MPC::Properties::readSV8() - \"RG\" packet is too short to parse.");
|
||||
break;
|
||||
}
|
||||
|
||||
readRG = true;
|
||||
|
||||
int replayGainVersion = data[0];
|
||||
const int replayGainVersion = data[0];
|
||||
if(replayGainVersion == 1) {
|
||||
d->trackGain = data.toShort(1, true);
|
||||
d->trackPeak = data.toShort(3, true);
|
||||
@@ -245,7 +296,7 @@ void MPC::Properties::readSV8(File *file)
|
||||
}
|
||||
}
|
||||
|
||||
void MPC::Properties::readSV7(const ByteVector &data)
|
||||
void MPC::Properties::readSV7(const ByteVector &data, long streamLength)
|
||||
{
|
||||
if(data.startsWith("MP+")) {
|
||||
d->version = data[3] & 15;
|
||||
@@ -254,11 +305,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
||||
|
||||
d->totalFrames = data.toUInt(4, false);
|
||||
|
||||
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt(8, false)));
|
||||
d->sampleRate = sftable[flags[17] * 2 + flags[16]];
|
||||
d->channels = 2;
|
||||
const unsigned int flags = data.toUInt(8, false);
|
||||
d->sampleRate = sftable[(flags >> 16) & 0x03];
|
||||
d->channels = 2;
|
||||
|
||||
uint gapless = data.toUInt(5, false);
|
||||
const unsigned int gapless = data.toUInt(5, false);
|
||||
|
||||
d->trackGain = data.toShort(14, false);
|
||||
d->trackPeak = data.toShort(12, false);
|
||||
@@ -286,19 +337,19 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
||||
|
||||
bool trueGapless = (gapless >> 31) & 0x0001;
|
||||
if(trueGapless) {
|
||||
uint lastFrameSamples = (gapless >> 20) & 0x07FF;
|
||||
unsigned int lastFrameSamples = (gapless >> 20) & 0x07FF;
|
||||
d->sampleFrames = d->totalFrames * 1152 - lastFrameSamples;
|
||||
}
|
||||
else
|
||||
d->sampleFrames = d->totalFrames * 1152 - 576;
|
||||
}
|
||||
else {
|
||||
uint headerData = data.toUInt(0, false);
|
||||
const unsigned int headerData = data.toUInt(0, false);
|
||||
|
||||
d->bitrate = (headerData >> 23) & 0x01ff;
|
||||
d->version = (headerData >> 11) & 0x03ff;
|
||||
d->bitrate = (headerData >> 23) & 0x01ff;
|
||||
d->version = (headerData >> 11) & 0x03ff;
|
||||
d->sampleRate = 44100;
|
||||
d->channels = 2;
|
||||
d->channels = 2;
|
||||
|
||||
if(d->version >= 5)
|
||||
d->totalFrames = data.toUInt(4, false);
|
||||
@@ -308,9 +359,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
||||
d->sampleFrames = d->totalFrames * 1152 - 576;
|
||||
}
|
||||
|
||||
d->length = d->sampleRate > 0 ? (d->sampleFrames + (d->sampleRate / 2)) / d->sampleRate : 0;
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
|
||||
if(!d->bitrate)
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
if(d->bitrate == 0)
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace TagLib {
|
||||
|
||||
class File;
|
||||
|
||||
static const uint HeaderSize = 8*7;
|
||||
static const unsigned int HeaderSize = 8 * 7;
|
||||
|
||||
//! An implementation of audio property reading for MPC
|
||||
|
||||
@@ -66,19 +66,55 @@ namespace TagLib {
|
||||
*/
|
||||
virtual ~Properties();
|
||||
|
||||
// Reimplementations.
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \note This method is just an alias of lengthInSeconds().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
virtual int length() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in seconds. The length is rounded down to
|
||||
* the nearest whole second.
|
||||
*
|
||||
* \see lengthInMilliseconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInSeconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the length of the file in milliseconds.
|
||||
*
|
||||
* \see lengthInSeconds()
|
||||
*/
|
||||
// BIC: make virtual
|
||||
int lengthInMilliseconds() const;
|
||||
|
||||
/*!
|
||||
* Returns the average bit rate of the file in kb/s.
|
||||
*/
|
||||
virtual int bitrate() const;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the version of the bitstream (SV4-SV8)
|
||||
*/
|
||||
int mpcVersion() const;
|
||||
uint totalFrames() const;
|
||||
uint sampleFrames() const;
|
||||
|
||||
unsigned int totalFrames() const;
|
||||
unsigned int sampleFrames() const;
|
||||
|
||||
/*!
|
||||
* Returns the track gain as an integer value,
|
||||
@@ -110,8 +146,8 @@ namespace TagLib {
|
||||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void readSV7(const ByteVector &data);
|
||||
void readSV8(File *file);
|
||||
void readSV7(const ByteVector &data, long streamLength);
|
||||
void readSV8(File *file, long streamLength);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
||||
@@ -27,193 +27,239 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace TagLib {
|
||||
namespace ID3v1 {
|
||||
|
||||
static const int genresSize = 148;
|
||||
static const String genres[] = {
|
||||
"Blues",
|
||||
"Classic Rock",
|
||||
"Country",
|
||||
"Dance",
|
||||
"Disco",
|
||||
"Funk",
|
||||
"Grunge",
|
||||
"Hip-Hop",
|
||||
"Jazz",
|
||||
"Metal",
|
||||
"New Age",
|
||||
"Oldies",
|
||||
"Other",
|
||||
"Pop",
|
||||
"R&B",
|
||||
"Rap",
|
||||
"Reggae",
|
||||
"Rock",
|
||||
"Techno",
|
||||
"Industrial",
|
||||
"Alternative",
|
||||
"Ska",
|
||||
"Death Metal",
|
||||
"Pranks",
|
||||
"Soundtrack",
|
||||
"Euro-Techno",
|
||||
"Ambient",
|
||||
"Trip-Hop",
|
||||
"Vocal",
|
||||
"Jazz+Funk",
|
||||
"Fusion",
|
||||
"Trance",
|
||||
"Classical",
|
||||
"Instrumental",
|
||||
"Acid",
|
||||
"House",
|
||||
"Game",
|
||||
"Sound Clip",
|
||||
"Gospel",
|
||||
"Noise",
|
||||
"Alternative Rock",
|
||||
"Bass",
|
||||
"Soul",
|
||||
"Punk",
|
||||
"Space",
|
||||
"Meditative",
|
||||
"Instrumental Pop",
|
||||
"Instrumental Rock",
|
||||
"Ethnic",
|
||||
"Gothic",
|
||||
"Darkwave",
|
||||
"Techno-Industrial",
|
||||
"Electronic",
|
||||
"Pop-Folk",
|
||||
"Eurodance",
|
||||
"Dream",
|
||||
"Southern Rock",
|
||||
"Comedy",
|
||||
"Cult",
|
||||
"Gangsta",
|
||||
"Top 40",
|
||||
"Christian Rap",
|
||||
"Pop/Funk",
|
||||
"Jungle",
|
||||
"Native American",
|
||||
"Cabaret",
|
||||
"New Wave",
|
||||
"Psychedelic",
|
||||
"Rave",
|
||||
"Showtunes",
|
||||
"Trailer",
|
||||
"Lo-Fi",
|
||||
"Tribal",
|
||||
"Acid Punk",
|
||||
"Acid Jazz",
|
||||
"Polka",
|
||||
"Retro",
|
||||
"Musical",
|
||||
"Rock & Roll",
|
||||
"Hard Rock",
|
||||
"Folk",
|
||||
"Folk/Rock",
|
||||
"National Folk",
|
||||
"Swing",
|
||||
"Fusion",
|
||||
"Bebob",
|
||||
"Latin",
|
||||
"Revival",
|
||||
"Celtic",
|
||||
"Bluegrass",
|
||||
"Avantgarde",
|
||||
"Gothic Rock",
|
||||
"Progressive Rock",
|
||||
"Psychedelic Rock",
|
||||
"Symphonic Rock",
|
||||
"Slow Rock",
|
||||
"Big Band",
|
||||
"Chorus",
|
||||
"Easy Listening",
|
||||
"Acoustic",
|
||||
"Humour",
|
||||
"Speech",
|
||||
"Chanson",
|
||||
"Opera",
|
||||
"Chamber Music",
|
||||
"Sonata",
|
||||
"Symphony",
|
||||
"Booty Bass",
|
||||
"Primus",
|
||||
"Porn Groove",
|
||||
"Satire",
|
||||
"Slow Jam",
|
||||
"Club",
|
||||
"Tango",
|
||||
"Samba",
|
||||
"Folklore",
|
||||
"Ballad",
|
||||
"Power Ballad",
|
||||
"Rhythmic Soul",
|
||||
"Freestyle",
|
||||
"Duet",
|
||||
"Punk Rock",
|
||||
"Drum Solo",
|
||||
"A Cappella",
|
||||
"Euro-House",
|
||||
"Dance Hall",
|
||||
"Goa",
|
||||
"Drum & Bass",
|
||||
"Club-House",
|
||||
"Hardcore",
|
||||
"Terror",
|
||||
"Indie",
|
||||
"BritPop",
|
||||
"Negerpunk",
|
||||
"Polsk Punk",
|
||||
"Beat",
|
||||
"Christian Gangsta Rap",
|
||||
"Heavy Metal",
|
||||
"Black Metal",
|
||||
"Crossover",
|
||||
"Contemporary Christian",
|
||||
"Christian Rock",
|
||||
"Merengue",
|
||||
"Salsa",
|
||||
"Thrash Metal",
|
||||
"Anime",
|
||||
"Jpop",
|
||||
"Synthpop"
|
||||
};
|
||||
}
|
||||
namespace
|
||||
{
|
||||
const wchar_t *genres[] = {
|
||||
L"Blues",
|
||||
L"Classic Rock",
|
||||
L"Country",
|
||||
L"Dance",
|
||||
L"Disco",
|
||||
L"Funk",
|
||||
L"Grunge",
|
||||
L"Hip-Hop",
|
||||
L"Jazz",
|
||||
L"Metal",
|
||||
L"New Age",
|
||||
L"Oldies",
|
||||
L"Other",
|
||||
L"Pop",
|
||||
L"R&B",
|
||||
L"Rap",
|
||||
L"Reggae",
|
||||
L"Rock",
|
||||
L"Techno",
|
||||
L"Industrial",
|
||||
L"Alternative",
|
||||
L"Ska",
|
||||
L"Death Metal",
|
||||
L"Pranks",
|
||||
L"Soundtrack",
|
||||
L"Euro-Techno",
|
||||
L"Ambient",
|
||||
L"Trip-Hop",
|
||||
L"Vocal",
|
||||
L"Jazz+Funk",
|
||||
L"Fusion",
|
||||
L"Trance",
|
||||
L"Classical",
|
||||
L"Instrumental",
|
||||
L"Acid",
|
||||
L"House",
|
||||
L"Game",
|
||||
L"Sound Clip",
|
||||
L"Gospel",
|
||||
L"Noise",
|
||||
L"Alternative Rock",
|
||||
L"Bass",
|
||||
L"Soul",
|
||||
L"Punk",
|
||||
L"Space",
|
||||
L"Meditative",
|
||||
L"Instrumental Pop",
|
||||
L"Instrumental Rock",
|
||||
L"Ethnic",
|
||||
L"Gothic",
|
||||
L"Darkwave",
|
||||
L"Techno-Industrial",
|
||||
L"Electronic",
|
||||
L"Pop-Folk",
|
||||
L"Eurodance",
|
||||
L"Dream",
|
||||
L"Southern Rock",
|
||||
L"Comedy",
|
||||
L"Cult",
|
||||
L"Gangsta",
|
||||
L"Top 40",
|
||||
L"Christian Rap",
|
||||
L"Pop/Funk",
|
||||
L"Jungle",
|
||||
L"Native American",
|
||||
L"Cabaret",
|
||||
L"New Wave",
|
||||
L"Psychedelic",
|
||||
L"Rave",
|
||||
L"Showtunes",
|
||||
L"Trailer",
|
||||
L"Lo-Fi",
|
||||
L"Tribal",
|
||||
L"Acid Punk",
|
||||
L"Acid Jazz",
|
||||
L"Polka",
|
||||
L"Retro",
|
||||
L"Musical",
|
||||
L"Rock & Roll",
|
||||
L"Hard Rock",
|
||||
L"Folk",
|
||||
L"Folk/Rock",
|
||||
L"National Folk",
|
||||
L"Swing",
|
||||
L"Fusion",
|
||||
L"Bebob",
|
||||
L"Latin",
|
||||
L"Revival",
|
||||
L"Celtic",
|
||||
L"Bluegrass",
|
||||
L"Avantgarde",
|
||||
L"Gothic Rock",
|
||||
L"Progressive Rock",
|
||||
L"Psychedelic Rock",
|
||||
L"Symphonic Rock",
|
||||
L"Slow Rock",
|
||||
L"Big Band",
|
||||
L"Chorus",
|
||||
L"Easy Listening",
|
||||
L"Acoustic",
|
||||
L"Humour",
|
||||
L"Speech",
|
||||
L"Chanson",
|
||||
L"Opera",
|
||||
L"Chamber Music",
|
||||
L"Sonata",
|
||||
L"Symphony",
|
||||
L"Booty Bass",
|
||||
L"Primus",
|
||||
L"Porn Groove",
|
||||
L"Satire",
|
||||
L"Slow Jam",
|
||||
L"Club",
|
||||
L"Tango",
|
||||
L"Samba",
|
||||
L"Folklore",
|
||||
L"Ballad",
|
||||
L"Power Ballad",
|
||||
L"Rhythmic Soul",
|
||||
L"Freestyle",
|
||||
L"Duet",
|
||||
L"Punk Rock",
|
||||
L"Drum Solo",
|
||||
L"A Cappella",
|
||||
L"Euro-House",
|
||||
L"Dance Hall",
|
||||
L"Goa",
|
||||
L"Drum & Bass",
|
||||
L"Club-House",
|
||||
L"Hardcore",
|
||||
L"Terror",
|
||||
L"Indie",
|
||||
L"BritPop",
|
||||
L"Negerpunk",
|
||||
L"Polsk Punk",
|
||||
L"Beat",
|
||||
L"Christian Gangsta Rap",
|
||||
L"Heavy Metal",
|
||||
L"Black Metal",
|
||||
L"Crossover",
|
||||
L"Contemporary Christian",
|
||||
L"Christian Rock",
|
||||
L"Merengue",
|
||||
L"Salsa",
|
||||
L"Thrash Metal",
|
||||
L"Anime",
|
||||
L"Jpop",
|
||||
L"Synthpop",
|
||||
L"Abstract",
|
||||
L"Art Rock",
|
||||
L"Baroque",
|
||||
L"Bhangra",
|
||||
L"Big Beat",
|
||||
L"Breakbeat",
|
||||
L"Chillout",
|
||||
L"Downtempo",
|
||||
L"Dub",
|
||||
L"EBM",
|
||||
L"Eclectic",
|
||||
L"Electro",
|
||||
L"Electroclash",
|
||||
L"Emo",
|
||||
L"Experimental",
|
||||
L"Garage",
|
||||
L"Global",
|
||||
L"IDM",
|
||||
L"Illbient",
|
||||
L"Industro-Goth",
|
||||
L"Jam Band",
|
||||
L"Krautrock",
|
||||
L"Leftfield",
|
||||
L"Lounge",
|
||||
L"Math Rock",
|
||||
L"New Romantic",
|
||||
L"Nu-Breakz",
|
||||
L"Post-Punk",
|
||||
L"Post-Rock",
|
||||
L"Psytrance",
|
||||
L"Shoegaze",
|
||||
L"Space Rock",
|
||||
L"Trop Rock",
|
||||
L"World Music",
|
||||
L"Neoclassical",
|
||||
L"Audiobook",
|
||||
L"Audio Theatre",
|
||||
L"Neue Deutsche Welle",
|
||||
L"Podcast",
|
||||
L"Indie Rock",
|
||||
L"G-Funk",
|
||||
L"Dubstep",
|
||||
L"Garage Rock",
|
||||
L"Psybient"
|
||||
};
|
||||
const int genresSize = sizeof(genres) / sizeof(genres[0]);
|
||||
}
|
||||
|
||||
StringList ID3v1::genreList()
|
||||
{
|
||||
static StringList l;
|
||||
if(l.isEmpty()) {
|
||||
for(int i = 0; i < genresSize; i++)
|
||||
l.append(genres[i]);
|
||||
StringList l;
|
||||
for(int i = 0; i < genresSize; i++) {
|
||||
l.append(genres[i]);
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
ID3v1::GenreMap ID3v1::genreMap()
|
||||
{
|
||||
static GenreMap m;
|
||||
if(m.isEmpty()) {
|
||||
for(int i = 0; i < genresSize; i++)
|
||||
m.insert(genres[i], i);
|
||||
GenreMap m;
|
||||
for(int i = 0; i < genresSize; i++) {
|
||||
m.insert(genres[i], i);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
String ID3v1::genre(int i)
|
||||
{
|
||||
if(i >= 0 && i < genresSize)
|
||||
return genres[i] + String::null; // always make a copy
|
||||
return String::null;
|
||||
return String(genres[i]); // always make a copy
|
||||
else
|
||||
return String();
|
||||
}
|
||||
|
||||
int ID3v1::genreIndex(const String &name)
|
||||
{
|
||||
if(genreMap().contains(name))
|
||||
return genreMap()[name];
|
||||
for(int i = 0; i < genresSize; ++i) {
|
||||
if(name == genres[i])
|
||||
return i;
|
||||
}
|
||||
|
||||
return 255;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns the name of the genre at \a index in the ID3v1 genre list. If
|
||||
* \a index is out of range -- less than zero or greater than 146 -- a null
|
||||
* \a index is out of range -- less than zero or greater than 191 -- a null
|
||||
* string will be returned.
|
||||
*/
|
||||
String TAGLIB_EXPORT genre(int index);
|
||||
|
||||
@@ -32,10 +32,20 @@
|
||||
using namespace TagLib;
|
||||
using namespace ID3v1;
|
||||
|
||||
namespace
|
||||
{
|
||||
const ID3v1::StringHandler defaultStringHandler;
|
||||
const ID3v1::StringHandler *stringHandler = &defaultStringHandler;
|
||||
}
|
||||
|
||||
class ID3v1::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate() : file(0), tagOffset(-1), track(0), genre(255) {}
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
tagOffset(0),
|
||||
track(0),
|
||||
genre(255) {}
|
||||
|
||||
File *file;
|
||||
long tagOffset;
|
||||
@@ -45,15 +55,10 @@ public:
|
||||
String album;
|
||||
String year;
|
||||
String comment;
|
||||
uchar track;
|
||||
uchar genre;
|
||||
|
||||
static const StringHandler *stringHandler;
|
||||
unsigned char track;
|
||||
unsigned char genre;
|
||||
};
|
||||
|
||||
static const StringHandler defaultStringHandler;
|
||||
const ID3v1::StringHandler *ID3v1::Tag::TagPrivate::stringHandler = &defaultStringHandler;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// StringHandler implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -69,26 +74,26 @@ String ID3v1::StringHandler::parse(const ByteVector &data) const
|
||||
|
||||
ByteVector ID3v1::StringHandler::render(const String &s) const
|
||||
{
|
||||
if(!s.isLatin1())
|
||||
{
|
||||
if(s.isLatin1())
|
||||
return s.data(String::Latin1);
|
||||
else
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
return s.data(String::Latin1);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ID3v1::Tag::Tag() : TagLib::Tag()
|
||||
ID3v1::Tag::Tag() :
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
}
|
||||
|
||||
ID3v1::Tag::Tag(File *file, long tagOffset) : TagLib::Tag()
|
||||
ID3v1::Tag::Tag(File *file, long tagOffset) :
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
d->file = file;
|
||||
d->tagOffset = tagOffset;
|
||||
|
||||
@@ -105,11 +110,11 @@ ByteVector ID3v1::Tag::render() const
|
||||
ByteVector data;
|
||||
|
||||
data.append(fileIdentifier());
|
||||
data.append(TagPrivate::stringHandler->render(d->title).resize(30));
|
||||
data.append(TagPrivate::stringHandler->render(d->artist).resize(30));
|
||||
data.append(TagPrivate::stringHandler->render(d->album).resize(30));
|
||||
data.append(TagPrivate::stringHandler->render(d->year).resize(4));
|
||||
data.append(TagPrivate::stringHandler->render(d->comment).resize(28));
|
||||
data.append(stringHandler->render(d->title).resize(30));
|
||||
data.append(stringHandler->render(d->artist).resize(30));
|
||||
data.append(stringHandler->render(d->album).resize(30));
|
||||
data.append(stringHandler->render(d->year).resize(4));
|
||||
data.append(stringHandler->render(d->comment).resize(28));
|
||||
data.append(char(0));
|
||||
data.append(char(d->track));
|
||||
data.append(char(d->genre));
|
||||
@@ -147,12 +152,12 @@ String ID3v1::Tag::genre() const
|
||||
return ID3v1::genre(d->genre);
|
||||
}
|
||||
|
||||
TagLib::uint ID3v1::Tag::year() const
|
||||
unsigned int ID3v1::Tag::year() const
|
||||
{
|
||||
return d->year.toInt();
|
||||
}
|
||||
|
||||
TagLib::uint ID3v1::Tag::track() const
|
||||
unsigned int ID3v1::Tag::track() const
|
||||
{
|
||||
return d->track;
|
||||
}
|
||||
@@ -182,32 +187,32 @@ void ID3v1::Tag::setGenre(const String &s)
|
||||
d->genre = ID3v1::genreIndex(s);
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setYear(TagLib::uint i)
|
||||
void ID3v1::Tag::setYear(unsigned int i)
|
||||
{
|
||||
d->year = i > 0 ? String::number(i) : String::null;
|
||||
d->year = i > 0 ? String::number(i) : String();
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setTrack(TagLib::uint i)
|
||||
void ID3v1::Tag::setTrack(unsigned int i)
|
||||
{
|
||||
d->track = i < 256 ? i : 0;
|
||||
}
|
||||
|
||||
TagLib::uint ID3v1::Tag::genreNumber() const
|
||||
unsigned int ID3v1::Tag::genreNumber() const
|
||||
{
|
||||
return d->genre;
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setGenreNumber(TagLib::uint i)
|
||||
void ID3v1::Tag::setGenreNumber(unsigned int i)
|
||||
{
|
||||
d->genre = i < 256 ? i : 255;
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setStringHandler(const StringHandler *handler)
|
||||
{
|
||||
if (handler)
|
||||
TagPrivate::stringHandler = handler;
|
||||
if(handler)
|
||||
stringHandler = handler;
|
||||
else
|
||||
TagPrivate::stringHandler = &defaultStringHandler;
|
||||
stringHandler = &defaultStringHandler;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -219,7 +224,7 @@ void ID3v1::Tag::read()
|
||||
if(d->file && d->file->isValid()) {
|
||||
d->file->seek(d->tagOffset);
|
||||
// read the tag -- always 128 bytes
|
||||
ByteVector data = d->file->readBlock(128);
|
||||
const ByteVector data = d->file->readBlock(128);
|
||||
|
||||
// some initial sanity checking
|
||||
if(data.size() == 128 && data.startsWith("TAG"))
|
||||
@@ -233,16 +238,16 @@ void ID3v1::Tag::parse(const ByteVector &data)
|
||||
{
|
||||
int offset = 3;
|
||||
|
||||
d->title = TagPrivate::stringHandler->parse(data.mid(offset, 30));
|
||||
d->title = stringHandler->parse(data.mid(offset, 30));
|
||||
offset += 30;
|
||||
|
||||
d->artist = TagPrivate::stringHandler->parse(data.mid(offset, 30));
|
||||
d->artist = stringHandler->parse(data.mid(offset, 30));
|
||||
offset += 30;
|
||||
|
||||
d->album = TagPrivate::stringHandler->parse(data.mid(offset, 30));
|
||||
d->album = stringHandler->parse(data.mid(offset, 30));
|
||||
offset += 30;
|
||||
|
||||
d->year = TagPrivate::stringHandler->parse(data.mid(offset, 4));
|
||||
d->year = stringHandler->parse(data.mid(offset, 4));
|
||||
offset += 4;
|
||||
|
||||
// Check for ID3v1.1 -- Note that ID3v1 *does not* support "track zero" -- this
|
||||
@@ -253,13 +258,13 @@ void ID3v1::Tag::parse(const ByteVector &data)
|
||||
if(data[offset + 28] == 0 && data[offset + 29] != 0) {
|
||||
// ID3v1.1 detected
|
||||
|
||||
d->comment = TagPrivate::stringHandler->parse(data.mid(offset, 28));
|
||||
d->track = uchar(data[offset + 29]);
|
||||
d->comment = stringHandler->parse(data.mid(offset, 28));
|
||||
d->track = static_cast<unsigned char>(data[offset + 29]);
|
||||
}
|
||||
else
|
||||
d->comment = data.mid(offset, 30);
|
||||
|
||||
offset += 30;
|
||||
|
||||
d->genre = uchar(data[offset]);
|
||||
d->genre = static_cast<unsigned char>(data[offset]);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace TagLib {
|
||||
//! The main class in the ID3v1 implementation
|
||||
|
||||
/*!
|
||||
* This is an implementation of the ID3v1 format. ID3v1 is both the simplist
|
||||
* This is an implementation of the ID3v1 format. ID3v1 is both the simplest
|
||||
* and most common of tag formats but is rather limited. Because of its
|
||||
* pervasiveness and the way that applications have been written around the
|
||||
* fields that it provides, the generic TagLib::Tag API is a mirror of what is
|
||||
@@ -140,31 +140,31 @@ namespace TagLib {
|
||||
virtual String album() const;
|
||||
virtual String comment() const;
|
||||
virtual String genre() const;
|
||||
virtual TagLib::uint year() const;
|
||||
virtual TagLib::uint track() const;
|
||||
virtual unsigned int year() const;
|
||||
virtual unsigned int track() const;
|
||||
|
||||
virtual void setTitle(const String &s);
|
||||
virtual void setArtist(const String &s);
|
||||
virtual void setAlbum(const String &s);
|
||||
virtual void setComment(const String &s);
|
||||
virtual void setGenre(const String &s);
|
||||
virtual void setYear(TagLib::uint i);
|
||||
virtual void setTrack(TagLib::uint i);
|
||||
virtual void setYear(unsigned int i);
|
||||
virtual void setTrack(unsigned int i);
|
||||
|
||||
/*!
|
||||
* Returns the genre in number.
|
||||
*
|
||||
* /note Normally 255 indicates that this tag contains no genre.
|
||||
* \note Normally 255 indicates that this tag contains no genre.
|
||||
*/
|
||||
TagLib::uint genreNumber() const;
|
||||
unsigned int genreNumber() const;
|
||||
|
||||
/*!
|
||||
* Sets the genre in number to \a i.
|
||||
*
|
||||
* /note Valid value is from 0 up to 255. Normally 255 indicates that
|
||||
* \note Valid value is from 0 up to 255. Normally 255 indicates that
|
||||
* this tag contains no genre.
|
||||
*/
|
||||
void setGenreNumber(TagLib::uint i);
|
||||
void setGenreNumber(unsigned int i);
|
||||
|
||||
/*!
|
||||
* Sets the string handler that decides how the ID3v1 data will be
|
||||
|
||||
@@ -137,7 +137,7 @@ void AttachedPictureFrame::parseFields(const ByteVector &data)
|
||||
|
||||
d->mimeType = readStringField(data, String::Latin1, &pos);
|
||||
/* Now we need at least two more bytes available */
|
||||
if (uint(pos) + 1 >= data.size()) {
|
||||
if(static_cast<unsigned int>(pos) + 1 >= data.size()) {
|
||||
debug("Truncated picture frame.");
|
||||
return;
|
||||
}
|
||||
|
||||
306
taglib/mpeg/id3v2/frames/chapterframe.cpp
Normal file
306
taglib/mpeg/id3v2/frames/chapterframe.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2013 by Lukas Krejci
|
||||
email : krejclu6@fel.cvut.cz
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "chapterframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
class ChapterFrame::ChapterFramePrivate
|
||||
{
|
||||
public:
|
||||
ChapterFramePrivate() :
|
||||
tagHeader(0)
|
||||
{
|
||||
embeddedFrameList.setAutoDelete(true);
|
||||
}
|
||||
|
||||
const ID3v2::Header *tagHeader;
|
||||
ByteVector elementID;
|
||||
unsigned int startTime;
|
||||
unsigned int endTime;
|
||||
unsigned int startOffset;
|
||||
unsigned int endOffset;
|
||||
FrameListMap embeddedFrameListMap;
|
||||
FrameList embeddedFrameList;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data) :
|
||||
ID3v2::Frame(data)
|
||||
{
|
||||
d = new ChapterFramePrivate;
|
||||
d->tagHeader = tagHeader;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
ChapterFrame::ChapterFrame(const ByteVector &elementID,
|
||||
unsigned int startTime, unsigned int endTime,
|
||||
unsigned int startOffset, unsigned int endOffset,
|
||||
const FrameList &embeddedFrames) :
|
||||
ID3v2::Frame("CHAP")
|
||||
{
|
||||
d = new ChapterFramePrivate;
|
||||
|
||||
// setElementID has a workaround for a previously silly API where you had to
|
||||
// specifically include the null byte.
|
||||
|
||||
setElementID(elementID);
|
||||
|
||||
d->startTime = startTime;
|
||||
d->endTime = endTime;
|
||||
d->startOffset = startOffset;
|
||||
d->endOffset = endOffset;
|
||||
|
||||
for(FrameList::ConstIterator it = embeddedFrames.begin();
|
||||
it != embeddedFrames.end(); ++it)
|
||||
addEmbeddedFrame(*it);
|
||||
}
|
||||
|
||||
ChapterFrame::~ChapterFrame()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
ByteVector ChapterFrame::elementID() const
|
||||
{
|
||||
return d->elementID;
|
||||
}
|
||||
|
||||
unsigned int ChapterFrame::startTime() const
|
||||
{
|
||||
return d->startTime;
|
||||
}
|
||||
|
||||
unsigned int ChapterFrame::endTime() const
|
||||
{
|
||||
return d->endTime;
|
||||
}
|
||||
|
||||
unsigned int ChapterFrame::startOffset() const
|
||||
{
|
||||
return d->startOffset;
|
||||
}
|
||||
|
||||
unsigned int ChapterFrame::endOffset() const
|
||||
{
|
||||
return d->endOffset;
|
||||
}
|
||||
|
||||
void ChapterFrame::setElementID(const ByteVector &eID)
|
||||
{
|
||||
d->elementID = eID;
|
||||
|
||||
if(d->elementID.endsWith(char(0)))
|
||||
d->elementID = d->elementID.mid(0, d->elementID.size() - 1);
|
||||
}
|
||||
|
||||
void ChapterFrame::setStartTime(const unsigned int &sT)
|
||||
{
|
||||
d->startTime = sT;
|
||||
}
|
||||
|
||||
void ChapterFrame::setEndTime(const unsigned int &eT)
|
||||
{
|
||||
d->endTime = eT;
|
||||
}
|
||||
|
||||
void ChapterFrame::setStartOffset(const unsigned int &sO)
|
||||
{
|
||||
d->startOffset = sO;
|
||||
}
|
||||
|
||||
void ChapterFrame::setEndOffset(const unsigned int &eO)
|
||||
{
|
||||
d->endOffset = eO;
|
||||
}
|
||||
|
||||
const FrameListMap &ChapterFrame::embeddedFrameListMap() const
|
||||
{
|
||||
return d->embeddedFrameListMap;
|
||||
}
|
||||
|
||||
const FrameList &ChapterFrame::embeddedFrameList() const
|
||||
{
|
||||
return d->embeddedFrameList;
|
||||
}
|
||||
|
||||
const FrameList &ChapterFrame::embeddedFrameList(const ByteVector &frameID) const
|
||||
{
|
||||
return d->embeddedFrameListMap[frameID];
|
||||
}
|
||||
|
||||
void ChapterFrame::addEmbeddedFrame(Frame *frame)
|
||||
{
|
||||
d->embeddedFrameList.append(frame);
|
||||
d->embeddedFrameListMap[frame->frameID()].append(frame);
|
||||
}
|
||||
|
||||
void ChapterFrame::removeEmbeddedFrame(Frame *frame, bool del)
|
||||
{
|
||||
// remove the frame from the frame list
|
||||
FrameList::Iterator it = d->embeddedFrameList.find(frame);
|
||||
d->embeddedFrameList.erase(it);
|
||||
|
||||
// ...and from the frame list map
|
||||
it = d->embeddedFrameListMap[frame->frameID()].find(frame);
|
||||
d->embeddedFrameListMap[frame->frameID()].erase(it);
|
||||
|
||||
// ...and delete as desired
|
||||
if(del)
|
||||
delete frame;
|
||||
}
|
||||
|
||||
void ChapterFrame::removeEmbeddedFrames(const ByteVector &id)
|
||||
{
|
||||
FrameList l = d->embeddedFrameListMap[id];
|
||||
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
|
||||
removeEmbeddedFrame(*it, true);
|
||||
}
|
||||
|
||||
String ChapterFrame::toString() const
|
||||
{
|
||||
String s = String(d->elementID) +
|
||||
": start time: " + String::number(d->startTime) +
|
||||
", end time: " + String::number(d->endTime);
|
||||
|
||||
if(d->startOffset != 0xFFFFFFFF)
|
||||
s += ", start offset: " + String::number(d->startOffset);
|
||||
|
||||
if(d->endOffset != 0xFFFFFFFF)
|
||||
s += ", end offset: " + String::number(d->endOffset);
|
||||
|
||||
if(!d->embeddedFrameList.isEmpty()) {
|
||||
StringList frameIDs;
|
||||
for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
|
||||
it != d->embeddedFrameList.end(); ++it)
|
||||
frameIDs.append((*it)->frameID());
|
||||
s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
PropertyMap ChapterFrame::asProperties() const
|
||||
{
|
||||
PropertyMap map;
|
||||
|
||||
map.unsupportedData().append(frameID() + String("/") + d->elementID);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static
|
||||
{
|
||||
ID3v2::FrameList comments = tag->frameList("CHAP");
|
||||
|
||||
for(ID3v2::FrameList::ConstIterator it = comments.begin();
|
||||
it != comments.end();
|
||||
++it)
|
||||
{
|
||||
ChapterFrame *frame = dynamic_cast<ChapterFrame *>(*it);
|
||||
if(frame && frame->elementID() == eID)
|
||||
return frame;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChapterFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
unsigned int size = data.size();
|
||||
if(size < 18) {
|
||||
debug("A CHAP frame must contain at least 18 bytes (1 byte element ID "
|
||||
"terminated by null and 4x4 bytes for start and end time and offset).");
|
||||
return;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
unsigned int embPos = 0;
|
||||
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
|
||||
d->startTime = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
d->endTime = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
d->startOffset = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
d->endOffset = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
size -= pos;
|
||||
|
||||
// Embedded frames are optional
|
||||
|
||||
if(size < header()->size())
|
||||
return;
|
||||
|
||||
while(embPos < size - header()->size()) {
|
||||
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0));
|
||||
|
||||
if(!frame)
|
||||
return;
|
||||
|
||||
// Checks to make sure that frame parsed correctly.
|
||||
if(frame->size() <= 0) {
|
||||
delete frame;
|
||||
return;
|
||||
}
|
||||
|
||||
embPos += frame->size() + header()->size();
|
||||
addEmbeddedFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ChapterFrame::renderFields() const
|
||||
{
|
||||
ByteVector data;
|
||||
|
||||
data.append(d->elementID);
|
||||
data.append('\0');
|
||||
data.append(ByteVector::fromUInt(d->startTime, true));
|
||||
data.append(ByteVector::fromUInt(d->endTime, true));
|
||||
data.append(ByteVector::fromUInt(d->startOffset, true));
|
||||
data.append(ByteVector::fromUInt(d->endOffset, true));
|
||||
FrameList l = d->embeddedFrameList;
|
||||
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
|
||||
data.append((*it)->render());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h) :
|
||||
Frame(h)
|
||||
{
|
||||
d = new ChapterFramePrivate;
|
||||
d->tagHeader = tagHeader;
|
||||
parseFields(fieldData(data));
|
||||
}
|
||||
249
taglib/mpeg/id3v2/frames/chapterframe.h
Normal file
249
taglib/mpeg/id3v2/frames/chapterframe.h
Normal file
@@ -0,0 +1,249 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2013 by Lukas Krejci
|
||||
email : krejclu6@fel.cvut.cz
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_CHAPTERFRAME
|
||||
#define TAGLIB_CHAPTERFRAME
|
||||
|
||||
#include "id3v2tag.h"
|
||||
#include "id3v2frame.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
/*!
|
||||
* This is an implementation of ID3v2 chapter frames. The purpose of this
|
||||
* frame is to describe a single chapter within an audio file.
|
||||
*/
|
||||
|
||||
//! An implementation of ID3v2 chapter frames
|
||||
|
||||
class TAGLIB_EXPORT ChapterFrame : public ID3v2::Frame
|
||||
{
|
||||
friend class FrameFactory;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Creates a chapter frame based on \a data. \a tagHeader is required as
|
||||
* the internal frames are parsed based on the tag version.
|
||||
*/
|
||||
ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data);
|
||||
|
||||
/*!
|
||||
* Creates a chapter frame with the element ID \a elementID, start time
|
||||
* \a startTime, end time \a endTime, start offset \a startOffset,
|
||||
* end offset \a endOffset and optionally a list of embedded frames,
|
||||
* whose ownership will then be taken over by this Frame, in
|
||||
* \a embeededFrames;
|
||||
*
|
||||
* All times are in milliseconds.
|
||||
*/
|
||||
ChapterFrame(const ByteVector &elementID,
|
||||
unsigned int startTime, unsigned int endTime,
|
||||
unsigned int startOffset, unsigned int endOffset,
|
||||
const FrameList &embeddedFrames = FrameList());
|
||||
|
||||
/*!
|
||||
* Destroys the frame.
|
||||
*/
|
||||
virtual ~ChapterFrame();
|
||||
|
||||
/*!
|
||||
* Returns the element ID of the frame. Element ID
|
||||
* is a null terminated string, however it's not human-readable.
|
||||
*
|
||||
* \see setElementID()
|
||||
*/
|
||||
ByteVector elementID() const;
|
||||
|
||||
/*!
|
||||
* Returns time of chapter's start (in milliseconds).
|
||||
*
|
||||
* \see setStartTime()
|
||||
*/
|
||||
unsigned int startTime() const;
|
||||
|
||||
/*!
|
||||
* Returns time of chapter's end (in milliseconds).
|
||||
*
|
||||
* \see setEndTime()
|
||||
*/
|
||||
unsigned int endTime() const;
|
||||
|
||||
/*!
|
||||
* Returns zero based byte offset (count of bytes from the beginning
|
||||
* of the audio file) of chapter's start.
|
||||
*
|
||||
* \note If returned value is 0xFFFFFFFF, start time should be used instead.
|
||||
* \see setStartOffset()
|
||||
*/
|
||||
unsigned int startOffset() const;
|
||||
|
||||
/*!
|
||||
* Returns zero based byte offset (count of bytes from the beginning
|
||||
* of the audio file) of chapter's end.
|
||||
*
|
||||
* \note If returned value is 0xFFFFFFFF, end time should be used instead.
|
||||
* \see setEndOffset()
|
||||
*/
|
||||
unsigned int endOffset() const;
|
||||
|
||||
/*!
|
||||
* Sets the element ID of the frame to \a eID. If \a eID isn't
|
||||
* null terminated, a null char is appended automatically.
|
||||
*
|
||||
* \see elementID()
|
||||
*/
|
||||
void setElementID(const ByteVector &eID);
|
||||
|
||||
/*!
|
||||
* Sets time of chapter's start (in milliseconds) to \a sT.
|
||||
*
|
||||
* \see startTime()
|
||||
*/
|
||||
void setStartTime(const unsigned int &sT);
|
||||
|
||||
/*!
|
||||
* Sets time of chapter's end (in milliseconds) to \a eT.
|
||||
*
|
||||
* \see endTime()
|
||||
*/
|
||||
void setEndTime(const unsigned int &eT);
|
||||
|
||||
/*!
|
||||
* Sets zero based byte offset (count of bytes from the beginning
|
||||
* of the audio file) of chapter's start to \a sO.
|
||||
*
|
||||
* \see startOffset()
|
||||
*/
|
||||
void setStartOffset(const unsigned int &sO);
|
||||
|
||||
/*!
|
||||
* Sets zero based byte offset (count of bytes from the beginning
|
||||
* of the audio file) of chapter's end to \a eO.
|
||||
*
|
||||
* \see endOffset()
|
||||
*/
|
||||
void setEndOffset(const unsigned int &eO);
|
||||
|
||||
/*!
|
||||
* Returns a reference to the frame list map. This is an FrameListMap of
|
||||
* all of the frames embedded in the CHAP frame.
|
||||
*
|
||||
* This is the most convenient structure for accessing the CHAP frame's
|
||||
* embedded frames. Many frame types allow multiple instances of the same
|
||||
* frame type so this is a map of lists. In most cases however there will
|
||||
* only be a single frame of a certain type.
|
||||
*
|
||||
* \warning You should not modify this data structure directly, instead
|
||||
* use addEmbeddedFrame() and removeEmbeddedFrame().
|
||||
*
|
||||
* \see embeddedFrameList()
|
||||
*/
|
||||
const FrameListMap &embeddedFrameListMap() const;
|
||||
|
||||
/*!
|
||||
* Returns a reference to the embedded frame list. This is an FrameList
|
||||
* of all of the frames embedded in the CHAP frame in the order that they
|
||||
* were parsed.
|
||||
*
|
||||
* This can be useful if for example you want iterate over the CHAP frame's
|
||||
* embedded frames in the order that they occur in the CHAP frame.
|
||||
*
|
||||
* \warning You should not modify this data structure directly, instead
|
||||
* use addEmbeddedFrame() and removeEmbeddedFrame().
|
||||
*/
|
||||
const FrameList &embeddedFrameList() const;
|
||||
|
||||
/*!
|
||||
* Returns the embedded frame list for frames with the id \a frameID
|
||||
* or an empty list if there are no embedded frames of that type. This
|
||||
* is just a convenience and is equivalent to:
|
||||
*
|
||||
* \code
|
||||
* embeddedFrameListMap()[frameID];
|
||||
* \endcode
|
||||
*
|
||||
* \see embeddedFrameListMap()
|
||||
*/
|
||||
const FrameList &embeddedFrameList(const ByteVector &frameID) const;
|
||||
|
||||
/*!
|
||||
* Add an embedded frame to the CHAP frame. At this point the CHAP frame
|
||||
* takes ownership of the embedded frame and will handle freeing its memory.
|
||||
*
|
||||
* \note Using this method will invalidate any pointers on the list
|
||||
* returned by embeddedFrameList()
|
||||
*/
|
||||
void addEmbeddedFrame(Frame *frame);
|
||||
|
||||
/*!
|
||||
* Remove an embedded frame from the CHAP frame. If \a del is true the frame's
|
||||
* memory will be freed; if it is false, it must be deleted by the user.
|
||||
*
|
||||
* \note Using this method will invalidate any pointers on the list
|
||||
* returned by embeddedFrameList()
|
||||
*/
|
||||
void removeEmbeddedFrame(Frame *frame, bool del = true);
|
||||
|
||||
/*!
|
||||
* Remove all embedded frames of type \a id from the CHAP frame and free their
|
||||
* memory.
|
||||
*
|
||||
* \note Using this method will invalidate any pointers on the list
|
||||
* returned by embeddedFrameList()
|
||||
*/
|
||||
void removeEmbeddedFrames(const ByteVector &id);
|
||||
|
||||
virtual String toString() const;
|
||||
|
||||
PropertyMap asProperties() const;
|
||||
|
||||
/*!
|
||||
* CHAP frames each have a unique element ID. This searches for a CHAP
|
||||
* frame with the element ID \a eID and returns a pointer to it. This
|
||||
* can be used to link CTOC and CHAP frames together.
|
||||
*
|
||||
* \see elementID()
|
||||
*/
|
||||
static ChapterFrame *findByElementID(const Tag *tag, const ByteVector &eID);
|
||||
|
||||
protected:
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
private:
|
||||
ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h);
|
||||
ChapterFrame(const ChapterFrame &);
|
||||
ChapterFrame &operator=(const ChapterFrame &);
|
||||
|
||||
class ChapterFramePrivate;
|
||||
ChapterFramePrivate *d;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -116,8 +116,6 @@ PropertyMap CommentsFrame::asProperties() const
|
||||
PropertyMap map;
|
||||
if(key.isEmpty() || key == "COMMENT")
|
||||
map.insert("COMMENT", text());
|
||||
else if(key.isNull())
|
||||
map.unsupportedData().append(L"COMM/" + description());
|
||||
else
|
||||
map.insert("COMMENT:" + key, text());
|
||||
return map;
|
||||
@@ -164,7 +162,7 @@ void CommentsFrame::parseFields(const ByteVector &data)
|
||||
} else {
|
||||
d->description = String(l.front(), d->textEncoding);
|
||||
d->text = String(l.back(), d->textEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace TagLib {
|
||||
//! An implementation of ID3v2 comments
|
||||
|
||||
/*!
|
||||
* This implements the ID3v2 comment format. An ID3v2 comment concists of
|
||||
* This implements the ID3v2 comment format. An ID3v2 comment consists of
|
||||
* a language encoding, a description and a single text field.
|
||||
*/
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* Sets the description of the comment to \a s.
|
||||
*
|
||||
* \see decription()
|
||||
* \see description()
|
||||
*/
|
||||
void setDescription(const String &s);
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Comments each have a unique description. This searches for a comment
|
||||
* frame with the decription \a d and returns a pointer to it. If no
|
||||
* frame with the description \a d and returns a pointer to it. If no
|
||||
* frame is found that matches the given description null is returned.
|
||||
*
|
||||
* \see description()
|
||||
|
||||
144
taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp
Normal file
144
taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2014 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include "eventtimingcodesframe.h"
|
||||
#include <tbytevectorlist.h>
|
||||
#include <id3v2tag.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
class EventTimingCodesFrame::EventTimingCodesFramePrivate
|
||||
{
|
||||
public:
|
||||
EventTimingCodesFramePrivate() :
|
||||
timestampFormat(EventTimingCodesFrame::AbsoluteMilliseconds) {}
|
||||
EventTimingCodesFrame::TimestampFormat timestampFormat;
|
||||
EventTimingCodesFrame::SynchedEventList synchedEvents;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EventTimingCodesFrame::EventTimingCodesFrame() :
|
||||
Frame("ETCO")
|
||||
{
|
||||
d = new EventTimingCodesFramePrivate;
|
||||
}
|
||||
|
||||
EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data) :
|
||||
Frame(data)
|
||||
{
|
||||
d = new EventTimingCodesFramePrivate;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
EventTimingCodesFrame::~EventTimingCodesFrame()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
String EventTimingCodesFrame::toString() const
|
||||
{
|
||||
return String();
|
||||
}
|
||||
|
||||
EventTimingCodesFrame::TimestampFormat
|
||||
EventTimingCodesFrame::timestampFormat() const
|
||||
{
|
||||
return d->timestampFormat;
|
||||
}
|
||||
|
||||
EventTimingCodesFrame::SynchedEventList
|
||||
EventTimingCodesFrame::synchedEvents() const
|
||||
{
|
||||
return d->synchedEvents;
|
||||
}
|
||||
|
||||
void EventTimingCodesFrame::setTimestampFormat(
|
||||
EventTimingCodesFrame::TimestampFormat f)
|
||||
{
|
||||
d->timestampFormat = f;
|
||||
}
|
||||
|
||||
void EventTimingCodesFrame::setSynchedEvents(
|
||||
const EventTimingCodesFrame::SynchedEventList &e)
|
||||
{
|
||||
d->synchedEvents = e;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EventTimingCodesFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
const int end = data.size();
|
||||
if(end < 1) {
|
||||
debug("An event timing codes frame must contain at least 1 byte.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->timestampFormat = TimestampFormat(data[0]);
|
||||
|
||||
int pos = 1;
|
||||
d->synchedEvents.clear();
|
||||
while(pos + 4 < end) {
|
||||
EventType type = static_cast<EventType>(static_cast<unsigned char>(data[pos++]));
|
||||
unsigned int time = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
d->synchedEvents.append(SynchedEvent(time, type));
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector EventTimingCodesFrame::renderFields() const
|
||||
{
|
||||
ByteVector v;
|
||||
|
||||
v.append(char(d->timestampFormat));
|
||||
for(SynchedEventList::ConstIterator it = d->synchedEvents.begin();
|
||||
it != d->synchedEvents.end();
|
||||
++it) {
|
||||
const SynchedEvent &entry = *it;
|
||||
v.append(char(entry.type));
|
||||
v.append(ByteVector::fromUInt(entry.time));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h)
|
||||
: Frame(h)
|
||||
{
|
||||
d = new EventTimingCodesFramePrivate();
|
||||
parseFields(fieldData(data));
|
||||
}
|
||||
185
taglib/mpeg/id3v2/frames/eventtimingcodesframe.h
Normal file
185
taglib/mpeg/id3v2/frames/eventtimingcodesframe.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2014 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_EVENTTIMINGCODESFRAME_H
|
||||
#define TAGLIB_EVENTTIMINGCODESFRAME_H
|
||||
|
||||
#include "id3v2frame.h"
|
||||
#include "tlist.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
//! ID3v2 event timing codes frame
|
||||
/*!
|
||||
* An implementation of ID3v2 event timing codes.
|
||||
*/
|
||||
class TAGLIB_EXPORT EventTimingCodesFrame : public Frame
|
||||
{
|
||||
friend class FrameFactory;
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Specifies the timestamp format used.
|
||||
*/
|
||||
enum TimestampFormat {
|
||||
//! The timestamp is of unknown format.
|
||||
Unknown = 0x00,
|
||||
//! The timestamp represents the number of MPEG frames since
|
||||
//! the beginning of the audio stream.
|
||||
AbsoluteMpegFrames = 0x01,
|
||||
//! The timestamp represents the number of milliseconds since
|
||||
//! the beginning of the audio stream.
|
||||
AbsoluteMilliseconds = 0x02
|
||||
};
|
||||
|
||||
/*!
|
||||
* Event types defined in id3v2.4.0-frames.txt 4.5. Event timing codes.
|
||||
*/
|
||||
enum EventType {
|
||||
Padding = 0x00,
|
||||
EndOfInitialSilence = 0x01,
|
||||
IntroStart = 0x02,
|
||||
MainPartStart = 0x03,
|
||||
OutroStart = 0x04,
|
||||
OutroEnd = 0x05,
|
||||
VerseStart = 0x06,
|
||||
RefrainStart = 0x07,
|
||||
InterludeStart = 0x08,
|
||||
ThemeStart = 0x09,
|
||||
VariationStart = 0x0a,
|
||||
KeyChange = 0x0b,
|
||||
TimeChange = 0x0c,
|
||||
MomentaryUnwantedNoise = 0x0d,
|
||||
SustainedNoise = 0x0e,
|
||||
SustainedNoiseEnd = 0x0f,
|
||||
IntroEnd = 0x10,
|
||||
MainPartEnd = 0x11,
|
||||
VerseEnd = 0x12,
|
||||
RefrainEnd = 0x13,
|
||||
ThemeEnd = 0x14,
|
||||
Profanity = 0x15,
|
||||
ProfanityEnd = 0x16,
|
||||
NotPredefinedSynch0 = 0xe0,
|
||||
NotPredefinedSynch1 = 0xe1,
|
||||
NotPredefinedSynch2 = 0xe2,
|
||||
NotPredefinedSynch3 = 0xe3,
|
||||
NotPredefinedSynch4 = 0xe4,
|
||||
NotPredefinedSynch5 = 0xe5,
|
||||
NotPredefinedSynch6 = 0xe6,
|
||||
NotPredefinedSynch7 = 0xe7,
|
||||
NotPredefinedSynch8 = 0xe8,
|
||||
NotPredefinedSynch9 = 0xe9,
|
||||
NotPredefinedSynchA = 0xea,
|
||||
NotPredefinedSynchB = 0xeb,
|
||||
NotPredefinedSynchC = 0xec,
|
||||
NotPredefinedSynchD = 0xed,
|
||||
NotPredefinedSynchE = 0xee,
|
||||
NotPredefinedSynchF = 0xef,
|
||||
AudioEnd = 0xfd,
|
||||
AudioFileEnds = 0xfe
|
||||
};
|
||||
|
||||
/*!
|
||||
* Single entry of time stamp and event.
|
||||
*/
|
||||
struct SynchedEvent {
|
||||
SynchedEvent(unsigned int ms, EventType t) : time(ms), type(t) {}
|
||||
unsigned int time;
|
||||
EventType type;
|
||||
};
|
||||
|
||||
/*!
|
||||
* List of synchronized events.
|
||||
*/
|
||||
typedef TagLib::List<SynchedEvent> SynchedEventList;
|
||||
|
||||
/*!
|
||||
* Construct an empty event timing codes frame.
|
||||
*/
|
||||
explicit EventTimingCodesFrame();
|
||||
|
||||
/*!
|
||||
* Construct a event timing codes frame based on the data in \a data.
|
||||
*/
|
||||
explicit EventTimingCodesFrame(const ByteVector &data);
|
||||
|
||||
/*!
|
||||
* Destroys this EventTimingCodesFrame instance.
|
||||
*/
|
||||
virtual ~EventTimingCodesFrame();
|
||||
|
||||
/*!
|
||||
* Returns a null string.
|
||||
*/
|
||||
virtual String toString() const;
|
||||
|
||||
/*!
|
||||
* Returns the timestamp format.
|
||||
*/
|
||||
TimestampFormat timestampFormat() const;
|
||||
|
||||
/*!
|
||||
* Returns the events with the time stamps.
|
||||
*/
|
||||
SynchedEventList synchedEvents() const;
|
||||
|
||||
/*!
|
||||
* Set the timestamp format.
|
||||
*
|
||||
* \see timestampFormat()
|
||||
*/
|
||||
void setTimestampFormat(TimestampFormat f);
|
||||
|
||||
/*!
|
||||
* Sets the text with the time stamps.
|
||||
*
|
||||
* \see text()
|
||||
*/
|
||||
void setSynchedEvents(const SynchedEventList &e);
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* The constructor used by the FrameFactory.
|
||||
*/
|
||||
EventTimingCodesFrame(const ByteVector &data, Header *h);
|
||||
EventTimingCodesFrame(const EventTimingCodesFrame &);
|
||||
EventTimingCodesFrame &operator=(const EventTimingCodesFrame &);
|
||||
|
||||
class EventTimingCodesFramePrivate;
|
||||
EventTimingCodesFramePrivate *d;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,7 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
|
||||
copyright : (C) 2006 by Aaron VonderHaar
|
||||
email : avh4@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
@@ -26,6 +27,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstringlist.h>
|
||||
|
||||
#include "generalencapsulatedobjectframe.h"
|
||||
|
||||
@@ -151,15 +153,21 @@ void GeneralEncapsulatedObjectFrame::parseFields(const ByteVector &data)
|
||||
|
||||
ByteVector GeneralEncapsulatedObjectFrame::renderFields() const
|
||||
{
|
||||
StringList sl;
|
||||
sl.append(d->fileName);
|
||||
sl.append(d->description);
|
||||
|
||||
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
|
||||
|
||||
ByteVector data;
|
||||
|
||||
data.append(char(d->textEncoding));
|
||||
data.append(char(encoding));
|
||||
data.append(d->mimeType.data(String::Latin1));
|
||||
data.append(textDelimiter(String::Latin1));
|
||||
data.append(d->fileName.data(d->textEncoding));
|
||||
data.append(textDelimiter(d->textEncoding));
|
||||
data.append(d->description.data(d->textEncoding));
|
||||
data.append(textDelimiter(d->textEncoding));
|
||||
data.append(d->fileName.data(encoding));
|
||||
data.append(textDelimiter(encoding));
|
||||
data.append(d->description.data(encoding));
|
||||
data.append(textDelimiter(encoding));
|
||||
data.append(d->data);
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
|
||||
copyright : (C) 2006 by Aaron VonderHaar
|
||||
email : avh4@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstringlist.h>
|
||||
#include <id3v2tag.h>
|
||||
|
||||
#include "ownershipframe.h"
|
||||
#include <id3v2tag.h>
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
@@ -113,24 +114,24 @@ void OwnershipFrame::setTextEncoding(String::Type encoding)
|
||||
void OwnershipFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
|
||||
// Get the text encoding
|
||||
d->textEncoding = String::Type(data[0]);
|
||||
pos += 1;
|
||||
|
||||
|
||||
// Read the price paid this is a null terminate string
|
||||
d->pricePaid = readStringField(data, String::Latin1, &pos);
|
||||
|
||||
|
||||
// If we don't have at least 8 bytes left then don't parse the rest of the
|
||||
// data
|
||||
if(data.size() - pos < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Read the date purchased YYYYMMDD
|
||||
d->datePurchased = String(data.mid(pos, 8));
|
||||
pos += 8;
|
||||
|
||||
|
||||
// Read the seller
|
||||
if(d->textEncoding == String::Latin1)
|
||||
d->seller = Tag::latin1StringHandler()->parse(data.mid(pos));
|
||||
@@ -140,14 +141,19 @@ void OwnershipFrame::parseFields(const ByteVector &data)
|
||||
|
||||
ByteVector OwnershipFrame::renderFields() const
|
||||
{
|
||||
StringList sl;
|
||||
sl.append(d->seller);
|
||||
|
||||
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
|
||||
|
||||
ByteVector v;
|
||||
|
||||
v.append(char(d->textEncoding));
|
||||
|
||||
v.append(char(encoding));
|
||||
v.append(d->pricePaid.data(String::Latin1));
|
||||
v.append(textDelimiter(String::Latin1));
|
||||
v.append(d->datePurchased.data(String::Latin1));
|
||||
v.append(d->seller.data(d->textEncoding));
|
||||
|
||||
v.append(d->seller.data(encoding));
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,21 +94,21 @@ namespace TagLib {
|
||||
* \see pricePaid()
|
||||
*/
|
||||
void setPricePaid(const String &pricePaid);
|
||||
|
||||
|
||||
/*!
|
||||
* Returns the seller.
|
||||
*
|
||||
* \see setSeller()
|
||||
*/
|
||||
String seller() const;
|
||||
|
||||
|
||||
/*!
|
||||
* Set the seller.
|
||||
*
|
||||
* \see seller()
|
||||
*/
|
||||
void setSeller(const String &seller);
|
||||
|
||||
|
||||
/*!
|
||||
* Returns the text encoding that will be used in rendering this frame.
|
||||
* This defaults to the type that was either specified in the constructor
|
||||
@@ -118,7 +118,7 @@ namespace TagLib {
|
||||
* \see render()
|
||||
*/
|
||||
String::Type textEncoding() const;
|
||||
|
||||
|
||||
/*!
|
||||
* Sets the text encoding to be used when rendering this frame to
|
||||
* \a encoding.
|
||||
|
||||
79
taglib/mpeg/id3v2/frames/podcastframe.cpp
Normal file
79
taglib/mpeg/id3v2/frames/podcastframe.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include "podcastframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
class PodcastFrame::PodcastFramePrivate
|
||||
{
|
||||
public:
|
||||
ByteVector fieldData;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PodcastFrame::PodcastFrame() : Frame("PCST")
|
||||
{
|
||||
d = new PodcastFramePrivate;
|
||||
d->fieldData = ByteVector(4, '\0');
|
||||
}
|
||||
|
||||
PodcastFrame::~PodcastFrame()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
String PodcastFrame::toString() const
|
||||
{
|
||||
return String();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PodcastFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
d->fieldData = data;
|
||||
}
|
||||
|
||||
ByteVector PodcastFrame::renderFields() const
|
||||
{
|
||||
return d->fieldData;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : Frame(h)
|
||||
{
|
||||
d = new PodcastFramePrivate;
|
||||
parseFields(fieldData(data));
|
||||
}
|
||||
80
taglib/mpeg/id3v2/frames/podcastframe.h
Normal file
80
taglib/mpeg/id3v2/frames/podcastframe.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_PODCASTFRAME_H
|
||||
#define TAGLIB_PODCASTFRAME_H
|
||||
|
||||
#include "id3v2frame.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
//! ID3v2 podcast frame
|
||||
/*!
|
||||
* An implementation of ID3v2 podcast flag, a frame with four zero bytes.
|
||||
*/
|
||||
class TAGLIB_EXPORT PodcastFrame : public Frame
|
||||
{
|
||||
friend class FrameFactory;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Construct a podcast frame.
|
||||
*/
|
||||
PodcastFrame();
|
||||
|
||||
/*!
|
||||
* Destroys this PodcastFrame instance.
|
||||
*/
|
||||
virtual ~PodcastFrame();
|
||||
|
||||
/*!
|
||||
* Returns a null string.
|
||||
*/
|
||||
virtual String toString() const;
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* The constructor used by the FrameFactory.
|
||||
*/
|
||||
PodcastFrame(const ByteVector &data, Header *h);
|
||||
PodcastFrame(const PodcastFrame &);
|
||||
PodcastFrame &operator=(const PodcastFrame &);
|
||||
|
||||
class PodcastFramePrivate;
|
||||
PodcastFramePrivate *d;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
PopularimeterFramePrivate() : rating(0), counter(0) {}
|
||||
String email;
|
||||
int rating;
|
||||
TagLib::uint counter;
|
||||
unsigned int counter;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -84,12 +84,12 @@ void PopularimeterFrame::setRating(int s)
|
||||
d->rating = s;
|
||||
}
|
||||
|
||||
TagLib::uint PopularimeterFrame::counter() const
|
||||
unsigned int PopularimeterFrame::counter() const
|
||||
{
|
||||
return d->counter;
|
||||
}
|
||||
|
||||
void PopularimeterFrame::setCounter(TagLib::uint s)
|
||||
void PopularimeterFrame::setCounter(unsigned int s)
|
||||
{
|
||||
d->counter = s;
|
||||
}
|
||||
@@ -109,7 +109,7 @@ void PopularimeterFrame::parseFields(const ByteVector &data)
|
||||
if(pos < size) {
|
||||
d->rating = (unsigned char)(data[pos++]);
|
||||
if(pos < size) {
|
||||
d->counter = data.toUInt(static_cast<uint>(pos));
|
||||
d->counter = data.toUInt(static_cast<unsigned int>(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace TagLib {
|
||||
//! An implementation of ID3v2 "popularimeter"
|
||||
|
||||
/*!
|
||||
* This implements the ID3v2 popularimeter (POPM frame). It concists of
|
||||
* This implements the ID3v2 popularimeter (POPM frame). It consists of
|
||||
* an email, a rating and an optional counter.
|
||||
*/
|
||||
|
||||
@@ -100,14 +100,14 @@ namespace TagLib {
|
||||
*
|
||||
* \see setCounter()
|
||||
*/
|
||||
uint counter() const;
|
||||
unsigned int counter() const;
|
||||
|
||||
/*!
|
||||
* Set the counter.
|
||||
*
|
||||
* \see counter()
|
||||
*/
|
||||
void setCounter(uint counter);
|
||||
void setCounter(unsigned int counter);
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
@@ -31,11 +31,6 @@
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
static inline int bitsToBytes(int i)
|
||||
{
|
||||
return i % 8 == 0 ? i / 8 : (i - i % 8) / 8 + 1;
|
||||
}
|
||||
|
||||
struct ChannelData
|
||||
{
|
||||
ChannelData() : channelType(RelativeVolumeFrame::Other), volumeAdjustment(0) {}
|
||||
@@ -185,19 +180,18 @@ void RelativeVolumeFrame::parseFields(const ByteVector &data)
|
||||
|
||||
while(pos <= (int)data.size() - 4) {
|
||||
|
||||
|
||||
ChannelType type = ChannelType(data[pos]);
|
||||
pos += 1;
|
||||
|
||||
ChannelData &channel = d->channels[type];
|
||||
|
||||
channel.volumeAdjustment = data.toShort(static_cast<uint>(pos));
|
||||
channel.volumeAdjustment = data.toShort(static_cast<unsigned int>(pos));
|
||||
pos += 2;
|
||||
|
||||
channel.peakVolume.bitsRepresentingPeak = data[pos];
|
||||
pos += 1;
|
||||
|
||||
int bytes = bitsToBytes(channel.peakVolume.bitsRepresentingPeak);
|
||||
const int bytes = (channel.peakVolume.bitsRepresentingPeak + 7) / 8;
|
||||
channel.peakVolume.peakVolume = data.mid(pos, bytes);
|
||||
pos += bytes;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace TagLib {
|
||||
|
||||
/*
|
||||
* There was a terrible API goof here, and while this can't be changed to
|
||||
* the way it appears below for binary compaibility reasons, let's at
|
||||
* the way it appears below for binary compatibility reasons, let's at
|
||||
* least pretend that it looks clean.
|
||||
*/
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns the relative volume adjustment "index". As indicated by the
|
||||
* ID3v2 standard this is a 16-bit signed integer that reflects the
|
||||
* decibils of adjustment when divided by 512.
|
||||
* decibels of adjustment when divided by 512.
|
||||
*
|
||||
* This defaults to returning the value for the master volume channel if
|
||||
* available and returns 0 if the specified channel does not exist.
|
||||
@@ -161,7 +161,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Set the volume adjustment to \a index. As indicated by the ID3v2
|
||||
* standard this is a 16-bit signed integer that reflects the decibils of
|
||||
* standard this is a 16-bit signed integer that reflects the decibels of
|
||||
* adjustment when divided by 512.
|
||||
*
|
||||
* By default this sets the value for the master volume.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user