1 /*
2 * $Id: helper.c,v 1.29 2002/12/08 14:10:34 alphix Exp $
3 *
4 * This file is part of Ample.
5 *
6 * Ample 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 * Ample 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
17 * along with Ample; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <libgen.h>
27 #include <string.h>
28 #ifdef HAVE_SYSLOG_H
29 #include <syslog.h>
30 #endif
31 #ifdef HAVE_STDINT_H
32 #include <stdint.h>
33 #endif
34 #include <stdarg.h>
35 #include <errno.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <arpa/inet.h>
39 #include <netdb.h>
40
41 #include "ample.h"
42 #include "entries.h"
43 #include "client.h"
44 #include "helper.h"
45
46 /* Used to signal errors in hostname lookups */
47 extern int h_errno;
48
49 /* Some utility macros used in getsonglength() */
50 #define CHECKSYNC(x) (((x >> 21) & 0x07FF) == 0x7FF)
51 #define BYTE0(x) ((x >> 24) & 0xFF)
52 #define BYTE1(x) ((x >> 16) & 0xFF)
53 #define BYTE2(x) ((x >> 8) & 0xFF)
54 #define BYTE3(x) ((x >> 0) & 0xFF)
55
56 /* Table of bitrates for MP3 files, all values in kilo.
57 * Indexed by version, layer and value of bit 15-12 in header.
58 */
59 const int bitrate_table[2][3][16] =
60 {
61 {
62 {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0},
63 {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0},
64 {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0}
65 },
66 {
67 {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0},
68 {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0},
69 {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}
70 }
71 };
72
73 /* Table of samples per frame for MP3 files.
74 * Indexed by layer.
75 */
76 const int bs[4] = {0, 384, 1152, 1152};
77
78
79 /* Table of sample frequency for MP3 files.
80 * Indexed by version and layer.
81 */
82 const int freqtab[2][4] =
83 {
84 {44100, 48000, 32000, 0},
85 {22050, 24000, 16000, 0},
86 };
87
88
89 /*
90 * Allows children to send status reports to the parent process.
91 *
92 * Argument: to - the udp socket to send the message to
93 * fmt - the format string of the message
94 * argp - the list of arguments to complement the format string
95 *
96 * Returns: void
97 */
98 void
sendstatusmsg(int to,char * fmt,...)99 sendstatusmsg(int to, char *fmt, ...)
100 {
101 char buffer[1000];
102 int length;
103 va_list argp;
104
105 if(to >= 0) {
106 va_start(argp, fmt);
107 length = vsnprintf(buffer, 1000, fmt, argp);
108 sprintf(&buffer[992], "<TRUNC>");
109 send(to, buffer, strlen(buffer) + 1, 0);
110 va_end(argp);
111 }
112 }
113
114
115 /*
116 * Generic function to deal with log, debug and death messages.
117 *
118 * Argument: type - the type of the message (see header)
119 * fmt - the format string of the message
120 * argp - the list of arguments to complement the format string
121 *
122 * Returns: void
123 */
124 static void
printlogmsg(int type,char * fmt,va_list argp)125 printlogmsg(int type, char *fmt, va_list argp)
126 {
127 char buffer[1000];
128 int length;
129
130 length = vsnprintf(buffer, 1000, fmt, argp);
131 sprintf(&buffer[992], "<TRUNC>");
132
133 #ifdef HAVE_SYSLOG_H
134 if(!gconf.trace) {
135 if(type == TYPE_LOG) {
136 syslog(LOG_INFO, "%s", buffer);
137 } else if(type == TYPE_DEBUG) {
138 syslog(LOG_DEBUG, "%s", buffer);
139 } else if(type == TYPE_DIE) {
140 if(errno != 0)
141 syslog(LOG_ERR, "died - %s, errno: %s", buffer,
142 strerror(errno));
143 else
144 syslog(LOG_ERR, "died - %s", buffer);
145 exit(EXIT_FAILURE);
146 }
147 return;
148 }
149 #endif
150
151 if(type == TYPE_LOG) {
152 printf("LOG[%d]: %s", getpid(), buffer);
153 } else if(type == TYPE_DEBUG) {
154 printf("DBG[%d]: %s", getpid(), buffer);
155 } else if(type == TYPE_DIE) {
156 if(errno != 0)
157 printf("DIE[%d]: (errno: %s) %s", getpid(),
158 strerror(errno), buffer);
159 else
160 printf("DIE[%d]: %s", getpid(), buffer);
161 exit(EXIT_FAILURE);
162 }
163 fflush(stdout);
164 }
165
166
167 /*
168 * Wrapper function to printlogmsg that is used to print ordinary log messages.
169 *
170 * Arguments: fmt - the format string of the message
171 * ... - the variables to complement the format string
172 *
173 * Returns: void
174 */
175 void
logmsg(char * fmt,...)176 logmsg(char *fmt, ...)
177 {
178 va_list argp;
179
180 va_start(argp, fmt);
181 printlogmsg(TYPE_LOG, fmt, argp);
182 va_end(argp);
183 }
184
185
186 /*
187 * Wrapper function to printlogmsg that is used to print debug messages if
188 * the debug level config option is high enough.
189 *
190 * Arguments: priority - the level of the debug message, higher number
191 * generally means more verbose debugging, if this
192 * number is higher than the debuglevel config option,
193 * nothing is printed
194 * fmt - the format string of the message
195 * ... - the variables to complement the format string
196 *
197 * Returns: void
198 */
199 void
debug(int priority,char * fmt,...)200 debug(int priority, char *fmt, ...)
201 {
202 va_list argp;
203
204 if(priority > gconf.debuglevel)
205 return;
206
207 va_start(argp, fmt);
208 printlogmsg(TYPE_DEBUG, fmt, argp);
209 va_end(argp);
210 }
211
212
213 /*
214 * Wrapper function to printlogmsg that is used to print death messages
215 * and then exit.
216 *
217 * Arguments: fmt - the format string of the message
218 * ... - the variables to complement the format string
219 *
220 * Returns: void
221 */
222 void
die(char * fmt,...)223 die(char *fmt, ...)
224 {
225 va_list argp;
226
227 va_start(argp, fmt);
228 printlogmsg(TYPE_DIE, fmt, argp);
229 va_end(argp);
230 }
231
232
233 static void
expandmalloc(void ** old,size_t * size,int line)234 expandmalloc(void **old, size_t *size, int line) {
235 *size = ((*size) * 2);
236 debug(4, "called from %i, gonna realloc with size %i and old %p\n",
237 line, *size, *old);
238 *old = realloc(*old, *size);
239 if(*old == NULL)
240 die("realloc failed\n");
241 }
242
243
244 /*
245 * Implements the behaviour of GNU getcwd using standard getcwd
246 * (GNU cwd auto-malloc's a buffer to hold the result)
247 *
248 * Argument: none
249 *
250 * Returns: A pointer to the newly allocated buffer
251 */
252 char *
mgetcwd()253 mgetcwd()
254 {
255 size_t size = 100;
256 char *buffer = (char *)malloc(size);
257
258 while (TRUE) {
259 if(getcwd (buffer, size) == buffer)
260 return buffer;
261 if(errno != ERANGE)
262 die("getcwd");
263 expandmalloc((void **)&buffer, &size, __LINE__);
264 }
265 }
266
267
268 /*
269 * The opposite of atoi, converts an integer to an ASCII string.
270 *
271 * Arguments: integer - the integer to convert
272 *
273 * Returns: an ASCII string stored in malloc:ed memory
274 */
275 char *
itoa(int integer)276 itoa(int integer) {
277 int tmp = integer;
278 int size;
279 char *result;
280
281 if(integer == 0)
282 size = 2;
283 else if(integer < 0)
284 size = 2;
285 else if(integer > 0)
286 size = 1;
287
288 while(tmp) {
289 tmp /= 10;
290 size++;
291 }
292
293 result = malloc(size);
294 snprintf(result, size, "%i", integer);
295 return result;
296 }
297
298
299 /*
300 * Encode function common for both HTML and URL encodings.
301 *
302 * Arguments: toencode - string to convert
303 * url - if TRUE, urlencode the string, otherwise htmlencode it
304 *
305 * Returns: an encoded ASCII string stored in malloc:ed memory
306 */
307 static char *
commonencode(char * toencode,bool url)308 commonencode(char *toencode, bool url) {
309 char *validchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
310 char *urlprefix = "%";
311 char *htmlprefix = "&#x";
312 char *urlsuffix = "";
313 char *htmlsuffix = ";";
314 char *prefix;
315 char *suffix;
316 char *tmp;
317 char *tmp2;
318 char *retval;
319 char buffer[3];
320 size_t size = 100;
321 size_t used = 1;
322
323 if(url) {
324 prefix = urlprefix;
325 suffix = urlsuffix;
326 } else {
327 prefix = htmlprefix;
328 suffix = htmlsuffix;
329 }
330
331 retval = malloc(size);
332 if(retval == NULL)
333 die("malloc failed\n");
334 *retval = '\0';
335
336 for(tmp = toencode; *tmp != '\0'; tmp++) {
337 if((size - used) < (strlen(prefix) + 2 + strlen(suffix)))
338 expandmalloc((void **)&retval, &size, __LINE__);
339 for(tmp2 = validchars; *tmp2 != '\0'; tmp2++) {
340 if((*tmp) == (*tmp2))
341 break;
342 }
343
344 if(*tmp2 != '\0') {
345 buffer[0] = *tmp2;
346 buffer[1] = '\0';
347 strcat(retval, buffer);
348 used++;
349 } else {
350 snprintf(buffer, 3, "%.2x", (int)(unsigned char)*tmp);
351 strcat(retval, prefix);
352 strcat(retval, buffer);
353 strcat(retval, suffix);
354 used += (strlen(prefix) + 2 + strlen(suffix));
355 }
356 }
357
358 return(retval);
359 }
360
361
362 /*
363 * urlencodes a string.
364 *
365 * Arguments: toencode - string to convert
366 *
367 * Returns: an encoded ASCII string stored in malloc:ed memory
368 */
369 char *
urlencode(char * toencode)370 urlencode(char *toencode) {
371 return(commonencode(toencode, TRUE));
372 }
373
374
375 /*
376 * htmlencodes a string.
377 *
378 * Arguments: toencode - string to convert
379 *
380 * Returns: an encoded ASCII string stored in malloc:ed memory
381 */
382 char *
htmlencode(char * toencode)383 htmlencode(char *toencode) {
384 return(commonencode(toencode, FALSE));
385 }
386
387
388 /*
389 * Takes a string and replaces variables with their values, then returns
390 * the adress of the new string to the client. This string will be
391 * valid until replacevariables is called again and doesn't need to
392 * be free:d. Used to create dynamic HTML pages.
393 *
394 * Arguments: input - the line to use as a base for the conversion
395 * cconf - session information
396 * entry - an (optional) entry to retrieve some of the
397 * variable values from
398 *
399 * Returns: void
400 */
401 char *
replacevariables(char * input,struct client_config * cconf,mp3entry * entry)402 replacevariables(char *input, struct client_config *cconf, mp3entry *entry)
403 {
404 static char *result = NULL;
405
406 char *str_nbsp = " ";
407 char *str_err = "Variable not allowed here";
408 char *str_dir = "DIR";
409 char *str_file = "FILE";
410 char *str_sep = "@";
411
412 char *start = input;
413 char *end;
414 char *variable;
415 char *encvariable;
416
417 /*
418 * VARIABLES USED:
419 *
420 * Static: (Can be used anywhere)
421 * @SERVERNAME@
422 * @PORT@
423 * @PATH@
424 *
425 * Non-Static: (Can only be used if tmp defined)
426 * FILE: DIR:
427 * ===== ====
428 * @NAME@ = tmp->name tmp->name
429 * @URL@ = tmp->name tmp->name + /index.html
430 * @LENGTH@ = tmp->length " "
431 * @TITLE@ = tmp->title "DIR"
432 * @SIZE@ = tmp->filesize " "
433 * @TYPE@ = "FILE" "DIR"
434 * @FPATH@ = tmp->path " "
435 */
436 char *variables[][2] = {
437 {"@SERVERNAME@", gconf.servername},
438 {"@PORT@", NULL},
439 {"@PATH@", cconf->requestpath},
440 {"@NAME@", str_err},
441 {"@URL@", str_err},
442 {"@LENGTH@", str_err},
443 {"@TITLE@", str_err},
444 {"@SIZE@", str_err},
445 {"@TYPE@", str_err},
446 {"@FPATH@", str_err},
447 {NULL, NULL}
448 };
449
450 int i;
451 size_t size = 100;
452 int used = 1;
453
454 /* Clean up after earlier invocations */
455 if(result != NULL) {
456 free(result);
457 result = NULL;
458 }
459
460 result = malloc(size);
461 if(result == NULL)
462 die("malloc failed\n");
463 *result = '\0';
464
465 variables[1][1] = itoa(gconf.port);
466 if(!entry) {
467 /* Do nothing */
468 } else if(IS_DIR(entry)) {
469 variables[3][1] = entry->name;
470 variables[4][1] = malloc(strlen(entry->name) +
471 strlen("/index.html") + 1);
472 sprintf(variables[4][1], "%s/index.html", entry->name);
473 variables[5][1] = str_nbsp;
474 variables[6][1] = str_dir;
475 variables[7][1] = str_nbsp;
476 variables[8][1] = str_dir;
477 variables[9][1] = str_nbsp;
478 } else {
479 variables[3][1] = entry->name;
480 variables[4][1] = entry->name;
481 variables[5][1] = itoa(entry->length);
482 variables[6][1] = entry->title;
483 variables[7][1] = itoa(entry->filesize);
484 variables[8][1] = str_file;
485 variables[9][1] = entry->path;
486 }
487
488 while((end = strchr(start, '@')) != NULL) {
489 debug(1, "end is %s\n", end);
490 debug(1, "end is %p, start is %p, diff %i, used is %i, size is %i\n", end, start, (int)(end - start), used, size);
491 while((used + end - start) > size) {
492 expandmalloc((void **)&result, &size, __LINE__);
493 }
494 strncat(result, start, (end - start));
495 used += (end - start);
496 variable = NULL;
497
498 for(i = 0; variables[i][0] != NULL; i++) {
499 if(!strncmp(end, variables[i][0],
500 strlen(variables[i][0]))) {
501 start = end + strlen(variables[i][0]);
502 variable = variables[i][1];
503 if(!strncmp(variables[i][0], "@URL@", strlen(variables[i][0])))
504 encvariable = urlencode(variable);
505 else
506 encvariable = htmlencode(variable);
507 }
508 }
509
510 if(!variable) {
511 start = end + 1;
512 variable = str_sep;
513 encvariable = str_sep;
514 }
515
516 while((used + strlen(encvariable)) > size)
517 expandmalloc((void **)&result, &size, __LINE__);
518 strcat(result, encvariable);
519 used += strlen(encvariable);
520
521 if(encvariable != str_sep)
522 free(encvariable);
523 }
524
525 while((used + strlen(start)) > size)
526 expandmalloc((void **)&result, &size, __LINE__);
527 strcat(result, start);
528 used += strlen(start);
529
530 free(variables[1][1]);
531 if(!entry) {
532 /* Do nothing */
533 } else if(IS_DIR(entry)) {
534 free(variables[4][1]);
535 } else {
536 free(variables[5][1]);
537 free(variables[7][1]);
538 }
539
540 return(result);
541 }
542
543
544 /*
545 * Given a hostentry, finds the FQDN (Fully Qualified Domain Name)
546 *
547 * Arguments: host - the hostentry to base the search on
548 *
549 * Returns: the FQDN as a string stored in malloc:ed memory
550 */
551 static char *
findfqdn(struct hostent * host)552 findfqdn(struct hostent *host)
553 {
554 int i;
555
556 if (strchr(host->h_name, '.'))
557 return strdup(host->h_name);
558
559 for (i=0; host->h_aliases[i] != NULL; i++) {
560 if (!strchr(host->h_aliases[i], '.'))
561 continue;
562 else if (!strncasecmp(host->h_aliases[i], host->h_name, strlen(host->h_name)))
563 return strdup(host->h_aliases[i]);
564 }
565
566 return NULL;
567 }
568
569
570 /*
571 * Finds the name (or address) which clients should use when connecting to
572 * the server.
573 *
574 * Arguments: none
575 *
576 * Returns: the name or address as a string stored in malloc:ed memory
577 */
578 char *
getservername()579 getservername()
580 {
581 #ifndef MAXHOSTNAMELEN
582 #define MAXHOSTNAMELEN 256
583 #endif
584
585 char buf[MAXHOSTNAMELEN + 1];
586 char *hostname = NULL;
587 struct hostent *host;
588
589 if (gethostname(buf, sizeof(buf) - 1) == 0) {
590 buf[sizeof(buf) - 1] = '\0';
591
592 if ((host = gethostbyname(buf)) &&
593 (hostname = findfqdn(host))) {
594 return hostname;
595 }
596
597 if (host && host->h_addr_list[0] != NULL)
598 return strdup(inet_ntoa(*(struct in_addr *)host->h_addr_list[0]));
599 }
600
601 logmsg("Failed to get hostname, please set serveraddress in configfile\n");
602 return NULL;
603 }
604
605
606 /*
607 * Writes ShoutCast type metadata to the client (for format information, see
608 * the developer docs at the Ample homepage).
609 *
610 * Arguments: to - the stream to write the data to
611 * entry - the entry that is currently playing
612 * metaflag - has information about this particular entry
613 * been written before?
614 *
615 * Returns: TRUE if the operation was successful, else FALSE
616 */
617 bool
writemetadata(FILE * to,mp3entry * entry,bool * metaflag)618 writemetadata(FILE *to, mp3entry *entry, bool *metaflag)
619 {
620 int msglen;
621 int padding;
622 int towrite;
623 int written;
624 char *buf;
625
626 if(*metaflag) {
627 *metaflag = FALSE;
628
629 msglen = strlen(entry->title) + 28;
630 padding = 16 - msglen % 16;
631 towrite = msglen + padding + 1;
632
633 buf = malloc(towrite);
634 memset(buf, 0, towrite);
635 sprintf(buf, "%cStreamTitle='%s';StreamUrl='';",
636 (msglen + padding)/16, entry->title);
637 } else {
638 towrite = 1;
639 buf = malloc(towrite);
640 memset(buf, 0, towrite);
641 }
642
643 written = fwrite(buf, sizeof(char), towrite, to);
644 free(buf);
645 if(written == towrite)
646 return(TRUE);
647 else
648 return(FALSE);
649 }
650
651
652 /*
653 * Removes trailing spaces from a string.
654 *
655 * Arguments: buffer - the string to process
656 *
657 * Returns: void
658 */
659 static void
stripspaces(char * buffer)660 stripspaces(char *buffer)
661 {
662 int i = 0;
663
664 while(*(buffer + i) != '\0')
665 i++;
666
667 for(;i >= 0; i--) {
668 if(*(buffer + i) == ' ')
669 *(buffer + i) = '\0';
670 else if(*(buffer + i) == '\0')
671 continue;
672 else
673 break;
674 }
675 }
676
677
678 /*
679 * Sets the title of an MP3 entry based on its ID3v1 tag.
680 *
681 * Arguments: file - the MP3 file to scen for a ID3v1 tag
682 * entry - the entry to set the title in
683 *
684 * Returns: TRUE if a title was found and created, else FALSE
685 */
686 static bool
setid3v1title(FILE * file,mp3entry * entry)687 setid3v1title(FILE *file, mp3entry *entry)
688 {
689 char buffer[31];
690 int offsets[3] = {-95,-65,-125};
691 int i;
692 char *result;
693
694 result = (char *)malloc(3*30 + 2*3 + 1);
695 *result = '\0';
696
697 for(i=0;i<3;i++) {
698 if(fseek(file, offsets[i], SEEK_END) != 0) {
699 free(result);
700 return FALSE;
701 }
702
703
704 fgets(buffer, 31, file);
705 stripspaces(buffer);
706 if(buffer[0] != '\0' && *result != '\0')
707 strcat(result, " - ");
708 strcat(result, buffer);
709 }
710
711 if(*result == '\0') {
712 free(result);
713 return(FALSE);
714 } else {
715 entry->title = (char *)realloc(result, strlen(result) + 1);
716 return(TRUE);
717 }
718 }
719
720
721 /*
722 * Sets the title of an MP3 entry based on its ID3v2 tag.
723 *
724 * Arguments: file - the MP3 file to scen for a ID3v2 tag
725 * entry - the entry to set the title in
726 *
727 * Returns: TRUE if a title was found and created, else FALSE
728 */
729 static bool
setid3v2title(FILE * file,mp3entry * entry)730 setid3v2title(FILE *file, mp3entry *entry)
731 {
732 char *buffer;
733 int minframesize;
734 int size, readsize = 0, headerlen;
735 char *title = NULL;
736 char *artist = NULL;
737 char header[10];
738 unsigned short int version;
739
740 /* 10 = headerlength */
741 if(entry->id3v2len < 10)
742 return(FALSE);
743
744 /* Check version */
745 fseek(file, 0, SEEK_SET);
746 fread(header, sizeof(char), 10, file);
747 version = (unsigned short int)header[3];
748
749 /* Read all frames in the tag */
750 size = entry->id3v2len - 10;
751 buffer = malloc(size + 1);
752 if(size != (int)fread(buffer, sizeof(char), size, file)) {
753 free(buffer);
754 return(FALSE);
755 }
756 *(buffer + size) = '\0';
757
758 /* Set minimun frame size according to ID3v2 version */
759 if(version > 2)
760 minframesize = 12;
761 else
762 minframesize = 8;
763
764 /*
765 * We must have at least minframesize bytes left for the
766 * remaining frames to be interesting
767 */
768 while(size - readsize > minframesize) {
769
770 /* Read frame header and check length */
771 if(version > 2) {
772 memcpy(header, (buffer + readsize), 10);
773 readsize += 10;
774 headerlen = UNSYNC(header[4], header[5],
775 header[6], header[7]);
776 } else {
777 memcpy(header, (buffer + readsize), 6);
778 readsize += 6;
779 headerlen = (header[3] << 16) +
780 (header[4] << 8) +
781 (header[5]);
782 }
783 if(headerlen < 1)
784 continue;
785
786 /* Check for certain frame headers */
787 if(!strncmp(header, "TPE1", strlen("TPE1")) ||
788 !strncmp(header, "TP1", strlen("TP1"))) {
789 readsize++;
790 headerlen--;
791 if(headerlen > (size - readsize))
792 headerlen = (size - readsize);
793 artist = malloc(headerlen + 1);
794 snprintf(artist, headerlen + 1, "%s",
795 (buffer + readsize));
796 readsize += headerlen;
797 } else if(!strncmp(header, "TIT2", strlen("TIT2")) ||
798 !strncmp(header, "TT2", strlen("TT2"))) {
799 readsize++;
800 headerlen--;
801 if(headerlen > (size - readsize))
802 headerlen = (size - readsize);
803 title = malloc(headerlen + 1);
804 snprintf(title, headerlen + 1, "%s",
805 (buffer + readsize));
806 readsize += headerlen;
807 }
808 }
809
810 /* Done, let's clean up */
811 if(artist && title) {
812 entry->title = malloc(strlen(artist) + strlen(title) + 4);
813 snprintf(entry->title, strlen(artist) + strlen(title) + 4,
814 "%s - %s", artist, title);
815 free(artist);
816 free(title);
817 } else if(artist) {
818 entry->title = artist;
819 } else if(title) {
820 entry->title = title;
821 }
822
823 free(buffer);
824 return(entry->title != NULL);
825 }
826
827
828 /*
829 * Calculates the size of the ID3v2 tag.
830 *
831 * Arguments: file - the file to search for a tag.
832 *
833 * Returns: the size of the tag or 0 if none was found
834 */
835 static int
getid3v2len(FILE * file)836 getid3v2len(FILE *file)
837 {
838 char buf[6];
839 int offset;
840
841 /* Make sure file has a ID3 tag */
842 if((fseek(file, 0, SEEK_SET) != 0) ||
843 (fread(buf, sizeof(char), 6, file) != 6) ||
844 (strncmp(buf, "ID3", strlen("ID3")) != 0))
845 offset = 0;
846 /* Now check what the ID3v2 size field says */
847 else if(fread(buf, sizeof(char), 4, file) != 4)
848 offset = 0;
849 else
850 offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
851
852 return(offset);
853 }
854
855
856 /*
857 * Calculates the size of the ID3v1 tag.
858 *
859 * Arguments: file - the file to search for a tag.
860 *
861 * Returns: the size of the tag or 0 if none was found
862 */
863 static int
getid3v1len(FILE * file)864 getid3v1len(FILE *file)
865 {
866 char buf[3];
867 int offset;
868
869 /* Check if we find "TAG" 128 bytes from EOF */
870 if((fseek(file, -128, SEEK_END) != 0) ||
871 (fread(buf, sizeof(char), 3, file) != 3) ||
872 (strncmp(buf, "TAG", 3) != 0))
873 offset = 0;
874 else
875 offset = 128;
876
877 return offset;
878 }
879
880
881 /*
882 * Calculates the length (in seconds) of an MP3 file. Currently this code
883 * doesn't care about VBR (Variable BitRate) files since it would have to
884 * scan through the entire file but this should become a config option
885 * in the future.
886 *
887 * Arguments: file - the file to calculate the length upon
888 * entry - the entry to update with the length
889 *
890 * Returns: the song length in seconds,
891 * -1 means that it couldn't be calculated
892 */
893 static int
getsonglength(FILE * file,mp3entry * entry)894 getsonglength(FILE *file, mp3entry *entry) {
895
896 #ifdef HAVE_STDINT_H
897 uint32_t header;
898 #else
899 long header;
900 #endif
901 int version;
902 int layer;
903 int bitindex;
904 int bitrate;
905 int freqindex;
906 int frequency;
907
908 double bpf;
909 double tpf;
910 int i;
911
912 /* Start searching after ID3v2 header */
913 if(fseek(file, entry->id3v2len, SEEK_SET))
914 return -1;
915
916 /* Fill up header with first 24 bits */
917 for(version = 0; version < 3; version++) {
918 header <<= 8;
919 if(!fread(&header, 1, 1, file))
920 return -1;
921 }
922
923 /* Loop trough file until we find a frame header */
924 restart:
925 do {
926 header <<= 8;
927 if(!fread(&header, 1, 1, file))
928 return -1;
929 } while(!CHECKSYNC(header));
930
931
932 /*
933 * Some files are filled with garbage in the beginning,
934 * if the bitrate index of the header is binary 1111
935 * that is a good indicator
936 */
937 if((header & 0xF000) == 0xF000)
938 goto restart;
939
940 debug(5, "We found %x-%x-%x-%x and checksync %i and test %x\n",
941 BYTE0(header), BYTE1(header), BYTE2(header), BYTE3(header),
942 CHECKSYNC(header), (header & 0xF000) == 0xF000);
943
944 /* MPEG Audio Version */
945 switch((header & 0x180000) >> 19) {
946 case 2:
947 version = 2;
948 break;
949 case 3:
950 version = 1;
951 break;
952 default:
953 return -1;
954 }
955
956 /* Layer */
957 switch((header & 0x060000) >> 17) {
958 case 1:
959 layer = 3;
960 break;
961 case 2:
962 layer = 2;
963 break;
964 case 3:
965 layer = 1;
966 break;
967 default:
968 return -1;
969 }
970
971 /* Bitrate */
972 bitindex = (header & 0xF000) >> 12;
973 bitrate = bitrate_table[version-1][layer-1][bitindex];
974 if(bitrate == 0)
975 return -1;
976
977 /* Sampling frequency */
978 freqindex = (header & 0x0C00) >> 10;
979 frequency = freqtab[version-1][freqindex];
980 if(frequency == 0)
981 return -1;
982
983 debug(2, "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i\n",
984 version, layer, bitindex, bitrate, freqindex, frequency);
985
986 /* Calculate bytes per frame, calculation depends on layer */
987 switch(layer) {
988 case 1:
989 bpf = bitrate_table[version - 1][layer - 1][bitindex];
990 bpf *= 12000.0 * 4.0;
991 bpf /= freqtab[version-1][freqindex] << (version - 1);
992 break;
993 case 2:
994 case 3:
995 bpf = bitrate_table[version - 1][layer - 1][bitindex];
996 bpf *= 144000;
997 bpf /= freqtab[version-1][freqindex] << (version - 1);
998 break;
999 default:
1000 bpf = 1.0;
1001 }
1002
1003 /* Calculate time per frame */
1004 tpf = bs[layer];
1005 tpf /= freqtab[version-1][freqindex] << (version - 1);
1006
1007 debug(3, "BitRate is %i, FileLength is %i, TPF is %f and BPF is %f, we have %f frames in one second\n", bitrate, entry->filesize, tpf, bpf, 1/tpf);
1008
1009 /*
1010 * Now song length is
1011 * ((filesize)/(bytes per frame))*(time per frame)
1012 */
1013 return (int)(((float)entry->filesize/bpf)*tpf);
1014 }
1015
1016
1017 /*
1018 * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc)
1019 * about an MP3 file and updates it's entry accordingly.
1020 *
1021 * Arguments: entry - the entry to check and update with the new information
1022 *
1023 * Returns: void
1024 */
1025 void
checkmp3info(mp3entry * entry)1026 checkmp3info(mp3entry *entry)
1027 {
1028 FILE *file;
1029 char *copy;
1030 char *title;
1031
1032 if((file = fopen(entry->path, "r")) == NULL)
1033 return;
1034
1035 entry->id3v2len = getid3v2len(file);
1036 entry->id3v1len = getid3v1len(file);
1037 entry->length = getsonglength(file, entry);
1038 entry->title = NULL;
1039
1040 if(HASID3V2(entry))
1041 setid3v2title(file, entry);
1042
1043 if(HASID3V1(entry) && !entry->title)
1044 setid3v1title(file, entry);
1045
1046 if(!entry->title) {
1047 copy = strdup(entry->path);
1048 title = basename(copy);
1049 entry->title = malloc(strlen(title) - 3);
1050 snprintf(entry->title, strlen(title) - 3, "%s", title);
1051 free(copy);
1052 }
1053
1054 fclose(file);
1055 }
1056