ab09c103f595ae9f2b9fee6e7f033e72576cb396
[picodrive.git] / cue.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "cue.h"
5
6 #include "../pico_int.h"
7 // #define elprintf(w,f,...) printf(f "\n",##__VA_ARGS__);
8
9 #ifdef _MSC_VER
10 #define snprintf _snprintf
11 #endif
12 #ifdef __EPOC32__
13 #define snprintf(b,s,...) sprintf(b,##__VA_ARGS__)
14 #endif
15
16 static char *mystrip(char *str)
17 {
18         int i, len;
19
20         len = strlen(str);
21         for (i = 0; i < len; i++)
22                 if (str[i] != ' ') break;
23         if (i > 0) memmove(str, str + i, len - i + 1);
24
25         len = strlen(str);
26         for (i = len - 1; i >= 0; i--)
27                 if (str[i] != ' ' && str[i] != '\r' && str[i] != '\n') break;
28         str[i+1] = 0;
29
30         return str;
31 }
32
33 static int get_token(const char *buff, char *dest, int len)
34 {
35         const char *p = buff;
36         char sep = ' ';
37         int d = 0, skip = 0;
38
39         while (*p && *p == ' ') {
40                 skip++;
41                 p++;
42         }
43
44         if (*p == '\"') {
45                 sep = '\"';
46                 p++;
47         }
48         while (*p && *p != sep && d < len-1)
49                 dest[d++] = *p++;
50         dest[d] = 0;
51
52         if (sep == '\"' && *p != sep)
53                 elprintf(EL_STATUS, "cue: bad token: \"%s\"", buff);
54
55         return d + skip;
56 }
57
58 static char *get_ext(char *fname)
59 {
60         int len = strlen(fname);
61         return (len >= 3) ? (fname + len - 3) : fname;
62 }
63
64
65 #define BEGINS(buff,str) (strncmp(buff,str,sizeof(str)-1) == 0)
66
67 /* note: tracks[0] is not used */
68 cue_data_t *cue_parse(const char *fname)
69 {
70         char buff[256], current_file[256], buff2[32], *current_filep;
71         FILE *f, *tmpf;
72         int ret, count = 0, count_alloc = 2, pending_pregap = 0;
73         cue_data_t *data;
74         void *tmp;
75
76         f = fopen(fname, "r");
77         if (f == NULL) return NULL;
78
79         snprintf(current_file, sizeof(current_file), "%s", fname);
80         for (current_filep = current_file + strlen(current_file); current_filep > current_file; current_filep--)
81                 if (current_filep[-1] == '/' || current_filep[-1] == '\\') break;
82
83         data = calloc(1, sizeof(*data) + count_alloc * sizeof(cue_track));
84         if (data == NULL) {
85                 fclose(f);
86                 return NULL;
87         }
88
89         while (!feof(f))
90         {
91                 tmp = fgets(buff, sizeof(buff), f);
92                 if (tmp == NULL) break;
93
94                 mystrip(buff);
95                 if (buff[0] == 0) continue;
96                 if      (BEGINS(buff, "TITLE ") || BEGINS(buff, "PERFORMER ") || BEGINS(buff, "SONGWRITER "))
97                         continue;       /* who would put those here? Ignore! */
98                 else if (BEGINS(buff, "FILE "))
99                 {
100                         get_token(buff+5, current_filep, sizeof(current_file) - (current_filep - current_file));
101                 }
102                 else if (BEGINS(buff, "TRACK "))
103                 {
104                         count++;
105                         if (count >= count_alloc) {
106                                 count_alloc *= 2;
107                                 tmp = realloc(data, sizeof(*data) + count_alloc * sizeof(cue_track));
108                                 if (tmp == NULL) { count--; break; }
109                                 data = tmp;
110                         }
111                         memset(&data->tracks[count], 0, sizeof(data->tracks[0]));
112                         if (count == 1 || strcmp(data->tracks[1].fname, current_file) != 0)
113                         {
114                                 data->tracks[count].fname = strdup(current_file);
115                                 if (data->tracks[count].fname == NULL) break;
116
117                                 tmpf = fopen(current_file, "rb");
118                                 if (tmpf == NULL) {
119                                         elprintf(EL_STATUS, "cue: bad/missing file: \"%s\"", current_file);
120                                         count--; break;
121                                 }
122                                 fclose(tmpf);
123                         }
124                         data->tracks[count].pregap = pending_pregap;
125                         pending_pregap = 0;
126                         // track number
127                         ret = get_token(buff+6, buff2, sizeof(buff2));
128                         if (count != atoi(buff2))
129                                 elprintf(EL_STATUS, "cue: track index mismatch: track %i is track %i in cue",
130                                         count, atoi(buff2));
131                         // check type
132                         get_token(buff+6+ret, buff2, sizeof(buff2));
133                         if      (strcmp(buff2, "MODE1/2352") == 0)
134                                 data->tracks[count].type = CT_BIN;
135                         else if (strcmp(buff2, "MODE1/2048") == 0)
136                                 data->tracks[count].type = CT_ISO;
137                         else if (strcmp(buff2, "AUDIO") == 0)
138                         {
139                                 if (data->tracks[count].fname != NULL)
140                                 {
141                                         // rely on extension, not type in cue..
142                                         char *ext = get_ext(data->tracks[count].fname);
143                                         if      (strcasecmp(ext, "mp3") == 0)
144                                                 data->tracks[count].type = CT_MP3;
145                                         else if (strcasecmp(ext, "wav") == 0)
146                                                 data->tracks[count].type = CT_WAV;
147                                         else {
148                                                 elprintf(EL_STATUS, "unhandled audio format: \"%s\"",
149                                                         data->tracks[count].fname);
150                                         }
151                                 }
152                                 else
153                                 {
154                                         // propagate previous
155                                         data->tracks[count].type = data->tracks[count-1].type;
156                                 }
157                         }
158                         else {
159                                 elprintf(EL_STATUS, "unhandled track type: \"%s\"", buff2);
160                         }
161                 }
162                 else if (BEGINS(buff, "INDEX "))
163                 {
164                         int m, s, f;
165                         // type
166                         ret = get_token(buff+6, buff2, sizeof(buff2));
167                         if (atoi(buff2) == 0) continue;
168                         if (atoi(buff2) != 1) {
169                                 elprintf(EL_STATUS, "cue: don't know how to handle: \"%s\"", buff);
170                                 count--; break;
171                         }
172                         // offset in file
173                         get_token(buff+6+ret, buff2, sizeof(buff2));
174                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
175                         if (ret != 3) {
176                                 elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
177                                 count--; break;
178                         }
179                         data->tracks[count].sector_offset = m*60*75 + s*75 + f;
180                         // some strange .cues may need this
181                         if (data->tracks[count].fname != NULL && strcmp(data->tracks[count].fname, current_file) != 0)
182                         {
183                                 free(data->tracks[count].fname);
184                                 data->tracks[count].fname = strdup(current_file);
185                         }
186                         if (data->tracks[count].fname == NULL && strcmp(data->tracks[1].fname, current_file) != 0)
187                         {
188                                 data->tracks[count].fname = strdup(current_file);
189                         }
190                 }
191                 else if (BEGINS(buff, "PREGAP ") || BEGINS(buff, "POSTGAP "))
192                 {
193                         int m, s, f;
194                         get_token(buff+7, buff2, sizeof(buff2));
195                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
196                         if (ret != 3) {
197                                 elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
198                                 continue;
199                         }
200                         // pregap overrides previous postgap?
201                         // by looking at some .cues produced by some programs I've decided that..
202                         if (BEGINS(buff, "PREGAP "))
203                                 data->tracks[count].pregap = m*60*75 + s*75 + f;
204                         else
205                                 pending_pregap = m*60*75 + s*75 + f;
206                 }
207                 else if (BEGINS(buff, "REM LENGTH ")) // custom "extension"
208                 {
209                         int m, s, f;
210                         get_token(buff+11, buff2, sizeof(buff2));
211                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
212                         if (ret != 3) continue;
213                         data->tracks[count].sector_xlength = m*60*75 + s*75 + f;
214                 }
215                 else if (BEGINS(buff, "REM"))
216                         continue;
217                 else
218                 {
219                         elprintf(EL_STATUS, "cue: unhandled line: \"%s\"", buff);
220                 }
221         }
222
223         if (count < 1 || data->tracks[1].fname == NULL) {
224                 // failed..
225                 for (; count > 0; count--)
226                         if (data->tracks[count].fname != NULL)
227                                 free(data->tracks[count].fname);
228                 free(data);
229                 return NULL;
230         }
231
232         data->track_count = count;
233         return data;
234 }
235
236
237 void cue_destroy(cue_data_t *data)
238 {
239         int c;
240
241         if (data == NULL) return;
242
243         for (c = data->track_count; c > 0; c--)
244                 if (data->tracks[c].fname != NULL)
245                         free(data->tracks[c].fname);
246         free(data);
247 }
248
249
250 #if 0
251 int main(int argc, char *argv[])
252 {
253         cue_data_t *data = cue_parse(argv[1]);
254         int c;
255
256         if (data == NULL) return 1;
257
258         for (c = 1; c <= data->track_count; c++)
259                 printf("%2i: %i %9i %02i:%02i:%02i %9i %s\n", c, data->tracks[c].type, data->tracks[c].sector_offset,
260                         data->tracks[c].sector_offset / (75*60), data->tracks[c].sector_offset / 75 % 60,
261                         data->tracks[c].sector_offset % 75, data->tracks[c].pregap, data->tracks[c].fname);
262
263         cue_destroy(data);
264
265         return 0;
266 }
267 #endif
268