2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
37 #include <sys/types.h>
44 const struct input_plugin_ops
*ops
;
45 struct input_plugin_data data
;
46 unsigned int open
: 1;
52 * pcm is converted to 16-bit signed little-endian stereo
53 * NOTE: no conversion is done if channels > 2 or bits > 16
55 void (*pcm_convert
)(char *, const char *, int);
56 void (*pcm_convert_in_place
)(char *, int);
59 * 2 if 8-bit stereo or 16-bit mono
62 int pcm_convert_scale
;
66 struct list_head node
;
70 const char * const *extensions
;
71 const char * const *mime_types
;
72 const struct input_plugin_ops
*ops
;
75 static const char * const plugin_dir
= LIBDIR
"/" PACKAGE
"/ip";
76 static LIST_HEAD(ip_head
);
79 static int http_connection_timeout
= 5e3
;
80 static int http_read_timeout
= 5e3
;
82 static const char *get_extension(const char *filename
)
86 ext
= filename
+ strlen(filename
) - 1;
87 while (ext
>= filename
&& *ext
!= '/') {
97 static const struct input_plugin_ops
*get_ops_by_filename(const char *filename
)
102 ext
= get_extension(filename
);
105 list_for_each_entry(ip
, &ip_head
, node
) {
106 const char * const *exts
= ip
->extensions
;
109 for (i
= 0; exts
[i
]; i
++) {
110 if (strcasecmp(ext
, exts
[i
]) == 0)
117 static const struct input_plugin_ops
*get_ops_by_mime_type(const char *mime_type
)
121 list_for_each_entry(ip
, &ip_head
, node
) {
122 const char * const *types
= ip
->mime_types
;
125 for (i
= 0; types
[i
]; i
++) {
126 if (strcasecmp(mime_type
, types
[i
]) == 0)
133 static int do_http_get(const char *uri
, struct http_header
**headersp
, int *codep
, char **reasonp
)
135 char *user
, *pass
, *host
, *path
, *reason
;
136 int port
, sock
, i
, rc
, code
;
137 struct http_header
*h
;
143 if (http_parse_uri(uri
, &user
, &pass
, &host
, &port
, &path
))
144 return -IP_ERROR_INVALID_URI
;
146 /* d_print("%s -> '%s':'%s'@'%s':%d'%s'\n", uri, user, pass, host, port, path); */
148 sock
= http_open(host
, port
, http_connection_timeout
);
154 return -IP_ERROR_ERRNO
;
157 h
= xnew(struct http_header
, 5);
159 h
[i
].key
= xstrdup("Host");
160 h
[i
].val
= xstrdup(host
);
162 h
[i
].key
= xstrdup("User-Agent");
163 h
[i
].val
= xstrdup(PACKAGE
"/" VERSION
);
165 h
[i
].key
= xstrdup("Icy-MetaData");
166 h
[i
].val
= xstrdup("1");
172 snprintf(buf
, sizeof(buf
), "%s:%s", user
, pass
);
173 encoded
= base64_encode(buf
);
174 if (encoded
== NULL
) {
175 d_print("couldn't base64 encode '%s'\n", buf
);
177 snprintf(buf
, sizeof(buf
), "Basic %s", encoded
);
179 h
[i
].key
= xstrdup("Authorization");
180 h
[i
].val
= xstrdup(buf
);
188 rc
= http_get(sock
, path
, h
, &code
, &reason
, headersp
, http_read_timeout
);
189 http_headers_free(h
);
196 d_print("error: %s\n", strerror(errno
));
198 return -IP_ERROR_ERRNO
;
200 d_print("error parsing HTTP response\n");
202 return -IP_ERROR_HTTP_RESPONSE
;
204 d_print("HTTP response: %d %s\n", code
, reason
);
209 return -IP_ERROR_HTTP_STATUS
;
215 static int setup_remote(struct input_plugin
*ip
, const struct http_header
*headers
, int sock
)
219 val
= http_headers_get_value(headers
, "Content-Type");
221 ip
->ops
= get_ops_by_mime_type(val
);
222 if (ip
->ops
== NULL
) {
223 d_print("unsupported content type: %s\n", val
);
225 return -IP_ERROR_FILE_FORMAT
;
228 const char *type
= "audio/mpeg";
230 d_print("assuming %s content type\n", type
);
231 ip
->ops
= get_ops_by_mime_type(type
);
232 if (ip
->ops
== NULL
) {
233 d_print("unsupported content type: %s\n", type
);
235 return -IP_ERROR_FILE_FORMAT
;
240 ip
->data
.metadata
= (char *)xmalloc(16 * 255 + 1);
242 val
= http_headers_get_value(headers
, "icy-metaint");
246 if (str_to_int(val
, &lint
) == 0 && lint
>= 0) {
247 ip
->data
.metaint
= lint
;
248 d_print("metaint: %d\n", ip
->data
.metaint
);
254 static void dump_lines(char **lines
)
258 for (i
= 0; lines
[i
]; i
++)
259 d_print("%d='%s'\n", i
, lines
[i
]);
262 static int read_pls(struct input_plugin
*ip
, int sock
)
264 struct http_header
*headers
;
269 rc
= http_read_body(sock
, &body
, http_read_timeout
);
272 return -IP_ERROR_ERRNO
;
274 lines
= pls_get_files(body
);
278 d_print("error parsing playlist\n");
279 return -IP_ERROR_HTTP_RESPONSE
;
282 if (lines
[0] == NULL
) {
284 d_print("empty playlist\n");
285 return -IP_ERROR_HTTP_RESPONSE
;
288 sock
= do_http_get(lines
[0], &headers
, &code
, &reason
);
289 free_str_array(lines
);
291 ip
->http_code
= code
;
292 ip
->http_reason
= reason
;
296 rc
= setup_remote(ip
, headers
, sock
);
297 http_headers_free(headers
);
301 static int read_m3u(struct input_plugin
*ip
, int sock
)
303 struct http_header
*headers
;
308 rc
= http_read_body(sock
, &body
, http_read_timeout
);
311 return -IP_ERROR_ERRNO
;
313 lines
= bsplit(body
, strlen(body
), '\n', 0);
316 for (i
= 0; lines
[i
]; i
++) {
317 char *ptr
= strchr(lines
[i
], '\r');
322 if (i
> 0 && lines
[i
- 1][0] == 0) {
328 if (lines
[0] == NULL
) {
330 d_print("empty playlist\n");
331 return -IP_ERROR_HTTP_RESPONSE
;
334 sock
= do_http_get(lines
[0], &headers
, &code
, &reason
);
335 free_str_array(lines
);
337 ip
->http_code
= code
;
338 ip
->http_reason
= reason
;
340 if (sock
== -IP_ERROR_INVALID_URI
)
341 sock
= -IP_ERROR_HTTP_RESPONSE
;
345 rc
= setup_remote(ip
, headers
, sock
);
346 http_headers_free(headers
);
350 static int open_remote(struct input_plugin
*ip
)
352 struct input_plugin_data
*d
= &ip
->data
;
355 struct http_header
*headers
;
358 sock
= do_http_get(d
->filename
, &headers
, &code
, &reason
);
360 ip
->http_code
= code
;
361 ip
->http_reason
= reason
;
365 val
= http_headers_get_value(headers
, "Content-Type");
367 d_print("Content-Type: %s\n", val
);
368 if (strcasecmp(val
, "audio/x-scpls") == 0) {
369 http_headers_free(headers
);
370 return read_pls(ip
, sock
);
371 } else if (strcasecmp(val
, "audio/m3u") == 0) {
372 http_headers_free(headers
);
373 return read_m3u(ip
, sock
);
377 rc
= setup_remote(ip
, headers
, sock
);
378 http_headers_free(headers
);
382 static int open_file(struct input_plugin
*ip
)
384 ip
->ops
= get_ops_by_filename(ip
->data
.filename
);
386 return -IP_ERROR_UNRECOGNIZED_FILE_TYPE
;
387 ip
->data
.fd
= open(ip
->data
.filename
, O_RDONLY
);
388 if (ip
->data
.fd
== -1) {
390 return -IP_ERROR_ERRNO
;
395 void ip_init_plugins(void)
400 dir
= opendir(plugin_dir
);
402 fprintf(stderr
, "couldn't open directory `%s': %s\n", plugin_dir
, strerror(errno
));
405 while ((d
= readdir(dir
)) != NULL
) {
411 if (d
->d_name
[0] == '.')
413 ext
= strrchr(d
->d_name
, '.');
416 if (strcmp(ext
, ".so"))
419 snprintf(filename
, sizeof(filename
), "%s/%s", plugin_dir
, d
->d_name
);
421 so
= dlopen(filename
, RTLD_NOW
);
423 fprintf(stderr
, "%s\n", dlerror());
427 ip
= xnew(struct ip
, 1);
429 if (!get_symbol(so
, "ip_extensions", filename
, (void **)&ip
->extensions
, 0)) {
435 if (!get_symbol(so
, "ip_mime_types", filename
, (void **)&ip
->mime_types
, 0)) {
441 if (!get_symbol(so
, "ip_ops", filename
, (void **)&ip
->ops
, 0)) {
447 ip
->name
= xstrndup(d
->d_name
, ext
- d
->d_name
);
450 list_add_tail(&ip
->node
, &ip_head
);
455 struct input_plugin
*ip_new(const char *filename
)
457 struct input_plugin
*ip
= xnew(struct input_plugin
, 1);
463 ip
->http_reason
= NULL
;
465 ip
->data
.filename
= xstrdup(filename
);
468 ip
->data
.remote
= is_url(filename
);
469 ip
->data
.metadata_changed
= 0;
470 ip
->data
.counter
= 0;
471 ip
->data
.metaint
= 0;
472 ip
->data
.metadata
= NULL
;
474 ip
->data
.private = NULL
;
478 void ip_delete(struct input_plugin
*ip
)
482 free(ip
->data
.filename
);
486 int ip_open(struct input_plugin
*ip
)
488 int rc
, bits
, is_signed
, channels
;
493 BUG_ON(ip
->data
.filename
== NULL
);
494 BUG_ON(ip
->data
.fd
!= -1);
497 if (ip
->data
.remote
) {
498 rc
= open_remote(ip
);
504 d_print("opening `%s' failed: %d %s\n", ip
->data
.filename
, rc
, rc
== -1 ? strerror(errno
) : "");
508 BUG_ON(ip
->data
.fd
== -1);
509 BUG_ON(ip
->ops
== NULL
);
511 BUG_ON(ip
->ops
->open
== NULL
);
512 BUG_ON(ip
->ops
->close
== NULL
);
513 BUG_ON(ip
->ops
->read
== NULL
);
514 BUG_ON(ip
->ops
->seek
== NULL
);
515 BUG_ON(ip
->ops
->read_comments
== NULL
);
516 BUG_ON(ip
->ops
->duration
== NULL
);
518 rc
= ip
->ops
->open(&ip
->data
);
520 d_print("opening file `%s' failed: %d %s\n", ip
->data
.filename
, rc
, rc
== -1 ? strerror(errno
) : "");
521 if (ip
->data
.fd
!= -1)
525 free(ip
->data
.metadata
);
526 ip
->data
.metadata
= NULL
;
530 ip
->pcm_convert_scale
= 1;
531 ip
->pcm_convert
= NULL
;
532 ip
->pcm_convert_in_place
= NULL
;
533 bits
= sf_get_bits(ip
->data
.sf
);
534 is_signed
= sf_get_signed(ip
->data
.sf
);
535 channels
= sf_get_channels(ip
->data
.sf
);
538 ip
->pcm_convert_scale
= 4;
540 ip
->pcm_convert
= convert_s8_1ch_to_s16_2ch
;
542 ip
->pcm_convert
= convert_u8_1ch_to_s16_2ch
;
544 } else if (channels
== 2) {
545 ip
->pcm_convert_scale
= 2;
547 ip
->pcm_convert
= convert_s8_2ch_to_s16_2ch
;
549 ip
->pcm_convert
= convert_u8_2ch_to_s16_2ch
;
552 } else if (bits
== 16) {
554 ip
->pcm_convert_scale
= 2;
555 ip
->pcm_convert
= convert_16_1ch_to_16_2ch
;
558 int bigendian
= sf_get_bigendian(ip
->data
.sf
);
562 ip
->pcm_convert_in_place
= convert_s16_be_to_s16_le
;
565 ip
->pcm_convert_in_place
= convert_u16_be_to_s16_le
;
567 ip
->pcm_convert_in_place
= convert_u16_le_to_s16_le
;
572 d_print("pcm convert: scale=%d convert=%d convert_in_place=%d\n",
573 ip
->pcm_convert_scale
,
574 ip
->pcm_convert
!= NULL
,
575 ip
->pcm_convert_in_place
!= NULL
);
581 int ip_close(struct input_plugin
*ip
)
587 rc
= ip
->ops
->close(&ip
->data
);
588 BUG_ON(ip
->data
.private);
589 if (ip
->data
.fd
!= -1)
591 free(ip
->data
.metadata
);
592 free(ip
->http_reason
);
594 ip
->data
.metadata
= NULL
;
595 ip
->http_reason
= NULL
;
600 ip
->pcm_convert_scale
= -1;
601 ip
->pcm_convert
= NULL
;
602 ip
->pcm_convert_in_place
= NULL
;
606 int ip_read(struct input_plugin
*ip
, char *buffer
, int count
)
610 /* 4608 seems to be optimal for mp3s, 4096 for oggs */
619 FD_SET(ip
->data
.fd
, &readfds
);
620 /* zero timeout -> return immediately */
623 rc
= select(ip
->data
.fd
+ 1, &readfds
, NULL
, NULL
, &tv
);
625 d_print("select: error: %s\n", strerror(errno
));
636 if (ip
->pcm_convert_scale
> 1) {
637 /* use tmp buffer for 16-bit mono and 8-bit */
639 count
/= ip
->pcm_convert_scale
;
640 if (count
> sizeof(tmp
))
644 rc
= ip
->ops
->read(&ip
->data
, buf
, count
);
648 d_print("error: %s\n", strerror(errno
));
651 int sample_size
= sf_get_sample_size(ip
->data
.sf
);
653 if (ip
->pcm_convert_in_place
!= NULL
)
654 ip
->pcm_convert_in_place(buf
, rc
/ sample_size
);
655 if (ip
->pcm_convert
!= NULL
)
656 ip
->pcm_convert(buffer
, tmp
, rc
/ sample_size
);
657 rc
*= ip
->pcm_convert_scale
;
662 int ip_seek(struct input_plugin
*ip
, double offset
)
669 return -IP_ERROR_FUNCTION_NOT_SUPPORTED
;
670 rc
= ip
->ops
->seek(&ip
->data
, offset
);
676 int ip_read_comments(struct input_plugin
*ip
, struct keyval
**comments
)
682 rc
= ip
->ops
->read_comments(&ip
->data
, comments
);
686 int ip_duration(struct input_plugin
*ip
)
692 rc
= ip
->ops
->duration(&ip
->data
);
696 sample_format_t
ip_get_sf(struct input_plugin
*ip
)
702 const char *ip_get_filename(struct input_plugin
*ip
)
704 return ip
->data
.filename
;
707 const char *ip_get_metadata(struct input_plugin
*ip
)
710 return ip
->data
.metadata
;
713 int ip_is_remote(struct input_plugin
*ip
)
715 return ip
->data
.remote
;
718 int ip_metadata_changed(struct input_plugin
*ip
)
720 int ret
= ip
->data
.metadata_changed
;
723 ip
->data
.metadata_changed
= 0;
727 int ip_eof(struct input_plugin
*ip
)
733 char *ip_get_error_msg(struct input_plugin
*ip
, int rc
, const char *arg
)
739 snprintf(buffer
, sizeof(buffer
), "%s: %s", arg
, strerror(errno
));
741 case IP_ERROR_UNRECOGNIZED_FILE_TYPE
:
742 snprintf(buffer
, sizeof(buffer
),
743 "%s: unrecognized filename extension", arg
);
745 case IP_ERROR_FUNCTION_NOT_SUPPORTED
:
746 snprintf(buffer
, sizeof(buffer
),
747 "%s: function not supported", arg
);
749 case IP_ERROR_FILE_FORMAT
:
750 snprintf(buffer
, sizeof(buffer
),
751 "%s: file format not supported or corrupted file",
754 case IP_ERROR_INVALID_URI
:
755 snprintf(buffer
, sizeof(buffer
), "%s: invalid URI", arg
);
757 case IP_ERROR_SAMPLE_FORMAT
:
758 snprintf(buffer
, sizeof(buffer
),
759 "%s: input plugin doesn't support the sample format",
762 case IP_ERROR_HTTP_RESPONSE
:
763 snprintf(buffer
, sizeof(buffer
), "%s: invalid HTTP response", arg
);
765 case IP_ERROR_HTTP_STATUS
:
766 snprintf(buffer
, sizeof(buffer
), "%s: %d %s", arg
, ip
->http_code
, ip
->http_reason
);
767 free(ip
->http_reason
);
768 ip
->http_reason
= NULL
;
771 case IP_ERROR_INTERNAL
:
772 snprintf(buffer
, sizeof(buffer
), "%s: internal error", arg
);
774 case IP_ERROR_SUCCESS
:
776 snprintf(buffer
, sizeof(buffer
),
777 "%s: this is not an error (%d), this is a bug",
781 return xstrdup(buffer
);
784 static int strptrcmp(const void *a
, const void *b
)
786 const char *as
= *(char **)a
;
787 const char *bs
= *(char **)b
;
789 return strcmp(as
, bs
);
792 char **ip_get_supported_extensions(void)
800 exts
= xnew(char *, size
);
801 list_for_each_entry(ip
, &ip_head
, node
) {
802 const char * const *e
= ip
->extensions
;
804 for (i
= 0; e
[i
]; i
++) {
805 if (count
== size
- 1) {
807 exts
= xrenew(char *, exts
, size
);
809 exts
[count
++] = xstrdup(e
[i
]);
813 qsort(exts
, count
, sizeof(char *), strptrcmp
);
817 void ip_dump_plugins(void)
822 printf("Input Plugins: %s\n", plugin_dir
);
823 list_for_each_entry(ip
, &ip_head
, node
) {
824 printf(" %s:\n File Types:", ip
->name
);
825 for (i
= 0; ip
->extensions
[i
]; i
++)
826 printf(" %s", ip
->extensions
[i
]);
827 printf("\n MIME Types:");
828 for (i
= 0; ip
->mime_types
[i
]; i
++)
829 printf(" %s", ip
->mime_types
[i
]);