diff options
author | Max Kellermann <max@duempel.org> | 2012-02-10 00:12:29 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2012-02-10 00:12:29 +0100 |
commit | abcc225763e543b75baaa4ff11342e911194910d (patch) | |
tree | c278ea61339d0a7c5ab1ed00881a15751c29b2c4 /src | |
parent | b9673fc521c453f8729544541ba48fcafefcf4e9 (diff) | |
download | mpd-abcc225763e543b75baaa4ff11342e911194910d.tar.gz mpd-abcc225763e543b75baaa4ff11342e911194910d.tar.xz mpd-abcc225763e543b75baaa4ff11342e911194910d.zip |
cue_parser: new line based CUE sheet parser
To replace libcue, the unmaintained and crashy library.
Diffstat (limited to 'src')
-rw-r--r-- | src/cue/cue_parser.c | 327 | ||||
-rw-r--r-- | src/cue/cue_parser.h | 58 | ||||
-rw-r--r-- | src/cue/cue_tag.c | 244 | ||||
-rw-r--r-- | src/cue/cue_tag.h | 23 | ||||
-rw-r--r-- | src/playlist/cue_playlist_plugin.c | 89 | ||||
-rw-r--r-- | src/playlist_list.c | 2 |
6 files changed, 413 insertions, 330 deletions
diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c new file mode 100644 index 000000000..034d4a1f9 --- /dev/null +++ b/src/cue/cue_parser.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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" +#include "cue_parser.h" +#include "string_util.h" +#include "song.h" +#include "tag.h" + +#include <assert.h> +#include <stdlib.h> + +struct cue_parser { + enum { + /** + * Parsing the CUE header. + */ + HEADER, + + /** + * Parsing a "FILE ... WAVE". + */ + WAVE, + + /** + * Ignore everything until the next "FILE". + */ + IGNORE_FILE, + + /** + * Parsing a "TRACK ... AUDIO". + */ + TRACK, + + /** + * Ignore everything until the next "TRACK". + */ + IGNORE_TRACK, + } state; + + struct tag *tag; + + char *filename; + + struct song *current, *previous, *finished; + + bool last_updated; +}; + +struct cue_parser * +cue_parser_new(void) +{ + struct cue_parser *parser = g_new(struct cue_parser, 1); + parser->state = HEADER; + parser->tag = tag_new(); + parser->filename = NULL; + parser->current = NULL; + parser->previous = NULL; + parser->finished = NULL; + return parser; +} + +void +cue_parser_free(struct cue_parser *parser) +{ + tag_free(parser->tag); + g_free(parser->filename); + + if (parser->current != NULL) + song_free(parser->current); + + if (parser->finished != NULL) + song_free(parser->finished); + + g_free(parser); +} + +static const char * +cue_next_word(char *p, char **pp) +{ + assert(p >= *pp); + assert(!g_ascii_isspace(*p)); + + const char *word = p; + while (*p != 0 && !g_ascii_isspace(*p)) + ++p; + + *p = 0; + *pp = p + 1; + return word; +} + +static const char * +cue_next_quoted(char *p, char **pp) +{ + assert(p >= *pp); + assert(p[-1] == '"'); + + char *end = strchr(p, '"'); + if (end == NULL) { + /* syntax error - ignore it silently */ + *pp = p + strlen(p); + return p; + } + + *end = 0; + *pp = end + 1; + + return p; +} + +static const char * +cue_next_token(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return NULL; + + return cue_next_word(p, pp); +} + +static const char * +cue_next_value(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return NULL; + + if (*p == '"') + return cue_next_quoted(p + 1, pp); + else + return cue_next_word(p, pp); +} + +static void +cue_add_tag(struct tag *tag, enum tag_type type, char *p) +{ + const char *value = cue_next_value(&p); + if (value != NULL) + tag_add_item(tag, type, value); + +} + +static void +cue_parse_rem(char *p, struct tag *tag) +{ + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + enum tag_type type2 = tag_name_parse_i(type); + if (type2 != TAG_NUM_OF_ITEM_TYPES) + cue_add_tag(tag, type2, p); +} + +static struct tag * +cue_current_tag(struct cue_parser *parser) +{ + if (parser->state == HEADER) + return parser->tag; + else if (parser->state == TRACK) + return parser->current->tag; + else + return NULL; +} + +static int +cue_parse_position(const char *p) +{ + char *endptr; + unsigned long minutes = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long seconds = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long frames = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != 0) + return -1; + + return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; +} + +static void +cue_parser_feed2(struct cue_parser *parser, char *p) +{ + assert(parser != NULL); + assert(p != NULL); + + const char *command = cue_next_token(&p); + if (command == NULL) + return; + + if (strcmp(command, "REM") == 0) { + struct tag *tag = cue_current_tag(parser); + if (tag != NULL) + cue_parse_rem(p, tag); + } else if (strcmp(command, "PERFORMER") == 0) { + struct tag *tag = cue_current_tag(parser); + if (tag != NULL) + cue_add_tag(tag, TAG_PERFORMER, p); + } else if (strcmp(command, "TITLE") == 0) { + if (parser->state == HEADER) + cue_add_tag(parser->tag, TAG_ALBUM, p); + else if (parser->state == TRACK) + cue_add_tag(parser->current->tag, TAG_TITLE, p); + } else if (strcmp(command, "FILE") == 0) { + cue_parser_finish(parser); + + const char *filename = cue_next_value(&p); + if (filename == NULL) + return; + + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + if (strcmp(type, "WAVE") != 0) { + parser->state = IGNORE_FILE; + return; + } + + parser->state = WAVE; + g_free(parser->filename); + parser->filename = g_strdup(filename); + } else if (parser->state == IGNORE_FILE) { + return; + } else if (strcmp(command, "TRACK") == 0) { + cue_parser_finish(parser); + + const char *nr = cue_next_token(&p); + if (nr == NULL) + return; + + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + if (strcmp(type, "AUDIO") != 0) { + parser->state = IGNORE_TRACK; + return; + } + + parser->state = TRACK; + parser->current = song_remote_new(parser->filename); + assert(parser->current->tag == NULL); + parser->current->tag = tag_dup(parser->tag); + tag_add_item(parser->current->tag, TAG_TRACK, nr); + parser->last_updated = false; + } else if (parser->state == IGNORE_TRACK) { + return; + } else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) { + const char *nr = cue_next_token(&p); + if (nr == NULL) + return; + + const char *position = cue_next_token(&p); + if (position == NULL) + return; + + int position_ms = cue_parse_position(position); + if (position_ms < 0) + return; + + if (!parser->last_updated && parser->previous != NULL && + parser->previous->start_ms < (unsigned)position_ms) { + parser->last_updated = true; + parser->previous->end_ms = position_ms; + parser->previous->tag->time = + (parser->previous->end_ms - parser->previous->start_ms + 500) / 1000; + } + + parser->current->start_ms = position_ms; + } +} + +void +cue_parser_feed(struct cue_parser *parser, const char *line) +{ + assert(parser != NULL); + assert(line != NULL); + + char *allocated = g_strdup(line); + cue_parser_feed2(parser, allocated); + g_free(allocated); +} + +void +cue_parser_finish(struct cue_parser *parser) +{ + if (parser->finished != NULL) + song_free(parser->finished); + + parser->finished = parser->previous; + parser->previous = parser->current; + parser->current = NULL; +} + +struct song * +cue_parser_get(struct cue_parser *parser) +{ + assert(parser != NULL); + + struct song *song = parser->finished; + parser->finished = NULL; + return song; +} diff --git a/src/cue/cue_parser.h b/src/cue/cue_parser.h new file mode 100644 index 000000000..d8d695739 --- /dev/null +++ b/src/cue/cue_parser.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef MPD_CUE_PARSER_H +#define MPD_CUE_PARSER_H + +#include "check.h" + +#include <stdbool.h> + +struct cue_parser * +cue_parser_new(void); + +void +cue_parser_free(struct cue_parser *parser); + +/** + * Feed a text line from the CUE file into the parser. Call + * cue_parser_get() after this to see if a song has been finished. + */ +void +cue_parser_feed(struct cue_parser *parser, const char *line); + +/** + * Tell the parser that the end of the file has been reached. Call + * cue_parser_get() after this to see if a song has been finished. + * This procedure must be done twice! + */ +void +cue_parser_finish(struct cue_parser *parser); + +/** + * Check if a song was finished by the last cue_parser_feed() or + * cue_parser_finish() call. + * + * @return a song object that must be freed by the caller, or NULL if + * no song was finished at this time + */ +struct song * +cue_parser_get(struct cue_parser *parser); + +#endif diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c deleted file mode 100644 index 6ee38bbd0..000000000 --- a/src/cue/cue_tag.c +++ /dev/null @@ -1,244 +0,0 @@ -#include "config.h" -#include "cue_tag.h" -#include "tag.h" - -#include <libcue/libcue.h> -#include <assert.h> - -static struct tag * -cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem) -{ - struct tag *tag; - char *tmp; - - assert(cdtext != NULL); - - tag = tag_new(); - - tag_begin_add(tag); - - /* TAG_ALBUM_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - /* TAG_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - /* TAG_PERFORMER */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_PERFORMER, tmp); - - /* TAG_COMPOSER */ - if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_COMPOSER, tmp); - - /* TAG_ALBUM */ - if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM, tmp); - - /* TAG_GENRE */ - if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_GENRE, tmp); - - /* TAG_DATE */ - if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_DATE, tmp); - - /* TAG_COMMENT */ - if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_COMMENT, tmp); - - /* TAG_DISC */ - if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_DISC, tmp); - - /* stream name, usually empty - * tag_add_item(tag, TAG_NAME,); - */ - - /* REM MUSICBRAINZ entry? - tag_add_item(tag, TAG_MUSICBRAINZ_ARTISTID,); - tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMID,); - tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMARTISTID,); - tag_add_item(tag, TAG_MUSICBRAINZ_TRACKID,); - */ - - tag_end_add(tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - return NULL; - } - - return tag; -} - -static struct tag * -cue_tag_track(struct Cdtext *cdtext, struct Rem *rem) -{ - struct tag *tag; - char *tmp; - - assert(cdtext != NULL); - - tag = tag_new(); - - tag_begin_add(tag); - - /* TAG_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - /* TAG_TITLE */ - if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_TITLE, tmp); - - /* TAG_GENRE */ - if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_GENRE, tmp); - - /* TAG_DATE */ - if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_DATE, tmp); - - /* TAG_COMPOSER */ - if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_COMPOSER, tmp); - - /* TAG_PERFORMER */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_PERFORMER, tmp); - - /* TAG_COMMENT */ - if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_COMMENT, tmp); - - /* TAG_DISC */ - if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_DISC, tmp); - - tag_end_add(tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - return NULL; - } - - return tag; -} - -struct tag * -cue_tag(struct Cd *cd, unsigned tnum) -{ - struct tag *cd_tag, *track_tag, *tag; - struct Track *track; - - assert(cd != NULL); - - track = cd_get_track(cd, tnum); - if (track == NULL) - return NULL; - - /* tag from CDtext info */ - cd_tag = cue_tag_cd(cd_get_cdtext(cd), cd_get_rem(cd)); - - /* tag from TRACKtext info */ - track_tag = cue_tag_track(track_get_cdtext(track), - track_get_rem(track)); - - tag = tag_merge_replace(cd_tag, track_tag); - if (tag == NULL) - return NULL; - - /* Create a tag number */ - - tag_clear_items_by_type(tag, TAG_TRACK); - - char convert_uinttostring[8]; - snprintf(convert_uinttostring, sizeof(convert_uinttostring), - "%02d/%02d", tnum, cd_get_ntrack(cd)); - tag_add_item(tag, TAG_TRACK, convert_uinttostring); - - tag->time = track_get_length(track) - - track_get_index(track, 1) - + track_get_zero_pre(track); - track = cd_get_track(cd, tnum + 1); - if (track != NULL) - tag->time += track_get_index(track, 1) - - track_get_zero_pre(track); - /* libcue returns the track duration in frames, and there are - 75 frames per second; this formula rounds down */ - tag->time = tag->time / 75; - - return tag; -} - -struct tag * -cue_tag_file(FILE *fp, unsigned tnum) -{ - struct Cd *cd; - struct tag *tag; - - assert(fp != NULL); - - if (tnum > 256) - return NULL; - - cd = cue_parse_file(fp); - if (cd == NULL) - return NULL; - - tag = cue_tag(cd, tnum); - cd_delete(cd); - - return tag; -} - -struct tag * -cue_tag_string(const char *str, unsigned tnum) -{ - struct Cd *cd; - struct tag *tag; - - assert(str != NULL); - - if (tnum > 256) - return NULL; - - cd = cue_parse_string(str); - if (cd == NULL) - return NULL; - - tag = cue_tag(cd, tnum); - cd_delete(cd); - - return tag; -} diff --git a/src/cue/cue_tag.h b/src/cue/cue_tag.h deleted file mode 100644 index 1ddaa59c8..000000000 --- a/src/cue/cue_tag.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef MPD_CUE_TAG_H -#define MPD_CUE_TAG_H - -#include "check.h" - -#ifdef HAVE_CUE /* libcue */ - -#include <stdio.h> - -struct tag; -struct Cd; - -struct tag * -cue_tag(struct Cd *cd, unsigned tnum); - -struct tag * -cue_tag_file(FILE *file, unsigned tnum); - -struct tag * -cue_tag_string(const char *str, unsigned tnum); - -#endif /* libcue */ -#endif diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c index 3f2d5b34c..b85de77d3 100644 --- a/src/playlist/cue_playlist_plugin.c +++ b/src/playlist/cue_playlist_plugin.c @@ -22,10 +22,11 @@ #include "playlist_plugin.h" #include "tag.h" #include "song.h" -#include "cue/cue_tag.h" +#include "cue/cue_parser.h" +#include "input_stream.h" +#include "text_input_stream.h" #include <glib.h> -#include <libcue/libcue.h> #include <assert.h> #include <string.h> @@ -35,32 +36,21 @@ struct cue_playlist { struct playlist_provider base; - struct Cd *cd; - - unsigned next; + struct input_stream *is; + struct text_input_stream *tis; + struct cue_parser *parser; }; static struct playlist_provider * -cue_playlist_open_uri(const char *uri, - G_GNUC_UNUSED GMutex *mutex, G_GNUC_UNUSED GCond *cond) +cue_playlist_open_stream(struct input_stream *is) { - struct cue_playlist *playlist; - FILE *file; - struct Cd *cd; - - file = fopen(uri, "rt"); - if (file == NULL) - return NULL; + struct cue_playlist *playlist = g_new(struct cue_playlist, 1); + playlist_provider_init(&playlist->base, &cue_playlist_plugin); - cd = cue_parse_file(file); - fclose(file); - if (cd == NULL) - return NULL; + playlist->is = is; + playlist->tis = text_input_stream_new(is); + playlist->parser = cue_parser_new(); - playlist = g_new(struct cue_playlist, 1); - playlist_provider_init(&playlist->base, &cue_playlist_plugin); - playlist->cd = cd; - playlist->next = 1; return &playlist->base; } @@ -70,7 +60,8 @@ cue_playlist_close(struct playlist_provider *_playlist) { struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - cd_delete(playlist->cd); + cue_parser_free(playlist->parser); + text_input_stream_free(playlist->tis); g_free(playlist); } @@ -78,45 +69,21 @@ static struct song * cue_playlist_read(struct playlist_provider *_playlist) { struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - struct Track *track; - struct tag *tag; - const char *filename; - struct song *song; - - track = cd_get_track(playlist->cd, playlist->next); - if (track == NULL) - return NULL; - - tag = cue_tag(playlist->cd, playlist->next); - if (tag == NULL) - return NULL; - - ++playlist->next; - - filename = track_get_filename(track); - if (*filename == 0 || filename[0] == '.' || - strchr(filename, '/') != NULL) { - /* unsafe characters found, bail out */ - tag_free(tag); - return NULL; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + const char *line; + while ((line = text_input_stream_read(playlist->tis)) != NULL) { + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; } - song = song_remote_new(filename); - song->tag = tag; - song->start_ms = ((track_get_start(track) - + track_get_index(track, 1) - - track_get_zero_pre(track)) * 1000) / 75; - - /* append pregap of the next track to the end of this one */ - track = cd_get_track(playlist->cd, playlist->next); - if (track != NULL) - song->end_ms = ((track_get_start(track) - + track_get_index(track, 1) - - track_get_zero_pre(track)) * 1000) / 75; - else - song->end_ms = 0; - - return song; + cue_parser_finish(playlist->parser); + return cue_parser_get(playlist->parser); } static const char *const cue_playlist_suffixes[] = { @@ -132,7 +99,7 @@ static const char *const cue_playlist_mime_types[] = { const struct playlist_plugin cue_playlist_plugin = { .name = "cue", - .open_uri = cue_playlist_open_uri, + .open_stream = cue_playlist_open_stream, .close = cue_playlist_close, .read = cue_playlist_read, diff --git a/src/playlist_list.c b/src/playlist_list.c index 1f220eee8..f89ab386d 100644 --- a/src/playlist_list.c +++ b/src/playlist_list.c @@ -54,9 +54,7 @@ static const struct playlist_plugin *const playlist_plugins[] = { #ifdef ENABLE_LASTFM &lastfm_playlist_plugin, #endif -#ifdef HAVE_CUE &cue_playlist_plugin, -#endif #ifdef HAVE_FLAC &flac_playlist_plugin, #endif |