Mercurial > audlegacy-plugins
view src/madplug/input.c @ 972:cf7021ca4e7b trunk
[svn] Add lastfm:// transport, an abstract VFS class which derives from curl
to provide lastfm radio support. Written by majeru with some cleanups
by me. Most last.fm metadata support isn't yet implemented, however, and
will need to be done by majeru. ;)
| author | nenolod |
|---|---|
| date | Sun, 22 Apr 2007 04:16:08 -0700 |
| parents | d200de50a1fc |
| children | 8b1685669148 |
line wrap: on
line source
/* * mad plugin for audacious * Copyright (C) 2005-2007 William Pitcock, Yoshiki Yazawa * * Portions derived from xmms-mad: * Copyright (C) 2001-2002 Sam Clegg - See COPYING * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #ifdef HAVE_ASSERT_H #include <assert.h> #endif /* HAVE_ASSERT_H */ #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif /* HAVE_SYS_TYPES_H */ #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> #endif /* HAVE_SYS_SOCKET_H */ #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif /* HAVE_NETINET_IN_H */ #ifdef HAVE_ARPA_INET_H #include <arpa/inet.h> #endif /* HAVE_ARPA_INET_H */ #ifdef HAVE_NETDB_H #include <netdb.h> #endif /* HAVE_NETDB_H */ #ifdef HAVE_SYS_STAT_H #include <sys/stat.h> #endif /* HAVE_SYS_STAT_H */ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif /* HAVE_SYS_TIME_H */ #include <fcntl.h> #include <errno.h> #include <audacious/util.h> #include "input.h" #include "replaygain.h" #define DIR_SEPARATOR '/' #define HEADER_SIZE 256 #define LINE_LENGTH 256 extern gboolean scan_file(struct mad_info_t *info, gboolean fast); /** * init the mad_info_t struct. */ gboolean input_init(struct mad_info_t * info, const char *url) { #ifdef DEBUG g_message("f: input_init"); #endif memset(info, 0, sizeof(struct mad_info_t)); info->fmt = FMT_S16_LE; info->channels = -1; info->mpeg_layer = -1; info->size = -1; info->freq = -1; info->seek = -1; info->duration = mad_timer_zero; info->pos = mad_timer_zero; info->url = g_strdup(url); info->current_frame = 0; info->frames = 0; info->bitrate = 0; info->vbr = 0; info->mode = 0; info->title = 0; info->offset = 0; info->prev_title = NULL; info->replaygain_album_str = 0; info->replaygain_track_str = 0; info->replaygain_album_peak_str = 0; info->replaygain_track_peak_str = 0; info->mp3gain_undo_str = 0; info->mp3gain_minmax_str = 0; // from input_read_replaygain() info->has_replaygain = FALSE; info->replaygain_album_scale = -1; info->replaygain_track_scale = -1; info->mp3gain_undo = -77; info->mp3gain_minmax = -77; info->tuple = NULL; info->filename = g_strdup(url); info->infile = vfs_fopen(info->filename, "rb"); if (info->infile == NULL) { return FALSE; } // obtain file size vfs_fseek(info->infile, 0, SEEK_END); info->size = vfs_ftell(info->infile); vfs_fseek(info->infile, 0, SEEK_SET); info->remote = info->size == 0 ? TRUE : FALSE; //proxy connection may result in non-zero size. if(audmad_is_remote((gchar *)url)) info->remote = TRUE; #ifdef DEBUG g_message("i: info->size = %lu", (long unsigned int)info->size); g_message("e: input_init"); #endif return TRUE; } /* return length in letters */ size_t mad_ucs4len(id3_ucs4_t *ucs) { id3_ucs4_t *ptr = ucs; size_t len = 0; while(*ptr++ != 0) len++; return len; } /* duplicate id3_ucs4_t string. new string will be terminated with 0. */ id3_ucs4_t *mad_ucs4dup(id3_ucs4_t *org) { id3_ucs4_t *new = NULL; size_t len = mad_ucs4len(org); new = g_malloc0((len + 1) * sizeof(id3_ucs4_t)); memcpy(new, org, len * sizeof(id3_ucs4_t)); *(new + len) = 0; //terminate return new; } #define BYTES(x) ((x) * sizeof(id3_ucs4_t)) id3_ucs4_t *mad_parse_genre(const id3_ucs4_t *string) { id3_ucs4_t *ret = NULL; id3_ucs4_t *tmp = NULL; id3_ucs4_t *genre = NULL; id3_ucs4_t *ptr, *end, *tail, *tp; size_t ret_len = 0; //num of ucs4 char! size_t tmp_len = 0; size_t string_len = 0; gboolean is_num = TRUE; if(!string) return NULL; string_len = mad_ucs4len((id3_ucs4_t *)string); tail = (id3_ucs4_t *)string + string_len; if(BYTES(string_len + 1) > 1024) { ret = g_malloc0(BYTES(string_len + 1)); } else { ret = g_malloc0(1024); } for(ptr = (id3_ucs4_t *)string; *ptr != 0 && ptr <= tail; ptr++) { if(*ptr == '(') { if(*(++ptr) == '(') { // escaped text like: ((something) for(end = ptr; *end != ')' && *end != 0;) { // copy "(something)" end++; } end++; //include trailing ')' memcpy(ret, ptr, BYTES(end - ptr)); ret_len += (end - ptr); *(ret + ret_len) = 0; //terminate ptr = end + 1; } else { // reference to an id3v1 genre code for(end = ptr; *end != ')' && *end != 0;) { end++; } tmp = g_malloc0(BYTES(end - ptr + 1)); memcpy(tmp, ptr, BYTES(end - ptr)); *(tmp + (end - ptr)) = 0; //terminate ptr += end - ptr; genre = (id3_ucs4_t *)id3_genre_name((const id3_ucs4_t *)tmp); g_free(tmp); tmp = NULL; tmp_len = mad_ucs4len(genre); memcpy(ret + BYTES(ret_len), genre, BYTES(tmp_len)); ret_len += tmp_len; *(ret + ret_len) = 0; //terminate } } else { for(end = ptr; *end != '(' && *end != 0; ) { end++; } // scan string to determine whether a genre code number or not tp = ptr; is_num = TRUE; while(tp < end) { if(*tp < '0' || *tp > '9') { // anything else than number appears. is_num = FALSE; break; } tp++; } if(is_num) { #ifdef DEBUG printf("is_num!\n"); #endif tmp = g_malloc0(BYTES(end - ptr + 1)); memcpy(tmp, ptr, BYTES(end - ptr)); *(tmp + (end - ptr)) = 0; //terminate ptr += end - ptr; genre = (id3_ucs4_t *)id3_genre_name((const id3_ucs4_t *)tmp); #ifdef DEBUG printf("genre length = %d\n", mad_ucs4len(genre)); #endif g_free(tmp); tmp = NULL; tmp_len = mad_ucs4len(genre); memcpy(ret + BYTES(ret_len), genre, BYTES(tmp_len)); ret_len += tmp_len; *(ret + ret_len) = 0; //terminate } else { // plain text #ifdef DEBUG printf("plain!\n"); printf("ret_len = %d\n", ret_len); printf("end - ptr = %d\n", BYTES(end - ptr)); #endif memcpy(ret + BYTES(ret_len), ptr, BYTES(end - ptr)); ret_len = ret_len + (end - ptr); *(ret + ret_len) = 0; //terminate ptr += (end - ptr); } } } return ret; } gchar *input_id3_get_string(struct id3_tag * tag, char *frame_name) { gchar *rtn0 = NULL, *rtn = NULL; const id3_ucs4_t *string_const = NULL; id3_ucs4_t *string = NULL; struct id3_frame *frame; union id3_field *field; int encoding = -1; frame = id3_tag_findframe(tag, frame_name, 0); if (!frame) return NULL; field = id3_frame_field(frame, 0); encoding = id3_field_gettextencoding(field); if (!strcmp(frame_name, ID3_FRAME_COMMENT)) field = id3_frame_field(frame, 3); else field = id3_frame_field(frame, 1); if (!field) return NULL; if (!strcmp(frame_name, ID3_FRAME_COMMENT)) string_const = id3_field_getfullstring(field); else string_const = id3_field_getstrings(field, 0); if (!string_const) return NULL; if (!strcmp(frame_name, ID3_FRAME_GENRE)) { string = mad_parse_genre(string_const); } else { string = mad_ucs4dup((id3_ucs4_t *)string_const); } if (!string) return NULL; switch (encoding) { case ID3_FIELD_TEXTENCODING_ISO_8859_1: rtn0 = (gchar *)id3_ucs4_latin1duplicate(string); rtn = str_to_utf8(rtn0); g_free(rtn0); break; case ID3_FIELD_TEXTENCODING_UTF_8: default: rtn = (gchar *)id3_ucs4_utf8duplicate(string); break; } g_free((void *)string); #ifdef DEBUG g_print("i: string = %s\n", rtn); #endif return rtn; } static void input_alloc_tag(struct mad_info_t *info) { TitleInput *title_input; if (info->tuple == NULL) { title_input = bmp_title_input_new(); info->tuple = title_input; info->tuple->length = -1; //will be refferd in decoder.c } else title_input = info->tuple; } /** * read the ID3 tag */ static void input_read_tag(struct mad_info_t *info) { gchar *string = NULL; TitleInput *title_input; glong curpos = 0; #ifdef DEBUG g_message("f: input_read_tag"); #endif if (info->tuple == NULL) { title_input = bmp_title_input_new(); info->tuple = title_input; } else title_input = info->tuple; if(info->infile) { curpos = vfs_ftell(info->infile); info->id3file = id3_file_vfsopen(info->infile, ID3_FILE_MODE_READONLY); } else info->id3file = id3_file_open(info->filename, ID3_FILE_MODE_READONLY); if (!info->id3file) { #ifdef DEBUG g_message("read_tag: no id3file"); #endif return; } info->tag = id3_file_tag(info->id3file); if (!info->tag) { #ifdef DEBUG g_message("read_tag: no tag"); #endif return; } title_input->performer = input_id3_get_string(info->tag, ID3_FRAME_ARTIST); title_input->track_name = input_id3_get_string(info->tag, ID3_FRAME_TITLE); title_input->album_name = input_id3_get_string(info->tag, ID3_FRAME_ALBUM); title_input->genre = input_id3_get_string(info->tag, ID3_FRAME_GENRE); title_input->comment = input_id3_get_string(info->tag, ID3_FRAME_COMMENT); string = input_id3_get_string(info->tag, ID3_FRAME_TRACK); if (string) { title_input->track_number = atoi(string); g_free(string); string = NULL; } // year string = NULL; string = input_id3_get_string(info->tag, ID3_FRAME_YEAR); //TDRC if (!string) string = input_id3_get_string(info->tag, "TYER"); if (string) { title_input->year = atoi(string); g_free(string); string = NULL; } // length title_input->length = -1; string = input_id3_get_string(info->tag, "TLEN"); if (string) { title_input->length = atoi(string); #ifdef DEBUG g_message("input_read_tag: TLEN = %d", title_input->length); #endif g_free(string); string = NULL; } title_input->file_name = g_strdup(g_basename(info->filename)); title_input->file_path = g_path_get_dirname(info->filename); if ((string = strrchr(title_input->file_name, '.'))) { title_input->file_ext = string + 1; *string = '\0'; // make filename end at dot. } info->title = xmms_get_titlestring(audmad_config.title_override == TRUE ? audmad_config.id3_format : xmms_get_gentitle_format(), title_input); // for connection via proxy, we have to stop transfer once. I can't explain the reason. if (info->infile != NULL) { vfs_fseek(info->infile, -1, SEEK_SET); // an impossible request vfs_fseek(info->infile, curpos, SEEK_SET); } #ifdef DEBUG g_message("e: input_read_tag"); #endif } void input_process_remote_metadata(struct mad_info_t *info) { if(info->remote && mad_timer_count(info->duration, MAD_UNITS_SECONDS) <= 0){ gchar *tmp = NULL; #ifdef DEBUG #ifdef DEBUG_INTENSIVELY g_message("process_remote_meta"); #endif #endif g_free(info->title); info->title = NULL; g_free(info->tuple->track_name); info->tuple->track_name = NULL; g_free(info->tuple->album_name); info->tuple->album_name = NULL; tmp = vfs_get_metadata(info->infile, "track-name"); if(tmp){ info->tuple->track_name = str_to_utf8(tmp); info->title = g_strdup(info->tuple->track_name); g_free(tmp); tmp = NULL; } tmp = vfs_get_metadata(info->infile, "stream-name"); if(tmp){ info->tuple->album_name = str_to_utf8(tmp); g_free(tmp); tmp = NULL; } if (info->tuple->track_name && info->tuple->album_name) tmp = g_strdup_printf("%s (%s)", info->tuple->track_name, info->tuple->album_name); else if (info->tuple->album_name) tmp = g_strdup(info->tuple->album_name); else tmp = g_strdup(g_basename(info->filename)); /* call set_info only if tmp is different from prev_tmp */ if ( ( ( info->prev_title != NULL ) && ( strcmp(info->prev_title,tmp) ) ) || ( info->prev_title == NULL ) ) { mad_plugin->set_info(tmp, -1, // indicate the stream is unseekable info->bitrate, info->freq, info->channels); if (info->prev_title) g_free(info->prev_title); info->prev_title = g_strdup(tmp); } g_free(tmp); } } /** * Retrieve meta-information about URL. * For local files this means ID3 tag etc. */ gboolean input_get_info(struct mad_info_t *info, gboolean fast_scan) { #ifdef DEBUG gchar *tmp = g_filename_to_utf8(info->filename, -1, NULL, NULL, NULL); g_message("f: input_get_info: %s, fast_scan = %s", tmp, fast_scan ? "TRUE" : "FALSE"); g_free(tmp); #endif /* DEBUG */ input_alloc_tag(info); input_read_tag(info); if(!info->remote) { // reduce startup delay read_replaygain(info); } /* scan mp3 file, decoding headers */ if (scan_file(info, fast_scan) == FALSE) { #ifdef DEBUG g_message("input_get_info: scan_file failed"); #endif return FALSE; } /* reset the input file to the start */ vfs_fseek(info->infile, 0, SEEK_SET); info->offset = 0; /* use the filename for the title as a last resort */ if (!info->title) { char *pos = strrchr(info->filename, DIR_SEPARATOR); if (pos) info->title = g_strdup(pos + 1); else info->title = g_strdup(info->filename); } #ifdef DEBUG g_message("e: input_get_info"); #endif /* DEBUG */ return TRUE; } /** * Read data from the source given my madinfo into the buffer * provided. Return the number of bytes read. * @return 0 on EOF * @return -1 on error */ // this function may be called before info->playback initialized. int input_get_data(struct mad_info_t *info, guchar * buffer, int buffer_size) { int len = 0; #ifdef DEBUG #ifdef DEBUG_INTENSIVELY g_message ("f: input_get_data: %d", buffer_size); #endif #endif /* simply read to data from the file */ len = vfs_fread(buffer, 1, buffer_size, info->infile); //vfs_fread returns num of elements. if(len == 0 && info->playback){ info->playback->eof = TRUE; } #ifdef DEBUG #ifdef DEBUG_INTENSIVELY g_message ("e: input_get_data: size=%d offset=%d", len, info->offset); #endif #endif info->offset += len; return len; } /** * Free up all mad_info_t related resourses. */ gboolean input_term(struct mad_info_t * info) { #ifdef DEBUG g_message("f: input_term"); #endif if (info->title) g_free(info->title); if (info->url) g_free(info->url); if (info->filename) g_free(info->filename); if (info->infile) vfs_fclose(info->infile); if (info->id3file) id3_file_close(info->id3file); if (info->replaygain_album_str) g_free(info->replaygain_album_str); if (info->replaygain_track_str) g_free(info->replaygain_track_str); if (info->replaygain_album_peak_str) g_free(info->replaygain_album_peak_str); if (info->replaygain_track_peak_str) g_free(info->replaygain_track_peak_str); if (info->mp3gain_undo_str) g_free(info->mp3gain_undo_str); if (info->mp3gain_minmax_str) g_free(info->mp3gain_minmax_str); if (info->tuple) { bmp_title_input_free(info->tuple); info->tuple = NULL; } if (info->prev_title) g_free(info->prev_title); /* set everything to zero in case it gets used again. */ memset(info, 0, sizeof(struct mad_info_t)); #ifdef DEBUG g_message("e: input_term"); #endif return TRUE; }
