diff --git a/Makefile.am b/Makefile.am index e2af42fa6..d01d87042 100644 --- a/Makefile.am +++ b/Makefile.am @@ -868,6 +868,7 @@ libtag_a_SOURCES =\ if ENABLE_ID3TAG libtag_a_SOURCES += \ + src/tag/Id3Load.cxx src/tag/Id3Load.hxx \ src/tag/TagId3.cxx src/tag/TagId3.hxx \ src/tag/TagRva2.cxx src/tag/TagRva2.hxx \ src/tag/Riff.cxx src/tag/Riff.hxx \ diff --git a/src/tag/Id3Load.cxx b/src/tag/Id3Load.cxx new file mode 100644 index 000000000..ef7e59cc0 --- /dev/null +++ b/src/tag/Id3Load.cxx @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2003-2016 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 "Id3Load.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "Riff.hxx" +#include "Aiff.hxx" +#include "fs/Path.hxx" +#include "fs/NarrowPath.hxx" +#include "fs/FileSystem.hxx" + +#include + +#include + +static constexpr Domain id3_domain("id3"); + +gcc_pure +static inline bool +tag_is_id3v1(struct id3_tag *tag) +{ + return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0; +} + +static size_t +fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence) +{ + if (fseek(stream, offset, whence) != 0) return 0; + return fread(buf, 1, size, stream); +} + +static long +get_id3v2_footer_size(FILE *stream, long offset, int whence) +{ + id3_byte_t buf[ID3_TAG_QUERYSIZE]; + size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + if (bufsize == 0) return 0; + return id3_tag_query(buf, bufsize); +} + +static struct id3_tag * +tag_id3_read(FILE *stream, long offset, int whence) +{ + /* It's ok if we get less than we asked for */ + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (query_buffer_size <= 0) + return nullptr; + + /* Look for a tag header */ + long tag_size = id3_tag_query(query_buffer, query_buffer_size); + if (tag_size <= 0) return nullptr; + + /* Found a tag. Allocate a buffer and read it in. */ + id3_byte_t *tag_buffer = new id3_byte_t[tag_size]; + int tag_buffer_size = fill_buffer(tag_buffer, tag_size, + stream, offset, whence); + if (tag_buffer_size < tag_size) { + delete[] tag_buffer; + return nullptr; + } + + id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size); + delete[] tag_buffer; + return tag; +} + +static struct id3_tag * +tag_id3_find_from_beginning(FILE *stream) +{ + id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET); + if (!tag) { + return nullptr; + } else if (tag_is_id3v1(tag)) { + /* id3v1 tags don't belong here */ + id3_tag_delete(tag); + return nullptr; + } + + /* We have an id3v2 tag, so let's look for SEEK frames */ + id3_frame *frame; + while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { + /* Found a SEEK frame, get it's value */ + int seek = id3_field_getint(id3_frame_field(frame, 0)); + if (seek < 0) + break; + + /* Get the tag specified by the SEEK frame */ + id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR); + if (!seektag || tag_is_id3v1(seektag)) + break; + + /* Replace the old tag with the new one */ + id3_tag_delete(tag); + tag = seektag; + } + + return tag; +} + +static struct id3_tag * +tag_id3_find_from_end(FILE *stream) +{ + /* Get an id3v1 tag from the end of file for later use */ + id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END); + + /* Get the id3v2 tag size from the footer (located before v1tag) */ + int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + if (tagsize >= 0) + return v1tag; + + /* Get the tag which the footer belongs to */ + id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR); + if (!tag) + return v1tag; + + /* We have an id3v2 tag, so ditch v1tag */ + id3_tag_delete(v1tag); + + return tag; +} + +static struct id3_tag * +tag_id3_riff_aiff_load(FILE *file) +{ + size_t size = riff_seek_id3(file); + if (size == 0) + size = aiff_seek_id3(file); + if (size == 0) + return nullptr; + + if (size > 4 * 1024 * 1024) + /* too large, don't allocate so much memory */ + return nullptr; + + id3_byte_t *buffer = new id3_byte_t[size]; + size_t ret = fread(buffer, size, 1, file); + if (ret != 1) { + LogWarning(id3_domain, "Failed to read RIFF chunk"); + delete[] buffer; + return nullptr; + } + + struct id3_tag *tag = id3_tag_parse(buffer, size); + delete[] buffer; + return tag; +} + +struct id3_tag * +tag_id3_load(Path path_fs, Error &error) +{ + FILE *file = FOpen(path_fs, PATH_LITERAL("rb")); + if (file == nullptr) { + error.FormatErrno("Failed to open file %s", + NarrowPath(path_fs).c_str()); + return nullptr; + } + + struct id3_tag *tag = tag_id3_find_from_beginning(file); + if (tag == nullptr) { + tag = tag_id3_riff_aiff_load(file); + if (tag == nullptr) + tag = tag_id3_find_from_end(file); + } + + fclose(file); + return tag; +} diff --git a/src/tag/Id3Load.hxx b/src/tag/Id3Load.hxx new file mode 100644 index 000000000..0921f64af --- /dev/null +++ b/src/tag/Id3Load.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2016 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_TAG_ID3_LOAD_HXX +#define MPD_TAG_ID3_LOAD_HXX + +#include "check.h" + +struct id3_tag; +class Path; +class Error; + +/** + * Loads the ID3 tags from the file into a libid3tag object. The + * return value must be freed with id3_tag_delete(). + * + * @return nullptr on error or if no ID3 tag was found in the file (no + * Error will be set) + */ +struct id3_tag * +tag_id3_load(Path path_fs, Error &error); + +#endif diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx index 1c0355466..4e7f1c8e1 100644 --- a/src/tag/TagId3.cxx +++ b/src/tag/TagId3.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2015 The Music Player Daemon Project + * Copyright (C) 2003-2016 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,29 +19,23 @@ #include "config.h" #include "TagId3.hxx" +#include "Id3Load.hxx" #include "TagHandler.hxx" #include "TagTable.hxx" #include "TagBuilder.hxx" #include "util/Alloc.hxx" #include "util/StringUtil.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" #include "config/ConfigGlobal.hxx" -#include "Riff.hxx" -#include "Aiff.hxx" #include "fs/Path.hxx" -#include "fs/NarrowPath.hxx" -#include "fs/FileSystem.hxx" #include #include #include -#include #include -#include # ifndef ID3_FRAME_COMPOSER # define ID3_FRAME_COMPOSER "TCOM" @@ -62,15 +56,6 @@ #define ID3_FRAME_ALBUM_ARTIST "TPE2" #endif -static constexpr Domain id3_domain("id3"); - -gcc_pure -static inline bool -tag_is_id3v1(struct id3_tag *tag) -{ - return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0; -} - gcc_pure static id3_utf8_t * tag_id3_getstring(const struct id3_frame *frame, unsigned i) @@ -358,152 +343,6 @@ tag_id3_import(struct id3_tag *tag) : tag_builder.CommitNew(); } -static size_t -fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence) -{ - if (fseek(stream, offset, whence) != 0) return 0; - return fread(buf, 1, size, stream); -} - -static long -get_id3v2_footer_size(FILE *stream, long offset, int whence) -{ - id3_byte_t buf[ID3_TAG_QUERYSIZE]; - size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); - if (bufsize == 0) return 0; - return id3_tag_query(buf, bufsize); -} - -static struct id3_tag * -tag_id3_read(FILE *stream, long offset, int whence) -{ - /* It's ok if we get less than we asked for */ - id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; - size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, - stream, offset, whence); - if (query_buffer_size <= 0) - return nullptr; - - /* Look for a tag header */ - long tag_size = id3_tag_query(query_buffer, query_buffer_size); - if (tag_size <= 0) return nullptr; - - /* Found a tag. Allocate a buffer and read it in. */ - id3_byte_t *tag_buffer = new id3_byte_t[tag_size]; - int tag_buffer_size = fill_buffer(tag_buffer, tag_size, - stream, offset, whence); - if (tag_buffer_size < tag_size) { - delete[] tag_buffer; - return nullptr; - } - - id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size); - delete[] tag_buffer; - return tag; -} - -static struct id3_tag * -tag_id3_find_from_beginning(FILE *stream) -{ - id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET); - if (!tag) { - return nullptr; - } else if (tag_is_id3v1(tag)) { - /* id3v1 tags don't belong here */ - id3_tag_delete(tag); - return nullptr; - } - - /* We have an id3v2 tag, so let's look for SEEK frames */ - id3_frame *frame; - while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { - /* Found a SEEK frame, get it's value */ - int seek = id3_field_getint(id3_frame_field(frame, 0)); - if (seek < 0) - break; - - /* Get the tag specified by the SEEK frame */ - id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR); - if (!seektag || tag_is_id3v1(seektag)) - break; - - /* Replace the old tag with the new one */ - id3_tag_delete(tag); - tag = seektag; - } - - return tag; -} - -static struct id3_tag * -tag_id3_find_from_end(FILE *stream) -{ - /* Get an id3v1 tag from the end of file for later use */ - id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END); - - /* Get the id3v2 tag size from the footer (located before v1tag) */ - int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); - if (tagsize >= 0) - return v1tag; - - /* Get the tag which the footer belongs to */ - id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR); - if (!tag) - return v1tag; - - /* We have an id3v2 tag, so ditch v1tag */ - id3_tag_delete(v1tag); - - return tag; -} - -static struct id3_tag * -tag_id3_riff_aiff_load(FILE *file) -{ - size_t size = riff_seek_id3(file); - if (size == 0) - size = aiff_seek_id3(file); - if (size == 0) - return nullptr; - - if (size > 4 * 1024 * 1024) - /* too large, don't allocate so much memory */ - return nullptr; - - id3_byte_t *buffer = new id3_byte_t[size]; - size_t ret = fread(buffer, size, 1, file); - if (ret != 1) { - LogWarning(id3_domain, "Failed to read RIFF chunk"); - delete[] buffer; - return nullptr; - } - - struct id3_tag *tag = id3_tag_parse(buffer, size); - delete[] buffer; - return tag; -} - -struct id3_tag * -tag_id3_load(Path path_fs, Error &error) -{ - FILE *file = FOpen(path_fs, PATH_LITERAL("rb")); - if (file == nullptr) { - error.FormatErrno("Failed to open file %s", - NarrowPath(path_fs).c_str()); - return nullptr; - } - - struct id3_tag *tag = tag_id3_find_from_beginning(file); - if (tag == nullptr) { - tag = tag_id3_riff_aiff_load(file); - if (tag == nullptr) - tag = tag_id3_find_from_end(file); - } - - fclose(file); - return tag; -} - bool tag_id3_scan(Path path_fs, const struct tag_handler *handler, void *handler_ctx) diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx index 94dfb1794..c7e658410 100644 --- a/src/tag/TagId3.hxx +++ b/src/tag/TagId3.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2015 The Music Player Daemon Project + * Copyright (C) 2003-2016 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,7 +27,6 @@ class Path; struct tag_handler; struct Tag; struct id3_tag; -class Error; #ifdef ENABLE_ID3TAG @@ -38,16 +37,6 @@ tag_id3_scan(Path path_fs, Tag * tag_id3_import(id3_tag *); -/** - * Loads the ID3 tags from the file into a libid3tag object. The - * return value must be freed with id3_tag_delete(). - * - * @return nullptr on error or if no ID3 tag was found in the file (no - * Error will be set) - */ -struct id3_tag * -tag_id3_load(Path path_fs, Error &error); - /** * Import all tags from the provided id3_tag *tag * diff --git a/test/dump_rva2.cxx b/test/dump_rva2.cxx index fff0aa044..34bfde0dc 100644 --- a/test/dump_rva2.cxx +++ b/test/dump_rva2.cxx @@ -18,7 +18,7 @@ */ #include "config.h" -#include "tag/TagId3.hxx" +#include "tag/Id3Load.hxx" #include "tag/TagRva2.hxx" #include "ReplayGainInfo.hxx" #include "config/ConfigGlobal.hxx"