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