1 #include <cstdio>
2 #include <cstdlib>
3 #include <cstring>
4 #include <string>
5 #include <new>
6 #include "lyrics3.h"
7
8 /*
9
10 copyright (c) 2006 squell <squell@alumina.nl>
11
12 use, modification, copying and distribution of this software is permitted
13 under the conditions described in the file 'COPYING'.
14
15 */
16
17 #if defined(_WIN32)
18 # include <io.h>
19 # define ftrunc(f) chsize(fileno(f), ftell(f))
20 #else
21 # include <unistd.h>
22 # define ftrunc(f) ftruncate(fileno(f), ftell(f))
23 #endif
24
25 using namespace std;
26
27 namespace lyrics3 {
28
29 /* ====================================================== */
30
31 // increases position in a string to where the next field would be
32
find_next(const info & s,info::size_type pos)33 info::size_type find_next(const info& s, info::size_type pos)
34 {
35 if(pos+8 < s.size()) {
36 const string& tmp = s.substr(pos+3, 5);
37 char* p;
38 unsigned long n = strtoul(tmp.c_str(), &p, 10);
39 if(*p != '\0') return false;
40 return (pos + 8 + n);
41 }
42 return false;
43 }
44
45 // searches a tag. given that most lyrics3 tags will consist of just a
46 // few frames, a raw linear search is efficient enough
47
find(const info & s,const string & sig)48 string find(const info& s, const string& sig)
49 {
50 string::size_type i, next;
51
52 for(i = 0; next=find_next(s, i); i = next)
53 if(s.substr(i, 3) == sig)
54 return i+=8, s.substr(i, next - i);
55
56 return string();
57 }
58
59 // validates a string as valid lyrics3v2 content
60
cast(const string & s)61 info cast(const string& s)
62 {
63 string::size_type i, next;
64
65 for(i = 0; next=find_next(s, i); i = next)
66 if(!isupper(s[i]) || !isupper(s[i+1]) || !isupper(s[i+2])) break;
67
68 return i == s.size()? s : string();
69 }
70
71 /* ====================================================== */
72
num(unsigned long n,int width)73 inline string num(unsigned long n, int width)
74 {
75 char buf[12];
76 if(sprintf(buf, "%0*lu", width, n & 0xFFFFFFFFul) == width)
77 return buf;
78 return string();
79 }
80
field(const string & id,const string & content)81 info field(const string& id, const string& content)
82 {
83 if(id.length() != 3 || !isupper(id[0]) || !isupper(id[1]) || !isupper(id[2]))
84 return info();
85 const string& size = num(content.size(),5);
86 return size.empty()? size : id + size + content;
87 }
88
89 /* ====================================================== */
90
91 // seeks the start of a lyrics tag
92
seek_start(FILE * f,char id3[128])93 size_t seek_start(FILE* f, char id3[128])
94 {
95 char buf[15]; // "xxxxxxLYRICS200"
96
97 if( fseek(f, -128, SEEK_END) == 0 &&
98 fread(id3, 1, 128, f) == 128 &&
99 memcmp(id3, "TAG", 3) == 0) {
100 if( fseek(f, -15-128, SEEK_END) != 0 ) return 0;
101 } else {
102 *id3 = '\0';
103 clearerr(f);
104 if( fseek(f, -15, SEEK_END) != 0 ) return 0;
105 }
106
107 buf[sizeof buf-1] = '\0'; // duct tape
108 fread(buf, 1, 15, f); // read end-tag
109
110 if(memcmp(buf+6, "LYRICS200", 9) == 0) {
111 char* p;
112 long size = strtoul(buf, &p, 10);
113 if(p == buf+6) {
114 if(fseek(f, -15 - size, SEEK_CUR) != 0) return 0;
115 fread(buf, 1, 11, f);
116 if(memcmp(buf, "LYRICSBEGIN", 11) == 0)
117 return size;
118 }
119 }
120
121 return 0;
122 }
123
124 // read a lyrics3v2 tag to a std::string
125
read(const char * fn,void * id3)126 info read(const char* fn, void* id3)
127 {
128 FILE* f = fopen(fn, "rb");
129 if( !f ) return string();
130
131 char tmp[128];
132 id3 || (id3=tmp);
133
134 if(size_t size = seek_start(f,(char*)id3)) {
135 struct scope {
136 char* data; ~scope() { delete[] data; }
137 } tmp = { new (nothrow) char[size -= 11] };
138 if(tmp.data && fread(tmp.data, 1, size, f) == size) {
139 fclose(f);
140 string data(tmp.data, size);
141 return cast(data);
142 }
143 }
144 fclose(f);
145 return string();
146 }
147
148 /* ====================================================== */
149
150 // write a lyrics3v2 string to a file
151
write(const char * fn,const info & tag,const void * newid3)152 int write(const char* fn, const info& tag, const void* newid3)
153 {
154 FILE* f = fopen(fn, "rb+");
155 if( !f ) return 1;
156
157 char id3[128];
158
159 int result;
160
161 if( seek_start(f,id3) ) {
162 result = fseek(f, -11, SEEK_CUR) == 0;
163 } else {
164 clearerr(f);
165 result = fseek(f, id3[0]?-128:0, SEEK_END) == 0;
166 }
167
168 newid3 || id3[0] && (newid3 = id3);
169
170 if(result++) {
171 const string& s = "LYRICSBEGIN"+tag+num(tag.size()+11,6)+"LYRICS200";
172 if(s.size() == tag.size()+11+6+9) {
173 if(tag.size() > 0)
174 fwrite(s.data(), 1, s.size(), f);
175 if(newid3)
176 fwrite(newid3, 1, 128, f);
177 result = -(ferror(f) || ftrunc(f) != 0);
178 }
179 }
180
181 return fclose(f) | result;
182 }
183
184 }
185
186