From 1cc17bfe7a470c2e2d74466a70ce3723e10baf4a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 19 Feb 2014 08:56:58 +0100 Subject: cue/CueParser: move to playlist/ --- src/cue/CueParser.cxx | 318 --------------------- src/cue/CueParser.hxx | 144 ---------- src/playlist/cue/CueParser.cxx | 318 +++++++++++++++++++++ src/playlist/cue/CueParser.hxx | 144 ++++++++++ src/playlist/plugins/CuePlaylistPlugin.cxx | 2 +- src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx | 2 +- 6 files changed, 464 insertions(+), 464 deletions(-) delete mode 100644 src/cue/CueParser.cxx delete mode 100644 src/cue/CueParser.hxx create mode 100644 src/playlist/cue/CueParser.cxx create mode 100644 src/playlist/cue/CueParser.hxx (limited to 'src') diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx deleted file mode 100644 index dc96218f4..000000000 --- a/src/cue/CueParser.cxx +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2003-2014 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 "CueParser.hxx" -#include "util/Alloc.hxx" -#include "util/StringUtil.hxx" -#include "util/CharUtil.hxx" -#include "DetachedSong.hxx" -#include "tag/Tag.hxx" - -#include -#include -#include - -CueParser::CueParser() - :state(HEADER), - current(nullptr), - previous(nullptr), - finished(nullptr), - end(false) {} - -CueParser::~CueParser() -{ - delete current; - delete previous; - delete finished; -} - -static const char * -cue_next_word(char *p, char **pp) -{ - assert(p >= *pp); - assert(!IsWhitespaceNotNull(*p)); - - const char *word = p; - while (!IsWhitespaceOrNull(*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 == nullptr) { - /* 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 nullptr; - - return cue_next_word(p, pp); -} - -static const char * -cue_next_value(char **pp) -{ - char *p = strchug_fast(*pp); - if (*p == 0) - return nullptr; - - if (*p == '"') - return cue_next_quoted(p + 1, pp); - else - return cue_next_word(p, pp); -} - -static void -cue_add_tag(TagBuilder &tag, TagType type, char *p) -{ - const char *value = cue_next_value(&p); - if (value != nullptr) - tag.AddItem(type, value); - -} - -static void -cue_parse_rem(char *p, TagBuilder &tag) -{ - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - TagType type2 = tag_name_parse_i(type); - if (type2 != TAG_NUM_OF_ITEM_TYPES) - cue_add_tag(tag, type2, p); -} - -TagBuilder * -CueParser::GetCurrentTag() -{ - if (state == HEADER) - return &header_tag; - else if (state == TRACK) - return &song_tag; - else - return nullptr; -} - -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; -} - -void -CueParser::Commit() -{ - /* the caller of this library must call cue_parser_get() often - enough */ - assert(finished == nullptr); - assert(!end); - - if (current == nullptr) - return; - - assert(!current->GetTag().IsDefined()); - current->SetTag(song_tag.Commit()); - - finished = previous; - previous = current; - current = nullptr; -} - -void -CueParser::Feed2(char *p) -{ - assert(!end); - assert(p != nullptr); - - const char *command = cue_next_token(&p); - if (command == nullptr) - return; - - if (strcmp(command, "REM") == 0) { - TagBuilder *tag = GetCurrentTag(); - if (tag != nullptr) - cue_parse_rem(p, *tag); - } else if (strcmp(command, "PERFORMER") == 0) { - /* MPD knows a "performer" tag, but it is not a good - match for this CUE tag; from the Hydrogenaudio - Knowledgebase: "At top-level this will specify the - CD artist, while at track-level it specifies the - track artist." */ - - TagType type = state == TRACK - ? TAG_ARTIST - : TAG_ALBUM_ARTIST; - - TagBuilder *tag = GetCurrentTag(); - if (tag != nullptr) - cue_add_tag(*tag, type, p); - } else if (strcmp(command, "TITLE") == 0) { - if (state == HEADER) - cue_add_tag(header_tag, TAG_ALBUM, p); - else if (state == TRACK) - cue_add_tag(song_tag, TAG_TITLE, p); - } else if (strcmp(command, "FILE") == 0) { - Commit(); - - const char *new_filename = cue_next_value(&p); - if (new_filename == nullptr) - return; - - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - if (strcmp(type, "WAVE") != 0 && - strcmp(type, "MP3") != 0 && - strcmp(type, "AIFF") != 0) { - state = IGNORE_FILE; - return; - } - - state = WAVE; - filename = new_filename; - } else if (state == IGNORE_FILE) { - return; - } else if (strcmp(command, "TRACK") == 0) { - Commit(); - - const char *nr = cue_next_token(&p); - if (nr == nullptr) - return; - - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - if (strcmp(type, "AUDIO") != 0) { - state = IGNORE_TRACK; - return; - } - - state = TRACK; - current = new DetachedSong(filename); - assert(!current->GetTag().IsDefined()); - - song_tag = header_tag; - song_tag.AddItem(TAG_TRACK, nr); - - last_updated = false; - } else if (state == IGNORE_TRACK) { - return; - } else if (state == TRACK && strcmp(command, "INDEX") == 0) { - const char *nr = cue_next_token(&p); - if (nr == nullptr) - return; - - const char *position = cue_next_token(&p); - if (position == nullptr) - return; - - int position_ms = cue_parse_position(position); - if (position_ms < 0) - return; - - if (!last_updated && previous != nullptr && - previous->GetStartMS() < (unsigned)position_ms) { - last_updated = true; - previous->SetEndMS(position_ms); - previous->WritableTag().time = - (previous->GetEndMS() - previous->GetStartMS() + 500) / 1000; - } - - current->SetStartMS(position_ms); - } -} - -void -CueParser::Feed(const char *line) -{ - assert(!end); - assert(line != nullptr); - - char *allocated = xstrdup(line); - Feed2(allocated); - free(allocated); -} - -void -CueParser::Finish() -{ - if (end) - /* has already been called, ignore */ - return; - - Commit(); - end = true; -} - -DetachedSong * -CueParser::Get() -{ - if (finished == nullptr && end) { - /* cue_parser_finish() has been called already: - deliver all remaining (partial) results */ - assert(current == nullptr); - - finished = previous; - previous = nullptr; - } - - DetachedSong *song = finished; - finished = nullptr; - return song; -} diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx deleted file mode 100644 index 7e040169b..000000000 --- a/src/cue/CueParser.hxx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2003-2014 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_HXX -#define MPD_CUE_PARSER_HXX - -#include "check.h" -#include "tag/TagBuilder.hxx" -#include "Compiler.h" - -#include - -class DetachedSong; -struct Tag; - -class CueParser { - 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; - - /** - * Tags read from the CUE header. - */ - TagBuilder header_tag; - - /** - * Tags read for the current song (attribute #current). When - * #current gets moved to #previous, TagBuilder::Commit() will - * be called. - */ - TagBuilder song_tag; - - std::string filename; - - /** - * The song currently being edited. - */ - DetachedSong *current; - - /** - * The previous song. It is remembered because its end_time - * will be set to the current song's start time. - */ - DetachedSong *previous; - - /** - * A song that is completely finished and can be returned to - * the caller via cue_parser_get(). - */ - DetachedSong *finished; - - /** - * Set to true after previous.end_time has been updated to the - * start time of the current song. - */ - bool last_updated; - - /** - * Tracks whether cue_parser_finish() has been called. If - * true, then all remaining (partial) results will be - * delivered by cue_parser_get(). - */ - bool end; - -public: - CueParser(); - ~CueParser(); - - /** - * 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 Feed(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 Finish(); - - /** - * 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 - */ - DetachedSong *Get(); - -private: - gcc_pure - TagBuilder *GetCurrentTag(); - - /** - * Commit the current song. It will be moved to "previous", - * so the next song may soon edit its end time (using the next - * song's start time). - */ - void Commit(); - - void Feed2(char *p); -}; - -#endif diff --git a/src/playlist/cue/CueParser.cxx b/src/playlist/cue/CueParser.cxx new file mode 100644 index 000000000..dc96218f4 --- /dev/null +++ b/src/playlist/cue/CueParser.cxx @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2003-2014 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 "CueParser.hxx" +#include "util/Alloc.hxx" +#include "util/StringUtil.hxx" +#include "util/CharUtil.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" + +#include +#include +#include + +CueParser::CueParser() + :state(HEADER), + current(nullptr), + previous(nullptr), + finished(nullptr), + end(false) {} + +CueParser::~CueParser() +{ + delete current; + delete previous; + delete finished; +} + +static const char * +cue_next_word(char *p, char **pp) +{ + assert(p >= *pp); + assert(!IsWhitespaceNotNull(*p)); + + const char *word = p; + while (!IsWhitespaceOrNull(*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 == nullptr) { + /* 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 nullptr; + + return cue_next_word(p, pp); +} + +static const char * +cue_next_value(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return nullptr; + + if (*p == '"') + return cue_next_quoted(p + 1, pp); + else + return cue_next_word(p, pp); +} + +static void +cue_add_tag(TagBuilder &tag, TagType type, char *p) +{ + const char *value = cue_next_value(&p); + if (value != nullptr) + tag.AddItem(type, value); + +} + +static void +cue_parse_rem(char *p, TagBuilder &tag) +{ + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + TagType type2 = tag_name_parse_i(type); + if (type2 != TAG_NUM_OF_ITEM_TYPES) + cue_add_tag(tag, type2, p); +} + +TagBuilder * +CueParser::GetCurrentTag() +{ + if (state == HEADER) + return &header_tag; + else if (state == TRACK) + return &song_tag; + else + return nullptr; +} + +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; +} + +void +CueParser::Commit() +{ + /* the caller of this library must call cue_parser_get() often + enough */ + assert(finished == nullptr); + assert(!end); + + if (current == nullptr) + return; + + assert(!current->GetTag().IsDefined()); + current->SetTag(song_tag.Commit()); + + finished = previous; + previous = current; + current = nullptr; +} + +void +CueParser::Feed2(char *p) +{ + assert(!end); + assert(p != nullptr); + + const char *command = cue_next_token(&p); + if (command == nullptr) + return; + + if (strcmp(command, "REM") == 0) { + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + cue_parse_rem(p, *tag); + } else if (strcmp(command, "PERFORMER") == 0) { + /* MPD knows a "performer" tag, but it is not a good + match for this CUE tag; from the Hydrogenaudio + Knowledgebase: "At top-level this will specify the + CD artist, while at track-level it specifies the + track artist." */ + + TagType type = state == TRACK + ? TAG_ARTIST + : TAG_ALBUM_ARTIST; + + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + cue_add_tag(*tag, type, p); + } else if (strcmp(command, "TITLE") == 0) { + if (state == HEADER) + cue_add_tag(header_tag, TAG_ALBUM, p); + else if (state == TRACK) + cue_add_tag(song_tag, TAG_TITLE, p); + } else if (strcmp(command, "FILE") == 0) { + Commit(); + + const char *new_filename = cue_next_value(&p); + if (new_filename == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "WAVE") != 0 && + strcmp(type, "MP3") != 0 && + strcmp(type, "AIFF") != 0) { + state = IGNORE_FILE; + return; + } + + state = WAVE; + filename = new_filename; + } else if (state == IGNORE_FILE) { + return; + } else if (strcmp(command, "TRACK") == 0) { + Commit(); + + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "AUDIO") != 0) { + state = IGNORE_TRACK; + return; + } + + state = TRACK; + current = new DetachedSong(filename); + assert(!current->GetTag().IsDefined()); + + song_tag = header_tag; + song_tag.AddItem(TAG_TRACK, nr); + + last_updated = false; + } else if (state == IGNORE_TRACK) { + return; + } else if (state == TRACK && strcmp(command, "INDEX") == 0) { + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *position = cue_next_token(&p); + if (position == nullptr) + return; + + int position_ms = cue_parse_position(position); + if (position_ms < 0) + return; + + if (!last_updated && previous != nullptr && + previous->GetStartMS() < (unsigned)position_ms) { + last_updated = true; + previous->SetEndMS(position_ms); + previous->WritableTag().time = + (previous->GetEndMS() - previous->GetStartMS() + 500) / 1000; + } + + current->SetStartMS(position_ms); + } +} + +void +CueParser::Feed(const char *line) +{ + assert(!end); + assert(line != nullptr); + + char *allocated = xstrdup(line); + Feed2(allocated); + free(allocated); +} + +void +CueParser::Finish() +{ + if (end) + /* has already been called, ignore */ + return; + + Commit(); + end = true; +} + +DetachedSong * +CueParser::Get() +{ + if (finished == nullptr && end) { + /* cue_parser_finish() has been called already: + deliver all remaining (partial) results */ + assert(current == nullptr); + + finished = previous; + previous = nullptr; + } + + DetachedSong *song = finished; + finished = nullptr; + return song; +} diff --git a/src/playlist/cue/CueParser.hxx b/src/playlist/cue/CueParser.hxx new file mode 100644 index 000000000..7e040169b --- /dev/null +++ b/src/playlist/cue/CueParser.hxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 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_HXX +#define MPD_CUE_PARSER_HXX + +#include "check.h" +#include "tag/TagBuilder.hxx" +#include "Compiler.h" + +#include + +class DetachedSong; +struct Tag; + +class CueParser { + 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; + + /** + * Tags read from the CUE header. + */ + TagBuilder header_tag; + + /** + * Tags read for the current song (attribute #current). When + * #current gets moved to #previous, TagBuilder::Commit() will + * be called. + */ + TagBuilder song_tag; + + std::string filename; + + /** + * The song currently being edited. + */ + DetachedSong *current; + + /** + * The previous song. It is remembered because its end_time + * will be set to the current song's start time. + */ + DetachedSong *previous; + + /** + * A song that is completely finished and can be returned to + * the caller via cue_parser_get(). + */ + DetachedSong *finished; + + /** + * Set to true after previous.end_time has been updated to the + * start time of the current song. + */ + bool last_updated; + + /** + * Tracks whether cue_parser_finish() has been called. If + * true, then all remaining (partial) results will be + * delivered by cue_parser_get(). + */ + bool end; + +public: + CueParser(); + ~CueParser(); + + /** + * 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 Feed(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 Finish(); + + /** + * 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 + */ + DetachedSong *Get(); + +private: + gcc_pure + TagBuilder *GetCurrentTag(); + + /** + * Commit the current song. It will be moved to "previous", + * so the next song may soon edit its end time (using the next + * song's start time). + */ + void Commit(); + + void Feed2(char *p); +}; + +#endif diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx index d6be4eb83..8776baace 100644 --- a/src/playlist/plugins/CuePlaylistPlugin.cxx +++ b/src/playlist/plugins/CuePlaylistPlugin.cxx @@ -21,7 +21,7 @@ #include "CuePlaylistPlugin.hxx" #include "../PlaylistPlugin.hxx" #include "../SongEnumerator.hxx" -#include "cue/CueParser.hxx" +#include "../cue/CueParser.hxx" #include "input/TextInputStream.hxx" #include diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx index 53f3feda0..2e903ae03 100644 --- a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx @@ -27,12 +27,12 @@ #include "EmbeddedCuePlaylistPlugin.hxx" #include "../PlaylistPlugin.hxx" #include "../SongEnumerator.hxx" +#include "../cue/CueParser.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" #include "tag/ApeTag.hxx" #include "DetachedSong.hxx" #include "TagFile.hxx" -#include "cue/CueParser.hxx" #include "fs/Traits.hxx" #include "fs/AllocatedPath.hxx" #include "util/ASCII.hxx" -- cgit v1.2.3