~ubuntu-branches/ubuntu/lucid/mpg123/lucid

« back to all changes in this revision

Viewing changes to src/playlist.c

Tags: upstream-0.60
ImportĀ upstreamĀ versionĀ 0.60

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
        playlist: playlist logic
 
3
 
 
4
        copyright 1995-2006 by the mpg123 project - free software under the terms of the LGPL 2.1
 
5
        see COPYING and AUTHORS files in distribution or http://mpg123.de
 
6
        initially written by Michael Hipp, outsourced/reorganized by Thomas Orgis
 
7
 
 
8
        If we officially support Windows again, we should have this reworked to really cope with Windows paths, too.
 
9
*/
 
10
 
 
11
#include "config.h"
 
12
#include "mpg123.h"
 
13
#include "getlopt.h" /* for loptind */
 
14
#include "debug.h"
 
15
#include "term.h" /* for term_restore */
 
16
#include "playlist.h"
 
17
 
 
18
#include <time.h>
 
19
 
 
20
/* increase linebuf in blocks of ... bytes */
 
21
#define LINEBUF_STEP 100
 
22
 
 
23
/* one global instance... add a pointer to this to every function definition and you have OO-style... */
 
24
playlist_struct pl;
 
25
 
 
26
/*
 
27
        functions to be called from outside and thus declared in the header:
 
28
 
 
29
        void prepare_playlist(int argc, char** argv);
 
30
        char *get_next_file(int argc, char **argv);
 
31
        void free_playlist();
 
32
*/
 
33
 
 
34
/* local functions */
 
35
 
 
36
int add_next_file (int argc, char *argv[]);
 
37
void shuffle_playlist();
 
38
void print_playlist();
 
39
void init_playlist();
 
40
int add_copy_to_playlist(char* new_entry);
 
41
int add_to_playlist(char* new_entry, char freeit);
 
42
 
 
43
/* used to be init_input */
 
44
void prepare_playlist(int argc, char** argv)
 
45
{
 
46
        /*
 
47
                fetch all playlist entries ... I don't consider playlists to be an endless stream.
 
48
                If you want to intentionally hang mpg123 on some other prog that may take infinite time to produce the full list (perhaps load tracks on demand), then just use the remote control and let that program print "load filename" instead of "filename".
 
49
                We may even provide a simple wrapper script that emulates the old playlist reading behaviour (for files and stdin, http playlists are actually a strong point on reading the list in _before_ starting playback since http connections don't last forever).
 
50
        */
 
51
        init_playlist();
 
52
        while (add_next_file(argc, argv)) {}
 
53
        if(param.verbose > 1)
 
54
        {
 
55
                fprintf(stderr, "\nplaylist in normal order:\n");
 
56
                print_playlist();
 
57
                fprintf(stderr, "\n");
 
58
        }
 
59
        if(param.shuffle == 1) shuffle_playlist();
 
60
        /* Don't need these anymore, we have copies! */
 
61
        free_stringbuf(&pl.linebuf);
 
62
        free_stringbuf(&pl.dir);
 
63
}
 
64
 
 
65
char *get_next_file()
 
66
{
 
67
        char *newfile;
 
68
 
 
69
        if(pl.fill == 0) newfile = NULL;
 
70
        else
 
71
        /* normal order, just pick next thing */
 
72
        if(param.shuffle < 2)
 
73
        {
 
74
                if(pl.pos < pl.fill) newfile = pl.list[pl.pos].url;
 
75
                else newfile = NULL;
 
76
                ++pl.pos;
 
77
        }
 
78
        /* randomly select files, with repeating */
 
79
        else newfile = pl.list[ (size_t) rand() % pl.fill ].url;
 
80
 
 
81
        return newfile;
 
82
}
 
83
 
 
84
/* It doesn't really matter on program exit, but anyway...
 
85
   Make sure you don't free() an item of argv! */
 
86
void free_playlist()
 
87
{
 
88
        if(pl.list != NULL)
 
89
        {
 
90
                debug("going to free() the playlist");
 
91
                while(pl.fill)
 
92
                {
 
93
                        --pl.fill;
 
94
                        debug1("free()ing entry %lu", (unsigned long)pl.fill);
 
95
                        if(pl.list[pl.fill].freeit) free(pl.list[pl.fill].url);
 
96
                }
 
97
                free(pl.list);
 
98
                pl.list = NULL;
 
99
                pl.size = 0;
 
100
                debug("free()d the playlist");
 
101
        }
 
102
        free_stringbuf(&pl.linebuf);
 
103
        free_stringbuf(&pl.dir);
 
104
}
 
105
 
 
106
/* the constructor... */
 
107
void init_playlist()
 
108
{
 
109
        srand(time(NULL));
 
110
        pl.file = NULL;
 
111
        pl.entry = 0;
 
112
        pl.size = 0;
 
113
        pl.fill = 0;
 
114
        pl.pos = 0;
 
115
        pl.list = NULL;
 
116
        pl.alloc_step = 10;
 
117
        init_stringbuf(&pl.dir);
 
118
        init_stringbuf(&pl.linebuf);
 
119
        pl.type = UNKNOWN;
 
120
}
 
121
 
 
122
/*
 
123
        slightly modified find_next_file from mpg123.c
 
124
        now doesn't return the next entry but adds it to playlist struct
 
125
        returns 1 if it found something, 0 on end
 
126
*/
 
127
int add_next_file (int argc, char *argv[])
 
128
{
 
129
        int firstline = 0;
 
130
 
 
131
        /* hack for url that has been detected as track, not playlist */
 
132
        if(pl.type == NO_LIST) return 0;
 
133
 
 
134
        /* Get playlist dirname to append it to the files in playlist */
 
135
        if (param.listname)
 
136
        {
 
137
                char* slashpos;
 
138
                /* Oh, right... that doesn't look good for Windows... */
 
139
                if ((slashpos=strrchr(param.listname, '/')))
 
140
                {
 
141
                        /* up to and including /, with space for \0 */
 
142
                        if(resize_stringbuf(&pl.dir, 2 + slashpos - param.listname))
 
143
                        {
 
144
                                memcpy(pl.dir.p, param.listname, pl.dir.size-1);
 
145
                                pl.dir.p[pl.dir.size-1] = 0;
 
146
                        }
 
147
                        else
 
148
                        {
 
149
                                error("cannot allocate memory for list directory!");
 
150
                                pl.dir.size = 0;
 
151
                        }
 
152
                }
 
153
        }
 
154
 
 
155
        if (param.listname || pl.file)
 
156
        {
 
157
                size_t line_offset = 0;
 
158
                if (!pl.file)
 
159
                {
 
160
                        /* empty or "-" */
 
161
                        if (!*param.listname || !strcmp(param.listname, "-"))
 
162
                        {
 
163
                                pl.file = stdin;
 
164
                                param.listname = NULL;
 
165
                                pl.entry = 0;
 
166
                        }
 
167
                        else if (!strncmp(param.listname, "http://", 7))
 
168
                        {
 
169
                                int fd;
 
170
                                char *listmime = NULL;
 
171
                                fd = http_open(param.listname, &listmime);
 
172
                                debug1("listmime: %p", (void*) listmime);
 
173
                                if(listmime != NULL)
 
174
                                {
 
175
                                        debug1("listmime value: %s", listmime);
 
176
                                        if(!strcmp("audio/x-mpegurl", listmime))        pl.type = M3U;
 
177
                                        else if(!strcmp("audio/x-scpls", listmime))     pl.type = PLS;
 
178
                                        else
 
179
                                        {
 
180
                                                if(fd >= 0) close(fd);
 
181
                                                if(!strcmp("audio/mpeg", listmime))
 
182
                                                {
 
183
                                                        pl.type = NO_LIST;
 
184
                                                        if(param.listentry < 0)
 
185
                                                        {
 
186
                                                                printf("#note you gave me a file url, no playlist, so...\n#entry 1\n%s\n", param.listname);
 
187
                                                                return 0;
 
188
                                                        }
 
189
                                                        else
 
190
                                                        {
 
191
                                                                fprintf(stderr, "Note: MIME type indicates that this is no playlist but an mpeg audio file... reopening as such.\n");
 
192
                                                                add_to_playlist(param.listname, 0);
 
193
                                                                return 1;
 
194
                                                        }
 
195
                                                }
 
196
                                                fprintf(stderr, "Error: unknown playlist MIME type %s; maybe "PACKAGE_NAME" can support it in future if you report this to the maintainer.\n", listmime);
 
197
                                                fd = -1;
 
198
                                        }
 
199
                                        free(listmime);
 
200
                                }
 
201
                                if(fd < 0)
 
202
                                {
 
203
                                        param.listname = NULL;
 
204
                                        pl.file = NULL;
 
205
                                        fprintf(stderr, "Error: invalid playlist from http_open()!\n");
 
206
                                }
 
207
                                else
 
208
                                {
 
209
                                        pl.entry = 0;
 
210
                                        pl.file = fdopen(fd,"r");
 
211
                                }
 
212
                        }
 
213
                        else if (!(pl.file = fopen(param.listname, "rb")))
 
214
                        {
 
215
                                perror (param.listname);
 
216
                                #ifdef HAVE_TERMIOS
 
217
                                if(param.term_ctrl)
 
218
                                term_restore();
 
219
                                #endif
 
220
                                exit (1);
 
221
                        }
 
222
                        else
 
223
                        {
 
224
                                debug("opened ordinary list file");
 
225
                                pl.entry = 0;
 
226
                        }
 
227
                        if (param.verbose && pl.file) fprintf (stderr, "Using playlist from %s ...\n",  param.listname ? param.listname : "standard input");
 
228
                        firstline = 1; /* just opened */
 
229
                }
 
230
                /* reading the file line by line */
 
231
                while (pl.file)
 
232
                {
 
233
                        /*
 
234
                                now read a string of arbitrary size...
 
235
                                read a chunk, see if lineend, realloc, read another chunk
 
236
                                
 
237
                                fgets reads at most size-1 bytes and always appends the \0 
 
238
                        */
 
239
                        size_t have = 0;
 
240
                        do
 
241
                        {
 
242
                                /* have is the length of the string read, without the closing \0 */
 
243
                                if(pl.linebuf.size <= have+1)
 
244
                                {
 
245
                                        if(!resize_stringbuf(&pl.linebuf, pl.linebuf.size+LINEBUF_STEP))
 
246
                                        {
 
247
                                                error("cannot increase line buffer");
 
248
                                                break;
 
249
                                        }
 
250
                                }
 
251
                                /* I rely on fgets writing the \0 at the end! */
 
252
                                if(fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.file))
 
253
                                {
 
254
                                        have += strlen(pl.linebuf.p+have);
 
255
                                        debug2("have read %lu characters into linebuf: [%s]", (unsigned long)have, pl.linebuf.p);
 
256
                                }
 
257
                                else
 
258
                                {
 
259
                                        debug("fgets failed to deliver something... file ended?");
 
260
                                        break;
 
261
                                }
 
262
                        } while(have && pl.linebuf.p[have-1] != '\r' && pl.linebuf.p[have-1] != '\n');
 
263
                        if(have)
 
264
                        {
 
265
                                size_t i;
 
266
                                pl.linebuf.p[strcspn(pl.linebuf.p, "\t\n\r")] = '\0';
 
267
                                /* a bit of fuzzyness */
 
268
                                if(firstline)
 
269
                                {
 
270
                                        if(pl.type == UNKNOWN)
 
271
                                        {
 
272
                                                if(!strcmp("[playlist]", pl.linebuf.p))
 
273
                                                {
 
274
                                                        fprintf(stderr, "Note: detected Shoutcast/Winamp PLS playlist\n");
 
275
                                                        pl.type = PLS;
 
276
                                                        continue;
 
277
                                                }
 
278
                                                else if
 
279
                                                (
 
280
                                                        (!strncasecmp("#M3U", pl.linebuf.p ,4))
 
281
                                                        ||
 
282
                                                        (!strncasecmp("#EXTM3U", pl.linebuf.p ,7))
 
283
                                                        ||
 
284
                                                        (param.listname != NULL && (strrchr(param.listname, '.')) != NULL && !strcasecmp(".m3u", strrchr(param.listname, '.')))
 
285
                                                )
 
286
                                                {
 
287
                                                        if(param.verbose) fprintf(stderr, "Note: detected M3U playlist type\n");
 
288
                                                        pl.type = M3U;
 
289
                                                }
 
290
                                                else
 
291
                                                {
 
292
                                                        if(param.verbose) fprintf(stderr, "Note: guessed M3U playlist type\n");
 
293
                                                        pl.type = M3U;
 
294
                                                }
 
295
                                        }
 
296
                                        else
 
297
                                        {
 
298
                                                if(param.verbose)
 
299
                                                {
 
300
                                                        fprintf(stderr, "Note: Interpreting as ");
 
301
                                                        switch(pl.type)
 
302
                                                        {
 
303
                                                                case M3U: fprintf(stderr, "M3U"); break;
 
304
                                                                case PLS: fprintf(stderr, "PLS (Winamp/Shoutcast)"); break;
 
305
                                                                default: fprintf(stderr, "???");
 
306
                                                        }
 
307
                                                        fprintf(stderr, " playlist\n");
 
308
                                                }
 
309
                                        }
 
310
                                        firstline = 0;
 
311
                                }
 
312
                                #if !defined(WIN32)
 
313
                                /* convert \ to / (from MS-like directory format) */
 
314
                                for (i=0;pl.linebuf.p[i]!='\0';i++)
 
315
                                {
 
316
                                        if (pl.linebuf.p[i] == '\\')    pl.linebuf.p[i] = '/';
 
317
                                }
 
318
                                #endif
 
319
                                if (pl.linebuf.p[0]=='\0') continue; /* skip empty lines... */
 
320
                                if (((pl.type == M3U) && (pl.linebuf.p[0]=='#')))
 
321
                                {
 
322
                                        /* a comment line in m3u file */
 
323
                                        if(param.listentry < 0) printf("%s\n", pl.linebuf.p);
 
324
                                        continue;
 
325
                                }
 
326
 
 
327
                                /* real filename may start at an offset */
 
328
                                line_offset = 0;
 
329
                                /* extract path out of PLS */
 
330
                                if(pl.type == PLS)
 
331
                                {
 
332
                                        if(!strncasecmp("File", pl.linebuf.p, 4))
 
333
                                        {
 
334
                                                /* too lazy to really check for file number... would have to change logic to support unordered file entries anyway */
 
335
                                                char* in_line;
 
336
                                                if((in_line = strchr(pl.linebuf.p+4, '=')) != NULL)
 
337
                                                {
 
338
                                                        /* FileN=? */
 
339
                                                        if(in_line[1] != 0)
 
340
                                                        {
 
341
                                                                ++in_line;
 
342
                                                                line_offset = (size_t) (in_line-pl.linebuf.p);
 
343
                                                        }
 
344
                                                        else
 
345
                                                        {
 
346
                                                                fprintf(stderr, "Warning: Invalid PLS line (empty filename) - corrupt playlist file?\n");
 
347
                                                                continue;
 
348
                                                        }
 
349
                                                }
 
350
                                                else
 
351
                                                {
 
352
                                                        fprintf(stderr, "Warning: Invalid PLS line (no '=' after 'File') - corrupt playlist file?\n");
 
353
                                                        continue;
 
354
                                                }
 
355
                                        }
 
356
                                        else
 
357
                                        {
 
358
                                                if(param.listentry < 0) printf("#metainfo %s\n", pl.linebuf.p);
 
359
                                                continue;
 
360
                                        }
 
361
                                }
 
362
 
 
363
                                /* make paths absolute */
 
364
                                /* Windows knows absolute paths with c: in front... should handle this if really supporting win32 again */
 
365
                                if
 
366
                                (
 
367
                                        (pl.dir.p != NULL)
 
368
                                        && (pl.linebuf.p[line_offset]!='/')
 
369
                                        && (pl.linebuf.p[line_offset]!='\\')
 
370
                                        && strncmp(pl.linebuf.p+line_offset, "http://", 7)
 
371
                                )
 
372
                                {
 
373
                                        size_t need;
 
374
                                        need = pl.dir.size + strlen(pl.linebuf.p+line_offset);
 
375
                                        if(pl.linebuf.size < need)
 
376
                                        {
 
377
                                                if(!resize_stringbuf(&pl.linebuf, need))
 
378
                                                {
 
379
                                                        error("unable to enlarge linebuf for appending path! skipping");
 
380
                                                        continue;
 
381
                                                }
 
382
                                        }
 
383
                                        /* move to have the space at beginning */
 
384
                                        memmove(pl.linebuf.p+pl.dir.size-1, pl.linebuf.p+line_offset, strlen(pl.linebuf.p+line_offset)+1);
 
385
                                        /* prepend path */
 
386
                                        memcpy(pl.linebuf.p, pl.dir.p, pl.dir.size-1);
 
387
                                        line_offset = 0;
 
388
                                }
 
389
                                ++pl.entry;
 
390
                                if(param.listentry < 0) printf("#entry %lu\n%s\n", (unsigned long)pl.entry,pl.linebuf.p+line_offset);
 
391
                                else if((param.listentry == 0) || (param.listentry == pl.entry))
 
392
                                {
 
393
                                        add_copy_to_playlist(pl.linebuf.p+line_offset);
 
394
                                        return 1;
 
395
                                }
 
396
                        }
 
397
                        else
 
398
                        {
 
399
                                if (param.listname)
 
400
                                fclose (pl.file);
 
401
                                param.listname = NULL;
 
402
                                pl.file = NULL;
 
403
                        }
 
404
                }
 
405
        }
 
406
        if(loptind < argc)
 
407
        {
 
408
                add_to_playlist(argv[loptind++], 0);
 
409
                return 1;
 
410
        }
 
411
        return 0;
 
412
}
 
413
 
 
414
void shuffle_playlist()
 
415
{
 
416
        size_t loop;
 
417
        size_t rannum;
 
418
        if(pl.fill >= 2)
 
419
        {
 
420
                for (loop = 0; loop < pl.fill; loop++)
 
421
                {
 
422
                        struct listitem tmp;
 
423
                        /*
 
424
                                rand gives integer 0 <= RAND_MAX
 
425
                                dividing this by (fill-1)*4 and taking the rest gives something 0 <= x < (fill-1)*4
 
426
                                now diving x by 4 gives 0 <= y < fill-1
 
427
                                then adding 1 if y >= loop index... makes 1 <= z <= fill-1
 
428
                                if y not >= loop index that means... what?
 
429
 
 
430
                                rannum = (rand() % (fill * 4 - 4)) / 4;
 
431
                                rannum += (rannum >= loop);
 
432
 
 
433
                                why not simply
 
434
 
 
435
                                rannum = ( rand() % fill);
 
436
                                
 
437
                                That directly results in a random number in the allowed range. I'm using this now until someone convinces me of the numerical benefits of the other.
 
438
                        */
 
439
                        rannum = (size_t) rand() % pl.fill;
 
440
                        /*
 
441
                                Small test on your binary operation skills (^ is XOR):
 
442
                                a = b^(a^b)
 
443
                                b = (a^b)^(b^(a^b))
 
444
                                And, understood? ;-)
 
445
                                
 
446
                                pl.list[loop] ^= pl.list[rannum];
 
447
                                pl.list[rannum] ^= pl.list[loop];
 
448
                                pl.list[loop] ^= pl.list[rannum];
 
449
                                
 
450
                                But since this is not allowed with pointers and any speed gain questionable (well, saving _some_ memory...), doing it the lame way:
 
451
                        */
 
452
                        tmp = pl.list[rannum];
 
453
                        pl.list[rannum] = pl.list[loop];
 
454
                        pl.list[loop] = tmp;
 
455
                }
 
456
        }
 
457
 
 
458
        if(param.verbose > 1)
 
459
        {
 
460
                /* print them */
 
461
                fprintf(stderr, "\nshuffled playlist:\n");
 
462
                print_playlist();
 
463
                fprintf(stderr, "\n");
 
464
        }
 
465
}
 
466
 
 
467
void print_playlist()
 
468
{
 
469
        size_t loop;
 
470
        for (loop = 0; loop < pl.fill; loop++)
 
471
        fprintf(stderr, "%s\n", pl.list[loop].url);
 
472
}
 
473
 
 
474
int add_copy_to_playlist(char* new_entry)
 
475
{
 
476
        char* cop;
 
477
        if((cop = (char*) malloc(strlen(new_entry)+1)) != NULL)
 
478
        {
 
479
                strcpy(cop, new_entry);
 
480
                return add_to_playlist(cop, 1);
 
481
        }
 
482
        else return 0;
 
483
}
 
484
 
 
485
/* add new entry to playlist - no string copy, just the pointer! */
 
486
int add_to_playlist(char* new_entry, char freeit)
 
487
{
 
488
        if(pl.fill == pl.size)
 
489
        {
 
490
                struct listitem* tmp = NULL;
 
491
                /* enlarge the list */
 
492
                tmp = (struct listitem*) realloc(pl.list, (pl.size + pl.alloc_step) * sizeof(struct listitem));
 
493
                if(!tmp)
 
494
                {
 
495
                        error("unable to allocate more memory for playlist");
 
496
                        perror("");
 
497
                        return 0;
 
498
                }
 
499
                else
 
500
                {
 
501
                        pl.list = tmp;
 
502
                        pl.size += pl.alloc_step;
 
503
                }
 
504
        }
 
505
        /* paranoid */
 
506
        if(pl.fill < pl.size)
 
507
        {
 
508
                pl.list[pl.fill].freeit = freeit;
 
509
                pl.list[pl.fill].url = new_entry;
 
510
                ++pl.fill;
 
511
        }
 
512
        else
 
513
        {
 
514
                error("playlist memory still too small?!");
 
515
                return 0;
 
516
        }
 
517
        return 1;
 
518
}
 
519