Mercurial > audlegacy-plugins
view src/tta/libtta.c @ 1152:949457464b28 trunk
[svn] - fix for crash when file extension is not available.
author | yaz |
---|---|
date | Tue, 29 May 2007 03:58:22 -0700 |
parents | 08979ba252ff |
children | 5f3b30860b48 |
line wrap: on
line source
/* * libtta.c * * Description: TTA input plug-in for Audacious * Developed by: Alexander Djourik <ald@true-audio.com> * Audacious port: Yoshiki Yazawa <yaz@cc.rim.or.jp> * * Copyright (c) 2007 Alexander Djourik. All rights reserved. * */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please see the file COPYING in this directory for full copyright * information. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <glib.h> #include <pthread.h> #include <string.h> #include <audacious/util.h> #include <audacious/plugin.h> #include <audacious/titlestring.h> #include <audacious/vfs.h> #include <audacious/output.h> #include <audacious/strings.h> #include <audacious/i18n.h> #include <audacious/id3tag.h> #define PLUGIN_VERSION "1.4" #define PROJECT_URL "<http://www.true-audio.com>" #include "ttalib.h" #define OUTPUT_ERROR (MEMORY_ERROR+1) #define MAX_BSIZE (MAX_BPS>>3) #define BYTES(x) ((x) * sizeof(id3_ucs4_t)) static void init (); static void cleanup (); static int is_our_file (char *filename); static void play_file (InputPlayback *playback); static void tta_pause (InputPlayback *playback, short paused); static void stop (InputPlayback *playback); static void seek (InputPlayback *playback, int time); static int get_time (InputPlayback *playback); static void get_song_info (char *filename, char **title, int *length); static void file_info (char *filename); static void about (); static TitleInput *get_song_tuple(char *filename); static gchar *extname(const char *filename); static pthread_t decode_thread; static char sample_buffer[PCM_BUFFER_LENGTH * MAX_BSIZE * MAX_NCH]; static tta_info info; // currently playing file info static int seek_position = -1; static int playing = FALSE; static int read_samples = -1; gchar *tta_fmts[] = { "tta", NULL }; InputPlugin tta_ip = { NULL, NULL, "True Audio Plugin", init, about, NULL, is_our_file, NULL, play_file, stop, tta_pause, seek, NULL, get_time, NULL, NULL, cleanup, NULL, NULL, NULL, NULL, get_song_info, file_info, NULL, get_song_tuple, // get_song_tuple NULL, // set_song_tuple NULL, // buffer NULL, // vfs tta_fmts, }; InputPlugin *tta_iplist[] = { &tta_ip, NULL }; DECLARE_PLUGIN(tta, NULL, NULL, tta_iplist, NULL, NULL, NULL, NULL); size_t file_size (char *filename) { VFSFile *f; size_t size = -1; if ((f = vfs_fopen (filename, "r"))) { vfs_fseek (f, 0, SEEK_END); size = vfs_ftell (f); vfs_fclose (f); } return size; } static void tta_error (int error) { char *message; static GtkWidget *errorbox; if (errorbox != NULL) return; switch (error) { case OPEN_ERROR: message = "Can't open file\n"; break; case FORMAT_ERROR: message = "Not supported file format\n"; break; case FILE_ERROR: message = "File is corrupted\n"; break; case READ_ERROR: message = "Can't read from file\n"; break; case MEMORY_ERROR: message = "Insufficient memory available\n"; break; case OUTPUT_ERROR: message = "Output plugin error\n"; break; default: message = "Unknown error\n"; break; } xmms_show_message ("TTA Decoder Error", message, "Ok", FALSE, NULL, NULL); gtk_signal_connect(GTK_OBJECT(errorbox), "destroy", G_CALLBACK(gtk_widget_destroyed), &errorbox); } static gchar * get_song_title(TitleInput *tuple) { return xmms_get_titlestring(xmms_get_gentitle_format(), tuple); } static void get_song_info (char *filename, char **title, int *length) { TitleInput *tuple; *length = -1; *title = NULL; if ((tuple = get_song_tuple(filename)) != NULL) { *length = tuple->length; *title = get_song_title(tuple); } bmp_title_input_free(tuple); } static void * play_loop (void *arg) { InputPlayback *playback = arg; int bufsize = PCM_BUFFER_LENGTH * info.BSIZE * info.NCH; //////////////////////////////////////// // decode PCM_BUFFER_LENGTH samples // into the current PCM buffer position while (playing) { while ((read_samples = get_samples (sample_buffer)) > 0) { while ((playback->output->buffer_free () < bufsize) && seek_position == -1) { if (!playing) goto DONE; xmms_usleep (10000); } if (seek_position == -1) { produce_audio(playback->output->written_time(), ((info.BPS == 8) ? FMT_U8 : FMT_S16_LE), info.NCH, read_samples * info.NCH * info.BSIZE, sample_buffer, NULL); } else { set_position (seek_position); playback->output->flush (seek_position * SEEK_STEP); seek_position = -1; } } playback->output->buffer_free (); playback->output->buffer_free (); xmms_usleep(10000); } DONE: //////////////////////// // destroy memory pools player_stop (); /////////////////////////////// // close currently playing file close_tta_file (&info); pthread_exit (NULL); } static void init () { memset (&info, 0, sizeof (tta_info)); } static void cleanup () { } static void about () { static GtkWidget *aboutbox; if (aboutbox != NULL) return; aboutbox = xmms_show_message( "About True Audio Plugin", "TTA input plugin" PLUGIN_VERSION "for BMP\n" "Copyright (c) 2004 True Audio Software\n" PROJECT_URL, "Ok", FALSE, NULL, NULL); gtk_signal_connect(GTK_OBJECT(aboutbox), "destroy", G_CALLBACK(gtk_widget_destroyed), &aboutbox); } static GtkWidget *window = NULL; static GtkWidget *filename_entry, *title_entry, *artist_entry, *album_entry, *year_entry, *tracknum_entry, *comment_entry, *genre_entry, *info_frame; static void file_info (char *filename) { tta_info ttainfo; char *title; gchar *utf_filename = NULL; if (!window) { GtkWidget *vbox, *hbox, *left_vbox, *table; GtkWidget *label, *filename_hbox, *button_ok; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); gtk_signal_connect(GTK_OBJECT(window), "destroy", G_CALLBACK(gtk_widget_destroyed), &window); gtk_container_set_border_width(GTK_CONTAINER(window), 10); vbox = gtk_vbox_new(FALSE, 10); gtk_container_add(GTK_CONTAINER(window), vbox); filename_hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), filename_hbox, FALSE, TRUE, 0); label = gtk_label_new("Filename:"); gtk_box_pack_start(GTK_BOX(filename_hbox), label, FALSE, TRUE, 0); filename_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(filename_entry), FALSE); gtk_box_pack_start(GTK_BOX(filename_hbox), filename_entry, TRUE, TRUE, 0); hbox = gtk_hbox_new(FALSE, 10); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); left_vbox = gtk_vbox_new(FALSE, 10); gtk_box_pack_start(GTK_BOX(hbox), left_vbox, FALSE, FALSE, 0); info_frame = gtk_frame_new("ID3 Tag:"); gtk_box_pack_start(GTK_BOX(left_vbox), info_frame, FALSE, FALSE, 0); table = gtk_table_new(5, 5, FALSE); gtk_container_set_border_width(GTK_CONTAINER(table), 5); gtk_container_add(GTK_CONTAINER(info_frame), table); label = gtk_label_new("Title:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 5); title_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(title_entry), FALSE); gtk_table_attach(GTK_TABLE(table), title_entry, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Artist:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 5); artist_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(artist_entry), FALSE); gtk_table_attach(GTK_TABLE(table), artist_entry, 1, 4, 1, 2, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Album:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 5, 5); album_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(album_entry), FALSE); gtk_table_attach(GTK_TABLE(table), album_entry, 1, 4, 2, 3, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Comment:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, GTK_FILL, 5, 5); comment_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(comment_entry), FALSE); gtk_table_attach(GTK_TABLE(table), comment_entry, 1, 4, 3, 4, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Year:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, GTK_FILL, 5, 5); year_entry = gtk_entry_new_with_max_length(4); gtk_editable_set_editable(GTK_EDITABLE(year_entry), FALSE); gtk_widget_set_usize(year_entry, 40, -1); gtk_table_attach(GTK_TABLE(table), year_entry, 1, 2, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Track number:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 2, 3, 4, 5, GTK_FILL, GTK_FILL, 5, 5); tracknum_entry = gtk_entry_new_with_max_length(3); gtk_editable_set_editable(GTK_EDITABLE(tracknum_entry), FALSE); gtk_widget_set_usize(tracknum_entry, 40, -1); gtk_table_attach(GTK_TABLE(table), tracknum_entry, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Genre:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 5, 6, GTK_FILL, GTK_FILL, 5, 5); genre_entry = gtk_entry_new_with_max_length(1024); gtk_editable_set_editable(GTK_EDITABLE(genre_entry), FALSE); gtk_widget_set_usize(genre_entry, 40, -1); gtk_table_attach(GTK_TABLE(table), genre_entry, 1, 4, 5, 6, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); button_ok = gtk_button_new_with_label("Ok"); gtk_signal_connect_object(GTK_OBJECT(button_ok), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(window)); GTK_WIDGET_SET_FLAGS(button_ok, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(vbox), button_ok, TRUE, TRUE, 0); gtk_widget_show_all (window); } utf_filename = str_to_utf8(filename); title = g_strdup_printf(_("File Info - %s"), g_basename(utf_filename)); gtk_window_set_title(GTK_WINDOW(window), title); g_free(title); gtk_entry_set_text(GTK_ENTRY(filename_entry), utf_filename); gtk_editable_set_position(GTK_EDITABLE(filename_entry), -1); title = g_strdup(g_basename(utf_filename)); gtk_entry_set_text(GTK_ENTRY(title_entry), title); g_free(title); g_free(utf_filename); if (open_tta_file (filename, &ttainfo, 0) >= 0) { gtk_entry_set_text(GTK_ENTRY(title_entry), ttainfo.ID3.title); gtk_entry_set_text(GTK_ENTRY(artist_entry), ttainfo.ID3.artist); gtk_entry_set_text(GTK_ENTRY(album_entry), ttainfo.ID3.album); gtk_entry_set_text(GTK_ENTRY(year_entry), ttainfo.ID3.year); gtk_entry_set_text(GTK_ENTRY(tracknum_entry), ttainfo.ID3.track); gtk_entry_set_text(GTK_ENTRY(comment_entry), ttainfo.ID3.comment); gtk_entry_set_text(GTK_ENTRY(genre_entry), ttainfo.ID3.genre); } close_tta_file (&ttainfo); gtk_widget_set_sensitive(info_frame, TRUE); } static int is_our_file (char *filename) { gchar *ext = strrchr(filename, '.'); if (ext && !strncasecmp(ext, ".tta", 4)) return TRUE; return FALSE; } static void play_file (InputPlayback *playback) { char *filename = playback->filename; char *title; int datasize, origsize, bitrate; TitleInput *tuple = NULL; playing = FALSE; //////////////////////////////////////// // open TTA file if (open_tta_file (filename, &info, 0) < 0) { tta_error (info.STATE); close_tta_file (&info); return; } //////////////////////////////////////// // initialize TTA player if (player_init (&info) < 0) { tta_error (info.STATE); close_tta_file (&info); return; } if (playback->output->open_audio ((info.BPS == 8) ? FMT_U8 : FMT_S16_LE, info.SAMPLERATE, info.NCH) == 0) { tta_error (OUTPUT_ERROR); close_tta_file (&info); return; } tuple = get_song_tuple(filename); title = get_song_title(tuple); bmp_title_input_free(tuple); datasize = file_size(filename) - info.DATAPOS; origsize = info.DATALENGTH * info.BSIZE * info.NCH; bitrate = (int) ((float) datasize / origsize * (info.SAMPLERATE * info.NCH * info.BPS)); tta_ip.set_info (title, 1000 * info.LENGTH, bitrate, info.SAMPLERATE, info.NCH); if (title) g_free (title); playing = TRUE; seek_position = -1; read_samples = -1; pthread_create (&decode_thread, NULL, play_loop, playback); } static void tta_pause (InputPlayback *playback, short paused) { playback->output->pause (paused); } static void stop (InputPlayback *playback) { if (playing) { playing = FALSE; pthread_join (decode_thread, NULL); playback->output->close_audio (); close_tta_file (&info); read_samples = 0; } } static void seek (InputPlayback *data, int time) { if (playing) { seek_position = 1000 * time / SEEK_STEP; while (seek_position != -1) xmms_usleep (10000); } } static int get_time (InputPlayback *playback) { if (playing && (read_samples || playback->output->buffer_playing())) return playback->output->output_time(); return -1; } static TitleInput * get_song_tuple(char *filename) { TitleInput *tuple = NULL; tta_info *ttainfo; VFSFile *file; ttainfo = g_malloc0(sizeof(tta_info)); if((file = vfs_fopen(filename, "rb")) != NULL) { if(open_tta_file(filename, ttainfo, 0) >= 0) { tuple = bmp_title_input_new(); tuple->file_name = g_path_get_basename(filename); tuple->file_path = g_path_get_dirname(filename); tuple->file_ext = extname(filename); tuple->length = ttainfo->LENGTH * 1000; if (ttainfo->ID3.id3has) { if(ttainfo->ID3.artist) tuple->performer = g_strdup(ttainfo->ID3.artist); if(ttainfo->ID3.album) tuple->album_name = g_strdup(ttainfo->ID3.album); if(ttainfo->ID3.title) tuple->track_name = g_strdup(ttainfo->ID3.title); if(ttainfo->ID3.year) tuple->year = atoi(ttainfo->ID3.year); if(ttainfo->ID3.track) tuple->track_number = atoi(ttainfo->ID3.track); if(ttainfo->ID3.genre) tuple->genre = g_strdup(ttainfo->ID3.genre); if(ttainfo->ID3.comment) tuple->comment = g_strdup(ttainfo->ID3.comment); } close_tta_file (ttainfo); } vfs_fclose(file); } return tuple; } static gchar * extname(const char *filename) { gchar *ext = strrchr(filename, '.'); if (ext != NULL) ++ext; return ext; } /* return length in letters */ size_t tta_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 *tta_ucs4dup(id3_ucs4_t *org) { id3_ucs4_t *new = NULL; size_t len = tta_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; } id3_ucs4_t *tta_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; gboolean is_num = TRUE; tail = (id3_ucs4_t *)string + tta_ucs4len((id3_ucs4_t *)string); 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 = tta_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", tta_ucs4len(genre)); #endif g_free(tmp); tmp = NULL; tmp_len = tta_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); #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 *tta_input_id3_get_string(struct id3_tag * tag, char *frame_name) { gchar *rtn; gchar *rtn2; const id3_ucs4_t *string_const; id3_ucs4_t *string; id3_ucs4_t *ucsptr; struct id3_frame *frame; union id3_field *field; gboolean flagutf = FALSE; frame = id3_tag_findframe(tag, frame_name, 0); if (!frame) return NULL; if (frame_name == ID3_FRAME_COMMENT) field = id3_frame_field(frame, 3); else field = id3_frame_field(frame, 1); if (!field) return NULL; if (frame_name == ID3_FRAME_COMMENT) string_const = id3_field_getfullstring(field); else string_const = id3_field_getstrings(field, 0); if (!string_const) return NULL; string = tta_ucs4dup((id3_ucs4_t *)string_const); if (frame_name == ID3_FRAME_GENRE) { id3_ucs4_t *string2 = NULL; string2 = tta_parse_genre(string); g_free((void *)string); string = string2; } ucsptr = (id3_ucs4_t *)string; while (*ucsptr) { if (*ucsptr > 0x000000ffL) { flagutf = TRUE; break; } ucsptr++; } if (flagutf) { #ifdef DEBUG g_message("aud-tta: flagutf!\n"); #endif rtn = id3_ucs4_utf8duplicate(string); } else { rtn = id3_ucs4_latin1duplicate(string); rtn2 = str_to_utf8(rtn); free(rtn); rtn = rtn2; } g_free(string); string = NULL; #ifdef DEBUG g_print("string = %s\n", rtn); #endif return rtn; } int get_id3_tags (const char *filename, tta_info *ttainfo) { int id3v2_size = 0; gchar *str = NULL; struct id3_file *id3file = NULL; struct id3_tag *tag = NULL; id3file = id3_file_open (filename, ID3_FILE_MODE_READONLY); if (id3file) { tag = id3_file_tag (id3file); if (tag) { ttainfo->ID3.id3has = 1; id3v2_size = tag->paddedsize; str = tta_input_id3_get_string (tag, ID3_FRAME_ARTIST); if(str) strncpy(ttainfo->ID3.artist, str, MAX_LINE); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_ALBUM); if(str) strncpy(ttainfo->ID3.album, str, MAX_LINE); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_TITLE); if(str) strncpy(ttainfo->ID3.title, str, MAX_LINE); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_YEAR); if(!str) str = tta_input_id3_get_string (tag, "TYER"); if(str) strncpy(ttainfo->ID3.year, str, MAX_YEAR); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_TRACK); if(str) strncpy(ttainfo->ID3.track, str, MAX_TRACK); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_GENRE); if(str) strncpy(ttainfo->ID3.genre, str, MAX_GENRE); free(str); str = NULL; str = tta_input_id3_get_string (tag, ID3_FRAME_COMMENT); if(str) strncpy(ttainfo->ID3.comment, str, MAX_LINE); free(str); str = NULL; } id3_file_close(id3file); } return id3v2_size; // not used }