1 /*
2 * AudioCD/VideoCD BinCue
3 *
4 * This file is part of MPlayer.
5 *
6 * MPlayer is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * MPlayer is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29
30 #include "config.h"
31 #include "mp_msg.h"
32 #include "help_mp.h"
33
34 #include "stream.h"
35
36 #include "help_mp.h"
37 #include "m_option.h"
38 #include "m_struct.h"
39 #include "libavutil/avstring.h"
40
41 #define SIZERAW 2352
42 #define SIZEISO_MODE1 2048
43 #define SIZEISO_MODE2_RAW 2352
44 #define SIZEISO_MODE2_FORM1 2048
45 #define SIZEISO_MODE2_FORM2 2336
46 #define AUDIO 0
47 #define MODE1 1
48 #define MODE2 2
49 #define MODE1_2352 10
50 #define MODE2_2352 20
51 #define MODE1_2048 30
52 #define MODE2_2336 40
53 #define UNKNOWN -1
54
55 static const struct stream_priv_s {
56 char* filename;
57 } stream_priv_dflts = {
58 NULL
59 };
60
61 #define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
62 /// URL definition
63 static const m_option_t stream_opts_fields[] = {
64 { "string", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL},
65 { NULL, NULL, 0, 0, 0, 0, NULL }
66 };
67 static const struct m_struct_st stream_opts = {
68 "cue",
69 sizeof(struct stream_priv_s),
70 &stream_priv_dflts,
71 stream_opts_fields
72 };
73
74 static char cue_filename[256];
75 static char bincue_path[256];
76
77
78 typedef struct track
79 {
80 unsigned short mode;
81 unsigned short minute;
82 unsigned short second;
83 unsigned short frame;
84
85 /* (min*60 + sec) * 75 + fps */
86
87 unsigned long start_sector;
88
89 /* = the sizes in bytes off all tracks bevor this one */
90 /* its needed if there are mode1 tracks befor the mpeg tracks */
91 unsigned long start_offset;
92
93 unsigned int sector_data_length;
94 /* unsigned char num[3]; */
95 } tTrack;
96
97 /* max 99 tracks on a cd */
98 static tTrack tracks[100];
99
100 static struct cue_track_pos {
101 int track;
102 unsigned short mode;
103 unsigned short minute;
104 unsigned short second;
105 unsigned short frame;
106 } cue_current_pos;
107
108 /* number of tracks on the cd */
109 static int nTracks = 0;
110
digits2int(const char s[2],int errval)111 static int digits2int(const char s[2], int errval) {
112 uint8_t a = s[0] - '0';
113 uint8_t b = s[1] - '0';
114 if (a > 9 || b > 9)
115 return errval;
116 return a * 10 + b;
117 }
118
119 /* presumes Line is preloaded with the "current" line of the file */
cue_getTrackinfo(FILE * fd_cue,char * Line,tTrack * track)120 static int cue_getTrackinfo(FILE *fd_cue, char *Line, tTrack *track)
121 {
122 int already_set = 0;
123
124 /* Get the 'mode' */
125 if (strncmp(&Line[2], "TRACK ", 6)==0)
126 {
127 /* strncpy(track->num, &Line[8], 2); track->num[2] = '\0'; */
128
129 track->mode = UNKNOWN;
130 if(strncmp(&Line[11], "AUDIO", 5)==0) track->mode = AUDIO;
131 if(strncmp(&Line[11], "MODE1/2352", 10)==0) track->mode = MODE1_2352;
132 if(strncmp(&Line[11], "MODE1/2048", 10)==0) track->mode = MODE1_2048;
133 if(strncmp(&Line[11], "MODE2/2352", 10)==0) track->mode = MODE2_2352;
134 if(strncmp(&Line[11], "MODE2/2336", 10)==0) track->mode = MODE2_2336;
135 track->sector_data_length = track->mode == AUDIO ? SIZERAW : VCD_SECTOR_DATA;
136 }
137 else return 1;
138
139 /* Get the track indexes */
140 while(1) {
141 if(! fgets( Line, 256, fd_cue ) ) { break;}
142
143 if (strncmp(&Line[2], "TRACK ", 6)==0)
144 {
145 /* next track starting */
146 break;
147 }
148
149 /* Track 0 or 1, take the first an get fill the values*/
150 if (strncmp(&Line[4], "INDEX ", 6)==0)
151 {
152 /* check stuff here so if the answer is false the else stuff below won't be executed */
153 if ((already_set == 0) && digits2int(Line + 10, 100) <= 1)
154 {
155 already_set = 1;
156
157 track->minute = digits2int(Line + 13, 0);
158 track->second = digits2int(Line + 16, 0);
159 track->frame = digits2int(Line + 19, 0);
160 }
161 }
162 else if (strncmp(&Line[4], "PREGAP ", 7)==0) { ; /* ignore */ }
163 else if (strncmp(&Line[4], "FLAGS ", 6)==0) { ; /* ignore */ }
164 else mp_msg (MSGT_OPEN,MSGL_INFO,
165 MSGTR_MPDEMUX_CUEREAD_UnexpectedCuefileLine, Line);
166 }
167 return 0;
168 }
169
170
171
172 /* FIXME: the string operations ( strcpy,strcat ) below depend
173 * on the arrays to have the same size, thus we need to make
174 * sure the sizes are in sync.
175 */
cue_find_bin(const char * firstline)176 static int cue_find_bin (const char *firstline) {
177 struct stat filestat;
178 const char *cur_name;
179 char bin_filename[256];
180 char s[256];
181 char t[256];
182 int fd_bin;
183 int i = 0;
184
185 /* get the filename out of that */
186 /* 12345 6 */
187 mp_msg (MSGT_OPEN,MSGL_INFO, MSGTR_MPDEMUX_CUEREAD_BinFilenameFound, firstline);
188 if (strncmp(firstline, "FILE \"",6)==0)
189 {
190 firstline += 6;
191 while ( *firstline && *firstline != '"')
192 {
193 bin_filename[i] = *firstline++;
194
195 /* if I found a path info, then delete all before it */
196 switch (bin_filename[i])
197 {
198 case '\\':
199 i = 0;
200 break;
201
202 case '/':
203 i = 0;
204 break;
205
206 default:
207 i++;
208 }
209 }
210 }
211 bin_filename[i] = '\0';
212
213 fd_bin = -1;
214 for (i = 0; fd_bin == -1 && i < 6; i++) {
215 if (i <=1 && bin_filename[0] == '\0')
216 continue;
217 if (i > 1 && strlen(cue_filename) < 3)
218 break;
219
220 switch (i) {
221 case 0:
222 /* now try to open that file, without path */
223 cur_name = bin_filename;
224 break;
225 case 1:
226 /* now try to find it with the path of the cue file */
227 snprintf(s,sizeof( s ),"%s/%s",bincue_path,bin_filename);
228 cur_name = s;
229 break;
230 case 2:
231 /* now I would say the whole filename is shit, build our own */
232 av_strlcpy(s, cue_filename, strlen(cue_filename) - 3 );
233 strcat(s, ".bin");
234 cur_name = s;
235 break;
236 case 3:
237 /* ok try it with path */
238 snprintf(t, sizeof( t ), "%s/%s", bincue_path, s);
239 cur_name = t;
240 break;
241 case 4:
242 /* now I would say the whole filename is shit, build our own */
243 av_strlcpy(s, cue_filename, strlen(cue_filename) - 3 );
244 strcat(s, ".img");
245 cur_name = s;
246 break;
247 case 5:
248 /* ok try it with path */
249 snprintf(t, sizeof( t ), "%s/%s", bincue_path, s);
250 cur_name = t;
251 break;
252 }
253 fd_bin = open(cur_name, O_RDONLY);
254 if (fd_bin != -1 && (fstat(fd_bin, &filestat) == -1 || !S_ISREG(filestat.st_mode))) {
255 close(fd_bin);
256 fd_bin = -1;
257 }
258 if (fd_bin == -1) {
259 mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_MPDEMUX_CUEREAD_BinFilenameTested,
260 cur_name);
261 }
262 }
263
264 if (fd_bin == -1)
265 {
266 /* I'll give up */
267 mp_msg(MSGT_OPEN,MSGL_ERR,
268 MSGTR_MPDEMUX_CUEREAD_CannotFindBinFile);
269 return -1;
270 }
271
272 mp_msg(MSGT_OPEN,MSGL_INFO,
273 MSGTR_MPDEMUX_CUEREAD_UsingBinFile, cur_name);
274 return fd_bin;
275 }
276
cue_get_first_track(int fd_bin)277 static int cue_get_first_track(int fd_bin) {
278 char vol_descriptor[5];
279
280 if (lseek(fd_bin, 16 * VCD_SECTOR_SIZE + VCD_SECTOR_OFFS + 1, SEEK_SET) != -1)
281 if (read(fd_bin, vol_descriptor, 5) == 5)
282 if (strncmp(vol_descriptor, "CD001", 5) == 0)
283 /* ISO 9660 filesystem, so data starting in track 2 */
284 return 2;
285
286 /* data starting in track 1 */
287 return 1;
288 }
289
cue_msf_2_sector(int minute,int second,int frame)290 static inline int cue_msf_2_sector(int minute, int second, int frame) {
291 return frame + (second + minute * 60 ) * 75;
292 }
293
cue_get_msf(void)294 static inline int cue_get_msf(void) {
295 return cue_msf_2_sector (cue_current_pos.minute,
296 cue_current_pos.second,
297 cue_current_pos.frame);
298 }
299
cue_set_msf(unsigned int sect)300 static inline void cue_set_msf(unsigned int sect){
301 cue_current_pos.frame=sect%75;
302 sect=sect/75;
303 cue_current_pos.second=sect%60;
304 sect=sect/60;
305 cue_current_pos.minute=sect;
306 }
307
cue_mode_2_sector_size(int mode)308 static inline int cue_mode_2_sector_size(int mode)
309 {
310 switch (mode)
311 {
312 case AUDIO: return SIZERAW;
313 case MODE1_2352: return SIZERAW;
314 case MODE1_2048: return SIZEISO_MODE1;
315 case MODE2_2352: return SIZEISO_MODE2_RAW;
316 case MODE2_2336: return SIZEISO_MODE2_FORM2;
317
318 default:
319 mp_msg(MSGT_OPEN,MSGL_FATAL,
320 MSGTR_MPDEMUX_CUEREAD_UnknownModeForBinfile);
321 abort();
322 }
323
324 }
325
326
cue_read_cue(const char * in_cue_filename)327 static int cue_read_cue (const char *in_cue_filename)
328 {
329 struct stat filestat;
330 char sLine[256];
331 unsigned int sect;
332 char *s,*t;
333 int i;
334 int fd_bin;
335 FILE *fd_cue;
336
337 /* we have no tracks at the beginning */
338 nTracks = 0;
339
340 /* split the filename into a path and filename part */
341 s = strdup(in_cue_filename);
342 t = strrchr(s, '/');
343 if (!t)
344 t = ".";
345 else {
346 *t = '\0';
347 t = s;
348 if (*t == '\0')
349 strcpy(t, "/");
350 }
351
352 av_strlcpy(bincue_path,t,sizeof( bincue_path ));
353 mp_msg(MSGT_OPEN,MSGL_V,"dirname: %s, cuepath: %s\n", t, bincue_path);
354 free(s);
355 s = t = NULL;
356
357 /* no path at all? */
358 if (strcmp(bincue_path, ".") == 0) {
359 mp_msg(MSGT_OPEN,MSGL_V,"bincue_path: %s\n", bincue_path);
360 av_strlcpy(cue_filename,in_cue_filename,sizeof( cue_filename ));
361 } else {
362 av_strlcpy(cue_filename,in_cue_filename + strlen(bincue_path) + 1,
363 sizeof( cue_filename ));
364 }
365
366
367
368 /* open the cue file */
369 fd_cue = fopen (in_cue_filename, "r");
370 if (fd_cue == NULL)
371 {
372 mp_msg(MSGT_OPEN,MSGL_ERR,
373 MSGTR_MPDEMUX_CUEREAD_CannotOpenCueFile, in_cue_filename);
374 return -1;
375 }
376
377 /* read the first line and hand it to find_bin, which will
378 test more than one possible name of the file */
379
380 if(! fgets( sLine, sizeof(sLine), fd_cue ) )
381 {
382 mp_msg(MSGT_OPEN,MSGL_ERR,
383 MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
384 fclose (fd_cue);
385 return -1;
386 }
387
388 fd_bin = cue_find_bin(sLine);
389 if (fd_bin == -1) {
390 fclose (fd_cue);
391 return -1;
392 }
393
394
395 /* now build the track list */
396 /* red the next line and call our track finder */
397 if(! fgets( sLine, sizeof(sLine), fd_cue ) )
398 {
399 mp_msg(MSGT_OPEN,MSGL_ERR,
400 MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
401 fclose (fd_cue);
402 close (fd_bin);
403 return -1;
404 }
405
406 while(!feof(fd_cue))
407 {
408 if (cue_getTrackinfo(fd_cue, sLine, &tracks[nTracks++]) != 0)
409 {
410 mp_msg(MSGT_OPEN,MSGL_ERR,
411 MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
412 fclose (fd_cue);
413 close (fd_bin);
414 return -1;
415 }
416 }
417
418 /* make a fake track with stands for the Lead out */
419 if (fstat (fd_bin, &filestat) == -1) {
420 mp_msg(MSGT_OPEN,MSGL_ERR,
421 MSGTR_MPDEMUX_CUEREAD_ErrGettingBinFileSize);
422 fclose (fd_cue);
423 close (fd_bin);
424 return -1;
425 }
426
427 sect = filestat.st_size / 2352;
428
429 tracks[nTracks].frame = sect%75;
430 sect=sect/75;
431 tracks[nTracks].second = sect%60;
432 sect=sect/60;
433 tracks[nTracks].minute = sect;
434
435
436 /* let's calculate the start sectors and offsets */
437 for(i = 0; i <= nTracks; i++)
438 {
439 tracks[i].start_sector = cue_msf_2_sector(tracks[i].minute,
440 tracks[i].second,
441 tracks[i].frame);
442
443 /* if we're the first track we don't need to offset of the one befor */
444 if (i == 0)
445 {
446 /* was always 0 on my svcds, but who knows */
447 tracks[0].start_offset = tracks[0].start_sector *
448 cue_mode_2_sector_size(tracks[0].mode);
449 } else
450 {
451 tracks[i].start_offset = tracks[i-1].start_offset +
452 (tracks[i].start_sector - tracks[i-1].start_sector) *
453 cue_mode_2_sector_size(tracks[i-1].mode);
454 }
455 }
456
457 fclose (fd_cue);
458
459 return fd_bin;
460 }
461
462
463
464
cue_read_toc_entry(int track)465 static int cue_read_toc_entry(int track) {
466 /* check if its a valid track, if not return -1 */
467 if (track <= 0 || track > nTracks)
468 return -1;
469
470
471 cue_current_pos.track = track;
472 track--;
473 switch (tracks[track].mode)
474 {
475 case AUDIO:
476 cue_current_pos.mode = AUDIO;
477 break;
478 case MODE1_2352:
479 cue_current_pos.mode = MODE1;
480 break;
481 case MODE1_2048:
482 cue_current_pos.mode = MODE1;
483 break;
484 default: /* MODE2_2352 and MODE2_2336 */
485 cue_current_pos.mode = MODE2;
486 }
487 cue_current_pos.minute = tracks[track].minute;
488 cue_current_pos.second = tracks[track].second;
489 cue_current_pos.frame = tracks[track].frame;
490
491 return 0;
492 }
493
cue_get_track_end(int track)494 static int cue_get_track_end (int track){
495 int sector = cue_msf_2_sector(tracks[track].minute, tracks[track].second,
496 tracks[track].frame);
497
498 return tracks[track-1].sector_data_length * sector;
499 }
500
seek(stream_t * s,int64_t newpos)501 static int seek(stream_t *s, int64_t newpos) {
502 s->pos=newpos;
503 cue_set_msf(s->pos/tracks[cue_current_pos.track-1].sector_data_length);
504 return 1;
505 }
506
cue_seek_to_track(stream_t * stream,int track)507 static int cue_seek_to_track (stream_t *stream, int track){
508 int pos;
509 if (cue_read_toc_entry (track))
510 return -1;
511
512 pos = tracks[track-1].sector_data_length * cue_get_msf();
513 stream->start_pos = pos;
514 stream->end_pos = cue_get_track_end(track);
515 seek(stream, pos);
516 return pos;
517 }
518
cue_read_toc(void)519 static void cue_read_toc(void){
520 int i;
521 for (i = 0; i < nTracks; ++i) {
522
523 mp_msg(MSGT_OPEN,MSGL_INFO,
524 MSGTR_MPDEMUX_CUEREAD_InfoTrackFormat,
525 i+1,
526 tracks[i].mode,
527 tracks[i].minute,
528 tracks[i].second,
529 tracks[i].frame
530 );
531 }
532 }
533
cue_read(stream_t * stream,char * mem,int size)534 static int cue_read(stream_t *stream, char *mem, int size) {
535 unsigned long position, offset;
536 int fd_bin = stream->fd;
537 int track = cue_current_pos.track - 1;
538
539 position = tracks[track].start_offset +
540 (cue_msf_2_sector(cue_current_pos.minute,
541 cue_current_pos.second,
542 cue_current_pos.frame) -
543 tracks[track].start_sector)
544 * cue_mode_2_sector_size(tracks[track].mode);
545
546
547 if(position >= tracks[track+1].start_offset)
548 return 0;
549
550 offset = tracks[track].mode == AUDIO ? 0 : VCD_SECTOR_OFFS;
551
552 if(lseek(fd_bin, position+offset, SEEK_SET) == -1) {
553 mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_MPDEMUX_CUEREAD_UnexpectedBinFileEOF);
554 return 0;
555 }
556
557 if(read(fd_bin, mem, tracks[track].sector_data_length) != tracks[track].sector_data_length) {
558 mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_MPDEMUX_CUEREAD_CannotReadNBytesOfPayload, tracks[track].sector_data_length);
559 return 0;
560 }
561
562 cue_current_pos.frame++;
563 if (cue_current_pos.frame==75){
564 cue_current_pos.frame=0;
565 cue_current_pos.second++;
566 if (cue_current_pos.second==60){
567 cue_current_pos.second=0;
568 cue_current_pos.minute++;
569 }
570 }
571
572 return tracks[track].sector_data_length;
573 }
574
control(stream_t * stream,int cmd,void * arg)575 static int control(stream_t *stream, int cmd, void *arg) {
576 switch(cmd) {
577 case STREAM_CTRL_GET_NUM_TITLES:
578 case STREAM_CTRL_GET_NUM_CHAPTERS:
579 {
580 *(unsigned int *)arg = nTracks;
581 return STREAM_OK;
582 }
583 case STREAM_CTRL_SEEK_TO_CHAPTER:
584 {
585 int r;
586 unsigned int track = *(unsigned int *)arg + 1;
587 r = cue_seek_to_track(stream, track);
588 if (r >= 0) {
589 return STREAM_OK;
590 }
591 break;
592 }
593 case STREAM_CTRL_GET_CURRENT_TITLE:
594 case STREAM_CTRL_GET_CURRENT_CHAPTER:
595 {
596 *(unsigned int *)arg = cue_current_pos.track - 1;
597 return STREAM_OK;
598 }
599 case STREAM_CTRL_GET_NUM_ANGLES:
600 {
601 *(unsigned int *)arg = tracks[cue_current_pos.track - 1].mode != AUDIO;
602 return STREAM_OK;
603 }
604 }
605 return STREAM_UNSUPPORTED;
606 }
607
open_s(stream_t * stream,int mode,void * opts,int * file_format)608 static int open_s(stream_t *stream,int mode, void* opts, int* file_format) {
609 struct stream_priv_s* p = (struct stream_priv_s*)opts;
610 int ret,f = -1,track = 0;
611 char *filename = NULL, *colon = NULL;
612
613 if(mode != STREAM_READ || !p->filename)
614 goto err_out;
615 filename = strdup(p->filename);
616 if(!filename)
617 goto err_out;
618 colon = strstr(filename, ":");
619 if(colon) {
620 if(strlen(colon)>1)
621 track = atoi(colon+1);
622 *colon = 0;
623 }
624 f = cue_read_cue(filename);
625 if(f < 0)
626 goto err_out;
627 if(!track)
628 track = cue_get_first_track(f);
629
630 cue_read_toc();
631 ret=cue_seek_to_track(stream, track);
632 if(ret<0){
633 mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_ErrTrackSelect " (seek)\n");
634 goto err_out;
635 }
636 mp_msg(MSGT_OPEN,MSGL_INFO,MSGTR_MPDEMUX_CUEREAD_CueStreamInfo_FilenameTrackTracksavail,
637 filename, track, ret, (int)stream->end_pos);
638
639 stream->fd = f;
640 stream->type = STREAMTYPE_BINCUE;
641 stream->sector_size = tracks[track-1].sector_data_length;
642 stream->flags = STREAM_READ | MP_STREAM_SEEK_FW;
643 stream->fill_buffer = cue_read;
644 stream->seek = seek;
645 stream->control = control;
646
647 free(filename);
648 m_struct_free(&stream_opts,opts);
649 return STREAM_OK;
650
651 err_out:
652 if (f >= 0) close(f);
653 free(filename);
654 m_struct_free(&stream_opts,opts);
655 return STREAM_UNSUPPORTED;
656 }
657
658 const stream_info_t stream_info_cue = {
659 "CUE track",
660 "cue",
661 "Albeu",
662 "based on the code from ???",
663 open_s,
664 { "cue", NULL },
665 &stream_opts,
666 1 // Urls are an option string
667 };
668