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;
}