view Plugins/Input/cue/cuesheet.c @ 1470:cefd1ff614dd trunk

[svn] - finishing touches
author nenolod
date Wed, 02 Aug 2006 19:49:45 -0700
parents 27b62d78f35e
children 4c5ceb777d60
line wrap: on
line source

/* Audacious: An advanced media player.
 * cuesheet.c: Support cuesheets as a media container.
 *
 * Copyright (C) 2006 William Pitcock <nenolod -at- nenolod.net>.
 *
 * This file was hacked out of of xmms-cueinfo,
 * Copyright (C) 2003  Oskar Liljeblad
 *
 * This software is copyrighted work licensed under the terms of the
 * GNU General Public License. Please consult the file "COPYING" for
 * details.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <audacious/plugin.h>
#include <audacious/output.h>
#include <audacious/playlist.h>
#include <libaudacious/beepctrl.h>

#define MAX_CUE_LINE_LENGTH 1000
#define MAX_CUE_TRACKS 1000

#define EMPTIZE(x) ((x)==NULL ? "":(x))

static void init(void);
static void cache_cue_file(FILE *file);
static void free_cue_info(void);
static void fix_cue_argument(char *line);
static gboolean is_our_file(gchar *filespec);
static void play(gchar *uri);
static void play_cue_uri(gchar *uri);
static gint get_time(void);
static void seek(gint time);
static void stop(void);
static TitleInput *get_tuple(gchar *uri);
static TitleInput *get_tuple_uri(gchar *uri);

static gchar *cue_performer = NULL;
static gchar *cue_title = NULL;
static gchar *cue_file = NULL;
static gint last_cue_track = 0;
static gint cur_cue_track = 0;
static gint entry_lock = 0;
static struct {
	gchar *performer;
	gchar *title;
	gint index;
} cue_tracks[MAX_CUE_TRACKS];
static gint previous_song = -1;
static gint previous_length = -2;
static gint timeout_tag = 0;

static InputPlugin *real_ip = NULL;

InputPlugin cue_ip =
{
	NULL,			/* handle */
	NULL,			/* filename */
	NULL,			/* description */
	NULL,	       	/* init */
	NULL,	       	/* about */
	NULL,	  	   	/* configure */
	is_our_file,
	NULL,		/* audio cd */
	play,
	stop,
	NULL,
	seek,
	NULL,		/* set eq */
	get_time,
	NULL,
	NULL,
	NULL,		/* cleanup */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,		/* XXX get_song_info iface */
	NULL,
	NULL,
	get_tuple,
	NULL
};

static gboolean is_our_file(gchar *filename)
{
	gchar *ext;
	gboolean ret = FALSE;
	
	/* is it a cue:// URI? */
	if (!strncasecmp(filename, "cue://", 6))
		return TRUE;

	ext = strrchr(filename, '.');

	if (!strncasecmp(ext, ".cue", 4))
	{
		gint i;
		FILE *f = fopen(filename, "rb");
		ret = TRUE;

		/* add the files, build cue urls, etc. */
		cache_cue_file(f);

		for (i = 1; i < last_cue_track; i++)
		{
			gchar _buf[65535];

			g_snprintf(_buf, 65535, "cue://%s?%d", filename, i);
			playlist_add_url(_buf);
		}

		fclose(f);
		free_cue_info();
	}

	return ret;
}

static gint get_time(void)
{
	return get_output_time();
}

static void play(gchar *uri)
{
	/* this isn't a cue:// uri? */
	if (strncasecmp("cue://", uri, 6))
	{
		gchar *tmp = g_strdup_printf("cue://%s?0", uri);
		play_cue_uri(tmp);
		g_free(tmp);
		return;
	}

	play_cue_uri(uri);
}

static TitleInput *get_tuple(gchar *uri)
{
	TitleInput *ret;

	/* this isn't a cue:// uri? */
	if (strncasecmp("cue://", uri, 6))
	{
		gchar *tmp = g_strdup_printf("cue://%s?0", uri);
		ret = get_tuple_uri(tmp);
		g_free(tmp);
		return ret;
	}

	return get_tuple_uri(uri);
}

static TitleInput *get_tuple_uri(gchar *uri)
{
        gchar *path2 = g_strdup(uri + 6);
        gchar *_path = strchr(path2, '?');
	gint track = 0;
	FILE *f;
	InputPlugin *dec;
	TitleInput *phys_tuple, *out;

        if (_path != NULL && *_path == '?')
        {
                *_path = '\0';
                _path++;
                track = atoi(_path);
        }	

	f = fopen(path2, "rb");
	cache_cue_file(f);
	fclose(f);

	dec = input_check_file(cue_file, FALSE);

	if (dec == NULL)
		return NULL;

	phys_tuple = dec->get_song_tuple(cue_file);

	out = bmp_title_input_new();

	out->genre = g_strdup(phys_tuple->genre);	
	out->album_name = g_strdup(phys_tuple->album_name);
	out->file_path = g_strdup(phys_tuple->file_path);	
	out->file_name = g_strdup(phys_tuple->file_name);
	out->file_ext = g_strdup(phys_tuple->file_ext);
	out->length = phys_tuple->length;

	bmp_title_input_free(phys_tuple);

	out->track_name = cue_tracks[track].title;
	out->performer = cue_tracks[track].performer;

	return out;
}

static void seek(gint time)
{
	if (real_ip != NULL)
		real_ip->seek(time);
}

static void stop(void)
{
	if (real_ip != NULL)
		real_ip->stop();

	free_cue_info();
}

static void play_cue_uri(gchar *uri)
{
        gchar *path2 = g_strdup(uri + 6);
        gchar *_path = strchr(path2, '?');
	gint track = 0;
	FILE *f;

        if (_path != NULL && *_path == '?')
        {
                *_path = '\0';
                _path++;
                track = atoi(_path);
        }	

	f = fopen(path2, "rb");
	cache_cue_file(f);
	fclose(f);

	real_ip = input_check_file(cue_file, FALSE);

	if (real_ip != NULL)
	{
		real_ip->output = cue_ip.output;
		real_ip->play_file(cue_file);
		real_ip->seek(cue_tracks[track].index / 1000);	/* XXX: seek doesn't use frames? strange... -nenolod */
	}

	cur_cue_track = track;
}

InputPlugin *get_iplugin_info(void)
{
	cue_ip.description = g_strdup_printf("Cuesheet Container Plugin");
	return &cue_ip;
}

/******************************************************** cuefile */

static void free_cue_info(void)
{
	g_free(cue_performer);
	cue_performer = NULL;
	g_free(cue_title);
	cue_title = NULL;
	g_free(cue_file);
	cue_file = NULL;
	for (; last_cue_track > 0; last_cue_track--) {
		g_free(cue_tracks[last_cue_track-1].performer);
		g_free(cue_tracks[last_cue_track-1].title);
	}
}

static void cache_cue_file(FILE *file)
{
	gchar line[MAX_CUE_LINE_LENGTH+1];

	while (TRUE) {
		gint p;
		gint q;

		if (fgets(line, MAX_CUE_LINE_LENGTH+1, file) == NULL)
			return;

		for (p = 0; line[p] && isspace(line[p]); p++);
		if (!line[p])
			continue;
		for (q = p; line[q] && !isspace(line[q]); q++);
		if (!line[q])
			continue;
		line[q] = '\0';
		for (q++; line[q] && isspace(line[q]); q++);

		if (strcasecmp(line+p, "PERFORMER") == 0) {
			fix_cue_argument(line+q);
			if (last_cue_track == 0) {
				if (!g_utf8_validate(line + q, -1, NULL)) {
					cue_performer = g_locale_to_utf8 (line + q, -1, NULL, NULL, NULL);
				} else
					cue_performer = g_strdup(line+q);
			} else {
				if (!g_utf8_validate(line + q, -1, NULL)) {
					cue_tracks[last_cue_track-1].performer = g_locale_to_utf8 (line + q, -1, NULL, NULL, NULL);
				} else
					cue_tracks[last_cue_track-1].performer = g_strdup(line+q);
			}
		}
		else if (strcasecmp(line+p, "FILE") == 0) {
			fix_cue_argument(line+q);
			cue_file = g_strdup(line+q);		/* XXX: yaz might need to UTF validate this?? -nenolod */
		}
		else if (strcasecmp(line+p, "TITLE") == 0) {
			fix_cue_argument(line+q);
			if (last_cue_track == 0) {
				if (!g_utf8_validate(line + q, -1, NULL)) {
					cue_title = g_locale_to_utf8 (line + q, -1, NULL, NULL, NULL);
				} else
					cue_title = g_strdup(line+q);
			} else {
				if (!g_utf8_validate(line + q, -1, NULL)) {
					cue_tracks[last_cue_track-1].title = g_locale_to_utf8 (line + q, -1, NULL, NULL, NULL);
				} else
					cue_tracks[last_cue_track-1].title = g_strdup(line+q);
			}
		}
		else if (strcasecmp(line+p, "TRACK") == 0) {
			gint track;

			fix_cue_argument(line+q);
			for (p = q; line[p] && isdigit(line[p]); p++);
			line[p] = '\0';
			for (; line[q] && line[q] == '0'; q++);
			if (!line[q])
				continue;
			track = atoi(line+q);
			if (track >= MAX_CUE_TRACKS)
				continue;
			last_cue_track = track;
			cue_tracks[last_cue_track-1].index = 0;
			cue_tracks[last_cue_track-1].performer = NULL;
			cue_tracks[last_cue_track-1].title = NULL;
		}
		else if (strcasecmp(line+p, "INDEX") == 0) {
			for (p = q; line[p] && !isspace(line[p]); p++);
			if (!line[p])
				continue;
			for (p++; line[p] && isspace(line[p]); p++);
			for (q = p; line[q] && !isspace(line[q]); q++);
			if (q-p >= 8 && line[p+2] == ':' && line[p+5] == ':') {
				cue_tracks[last_cue_track-1].index =
						((line[p+0]-'0')*10 + (line[p+1]-'0')) * 60000 +
						((line[p+3]-'0')*10 + (line[p+4]-'0')) * 1000 +
						((line[p+6]-'0')*10 + (line[p+7]-'0')) * 10;
			}
		}
	}
}

static void fix_cue_argument(char *line)
{
	if (line[0] == '"') {
		gchar *l2;
		for (l2 = line+1; *l2 && *l2 != '"'; l2++)
				*(l2-1) = *l2;
			*(l2-1) = *l2;
		for (; *line && *line != '"'; line++) {
			if (*line == '\\' && *(line+1)) {
				for (l2 = line+1; *l2 && *l2 != '"'; l2++)
					*(l2-1) = *l2;
				*(l2-1) = *l2;
			}
		}
		*line = '\0';
	}
	else {
		for (; *line && *line != '\r' && *line != '\n'; line++);
		*line = '\0';
	}
}