Mercurial > audlegacy-plugins
view src/madplug/input.c @ 3030:c442f8407dcb
Show playlist popup information reliably (Debian bug #460802)
author | John Lindgren <john.lindgren@tds.net> |
---|---|
date | Fri, 10 Apr 2009 01:46:08 -0400 |
parents | f1b6f1b2cdb3 |
children |
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "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, VFSFile *fd) { AUDDBG("f: input_init\n"); memset(info, 0, sizeof(struct mad_info_t)); // all fields are cleared to 0 --yaz info->fmt = FMT_FIXED32; 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->filename = g_strdup(url); // from input_read_replaygain() info->replaygain_track_peak = 0.0; info->replaygain_track_scale = 0.0; info->replaygain_album_peak = 0.0; info->replaygain_album_scale = 0.0; info->mp3gain_undo = -77; info->mp3gain_minmax = -77; if(!fd){ info->infile = aud_vfs_fopen(info->filename, "rb"); if (info->infile == NULL) { return FALSE; } } else{ AUDDBG("input_init: aud_vfs_dup\n"); info->infile = aud_vfs_dup(fd); } // obtain file size info->size = aud_vfs_fsize(info->infile); info->remote = info->size == 0 ? TRUE : FALSE; //proxy connection may result in non-zero size. if(aud_vfs_is_remote((gchar *)url)) info->remote = TRUE; info->fileinfo_request = FALSE; AUDDBG("i: info->size = %lu\n", (long unsigned int)info->size); AUDDBG("e: input_init\n"); 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 *res; size_t len = mad_ucs4len(org); res = g_malloc0((len + 1) * sizeof(id3_ucs4_t)); memcpy(res, org, len * sizeof(id3_ucs4_t)); *(res + len) = 0; //terminate return res; } #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 <= tail && *ptr != 0; ptr++) { if(*ptr == '(') { if(ptr < tail && *(++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 if (ptr <= tail && *ptr != 0) { // reference to an id3v1 genre code for(tmp_len = 0, end = ptr; *end != ')' && *end != 0;) { end++; tmp_len++; } tmp = g_malloc0(BYTES(tmp_len + 1)); memcpy(tmp, ptr, BYTES(tmp_len)); *(tmp + tmp_len) = 0; //terminate ptr += tmp_len; /* id3_genre_name may, in some cases, return the given string * so we must free it after we're done, not before. */ genre = (id3_ucs4_t *)id3_genre_name((const id3_ucs4_t *)tmp); tmp_len = mad_ucs4len(genre); memcpy(ret + ret_len, genre, BYTES(tmp_len)); ret_len += tmp_len; *(ret + ret_len) = 0; //terminate g_free(tmp); } } 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) { AUDDBG("is_num!\n"); tmp_len = end - ptr; tmp = g_malloc0(BYTES(tmp_len + 1)); memcpy(tmp, ptr, BYTES(tmp_len)); *(tmp + tmp_len) = 0; //terminate ptr += tmp_len; /* id3_genre_name may, in some cases, return the given string * so we must free it after we're done, not before. */ genre = (id3_ucs4_t *)id3_genre_name((const id3_ucs4_t *)tmp); AUDDBG("genre length = %d\n", mad_ucs4len(genre)); tmp_len = mad_ucs4len(genre); memcpy(ret + ret_len, genre, BYTES(tmp_len)); ret_len += tmp_len; *(ret + ret_len) = 0; //terminate g_free(tmp); } else { // plain text tmp_len = end - ptr; AUDDBG("plain!\n"); AUDDBG("ret_len = %d\n", ret_len); AUDDBG("end - ptr = %d\n", BYTES(tmp_len)); memcpy(ret + ret_len, ptr, BYTES(tmp_len)); ret_len = ret_len + tmp_len; *(ret + ret_len) = 0; //terminate ptr += tmp_len; } } } return ret; } gchar * input_id3_get_string(struct id3_tag * tag, const gchar *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 = aud_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); AUDDBG("i: string = %s\n", rtn); return rtn; } static void input_set_and_free_tag(struct id3_tag *tag, Tuple *tuple, const gchar *frame, const gint nfield) { gchar *scratch = input_id3_get_string(tag, frame); aud_tuple_associate_string(tuple, nfield, NULL, scratch); aud_tuple_associate_string(tuple, -1, frame, scratch); g_free(scratch); } static void input_alloc_tag(struct mad_info_t *info) { Tuple *tuple; if (info->tuple == NULL) { tuple = aud_tuple_new(); info->tuple = tuple; aud_tuple_associate_int(info->tuple, FIELD_LENGTH, NULL, -1); } } /** * read the ID3 tag */ static void input_read_tag(struct mad_info_t *info) { gchar *string; Tuple *tuple; glong curpos = 0; AUDDBG("f: input_read_tag\n"); if (info->tuple != NULL) aud_tuple_free(info->tuple); tuple = aud_tuple_new_from_filename(info->filename); info->tuple = tuple; if(info->infile) { curpos = aud_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) { AUDDBG("read_tag: no id3file\n"); return; } info->tag = id3_file_tag(info->id3file); if (!info->tag) { AUDDBG("read_tag: no tag\n"); return; } input_set_and_free_tag(info->tag, tuple, ID3_FRAME_ARTIST, FIELD_ARTIST); input_set_and_free_tag(info->tag, tuple, ID3_FRAME_TITLE, FIELD_TITLE); input_set_and_free_tag(info->tag, tuple, ID3_FRAME_ALBUM, FIELD_ALBUM); input_set_and_free_tag(info->tag, tuple, ID3_FRAME_GENRE, FIELD_GENRE); input_set_and_free_tag(info->tag, tuple, ID3_FRAME_COMMENT, FIELD_COMMENT); string = input_id3_get_string(info->tag, ID3_FRAME_TRACK); if (string) { aud_tuple_associate_int(tuple, FIELD_TRACK_NUMBER, NULL, atoi(string)); g_free(string); } // year string = input_id3_get_string(info->tag, ID3_FRAME_YEAR); //TDRC if (!string) string = input_id3_get_string(info->tag, "TYER"); if (string) { aud_tuple_associate_int(tuple, FIELD_YEAR, NULL, atoi(string)); g_free(string); } // length string = input_id3_get_string(info->tag, "TLEN"); if (string && atoi(string)) { aud_tuple_associate_int(tuple, FIELD_LENGTH, NULL, atoi(string)); AUDDBG("input_read_tag: TLEN = %d\n", atoi(string)); g_free(string); } else aud_tuple_associate_int(tuple, FIELD_LENGTH, NULL, -1); aud_tuple_associate_string(tuple, FIELD_CODEC, NULL, "MPEG Audio (MP3)"); aud_tuple_associate_string(tuple, FIELD_QUALITY, NULL, "lossy"); info->title = aud_tuple_formatter_make_title_string(tuple, audmad_config->title_override == TRUE ? audmad_config->id3_format : aud_get_gentitle_format()); // for connection via proxy, we have to stop transfer once. I can't explain the reason. if (info->infile != NULL) { aud_vfs_fseek(info->infile, -1, SEEK_SET); // an impossible request aud_vfs_fseek(info->infile, curpos, SEEK_SET); } AUDDBG("e: input_read_tag\n"); } void input_process_remote_metadata(struct mad_info_t *info) { gboolean metadata = FALSE; if(info->remote && mad_timer_count(info->duration, MAD_UNITS_SECONDS) <= 0){ gchar *tmp; #ifdef DEBUG_INTENSIVELY AUDDBG("process_remote_meta\n"); #endif g_free(info->title); info->title = NULL; aud_tuple_disassociate(info->tuple, FIELD_TITLE, NULL); aud_tuple_disassociate(info->tuple, FIELD_ALBUM, NULL); tmp = aud_vfs_get_metadata(info->infile, "track-name"); if(tmp){ metadata = TRUE; gchar *scratch = aud_str_to_utf8(tmp); aud_tuple_associate_string(info->tuple, FIELD_TITLE, NULL, scratch); g_free(scratch); g_free(tmp); } tmp = aud_vfs_get_metadata(info->infile, "stream-name"); if(tmp){ metadata = TRUE; gchar *scratch = aud_str_to_utf8(tmp); aud_tuple_associate_string(info->tuple, FIELD_ALBUM, NULL, scratch); aud_tuple_associate_string(info->tuple, -1, "stream", scratch); g_free(scratch); g_free(tmp); } if (metadata) tmp = aud_tuple_formatter_process_string(info->tuple, "${?title:${title}}${?stream: (${stream})}"); else { gchar *realfn = g_filename_from_uri(info->filename, NULL, NULL); gchar *tmp2 = g_path_get_basename(realfn ? realfn : info->filename); // info->filename is uri. --yaz tmp = aud_str_to_utf8(tmp2); g_free(tmp2); g_free(realfn); // tmp = g_strdup(g_basename(info->filename)); //XXX maybe ok. --yaz } /* 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 ) ) { info->playback->set_params(info->playback, 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 AUD_DEBUG gchar *tmp = g_filename_to_utf8(info->filename, -1, NULL, NULL, NULL); AUDDBG("f: input_get_info: %s, fast_scan = %s\n", tmp, fast_scan ? "TRUE" : "FALSE"); g_free(tmp); #endif /* DEBUG */ input_alloc_tag(info); input_read_tag(info); if(!info->remote) { // reduce startup delay audmad_read_replaygain(info); } /* scan mp3 file, decoding headers */ if (scan_file(info, fast_scan) == FALSE) { AUDDBG("input_get_info: scan_file failed\n"); return FALSE; } /* reset the input file to the start */ aud_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); //XXX info->filename is uri. --yaz if (pos) info->title = g_strdup(pos + 1); else info->title = g_strdup(info->filename); //XXX info->filename is uri. --yaz } AUDDBG("e: input_get_info\n"); 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_INTENSIVELY AUDDBG ("f: input_get_data: %d\n", buffer_size); #endif /* simply read to data from the file */ len = aud_vfs_fread(buffer, 1, buffer_size, info->infile); //aud_vfs_fread returns num of elements. if(len == 0 && info->playback){ info->playback->eof = TRUE; } #ifdef DEBUG_INTENSIVELY AUDDBG ("e: input_get_data: size=%d offset=%d\n", len, info->offset); #endif info->offset += len; return len; } /** * Free up all mad_info_t related resourses. */ gboolean input_term(struct mad_info_t * info) { AUDDBG("f: input_term\n"); g_free(info->title); g_free(info->url); g_free(info->filename); if (info->infile) aud_vfs_fclose(info->infile); if (info->id3file) id3_file_close(info->id3file); g_free(info->replaygain_album_str); g_free(info->replaygain_track_str); g_free(info->replaygain_album_peak_str); g_free(info->replaygain_track_peak_str); g_free(info->mp3gain_undo_str); g_free(info->mp3gain_minmax_str); if (info->tuple) { aud_tuple_free(info->tuple); info->tuple = NULL; } g_free(info->prev_title); /* set everything to zero in case it gets used again. */ memset(info, 0, sizeof(struct mad_info_t)); AUDDBG("e: input_term\n"); return TRUE; }