tag/Handler: convert to class with virtual methods
This commit is contained in:
		| @@ -25,7 +25,7 @@ | ||||
|  | ||||
| bool | ||||
| tag_archive_scan(ArchiveFile &archive, const char *path_utf8, | ||||
| 		 const TagHandler &handler, void *handler_ctx) noexcept | ||||
| 		 TagHandler &handler) noexcept | ||||
| try { | ||||
| 	Mutex mutex; | ||||
|  | ||||
| @@ -33,7 +33,7 @@ try { | ||||
| 	if (!is) | ||||
| 		return false; | ||||
|  | ||||
| 	return tag_stream_scan(*is, handler, handler_ctx); | ||||
| 	return tag_stream_scan(*is, handler); | ||||
| } catch (const std::exception &e) { | ||||
| 	return false; | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| #include "check.h" | ||||
|  | ||||
| class ArchiveFile; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class TagBuilder; | ||||
|  | ||||
| /** | ||||
| @@ -36,7 +36,7 @@ class TagBuilder; | ||||
|  */ | ||||
| bool | ||||
| tag_archive_scan(ArchiveFile &archive, const char *path_utf8, | ||||
| 		 const TagHandler &handler, void *handler_ctx) noexcept; | ||||
| 		 TagHandler &handler) noexcept; | ||||
|  | ||||
| /** | ||||
|  * Scan the tags of a song file inside an archive.  Invokes matching | ||||
|   | ||||
| @@ -36,21 +36,20 @@ class TagFileScan { | ||||
| 	const Path path_fs; | ||||
| 	const char *const suffix; | ||||
|  | ||||
| 	const TagHandler &handler; | ||||
| 	void *handler_ctx; | ||||
| 	TagHandler &handler; | ||||
|  | ||||
| 	Mutex mutex; | ||||
| 	InputStreamPtr is; | ||||
|  | ||||
| public: | ||||
| 	TagFileScan(Path _path_fs, const char *_suffix, | ||||
| 		    const TagHandler &_handler, void *_handler_ctx) noexcept | ||||
| 		    TagHandler &_handler) noexcept | ||||
| 		:path_fs(_path_fs), suffix(_suffix), | ||||
| 		 handler(_handler), handler_ctx(_handler_ctx) , | ||||
| 		 handler(_handler), | ||||
| 		 is(nullptr) {} | ||||
|  | ||||
| 	bool ScanFile(const DecoderPlugin &plugin) noexcept { | ||||
| 		return plugin.ScanFile(path_fs, handler, handler_ctx); | ||||
| 		return plugin.ScanFile(path_fs, handler); | ||||
| 	} | ||||
|  | ||||
| 	bool ScanStream(const DecoderPlugin &plugin) noexcept { | ||||
| @@ -72,7 +71,7 @@ public: | ||||
| 		} | ||||
|  | ||||
| 		/* now try the stream_tag() method */ | ||||
| 		return plugin.ScanStream(*is, handler, handler_ctx); | ||||
| 		return plugin.ScanStream(*is, handler); | ||||
| 	} | ||||
|  | ||||
| 	bool Scan(const DecoderPlugin &plugin) noexcept { | ||||
| @@ -82,8 +81,7 @@ public: | ||||
| }; | ||||
|  | ||||
| bool | ||||
| tag_file_scan(Path path_fs, | ||||
| 	      const TagHandler &handler, void *handler_ctx) noexcept | ||||
| tag_file_scan(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	assert(!path_fs.IsNull()); | ||||
|  | ||||
| @@ -95,7 +93,7 @@ tag_file_scan(Path path_fs, | ||||
|  | ||||
| 	const auto suffix_utf8 = Path::FromFS(suffix).ToUTF8(); | ||||
|  | ||||
| 	TagFileScan tfs(path_fs, suffix_utf8.c_str(), handler, handler_ctx); | ||||
| 	TagFileScan tfs(path_fs, suffix_utf8.c_str(), handler); | ||||
| 	return decoder_plugins_try([&](const DecoderPlugin &plugin){ | ||||
| 			return tfs.Scan(plugin); | ||||
| 		}); | ||||
| @@ -104,11 +102,13 @@ tag_file_scan(Path path_fs, | ||||
| bool | ||||
| tag_file_scan(Path path, TagBuilder &builder) noexcept | ||||
| { | ||||
| 	if (!tag_file_scan(path, full_tag_handler, &builder)) | ||||
| 	FullTagHandler h(builder); | ||||
|  | ||||
| 	if (!tag_file_scan(path, h)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (builder.empty()) | ||||
| 		ScanGenericTags(path, full_tag_handler, &builder); | ||||
| 		ScanGenericTags(path, h); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| #include "check.h" | ||||
|  | ||||
| class Path; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class TagBuilder; | ||||
|  | ||||
| /** | ||||
| @@ -34,8 +34,7 @@ class TagBuilder; | ||||
|  * found) | ||||
|  */ | ||||
| bool | ||||
| tag_file_scan(Path path, | ||||
| 	      const TagHandler &handler, void *handler_ctx) noexcept; | ||||
| tag_file_scan(Path path, TagHandler &handler) noexcept; | ||||
|  | ||||
| /** | ||||
|  * Scan the tags of a song file.  Invokes matching decoder plugins, | ||||
|   | ||||
| @@ -46,7 +46,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin, | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(InputStream &is, const TagHandler &handler, void *ctx) | ||||
| tag_stream_scan(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	assert(is.IsReady()); | ||||
|  | ||||
| @@ -62,44 +62,46 @@ tag_stream_scan(InputStream &is, const TagHandler &handler, void *ctx) | ||||
| 		mime = (mime_base = GetMimeTypeBase(mime)).c_str(); | ||||
|  | ||||
| 	return decoder_plugins_try([suffix, mime, &is, | ||||
| 				    &handler, ctx](const DecoderPlugin &plugin){ | ||||
| 				    &handler](const DecoderPlugin &plugin){ | ||||
| 			try { | ||||
| 				is.LockRewind(); | ||||
| 			} catch (...) { | ||||
| 			} | ||||
|  | ||||
| 			return CheckDecoderPlugin(plugin, suffix, mime) && | ||||
| 				plugin.ScanStream(is, handler, ctx); | ||||
| 				plugin.ScanStream(is, handler); | ||||
| 		}); | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(const char *uri, const TagHandler &handler, void *ctx) | ||||
| tag_stream_scan(const char *uri, TagHandler &handler) noexcept | ||||
| try { | ||||
| 	Mutex mutex; | ||||
|  | ||||
| 	auto is = InputStream::OpenReady(uri, mutex); | ||||
| 	return tag_stream_scan(*is, handler, ctx); | ||||
| 	return tag_stream_scan(*is, handler); | ||||
| } catch (const std::exception &e) { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(InputStream &is, TagBuilder &builder) | ||||
| tag_stream_scan(InputStream &is, TagBuilder &builder) noexcept | ||||
| { | ||||
| 	assert(is.IsReady()); | ||||
|  | ||||
| 	if (!tag_stream_scan(is, full_tag_handler, &builder)) | ||||
| 	FullTagHandler h(builder); | ||||
|  | ||||
| 	if (!tag_stream_scan(is, h)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (builder.empty()) | ||||
| 		ScanGenericTags(is, full_tag_handler, &builder); | ||||
| 		ScanGenericTags(is, h); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(const char *uri, TagBuilder &builder) | ||||
| tag_stream_scan(const char *uri, TagBuilder &builder) noexcept | ||||
| try { | ||||
| 	Mutex mutex; | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| #include "check.h" | ||||
|  | ||||
| class InputStream; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class TagBuilder; | ||||
|  | ||||
| /** | ||||
| @@ -34,10 +34,10 @@ class TagBuilder; | ||||
|  * found) | ||||
|  */ | ||||
| bool | ||||
| tag_stream_scan(InputStream &is, const TagHandler &handler, void *ctx); | ||||
| tag_stream_scan(InputStream &is, TagHandler &handler) noexcept; | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(const char *uri, const TagHandler &handler, void *ctx); | ||||
| tag_stream_scan(const char *uri, TagHandler &handler) noexcept; | ||||
|  | ||||
| /** | ||||
|  * Scan the tags of an #InputStream.  Invokes matching decoder | ||||
| @@ -48,9 +48,9 @@ tag_stream_scan(const char *uri, const TagHandler &handler, void *ctx); | ||||
|  * found) | ||||
|  */ | ||||
| bool | ||||
| tag_stream_scan(InputStream &is, TagBuilder &builder); | ||||
| tag_stream_scan(InputStream &is, TagBuilder &builder) noexcept; | ||||
|  | ||||
| bool | ||||
| tag_stream_scan(const char *uri, TagBuilder &builder); | ||||
| tag_stream_scan(const char *uri, TagBuilder &builder) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -137,25 +137,24 @@ IsValidValue(const char *p) noexcept | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static void | ||||
| print_pair(const char *key, const char *value, void *ctx) | ||||
| { | ||||
| 	auto &r = *(Response *)ctx; | ||||
| class PrintCommentHandler final : public NullTagHandler { | ||||
| 	Response &response; | ||||
|  | ||||
| 	if (IsValidName(key) && IsValidValue(value)) | ||||
| 		r.Format("%s: %s\n", key, value); | ||||
| } | ||||
| public: | ||||
| 	explicit PrintCommentHandler(Response &_response) noexcept | ||||
| 		:NullTagHandler(WANT_PAIR), response(_response) {} | ||||
|  | ||||
| static constexpr TagHandler print_comment_handler = { | ||||
| 	nullptr, | ||||
| 	nullptr, | ||||
| 	print_pair, | ||||
| 	void OnPair(const char *key, const char *value) noexcept override { | ||||
| 		if (IsValidName(key) && IsValidValue(value)) | ||||
| 			response.Format("%s: %s\n", key, value); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| static CommandResult | ||||
| read_stream_comments(Response &r, const char *uri) | ||||
| { | ||||
| 	if (!tag_stream_scan(uri, print_comment_handler, &r)) { | ||||
| 	PrintCommentHandler h(r); | ||||
| 	if (!tag_stream_scan(uri, h)) { | ||||
| 		r.Error(ACK_ERROR_NO_EXIST, "Failed to load file"); | ||||
| 		return CommandResult::ERROR; | ||||
| 	} | ||||
| @@ -167,12 +166,13 @@ read_stream_comments(Response &r, const char *uri) | ||||
| static CommandResult | ||||
| read_file_comments(Response &r, const Path path_fs) | ||||
| { | ||||
| 	if (!tag_file_scan(path_fs, print_comment_handler, &r)) { | ||||
| 	PrintCommentHandler h(r); | ||||
| 	if (!tag_file_scan(path_fs, h)) { | ||||
| 		r.Error(ACK_ERROR_NO_EXIST, "Failed to load file"); | ||||
| 		return CommandResult::ERROR; | ||||
| 	} | ||||
|  | ||||
| 	ScanGenericTags(path_fs, print_comment_handler, &r); | ||||
| 	ScanGenericTags(path_fs, h); | ||||
|  | ||||
| 	return CommandResult::OK; | ||||
|  | ||||
|   | ||||
| @@ -93,15 +93,6 @@ handle_kill(gcc_unused Client &client, gcc_unused Request request, | ||||
| 	return CommandResult::KILL; | ||||
| } | ||||
|  | ||||
| static void | ||||
| print_tag(TagType type, const char *value, void *ctx) | ||||
| { | ||||
| 	auto &r = *(Response *)ctx; | ||||
|  | ||||
| 	if (r.GetClient().tag_mask.Test(type)) | ||||
| 		tag_print(r, type, value); | ||||
| } | ||||
|  | ||||
| CommandResult | ||||
| handle_listfiles(Client &client, Request args, Response &r) | ||||
| { | ||||
| @@ -149,16 +140,24 @@ handle_listfiles(Client &client, Request args, Response &r) | ||||
| 	gcc_unreachable(); | ||||
| } | ||||
|  | ||||
| static constexpr TagHandler print_tag_handler = { | ||||
| 	nullptr, | ||||
| 	print_tag, | ||||
| 	nullptr, | ||||
| class PrintTagHandler final : public NullTagHandler { | ||||
| 	Response &response; | ||||
|  | ||||
| public: | ||||
| 	explicit PrintTagHandler(Response &_response) noexcept | ||||
| 		:NullTagHandler(WANT_TAG), response(_response) {} | ||||
|  | ||||
| 	void OnTag(TagType type, const char *value) noexcept override { | ||||
| 		if (response.GetClient().tag_mask.Test(type)) | ||||
| 			tag_print(response, type, value); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| static CommandResult | ||||
| handle_lsinfo_absolute(Response &r, const char *uri) | ||||
| { | ||||
| 	if (!tag_stream_scan(uri, print_tag_handler, &r)) { | ||||
| 	PrintTagHandler h(r); | ||||
| 	if (!tag_stream_scan(uri, h)) { | ||||
| 		r.Error(ACK_ERROR_NO_EXIST, "No such file"); | ||||
| 		return CommandResult::ERROR; | ||||
| 	} | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|  | ||||
| struct ConfigBlock; | ||||
| class InputStream; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class Path; | ||||
| class DecoderClient; | ||||
| class DetachedSong; | ||||
| @@ -71,18 +71,14 @@ struct DecoderPlugin { | ||||
| 	 * | ||||
| 	 * @return false if the operation has failed | ||||
| 	 */ | ||||
| 	bool (*scan_file)(Path path_fs, | ||||
| 			  const TagHandler &handler, | ||||
| 			  void *handler_ctx) noexcept; | ||||
| 	bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept; | ||||
|  | ||||
| 	/** | ||||
| 	 * Scan metadata of a file. | ||||
| 	 * | ||||
| 	 * @return false if the operation has failed | ||||
| 	 */ | ||||
| 	bool (*scan_stream)(InputStream &is, | ||||
| 			    const TagHandler &handler, | ||||
| 			    void *handler_ctx) noexcept; | ||||
| 	bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept; | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Return a "virtual" filename for subtracks in | ||||
| @@ -139,20 +135,18 @@ struct DecoderPlugin { | ||||
| 	 * Read the tag of a file. | ||||
| 	 */ | ||||
| 	template<typename P> | ||||
| 	bool ScanFile(P path_fs, | ||||
| 		      const TagHandler &handler, void *handler_ctx) const noexcept { | ||||
| 	bool ScanFile(P path_fs, TagHandler &handler) const noexcept { | ||||
| 		return scan_file != nullptr | ||||
| 			? scan_file(path_fs, handler, handler_ctx) | ||||
| 			? scan_file(path_fs, handler) | ||||
| 			: false; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Read the tag of a stream. | ||||
| 	 */ | ||||
| 	bool ScanStream(InputStream &is, | ||||
| 			const TagHandler &handler, void *handler_ctx) const noexcept { | ||||
| 	bool ScanStream(InputStream &is, TagHandler &handler) const noexcept { | ||||
| 		return scan_stream != nullptr | ||||
| 			? scan_stream(is, handler, handler_ctx) | ||||
| 			? scan_stream(is, handler) | ||||
| 			: false; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -83,16 +83,14 @@ adplug_file_decode(DecoderClient &client, Path path_fs) | ||||
|  | ||||
| static void | ||||
| adplug_scan_tag(TagType type, const std::string &value, | ||||
| 		const TagHandler &handler, void *handler_ctx) noexcept | ||||
| 		TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (!value.empty()) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       type, value.c_str()); | ||||
| 		handler.OnTag(type, value.c_str()); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| adplug_scan_file(Path path_fs, | ||||
| 		 const TagHandler &handler, void *handler_ctx) noexcept | ||||
| adplug_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	CEmuopl opl(sample_rate, true, true); | ||||
| 	opl.init(); | ||||
| @@ -101,16 +99,15 @@ adplug_scan_file(Path path_fs, | ||||
| 	if (player == nullptr) | ||||
| 		return false; | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 				    SongTime::FromMS(player->songlength())); | ||||
| 	handler.OnDuration(SongTime::FromMS(player->songlength())); | ||||
|  | ||||
| 	if (handler.tag != nullptr) { | ||||
| 	if (handler.WantTag()) { | ||||
| 		adplug_scan_tag(TAG_TITLE, player->gettitle(), | ||||
| 				handler, handler_ctx); | ||||
| 				handler); | ||||
| 		adplug_scan_tag(TAG_ARTIST, player->getauthor(), | ||||
| 				handler, handler_ctx); | ||||
| 				handler); | ||||
| 		adplug_scan_tag(TAG_COMMENT, player->getdesc(), | ||||
| 				handler, handler_ctx); | ||||
| 				handler); | ||||
| 	} | ||||
|  | ||||
| 	delete player; | ||||
|   | ||||
| @@ -256,14 +256,13 @@ audiofile_get_duration(InputStream &is) noexcept | ||||
| } | ||||
|  | ||||
| static bool | ||||
| audiofile_scan_stream(InputStream &is, | ||||
| 		      const TagHandler &handler, void *handler_ctx) noexcept | ||||
| audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	const auto duration = audiofile_get_duration(is); | ||||
| 	if (duration.IsNegative()) | ||||
| 		return false; | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); | ||||
| 	handler.OnDuration(SongTime(duration)); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -115,9 +115,8 @@ dsdlib_valid_freq(uint32_t samplefreq) noexcept | ||||
|  | ||||
| #ifdef ENABLE_ID3TAG | ||||
| void | ||||
| dsdlib_tag_id3(InputStream &is, | ||||
| 	       const TagHandler &handler, | ||||
| 	       void *handler_ctx, offset_type tagoffset) | ||||
| dsdlib_tag_id3(InputStream &is, TagHandler &handler, | ||||
| 	       offset_type tagoffset) | ||||
| { | ||||
| 	if (tagoffset == 0 || !is.KnownSize()) | ||||
| 		return; | ||||
| @@ -150,7 +149,7 @@ dsdlib_tag_id3(InputStream &is, | ||||
| 	if (id3_tag == nullptr) | ||||
| 		return; | ||||
|  | ||||
| 	scan_id3_tag(id3_tag, handler, handler_ctx); | ||||
| 	scan_id3_tag(id3_tag, handler); | ||||
|  | ||||
| 	id3_tag_delete(id3_tag); | ||||
| 	return; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class DecoderClient; | ||||
| class InputStream; | ||||
|  | ||||
| @@ -79,8 +79,7 @@ dsdlib_valid_freq(uint32_t samplefreq) noexcept; | ||||
|  * DSF and DSDIFF files are imported | ||||
|  */ | ||||
| void | ||||
| dsdlib_tag_id3(InputStream &is, | ||||
| 	       const TagHandler &handler, | ||||
| 	       void *handler_ctx, offset_type tagoffset); | ||||
| dsdlib_tag_id3(InputStream &is, TagHandler &handler, | ||||
| 	       offset_type tagoffset); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -186,8 +186,8 @@ dsdiff_read_prop(DecoderClient *client, InputStream &is, | ||||
|  | ||||
| static void | ||||
| dsdiff_handle_native_tag(InputStream &is, | ||||
| 			 const TagHandler &handler, | ||||
| 			 void *handler_ctx, offset_type tagoffset, | ||||
| 			 TagHandler &handler, | ||||
| 			 offset_type tagoffset, | ||||
| 			 TagType type) | ||||
| { | ||||
| 	if (!dsdlib_skip_to(nullptr, is, tagoffset)) | ||||
| @@ -212,7 +212,7 @@ dsdiff_handle_native_tag(InputStream &is, | ||||
| 		return; | ||||
|  | ||||
| 	string[length] = '\0'; | ||||
| 	tag_handler_invoke_tag(handler, handler_ctx, type, label); | ||||
| 	handler.OnTag(type, label); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| @@ -228,8 +228,7 @@ static bool | ||||
| dsdiff_read_metadata_extra(DecoderClient *client, InputStream &is, | ||||
| 			   DsdiffMetaData *metadata, | ||||
| 			   DsdiffChunkHeader *chunk_header, | ||||
| 			   const TagHandler &handler, | ||||
| 			   void *handler_ctx) | ||||
| 			   TagHandler &handler) | ||||
| { | ||||
|  | ||||
| 	/* skip from DSD data to next chunk header */ | ||||
| @@ -286,17 +285,17 @@ dsdiff_read_metadata_extra(DecoderClient *client, InputStream &is, | ||||
| 	if (id3_offset != 0) { | ||||
| 		/* a ID3 tag has preference over the other tags, do not process | ||||
| 		   other tags if we have one */ | ||||
| 		dsdlib_tag_id3(is, handler, handler_ctx, id3_offset); | ||||
| 		dsdlib_tag_id3(is, handler, id3_offset); | ||||
| 		return true; | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	if (artist_offset != 0) | ||||
| 		dsdiff_handle_native_tag(is, handler, handler_ctx, | ||||
| 		dsdiff_handle_native_tag(is, handler, | ||||
| 					 artist_offset, TAG_ARTIST); | ||||
|  | ||||
| 	if (title_offset != 0) | ||||
| 		dsdiff_handle_native_tag(is, handler, handler_ctx, | ||||
| 		dsdiff_handle_native_tag(is, handler, | ||||
| 					 title_offset, TAG_TITLE); | ||||
| 	return true; | ||||
| } | ||||
| @@ -449,9 +448,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| dsdiff_scan_stream(InputStream &is, | ||||
| 		   gcc_unused const TagHandler &handler, | ||||
| 		   gcc_unused void *handler_ctx) noexcept | ||||
| dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	DsdiffMetaData metadata; | ||||
| 	DsdiffChunkHeader chunk_header; | ||||
| @@ -469,11 +466,11 @@ dsdiff_scan_stream(InputStream &is, | ||||
| 	uint64_t n_frames = metadata.chunk_size / metadata.channels; | ||||
| 	auto songtime = SongTime::FromScale<uint64_t>(n_frames, | ||||
| 						      sample_rate); | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, songtime); | ||||
| 	handler.OnDuration(songtime); | ||||
|  | ||||
| 	/* Read additional metadata and created tags if available */ | ||||
| 	dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, | ||||
| 				   handler, handler_ctx); | ||||
| 				   handler); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -325,9 +325,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| dsf_scan_stream(InputStream &is, | ||||
| 		gcc_unused const TagHandler &handler, | ||||
| 		gcc_unused void *handler_ctx) noexcept | ||||
| dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	/* check DSF metadata */ | ||||
| 	DsfMetaData metadata; | ||||
| @@ -342,11 +340,11 @@ dsf_scan_stream(InputStream &is, | ||||
| 	const auto n_blocks = metadata.n_blocks; | ||||
| 	auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE, | ||||
| 						      sample_rate); | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, songtime); | ||||
| 	handler.OnDuration(songtime); | ||||
|  | ||||
| #ifdef ENABLE_ID3TAG | ||||
| 	/* Add available tags from the ID3 tag */ | ||||
| 	dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); | ||||
| 	dsdlib_tag_id3(is, handler, metadata.id3_offset); | ||||
| #endif | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -414,16 +414,14 @@ faad_stream_decode(DecoderClient &client, InputStream &is) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| faad_scan_stream(InputStream &is, | ||||
| 		 const TagHandler &handler, void *handler_ctx) noexcept | ||||
| faad_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	auto result = faad_get_file_time(is); | ||||
| 	if (!result.first) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!result.second.IsNegative()) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    SongTime(result.second)); | ||||
| 		handler.OnDuration(SongTime(result.second)); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -572,21 +572,20 @@ FfmpegParseMetaData(DecoderClient &client, | ||||
| } | ||||
|  | ||||
| static void | ||||
| FfmpegScanMetadata(const AVStream &stream, | ||||
| 		   const TagHandler &handler, void *handler_ctx) | ||||
| FfmpegScanMetadata(const AVStream &stream, TagHandler &handler) noexcept | ||||
| { | ||||
| 	FfmpegScanDictionary(stream.metadata, handler, handler_ctx); | ||||
| 	FfmpegScanDictionary(stream.metadata, handler); | ||||
| } | ||||
|  | ||||
| static void | ||||
| FfmpegScanMetadata(const AVFormatContext &format_context, int audio_stream, | ||||
| 		   const TagHandler &handler, void *handler_ctx) | ||||
| 		   TagHandler &handler) noexcept | ||||
| { | ||||
| 	assert(audio_stream >= 0); | ||||
|  | ||||
| 	FfmpegScanDictionary(format_context.metadata, handler, handler_ctx); | ||||
| 	FfmpegScanDictionary(format_context.metadata, handler); | ||||
| 	FfmpegScanMetadata(*format_context.streams[audio_stream], | ||||
| 			   handler, handler_ctx); | ||||
| 			   handler); | ||||
| } | ||||
|  | ||||
| #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0) | ||||
| @@ -595,8 +594,8 @@ static void | ||||
| FfmpegScanTag(const AVFormatContext &format_context, int audio_stream, | ||||
| 	      TagBuilder &tag) | ||||
| { | ||||
| 	FfmpegScanMetadata(format_context, audio_stream, | ||||
| 			   full_tag_handler, &tag); | ||||
| 	FullTagHandler h(tag); | ||||
| 	FfmpegScanMetadata(format_context, audio_stream, h); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -828,7 +827,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input) | ||||
|  | ||||
| static bool | ||||
| FfmpegScanStream(AVFormatContext &format_context, | ||||
| 		 const TagHandler &handler, void *handler_ctx) | ||||
| 		 TagHandler &handler) noexcept | ||||
| { | ||||
| 	const int find_result = | ||||
| 		avformat_find_stream_info(&format_context, nullptr); | ||||
| @@ -841,22 +840,19 @@ FfmpegScanStream(AVFormatContext &format_context, | ||||
|  | ||||
| 	const AVStream &stream = *format_context.streams[audio_stream]; | ||||
| 	if (stream.duration != (int64_t)AV_NOPTS_VALUE) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    FromFfmpegTime(stream.duration, | ||||
| 							   stream.time_base)); | ||||
| 		handler.OnDuration(FromFfmpegTime(stream.duration, | ||||
| 						  stream.time_base)); | ||||
| 	else if (format_context.duration != (int64_t)AV_NOPTS_VALUE) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    FromFfmpegTime(format_context.duration, | ||||
| 							   AV_TIME_BASE_Q)); | ||||
| 		handler.OnDuration(FromFfmpegTime(format_context.duration, | ||||
| 						  AV_TIME_BASE_Q)); | ||||
|  | ||||
| 	FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx); | ||||
| 	FfmpegScanMetadata(format_context, audio_stream, handler); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| ffmpeg_scan_stream(InputStream &is, | ||||
| 		   const TagHandler &handler, void *handler_ctx) noexcept | ||||
| ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	AVInputFormat *input_format = ffmpeg_probe(nullptr, is); | ||||
| 	if (input_format == nullptr) | ||||
| @@ -877,7 +873,7 @@ ffmpeg_scan_stream(InputStream &is, | ||||
| 		avformat_close_input(&f); | ||||
| 	}; | ||||
|  | ||||
| 	return FfmpegScanStream(*f, handler, handler_ctx); | ||||
| 	return FfmpegScanStream(*f, handler); | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -43,46 +43,42 @@ static constexpr struct tag_table ffmpeg_tags[] = { | ||||
| static void | ||||
| FfmpegScanTag(TagType type, | ||||
| 	      AVDictionary *m, const char *name, | ||||
| 	      const TagHandler &handler, void *handler_ctx) | ||||
| 	      TagHandler &handler) noexcept | ||||
| { | ||||
| 	AVDictionaryEntry *mt = nullptr; | ||||
|  | ||||
| 	while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       type, mt->value); | ||||
| 		handler.OnTag(type, mt->value); | ||||
| } | ||||
|  | ||||
| static void | ||||
| FfmpegScanPairs(AVDictionary *dict, | ||||
| 		const TagHandler &handler, void *handler_ctx) | ||||
| FfmpegScanPairs(AVDictionary *dict, TagHandler &handler) noexcept | ||||
| { | ||||
| 	AVDictionaryEntry *i = nullptr; | ||||
|  | ||||
| 	while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) | ||||
| 		tag_handler_invoke_pair(handler, handler_ctx, | ||||
| 					i->key, i->value); | ||||
| 		handler.OnPair(i->key, i->value); | ||||
| } | ||||
|  | ||||
| void | ||||
| FfmpegScanDictionary(AVDictionary *dict, | ||||
| 		     const TagHandler &handler, void *handler_ctx) | ||||
| FfmpegScanDictionary(AVDictionary *dict, TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (handler.tag != nullptr) { | ||||
| 	if (handler.WantTag()) { | ||||
| 		for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) | ||||
| 			FfmpegScanTag(TagType(i), dict, tag_item_names[i], | ||||
| 				      handler, handler_ctx); | ||||
| 				      handler); | ||||
|  | ||||
| 		for (const struct tag_table *i = ffmpeg_tags; | ||||
| 		     i->name != nullptr; ++i) | ||||
| 			FfmpegScanTag(i->type, dict, i->name, | ||||
| 				      handler, handler_ctx); | ||||
| 				      handler); | ||||
|  | ||||
| 		for (const struct tag_table *i = musicbrainz_txxx_tags; | ||||
| 		     i->name != nullptr; ++i) | ||||
| 			FfmpegScanTag(i->type, dict, i->name, | ||||
| 				      handler, handler_ctx); | ||||
| 				      handler); | ||||
| 	} | ||||
|  | ||||
| 	if (handler.pair != nullptr) | ||||
| 		FfmpegScanPairs(dict, handler, handler_ctx); | ||||
| 	if (handler.WantPair()) | ||||
| 		FfmpegScanPairs(dict, handler); | ||||
| } | ||||
|   | ||||
| @@ -21,10 +21,9 @@ | ||||
| #define MPD_FFMPEG_METADATA_HXX | ||||
|  | ||||
| struct AVDictionary; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
|  | ||||
| void | ||||
| FfmpegScanDictionary(AVDictionary *dict, | ||||
| 		     const TagHandler &handler, void *handler_ctx); | ||||
| FfmpegScanDictionary(AVDictionary *dict, TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -69,8 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, | ||||
| } | ||||
|  | ||||
| static bool | ||||
| flac_scan_file(Path path_fs, | ||||
| 	       const TagHandler &handler, void *handler_ctx) noexcept | ||||
| flac_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	FlacMetadataChain chain; | ||||
| 	if (!chain.Read(NarrowPath(path_fs))) { | ||||
| @@ -80,13 +79,12 @@ flac_scan_file(Path path_fs, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	chain.Scan(handler, handler_ctx); | ||||
| 	chain.Scan(handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| flac_scan_stream(InputStream &is, | ||||
| 		 const TagHandler &handler, void *handler_ctx) noexcept | ||||
| flac_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	FlacMetadataChain chain; | ||||
| 	if (!chain.Read(is)) { | ||||
| @@ -96,7 +94,7 @@ flac_scan_stream(InputStream &is, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	chain.Scan(handler, handler_ctx); | ||||
| 	chain.Scan(handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -315,8 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| oggflac_scan_file(Path path_fs, | ||||
| 		  const TagHandler &handler, void *handler_ctx) noexcept | ||||
| oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	FlacMetadataChain chain; | ||||
| 	if (!chain.ReadOgg(NarrowPath(path_fs))) { | ||||
| @@ -326,13 +323,12 @@ oggflac_scan_file(Path path_fs, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	chain.Scan(handler, handler_ctx); | ||||
| 	chain.Scan(handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| oggflac_scan_stream(InputStream &is, | ||||
| 		    const TagHandler &handler, void *handler_ctx) noexcept | ||||
| oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	FlacMetadataChain chain; | ||||
| 	if (!chain.ReadOgg(is)) { | ||||
| @@ -342,7 +338,7 @@ oggflac_scan_stream(InputStream &is, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	chain.Scan(handler, handler_ctx); | ||||
| 	chain.Scan(handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -79,11 +79,11 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, | ||||
| static bool | ||||
| flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, | ||||
| 		  const char *name, TagType tag_type, | ||||
| 		  const TagHandler &handler, void *handler_ctx) | ||||
| 		  TagHandler &handler) noexcept | ||||
| { | ||||
| 	const char *value = flac_comment_value(entry, name); | ||||
| 	if (value != nullptr) { | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); | ||||
| 		handler.OnTag(tag_type, value); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| @@ -92,36 +92,33 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, | ||||
|  | ||||
| static void | ||||
| flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, | ||||
| 		  const TagHandler &handler, void *handler_ctx) | ||||
| 		  TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (handler.pair != nullptr) { | ||||
| 	if (handler.WantPair()) { | ||||
| 		const char *comment = (const char *)entry->entry; | ||||
| 		const DivideString split(comment, '='); | ||||
| 		if (split.IsDefined() && !split.empty()) | ||||
| 			tag_handler_invoke_pair(handler, handler_ctx, | ||||
| 						split.GetFirst(), | ||||
| 						split.GetSecond()); | ||||
| 			handler.OnPair(split.GetFirst(), split.GetSecond()); | ||||
| 	} | ||||
|  | ||||
| 	for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) | ||||
| 		if (flac_copy_comment(entry, i->name, i->type, | ||||
| 				      handler, handler_ctx)) | ||||
| 		if (flac_copy_comment(entry, i->name, i->type, handler)) | ||||
| 			return; | ||||
|  | ||||
| 	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) | ||||
| 		if (flac_copy_comment(entry, | ||||
| 				      tag_item_names[i], (TagType)i, | ||||
| 				      handler, handler_ctx)) | ||||
| 				      handler)) | ||||
| 			return; | ||||
| } | ||||
|  | ||||
| static void | ||||
| flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, | ||||
| 		   const TagHandler &handler, void *handler_ctx) | ||||
| 		   TagHandler &handler) noexcept | ||||
| { | ||||
| 	for (unsigned i = 0; i < comment->num_comments; ++i) | ||||
| 		flac_scan_comment(&comment->comments[i], | ||||
| 				  handler, handler_ctx); | ||||
| 				  handler); | ||||
| } | ||||
|  | ||||
| gcc_pure | ||||
| @@ -136,18 +133,17 @@ flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) noexcept | ||||
|  | ||||
| void | ||||
| flac_scan_metadata(const FLAC__StreamMetadata *block, | ||||
| 		   const TagHandler &handler, void *handler_ctx) | ||||
| 		   TagHandler &handler) noexcept | ||||
| { | ||||
| 	switch (block->type) { | ||||
| 	case FLAC__METADATA_TYPE_VORBIS_COMMENT: | ||||
| 		flac_scan_comments(&block->data.vorbis_comment, | ||||
| 				   handler, handler_ctx); | ||||
| 				   handler); | ||||
| 		break; | ||||
|  | ||||
| 	case FLAC__METADATA_TYPE_STREAMINFO: | ||||
| 		if (block->data.stream_info.sample_rate > 0) | ||||
| 			tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 						    flac_duration(&block->data.stream_info)); | ||||
| 			handler.OnDuration(flac_duration(&block->data.stream_info)); | ||||
| 		break; | ||||
|  | ||||
| 	default: | ||||
| @@ -159,12 +155,13 @@ Tag | ||||
| flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) | ||||
| { | ||||
| 	TagBuilder tag_builder; | ||||
| 	flac_scan_comments(comment, add_tag_handler, &tag_builder); | ||||
| 	AddTagHandler h(tag_builder); | ||||
| 	flac_scan_comments(comment, h); | ||||
| 	return tag_builder.Commit(); | ||||
| } | ||||
|  | ||||
| void | ||||
| FlacMetadataChain::Scan(const TagHandler &handler, void *handler_ctx) | ||||
| FlacMetadataChain::Scan(TagHandler &handler) noexcept | ||||
| { | ||||
| 	FLACMetadataIterator iterator(*this); | ||||
|  | ||||
| @@ -173,6 +170,6 @@ FlacMetadataChain::Scan(const TagHandler &handler, void *handler_ctx) | ||||
| 		if (block == nullptr) | ||||
| 			break; | ||||
|  | ||||
| 		flac_scan_metadata(block, handler, handler_ctx); | ||||
| 		flac_scan_metadata(block, handler); | ||||
| 	} while (iterator.Next()); | ||||
| } | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|  | ||||
| #include <FLAC/metadata.h> | ||||
|  | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class MixRampInfo; | ||||
|  | ||||
| class FlacMetadataChain { | ||||
| @@ -82,7 +82,7 @@ public: | ||||
| 		return FLAC__Metadata_ChainStatusString[GetStatus()]; | ||||
| 	} | ||||
|  | ||||
| 	void Scan(const TagHandler &handler, void *handler_ctx); | ||||
| 	void Scan(TagHandler &handler) noexcept; | ||||
| }; | ||||
|  | ||||
| class FLACMetadataIterator { | ||||
| @@ -126,6 +126,6 @@ flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); | ||||
|  | ||||
| void | ||||
| flac_scan_metadata(const FLAC__StreamMetadata *block, | ||||
| 		   const TagHandler &handler, void *handler_ctx); | ||||
| 		   TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -193,8 +193,7 @@ fluidsynth_file_decode(DecoderClient &client, Path path_fs) | ||||
|  | ||||
| static bool | ||||
| fluidsynth_scan_file(Path path_fs, | ||||
| 		     gcc_unused const TagHandler &handler, | ||||
| 		     gcc_unused void *handler_ctx) noexcept | ||||
| 		     gcc_unused TagHandler &handler) noexcept | ||||
| { | ||||
| 	return fluid_is_midifile(path_fs.c_str()); | ||||
| } | ||||
|   | ||||
| @@ -215,15 +215,13 @@ gme_file_decode(DecoderClient &client, Path path_fs) | ||||
|  | ||||
| static void | ||||
| ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count, | ||||
| 	    const TagHandler &handler, void *handler_ctx) | ||||
| 	    TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (info.play_length > 0) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    SongTime::FromMS(info.play_length)); | ||||
| 		handler.OnDuration(SongTime::FromMS(info.play_length)); | ||||
|  | ||||
| 	if (track_count > 1) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, | ||||
| 				       StringFormat<16>("%u", song_num + 1)); | ||||
| 		handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1)); | ||||
|  | ||||
| 	if (info.song != nullptr) { | ||||
| 		if (track_count > 1) { | ||||
| @@ -232,33 +230,26 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count, | ||||
| 				StringFormat<1024>("%s (%u/%d)", | ||||
| 						   info.song, song_num + 1, | ||||
| 						   track_count); | ||||
| 			tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 					       TAG_TITLE, tag_title); | ||||
| 			handler.OnTag(TAG_TITLE, tag_title); | ||||
| 		} else | ||||
| 			tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 					       TAG_TITLE, info.song); | ||||
| 			handler.OnTag(TAG_TITLE, info.song); | ||||
| 	} | ||||
|  | ||||
| 	if (info.author != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_ARTIST, info.author); | ||||
| 		handler.OnTag(TAG_ARTIST, info.author); | ||||
|  | ||||
| 	if (info.game != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_ALBUM, info.game); | ||||
| 		handler.OnTag(TAG_ALBUM, info.game); | ||||
|  | ||||
| 	if (info.comment != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_COMMENT, info.comment); | ||||
| 		handler.OnTag(TAG_COMMENT, info.comment); | ||||
|  | ||||
| 	if (info.copyright != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_DATE, info.copyright); | ||||
| 		handler.OnTag(TAG_DATE, info.copyright); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| ScanMusicEmu(Music_Emu *emu, unsigned song_num, | ||||
| 	     const TagHandler &handler, void *handler_ctx) | ||||
| ScanMusicEmu(Music_Emu *emu, unsigned song_num, TagHandler &handler) noexcept | ||||
| { | ||||
| 	gme_info_t *ti; | ||||
| 	const char *gme_err = gme_track_info(emu, &ti, song_num); | ||||
| @@ -271,14 +262,12 @@ ScanMusicEmu(Music_Emu *emu, unsigned song_num, | ||||
|  | ||||
| 	AtScopeExit(ti) { gme_free_info(ti); }; | ||||
|  | ||||
| 	ScanGmeInfo(*ti, song_num, gme_track_count(emu), | ||||
| 		    handler, handler_ctx); | ||||
| 	ScanGmeInfo(*ti, song_num, gme_track_count(emu), handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| gme_scan_file(Path path_fs, | ||||
| 	      const TagHandler &handler, void *handler_ctx) noexcept | ||||
| gme_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	const auto container = ParseContainerPath(path_fs); | ||||
|  | ||||
| @@ -289,7 +278,7 @@ gme_scan_file(Path path_fs, | ||||
|  | ||||
| 	AtScopeExit(emu) { gme_delete(emu); }; | ||||
|  | ||||
| 	return ScanMusicEmu(emu, container.track, handler, handler_ctx); | ||||
| 	return ScanMusicEmu(emu, container.track, handler); | ||||
| } | ||||
|  | ||||
| static std::forward_list<DetachedSong> | ||||
| @@ -316,8 +305,8 @@ gme_container_scan(Path path_fs) | ||||
|  | ||||
| 	auto tail = list.before_begin(); | ||||
| 	for (unsigned i = 0; i < num_songs; ++i) { | ||||
| 		ScanMusicEmu(emu, i, | ||||
| 			     add_tag_handler, &tag_builder); | ||||
| 		AddTagHandler h(tag_builder); | ||||
| 		ScanMusicEmu(emu, i, h); | ||||
|  | ||||
| 		const auto track_name = | ||||
| 			StringFormat<64>(SUBTUNE_PREFIX "%03u.%s", i+1, | ||||
|   | ||||
| @@ -1062,16 +1062,14 @@ mp3_decode(DecoderClient &client, InputStream &input_stream) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| mad_decoder_scan_stream(InputStream &is, | ||||
| 			const TagHandler &handler, void *handler_ctx) noexcept | ||||
| mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	const auto result = mad_decoder_total_file_time(is); | ||||
| 	if (!result.first) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!result.second.IsNegative()) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    SongTime(result.second)); | ||||
| 		handler.OnDuration(SongTime(result.second)); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -185,8 +185,7 @@ mikmod_decoder_file_decode(DecoderClient &client, Path path_fs) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| mikmod_decoder_scan_file(Path path_fs, | ||||
| 			 const TagHandler &handler, void *handler_ctx) noexcept | ||||
| mikmod_decoder_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	/* deconstify the path because libmikmod wants a non-const | ||||
| 	   string pointer */ | ||||
| @@ -204,8 +203,7 @@ mikmod_decoder_scan_file(Path path_fs, | ||||
|  | ||||
| 	char *title = Player_LoadTitle(path2); | ||||
| 	if (title != nullptr) { | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_TITLE, title); | ||||
| 		handler.OnTag(TAG_TITLE, title); | ||||
| #if (LIBMIKMOD_VERSION >= 0x030200) | ||||
| 		MikMod_free(title); | ||||
| #else | ||||
|   | ||||
| @@ -175,20 +175,17 @@ mod_decode(DecoderClient &client, InputStream &is) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| modplug_scan_stream(InputStream &is, | ||||
| 		    const TagHandler &handler, void *handler_ctx) noexcept | ||||
| modplug_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	ModPlugFile *f = LoadModPlugFile(nullptr, is); | ||||
| 	if (f == nullptr) | ||||
| 		return false; | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 				    SongTime::FromMS(ModPlug_GetLength(f))); | ||||
| 	handler.OnDuration(SongTime::FromMS(ModPlug_GetLength(f))); | ||||
|  | ||||
| 	const char *title = ModPlug_GetName(f); | ||||
| 	if (title != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_TITLE, title); | ||||
| 		handler.OnTag(TAG_TITLE, title); | ||||
|  | ||||
| 	ModPlug_Unload(f); | ||||
|  | ||||
|   | ||||
| @@ -257,14 +257,13 @@ mpcdec_get_file_duration(InputStream &is) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| mpcdec_scan_stream(InputStream &is, | ||||
| 		   const TagHandler &handler, void *handler_ctx) noexcept | ||||
| mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	const auto duration = mpcdec_get_file_duration(is); | ||||
| 	if (duration.IsNegative()) | ||||
| 		return false; | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); | ||||
| 	handler.OnDuration(SongTime(duration)); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -279,8 +279,7 @@ mpd_mpg123_file_decode(DecoderClient &client, Path path_fs) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| mpd_mpg123_scan_file(Path path_fs, | ||||
| 		     const TagHandler &handler, void *handler_ctx) noexcept | ||||
| mpd_mpg123_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	int error; | ||||
| 	mpg123_handle *const handle = mpg123_new(nullptr, &error); | ||||
| @@ -316,7 +315,7 @@ mpd_mpg123_scan_file(Path path_fs, | ||||
| 		SongTime::FromScale<uint64_t>(num_samples, | ||||
| 					      audio_format.sample_rate); | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, duration); | ||||
| 	handler.OnDuration(duration); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -207,10 +207,9 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet) | ||||
| 	rgi.Clear(); | ||||
|  | ||||
| 	TagBuilder tag_builder; | ||||
| 	AddTagHandler h(tag_builder); | ||||
|  | ||||
| 	if (ScanOpusTags(packet.packet, packet.bytes, | ||||
| 			 &rgi, | ||||
| 			 add_tag_handler, &tag_builder) && | ||||
| 	if (ScanOpusTags(packet.packet, packet.bytes, &rgi, h) && | ||||
| 	    !tag_builder.empty()) { | ||||
| 		client.SubmitReplayGain(&rgi); | ||||
|  | ||||
| @@ -314,7 +313,7 @@ ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream, | ||||
|  | ||||
| static bool | ||||
| ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream, | ||||
| 		     const TagHandler &handler, void *handler_ctx) | ||||
| 		     TagHandler &handler) | ||||
| { | ||||
| 	ogg_packet packet; | ||||
|  | ||||
| @@ -322,12 +321,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream, | ||||
| 		IsOpusTags(packet) && | ||||
| 		ScanOpusTags(packet.packet, packet.bytes, | ||||
| 			     nullptr, | ||||
| 			     handler, handler_ctx); | ||||
| 			     handler); | ||||
| } | ||||
|  | ||||
| static void | ||||
| VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream, | ||||
| 		  const TagHandler &handler, void *handler_ctx) | ||||
| 		  TagHandler &handler) | ||||
| { | ||||
| 	ogg_packet packet; | ||||
|  | ||||
| @@ -335,13 +334,12 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream, | ||||
| 		const auto duration = | ||||
| 			SongTime::FromScale<uint64_t>(packet.granulepos, | ||||
| 						      opus_sample_rate); | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, duration); | ||||
| 		handler.OnDuration(duration); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static bool | ||||
| mpd_opus_scan_stream(InputStream &is, | ||||
| 		     const TagHandler &handler, void *handler_ctx) noexcept | ||||
| mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	InputStreamReader reader(is); | ||||
| 	OggSyncState oy(reader); | ||||
| @@ -354,10 +352,10 @@ mpd_opus_scan_stream(InputStream &is, | ||||
|  | ||||
| 	unsigned channels; | ||||
| 	if (!ReadAndParseOpusHead(oy, os, channels) || | ||||
| 	    !ReadAndVisitOpusTags(oy, os, handler, handler_ctx)) | ||||
| 	    !ReadAndVisitOpusTags(oy, os, handler)) | ||||
| 		return false; | ||||
|  | ||||
| 	VisitOpusDuration(is, oy, os, handler, handler_ctx); | ||||
| 	VisitOpusDuration(is, oy, os, handler); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ ParseOpusTagName(const char *name) noexcept | ||||
| static void | ||||
| ScanOneOpusTag(const char *name, const char *value, | ||||
| 	       ReplayGainInfo *rgi, | ||||
| 	       const TagHandler &handler, void *ctx) | ||||
| 	       TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { | ||||
| 		/* R128_TRACK_GAIN is a Q7.8 fixed point number in | ||||
| @@ -63,25 +63,25 @@ ScanOneOpusTag(const char *name, const char *value, | ||||
| 			rgi->album.gain = double(l) / 256.; | ||||
| 	} | ||||
|  | ||||
| 	tag_handler_invoke_pair(handler, ctx, name, value); | ||||
| 	handler.OnPair(name, value); | ||||
|  | ||||
| 	if (handler.tag != nullptr) { | ||||
| 	if (handler.WantTag()) { | ||||
| 		TagType t = ParseOpusTagName(name); | ||||
| 		if (t != TAG_NUM_OF_ITEM_TYPES) | ||||
| 			tag_handler_invoke_tag(handler, ctx, t, value); | ||||
| 			handler.OnTag(t, value); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool | ||||
| ScanOpusTags(const void *data, size_t size, | ||||
| 	     ReplayGainInfo *rgi, | ||||
| 	     const TagHandler &handler, void *ctx) | ||||
| 	     TagHandler &handler) noexcept | ||||
| { | ||||
| 	OpusReader r(data, size); | ||||
| 	if (!r.Expect("OpusTags", 8)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (handler.pair == nullptr && handler.tag == nullptr) | ||||
| 	if (!handler.WantPair() && !handler.WantTag()) | ||||
| 		return true; | ||||
|  | ||||
| 	if (!r.SkipString()) | ||||
| @@ -100,7 +100,7 @@ ScanOpusTags(const void *data, size_t size, | ||||
| 		if (eq != nullptr && eq > p) { | ||||
| 			*eq = 0; | ||||
|  | ||||
| 			ScanOneOpusTag(p, eq + 1, rgi, handler, ctx); | ||||
| 			ScanOneOpusTag(p, eq + 1, rgi, handler); | ||||
| 		} | ||||
|  | ||||
| 		delete[] p; | ||||
|   | ||||
| @@ -25,11 +25,11 @@ | ||||
| #include <stddef.h> | ||||
|  | ||||
| struct ReplayGainInfo; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
|  | ||||
| bool | ||||
| ScanOpusTags(const void *data, size_t size, | ||||
| 	     ReplayGainInfo *rgi, | ||||
| 	     const TagHandler &handler, void *ctx); | ||||
| 	     TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -405,7 +405,7 @@ GetInfoString(const SidTuneInfo &info, unsigned i) noexcept | ||||
|  | ||||
| static void | ||||
| ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks, | ||||
| 		const TagHandler &handler, void *handler_ctx) | ||||
| 		TagHandler &handler) noexcept | ||||
| { | ||||
| 	/* title */ | ||||
| 	const char *title = GetInfoString(info, 0); | ||||
| @@ -416,31 +416,26 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks, | ||||
| 		const auto tag_title = | ||||
| 			StringFormat<1024>("%s (%u/%u)", | ||||
| 					   title, track, n_tracks); | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_TITLE, tag_title); | ||||
| 		handler.OnTag(TAG_TITLE, tag_title); | ||||
| 	} else | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); | ||||
| 		handler.OnTag(TAG_TITLE, title); | ||||
|  | ||||
| 	/* artist */ | ||||
| 	const char *artist = GetInfoString(info, 1); | ||||
| 	if (artist != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, | ||||
| 				       artist); | ||||
| 		handler.OnTag(TAG_ARTIST, artist); | ||||
|  | ||||
| 	/* date */ | ||||
| 	const char *date = GetInfoString(info, 2); | ||||
| 	if (date != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, TAG_DATE, | ||||
| 				       date); | ||||
| 		handler.OnTag(TAG_DATE, date); | ||||
|  | ||||
| 	/* track */ | ||||
| 	tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, | ||||
| 			       StringFormat<16>("%u", track)); | ||||
| 	handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track)); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| sidplay_scan_file(Path path_fs, | ||||
| 		  const TagHandler &handler, void *handler_ctx) noexcept | ||||
| sidplay_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	const auto container = ParseContainerPath(path_fs); | ||||
| 	const unsigned song_num = container.track; | ||||
| @@ -463,13 +458,12 @@ sidplay_scan_file(Path path_fs, | ||||
| 	const unsigned n_tracks = info.songs; | ||||
| #endif | ||||
|  | ||||
| 	ScanSidTuneInfo(info, song_num, n_tracks, handler, handler_ctx); | ||||
| 	ScanSidTuneInfo(info, song_num, n_tracks, handler); | ||||
|  | ||||
| 	/* time */ | ||||
| 	const auto duration = get_song_length(tune); | ||||
| 	if (!duration.IsNegative()) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 					    SongTime(duration)); | ||||
| 		handler.OnDuration(SongTime(duration)); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
| @@ -506,8 +500,8 @@ sidplay_container_scan(Path path_fs) | ||||
| 	for (unsigned i = 1; i <= n_tracks; ++i) { | ||||
| 		tune.selectSong(i); | ||||
|  | ||||
| 		ScanSidTuneInfo(info, i, n_tracks, | ||||
| 				add_tag_handler, &tag_builder); | ||||
| 		AddTagHandler h(tag_builder); | ||||
| 		ScanSidTuneInfo(info, i, n_tracks, h); | ||||
|  | ||||
| 		char track_name[32]; | ||||
| 		/* Construct container/tune path names, eg. | ||||
|   | ||||
| @@ -240,11 +240,11 @@ sndfile_stream_decode(DecoderClient &client, InputStream &is) | ||||
|  | ||||
| static void | ||||
| sndfile_handle_tag(SNDFILE *sf, int str, TagType tag, | ||||
| 		   const TagHandler &handler, void *handler_ctx) | ||||
| 		   TagHandler &handler) noexcept | ||||
| { | ||||
| 	const char *value = sf_get_string(sf, str); | ||||
| 	if (value != nullptr) | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, tag, value); | ||||
| 		handler.OnTag(tag, value); | ||||
| } | ||||
|  | ||||
| static constexpr struct { | ||||
| @@ -261,8 +261,7 @@ static constexpr struct { | ||||
| }; | ||||
|  | ||||
| static bool | ||||
| sndfile_scan_stream(InputStream &is, | ||||
| 		    const TagHandler &handler, void *handler_ctx) noexcept | ||||
| sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	SF_INFO info; | ||||
|  | ||||
| @@ -280,11 +279,10 @@ sndfile_scan_stream(InputStream &is, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, | ||||
| 				    sndfile_duration(info)); | ||||
| 	handler.OnDuration(sndfile_duration(info)); | ||||
|  | ||||
| 	for (auto i : sndfile_tags) | ||||
| 		sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx); | ||||
| 		sndfile_handle_tag(sf, i.str, i.tag, handler); | ||||
|  | ||||
| 	sf_close(sf); | ||||
|  | ||||
|   | ||||
| @@ -348,7 +348,7 @@ static void | ||||
| VisitVorbisDuration(InputStream &is, | ||||
| 		    OggSyncState &sync, OggStreamState &stream, | ||||
| 		    unsigned sample_rate, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| 		    TagHandler &handler) noexcept | ||||
| { | ||||
| 	ogg_packet packet; | ||||
|  | ||||
| @@ -358,12 +358,11 @@ VisitVorbisDuration(InputStream &is, | ||||
| 	const auto duration = | ||||
| 		SongTime::FromScale<uint64_t>(packet.granulepos, | ||||
| 					      sample_rate); | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, duration); | ||||
| 	handler.OnDuration(duration); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| vorbis_scan_stream(InputStream &is, | ||||
| 		   const TagHandler &handler, void *handler_ctx) noexcept | ||||
| vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	/* initialize libogg */ | ||||
|  | ||||
| @@ -397,12 +396,11 @@ vorbis_scan_stream(InputStream &is, | ||||
|  | ||||
| 	/* visit the Vorbis comments we just read */ | ||||
|  | ||||
| 	vorbis_comments_scan(vc.user_comments, | ||||
| 			     handler, handler_ctx); | ||||
| 	vorbis_comments_scan(vc.user_comments, handler); | ||||
|  | ||||
| 	/* check the song duration by locating the e_o_s packet */ | ||||
|  | ||||
| 	VisitVorbisDuration(is, sync, stream, vi.rate, handler, handler_ctx); | ||||
| 	VisitVorbisDuration(is, sync, stream, vi.rate, handler); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -578,8 +578,7 @@ wavpack_filedecode(DecoderClient &client, Path path_fs) | ||||
|  * Reads metainfo from the specified file. | ||||
|  */ | ||||
| static bool | ||||
| wavpack_scan_file(Path path_fs, | ||||
| 		  const TagHandler &handler, void *handler_ctx) noexcept | ||||
| wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	WavpackContext *wpc; | ||||
|  | ||||
| @@ -595,14 +594,13 @@ wavpack_scan_file(Path path_fs, | ||||
|  | ||||
| 	const auto duration = GetDuration(wpc); | ||||
| 	if (!duration.IsNegative()) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); | ||||
| 		handler.OnDuration(SongTime(duration)); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| wavpack_scan_stream(InputStream &is, | ||||
| 		    const TagHandler &handler, void *handler_ctx) noexcept | ||||
| wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	WavpackInput isp(nullptr, is); | ||||
|  | ||||
| @@ -620,7 +618,7 @@ wavpack_scan_stream(InputStream &is, | ||||
|  | ||||
| 	const auto duration = GetDuration(wpc); | ||||
| 	if (!duration.IsNegative()) | ||||
| 		tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); | ||||
| 		handler.OnDuration(SongTime(duration)); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -126,8 +126,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs) | ||||
| } | ||||
|  | ||||
| static bool | ||||
| wildmidi_scan_file(Path path_fs, | ||||
| 		   const TagHandler &handler, void *handler_ctx) noexcept | ||||
| wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept | ||||
| { | ||||
| 	midi *wm = WildMidi_Open(path_fs.c_str()); | ||||
| 	if (wm == nullptr) | ||||
| @@ -142,7 +141,7 @@ wildmidi_scan_file(Path path_fs, | ||||
| 	const auto duration = | ||||
| 		SongTime::FromScale<uint64_t>(info->approx_total_samples, | ||||
| 					      WILDMIDI_SAMPLE_RATE); | ||||
| 	tag_handler_invoke_duration(handler, handler_ctx, duration); | ||||
| 	handler.OnDuration(duration); | ||||
|  | ||||
| 	WildMidi_Close(wm); | ||||
|  | ||||
|   | ||||
| @@ -53,13 +53,13 @@ vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) noexcept | ||||
| static bool | ||||
| vorbis_copy_comment(const char *comment, | ||||
| 		    const char *name, TagType tag_type, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| 		    TagHandler &handler) noexcept | ||||
| { | ||||
| 	const char *value; | ||||
|  | ||||
| 	value = vorbis_comment_value(comment, name); | ||||
| 	if (value != nullptr) { | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); | ||||
| 		handler.OnTag(tag_type, value); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| @@ -67,36 +67,31 @@ vorbis_copy_comment(const char *comment, | ||||
| } | ||||
|  | ||||
| static void | ||||
| vorbis_scan_comment(const char *comment, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| vorbis_scan_comment(const char *comment, TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (handler.pair != nullptr) { | ||||
| 	if (handler.WantPair()) { | ||||
| 		const DivideString split(comment, '='); | ||||
| 		if (split.IsDefined() && !split.empty()) | ||||
| 			tag_handler_invoke_pair(handler, handler_ctx, | ||||
| 						split.GetFirst(), | ||||
| 						split.GetSecond()); | ||||
| 			handler.OnPair(split.GetFirst(), split.GetSecond()); | ||||
| 	} | ||||
|  | ||||
| 	for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) | ||||
| 		if (vorbis_copy_comment(comment, i->name, i->type, | ||||
| 					handler, handler_ctx)) | ||||
| 					handler)) | ||||
| 			return; | ||||
|  | ||||
| 	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) | ||||
| 		if (vorbis_copy_comment(comment, | ||||
| 					tag_item_names[i], TagType(i), | ||||
| 					handler, handler_ctx)) | ||||
| 					handler)) | ||||
| 			return; | ||||
| } | ||||
|  | ||||
| void | ||||
| vorbis_comments_scan(char **comments, | ||||
| 		     const TagHandler &handler, void *handler_ctx) | ||||
| vorbis_comments_scan(char **comments, TagHandler &handler) noexcept | ||||
| { | ||||
| 	while (*comments) | ||||
| 		vorbis_scan_comment(*comments++, | ||||
| 				    handler, handler_ctx); | ||||
| 		vorbis_scan_comment(*comments++, handler); | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -104,7 +99,8 @@ std::unique_ptr<Tag> | ||||
| vorbis_comments_to_tag(char **comments) noexcept | ||||
| { | ||||
| 	TagBuilder tag_builder; | ||||
| 	vorbis_comments_scan(comments, add_tag_handler, &tag_builder); | ||||
| 	AddTagHandler h(tag_builder); | ||||
| 	vorbis_comments_scan(comments, h); | ||||
| 	return tag_builder.empty() | ||||
| 		? nullptr | ||||
| 		: tag_builder.CommitNew(); | ||||
|   | ||||
| @@ -25,15 +25,14 @@ | ||||
| #include <memory> | ||||
|  | ||||
| struct ReplayGainInfo; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| struct Tag; | ||||
|  | ||||
| bool | ||||
| vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) noexcept; | ||||
|  | ||||
| void | ||||
| vorbis_comments_scan(char **comments, | ||||
| 		     const TagHandler &handler, void *handler_ctx); | ||||
| vorbis_comments_scan(char **comments, TagHandler &handler) noexcept; | ||||
|  | ||||
| std::unique_ptr<Tag> | ||||
| vorbis_comments_to_tag(char **comments) noexcept; | ||||
|   | ||||
| @@ -71,22 +71,23 @@ public: | ||||
| 	virtual std::unique_ptr<DetachedSong> NextSong() override; | ||||
| }; | ||||
|  | ||||
| static void | ||||
| embcue_tag_pair(const char *name, const char *value, void *ctx) | ||||
| { | ||||
| 	EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; | ||||
| class ExtractCuesheetTagHandler final : public NullTagHandler { | ||||
| public: | ||||
| 	std::string cuesheet; | ||||
|  | ||||
| 	if (playlist->cuesheet.empty() && | ||||
| 	    StringEqualsCaseASCII(name, "cuesheet")) | ||||
| 		playlist->cuesheet = value; | ||||
| } | ||||
| 	ExtractCuesheetTagHandler() noexcept:NullTagHandler(WANT_PAIR) {} | ||||
|  | ||||
| static constexpr TagHandler embcue_tag_handler = { | ||||
| 	nullptr, | ||||
| 	nullptr, | ||||
| 	embcue_tag_pair, | ||||
| 	void OnPair(const char *key, const char *value) noexcept override; | ||||
| }; | ||||
|  | ||||
| void | ||||
| ExtractCuesheetTagHandler::OnPair(const char *name, const char *value) noexcept | ||||
| { | ||||
| 	if (cuesheet.empty() && | ||||
| 	    StringEqualsCaseASCII(name, "cuesheet")) | ||||
| 		cuesheet = value; | ||||
| } | ||||
|  | ||||
| static std::unique_ptr<SongEnumerator> | ||||
| embcue_playlist_open_uri(const char *uri, | ||||
| 			 gcc_unused Mutex &mutex) | ||||
| @@ -97,18 +98,21 @@ embcue_playlist_open_uri(const char *uri, | ||||
|  | ||||
| 	const auto path_fs = AllocatedPath::FromUTF8Throw(uri); | ||||
|  | ||||
| 	auto playlist = std::make_unique<EmbeddedCuePlaylist>(); | ||||
| 	ExtractCuesheetTagHandler extract_cuesheet; | ||||
| 	tag_file_scan(path_fs, extract_cuesheet); | ||||
| 	if (extract_cuesheet.cuesheet.empty()) | ||||
| 		ScanGenericTags(path_fs, extract_cuesheet); | ||||
|  | ||||
| 	tag_file_scan(path_fs, embcue_tag_handler, playlist.get()); | ||||
| 	if (playlist->cuesheet.empty()) | ||||
| 		ScanGenericTags(path_fs, embcue_tag_handler, playlist.get()); | ||||
|  | ||||
| 	if (playlist->cuesheet.empty()) | ||||
| 	if (extract_cuesheet.cuesheet.empty()) | ||||
| 		/* no "CUESHEET" tag found */ | ||||
| 		return nullptr; | ||||
|  | ||||
| 	auto playlist = std::make_unique<EmbeddedCuePlaylist>(); | ||||
|  | ||||
| 	playlist->filename = PathTraitsUTF8::GetBase(uri); | ||||
|  | ||||
| 	playlist->cuesheet = std::move(extract_cuesheet.cuesheet); | ||||
|  | ||||
| 	playlist->next = &playlist->cuesheet[0]; | ||||
| 	playlist->parser = new CueParser(); | ||||
|  | ||||
|   | ||||
| @@ -76,7 +76,7 @@ ForEachValue(const char *value, const char *end, C &&callback) | ||||
| static bool | ||||
| tag_ape_import_item(unsigned long flags, | ||||
| 		    const char *key, StringView value, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| 		    TagHandler &handler) noexcept | ||||
| { | ||||
| 	/* we only care about utf-8 text tags */ | ||||
| 	if ((flags & (0x3 << 1)) != 0) | ||||
| @@ -85,36 +85,31 @@ tag_ape_import_item(unsigned long flags, | ||||
| 	const auto begin = value.begin(); | ||||
| 	const auto end = value.end(); | ||||
|  | ||||
| 	if (handler.pair != nullptr) | ||||
| 		ForEachValue(begin, end, [handler, handler_ctx, | ||||
| 					  key](const char *_value) { | ||||
| 				handler.pair(key, _value, handler_ctx); | ||||
| 	if (handler.WantPair()) | ||||
| 		ForEachValue(begin, end, [&handler, key](const char *_value) { | ||||
| 				handler.OnPair(key, _value); | ||||
| 			}); | ||||
|  | ||||
| 	TagType type = tag_ape_name_parse(key); | ||||
| 	if (type == TAG_NUM_OF_ITEM_TYPES) | ||||
| 		return false; | ||||
|  | ||||
| 	ForEachValue(begin, end, [handler, handler_ctx, | ||||
| 				  type](const char *_value) { | ||||
| 			tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 					       type, _value); | ||||
| 	ForEachValue(begin, end, [&handler, type](const char *_value) { | ||||
| 			handler.OnTag(type, _value); | ||||
| 		}); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_ape_scan2(InputStream &is, | ||||
| 	      const TagHandler &handler, void *handler_ctx) | ||||
| tag_ape_scan2(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	bool recognized = false; | ||||
|  | ||||
| 	auto callback = [handler, handler_ctx, &recognized] | ||||
| 	auto callback = [&handler, &recognized] | ||||
| 		(unsigned long flags, const char *key, | ||||
| 		 StringView value) { | ||||
| 		recognized |= tag_ape_import_item(flags, key, value, | ||||
| 						  handler, handler_ctx); | ||||
| 		recognized |= tag_ape_import_item(flags, key, value, handler); | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
|   | ||||
| @@ -20,10 +20,8 @@ | ||||
| #ifndef MPD_APE_TAG_HXX | ||||
| #define MPD_APE_TAG_HXX | ||||
|  | ||||
| #include "Table.hxx" | ||||
|  | ||||
| class InputStream; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
|  | ||||
| /** | ||||
|  * Scan the APE tags of a stream. | ||||
| @@ -31,7 +29,6 @@ struct TagHandler; | ||||
|  * @param path_fs the path of the file in filesystem encoding | ||||
|  */ | ||||
| bool | ||||
| tag_ape_scan2(InputStream &is, | ||||
| 	      const TagHandler &handler, void *handler_ctx); | ||||
| tag_ape_scan2(InputStream &is, TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -30,9 +30,9 @@ | ||||
| #include <exception> | ||||
|  | ||||
| bool | ||||
| ScanGenericTags(InputStream &is, const TagHandler &handler, void *ctx) | ||||
| ScanGenericTags(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (tag_ape_scan2(is, handler, ctx)) | ||||
| 	if (tag_ape_scan2(is, handler)) | ||||
| 		return true; | ||||
|  | ||||
| #ifdef ENABLE_ID3TAG | ||||
| @@ -42,19 +42,19 @@ ScanGenericTags(InputStream &is, const TagHandler &handler, void *ctx) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	return tag_id3_scan(is, handler, ctx); | ||||
| 	return tag_id3_scan(is, handler); | ||||
| #else | ||||
| 	return false; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| bool | ||||
| ScanGenericTags(Path path, const TagHandler &handler, void *ctx) | ||||
| ScanGenericTags(Path path, TagHandler &handler) noexcept | ||||
| try { | ||||
| 	Mutex mutex; | ||||
|  | ||||
| 	auto is = OpenLocalInputStream(path, mutex); | ||||
| 	return ScanGenericTags(*is, handler, ctx); | ||||
| 	return ScanGenericTags(*is, handler); | ||||
| } catch (...) { | ||||
| 	LogError(std::current_exception()); | ||||
| 	return false; | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| #include "check.h" | ||||
|  | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| class InputStream; | ||||
| class Path; | ||||
|  | ||||
| @@ -31,12 +31,12 @@ class Path; | ||||
|  * stream does not need to be rewound. | ||||
|  */ | ||||
| bool | ||||
| ScanGenericTags(InputStream &is, const TagHandler &handler, void *ctx); | ||||
| ScanGenericTags(InputStream &is, TagHandler &handler) noexcept; | ||||
|  | ||||
| /** | ||||
|  * Attempts to scan APE or ID3 tags from the specified file. | ||||
|  */ | ||||
| bool | ||||
| ScanGenericTags(Path path, const TagHandler &handler, void *ctx); | ||||
| ScanGenericTags(Path path, TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -25,19 +25,15 @@ | ||||
|  | ||||
| #include <stdlib.h> | ||||
|  | ||||
| static void | ||||
| add_tag_duration(SongTime duration, void *ctx) | ||||
| void | ||||
| AddTagHandler::OnDuration(SongTime duration) noexcept | ||||
| { | ||||
| 	TagBuilder &tag = *(TagBuilder *)ctx; | ||||
|  | ||||
| 	tag.SetDuration(duration); | ||||
| } | ||||
|  | ||||
| static void | ||||
| add_tag_tag(TagType type, const char *value, void *ctx) | ||||
| void | ||||
| AddTagHandler::OnTag(TagType type, const char *value) noexcept | ||||
| { | ||||
| 	TagBuilder &tag = *(TagBuilder *)ctx; | ||||
|  | ||||
| 	if (type == TAG_TRACK || type == TAG_DISC) { | ||||
| 		/* filter out this extra data and leading zeroes */ | ||||
| 		char *end; | ||||
| @@ -48,24 +44,10 @@ add_tag_tag(TagType type, const char *value, void *ctx) | ||||
| 		tag.AddItem(type, value); | ||||
| } | ||||
|  | ||||
| const TagHandler add_tag_handler = { | ||||
| 	add_tag_duration, | ||||
| 	add_tag_tag, | ||||
| 	nullptr, | ||||
| }; | ||||
|  | ||||
| static void | ||||
| full_tag_pair(const char *name, gcc_unused const char *value, void *ctx) | ||||
| void | ||||
| FullTagHandler::OnPair(const char *name, gcc_unused const char *value) noexcept | ||||
| { | ||||
| 	TagBuilder &tag = *(TagBuilder *)ctx; | ||||
|  | ||||
| 	if (StringEqualsCaseASCII(name, "cuesheet")) | ||||
| 		tag.SetHasPlaylist(true); | ||||
| } | ||||
|  | ||||
| const TagHandler full_tag_handler = { | ||||
| 	add_tag_duration, | ||||
| 	add_tag_tag, | ||||
| 	full_tag_pair, | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -23,19 +23,45 @@ | ||||
| #include "check.h" | ||||
| #include "Type.h" | ||||
| #include "Chrono.hxx" | ||||
| #include "Compiler.h" | ||||
|  | ||||
| #include <assert.h> | ||||
| class TagBuilder; | ||||
|  | ||||
| /** | ||||
|  * A callback table for receiving metadata of a song. | ||||
|  * An interface for receiving metadata of a song. | ||||
|  */ | ||||
| struct TagHandler { | ||||
| class TagHandler { | ||||
| 	const unsigned want_mask; | ||||
|  | ||||
| public: | ||||
| 	static constexpr unsigned WANT_DURATION = 0x1; | ||||
| 	static constexpr unsigned WANT_TAG = 0x2; | ||||
| 	static constexpr unsigned WANT_PAIR = 0x4; | ||||
|  | ||||
| 	explicit TagHandler(unsigned _want_mask) noexcept | ||||
| 		:want_mask(_want_mask) {} | ||||
|  | ||||
| 	TagHandler(const TagHandler &) = delete; | ||||
| 	TagHandler &operator=(const TagHandler &) = delete; | ||||
|  | ||||
| 	bool WantDuration() const noexcept { | ||||
| 		return want_mask & WANT_DURATION; | ||||
| 	} | ||||
|  | ||||
| 	bool WantTag() const noexcept { | ||||
| 		return want_mask & WANT_TAG; | ||||
| 	} | ||||
|  | ||||
| 	bool WantPair() const noexcept { | ||||
| 		return want_mask & WANT_PAIR; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Declare the duration of a song.  Do not call | ||||
| 	 * this when the duration could not be determined, because | ||||
| 	 * there is no magic value for "unknown duration". | ||||
| 	 */ | ||||
| 	void (*duration)(SongTime duration, void *ctx); | ||||
| 	virtual void OnDuration(SongTime duration) noexcept = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * A tag has been read. | ||||
| @@ -43,56 +69,57 @@ struct TagHandler { | ||||
| 	 * @param the value of the tag; the pointer will become | ||||
| 	 * invalid after returning | ||||
| 	 */ | ||||
| 	void (*tag)(TagType type, const char *value, void *ctx); | ||||
| 	virtual void OnTag(TagType type, const char *value) noexcept = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * A name-value pair has been read.  It is the codec specific | ||||
| 	 * representation of tags. | ||||
| 	 */ | ||||
| 	void (*pair)(const char *key, const char *value, void *ctx); | ||||
| 	virtual void OnPair(const char *key, const char *value) noexcept = 0; | ||||
| }; | ||||
|  | ||||
| static inline void | ||||
| tag_handler_invoke_duration(const TagHandler &handler, void *ctx, | ||||
| 			    SongTime duration) noexcept | ||||
| { | ||||
| 	if (handler.duration != nullptr) | ||||
| 		handler.duration(duration, ctx); | ||||
| } | ||||
| class NullTagHandler : public TagHandler { | ||||
| public: | ||||
| 	explicit NullTagHandler(unsigned _want_mask) noexcept | ||||
| 		:TagHandler(_want_mask) {} | ||||
|  | ||||
| static inline void | ||||
| tag_handler_invoke_tag(const TagHandler &handler, void *ctx, | ||||
| 		       TagType type, const char *value) noexcept | ||||
| { | ||||
| 	assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES); | ||||
| 	assert(value != nullptr); | ||||
|  | ||||
| 	if (handler.tag != nullptr) | ||||
| 		handler.tag(type, value, ctx); | ||||
| } | ||||
|  | ||||
| static inline void | ||||
| tag_handler_invoke_pair(const TagHandler &handler, void *ctx, | ||||
| 			const char *name, const char *value) noexcept | ||||
| { | ||||
| 	assert(name != nullptr); | ||||
| 	assert(value != nullptr); | ||||
|  | ||||
| 	if (handler.pair != nullptr) | ||||
| 		handler.pair(name, value, ctx); | ||||
| } | ||||
| 	void OnDuration(gcc_unused SongTime duration) noexcept override {} | ||||
| 	void OnTag(gcc_unused TagType type, | ||||
| 		   gcc_unused const char *value) noexcept override {} | ||||
| 	void OnPair(gcc_unused const char *key, | ||||
| 		    gcc_unused const char *value) noexcept override {} | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * This #TagHandler implementation adds tag values to a #TagBuilder object | ||||
|  * (casted from the context pointer). | ||||
|  * This #TagHandler implementation adds tag values to a #TagBuilder | ||||
|  * object. | ||||
|  */ | ||||
| extern const TagHandler add_tag_handler; | ||||
| class AddTagHandler : public NullTagHandler { | ||||
| protected: | ||||
| 	TagBuilder &tag; | ||||
|  | ||||
| 	AddTagHandler(unsigned _want_mask, TagBuilder &_builder) noexcept | ||||
| 		:NullTagHandler(_want_mask), tag(_builder) {} | ||||
|  | ||||
| public: | ||||
| 	explicit AddTagHandler(TagBuilder &_builder) noexcept | ||||
| 		:AddTagHandler(WANT_DURATION|WANT_TAG, _builder) {} | ||||
|  | ||||
| 	void OnDuration(SongTime duration) noexcept override; | ||||
| 	void OnTag(TagType type, const char *value) noexcept override; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * This #TagHandler implementation adds tag values to a #TagBuilder object | ||||
|  * (casted from the context pointer), and supports the has_playlist | ||||
|  * attribute. | ||||
|  */ | ||||
| extern const TagHandler full_tag_handler; | ||||
| class FullTagHandler : public AddTagHandler { | ||||
| public: | ||||
| 	explicit FullTagHandler(TagBuilder &_builder) noexcept | ||||
| 		:AddTagHandler(WANT_DURATION|WANT_TAG|WANT_PAIR, _builder) {} | ||||
|  | ||||
| 	void OnPair(const char *key, const char *value) noexcept override; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -100,7 +100,7 @@ import_id3_string(const id3_ucs4_t *ucs4) | ||||
| static void | ||||
| tag_id3_import_text_frame(const struct id3_frame *frame, | ||||
| 			  TagType type, | ||||
| 			  const TagHandler &handler, void *handler_ctx) | ||||
| 			  TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (frame->nfields != 2) | ||||
| 		return; | ||||
| @@ -133,8 +133,7 @@ tag_id3_import_text_frame(const struct id3_frame *frame, | ||||
|  | ||||
| 		AtScopeExit(utf8) { free(utf8); }; | ||||
|  | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       type, (const char *)utf8); | ||||
| 		handler.OnTag(type, (const char *)utf8); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -144,13 +143,13 @@ tag_id3_import_text_frame(const struct id3_frame *frame, | ||||
|  */ | ||||
| static void | ||||
| tag_id3_import_text(struct id3_tag *tag, const char *id, TagType type, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| 		    TagHandler &handler) noexcept | ||||
| { | ||||
| 	const struct id3_frame *frame; | ||||
| 	for (unsigned i = 0; | ||||
| 	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i) | ||||
| 		tag_id3_import_text_frame(frame, type, | ||||
| 					  handler, handler_ctx); | ||||
| 					  handler); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -164,8 +163,7 @@ tag_id3_import_text(struct id3_tag *tag, const char *id, TagType type, | ||||
|  */ | ||||
| static void | ||||
| tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type, | ||||
| 			     const TagHandler &handler, | ||||
| 			     void *handler_ctx) | ||||
| 			     TagHandler &handler) noexcept | ||||
| { | ||||
| 	if (frame->nfields != 4) | ||||
| 		return; | ||||
| @@ -185,7 +183,7 @@ tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type, | ||||
|  | ||||
| 	AtScopeExit(utf8) { free(utf8); }; | ||||
|  | ||||
| 	tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8); | ||||
| 	handler.OnTag(type, (const char *)utf8); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -194,13 +192,13 @@ tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type, | ||||
|  */ | ||||
| static void | ||||
| tag_id3_import_comment(struct id3_tag *tag, const char *id, TagType type, | ||||
| 		       const TagHandler &handler, void *handler_ctx) | ||||
| 		       TagHandler &handler) noexcept | ||||
| { | ||||
| 	const struct id3_frame *frame; | ||||
| 	for (unsigned i = 0; | ||||
| 	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i) | ||||
| 		tag_id3_import_comment_frame(frame, type, | ||||
| 					     handler, handler_ctx); | ||||
| 					     handler); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -220,8 +218,7 @@ tag_id3_parse_txxx_name(const char *name) noexcept | ||||
|  */ | ||||
| static void | ||||
| tag_id3_import_musicbrainz(struct id3_tag *id3_tag, | ||||
| 			   const TagHandler &handler, | ||||
| 			   void *handler_ctx) | ||||
| 			   TagHandler &handler) noexcept | ||||
| { | ||||
| 	for (unsigned i = 0;; ++i) { | ||||
| 		const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i); | ||||
| @@ -240,15 +237,12 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, | ||||
|  | ||||
| 		AtScopeExit(value) { free(value); }; | ||||
|  | ||||
| 		tag_handler_invoke_pair(handler, handler_ctx, | ||||
| 					(const char *)name, | ||||
| 					(const char *)value); | ||||
| 		handler.OnPair((const char *)name, (const char *)value); | ||||
|  | ||||
| 		TagType type = tag_id3_parse_txxx_name((const char*)name); | ||||
|  | ||||
| 		if (type != TAG_NUM_OF_ITEM_TYPES) | ||||
| 			tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 					       type, (const char*)value); | ||||
| 			handler.OnTag(type, (const char*)value); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -257,7 +251,7 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, | ||||
|  */ | ||||
| static void | ||||
| tag_id3_import_ufid(struct id3_tag *id3_tag, | ||||
| 		    const TagHandler &handler, void *handler_ctx) | ||||
| 		    TagHandler &handler) noexcept | ||||
| { | ||||
| 	for (unsigned i = 0;; ++i) { | ||||
| 		const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i); | ||||
| @@ -284,65 +278,63 @@ tag_id3_import_ufid(struct id3_tag *id3_tag, | ||||
| 			continue; | ||||
|  | ||||
| 		std::string p((const char *)value, length); | ||||
| 		tag_handler_invoke_tag(handler, handler_ctx, | ||||
| 				       TAG_MUSICBRAINZ_TRACKID, p.c_str()); | ||||
| 		handler.OnTag(TAG_MUSICBRAINZ_TRACKID, p.c_str()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void | ||||
| scan_id3_tag(struct id3_tag *tag, | ||||
| 	     const TagHandler &handler, void *handler_ctx) | ||||
| scan_id3_tag(struct id3_tag *tag, TagHandler &handler) noexcept | ||||
| { | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST, | ||||
| 			    TAG_ALBUM_ARTIST, handler, handler_ctx); | ||||
| 			    TAG_ALBUM_ARTIST, handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT, | ||||
| 			    TAG_ARTIST_SORT, handler, handler_ctx); | ||||
| 			    TAG_ARTIST_SORT, handler); | ||||
|  | ||||
| 	tag_id3_import_text(tag, "TSOA", TAG_ALBUM_SORT, handler, handler_ctx); | ||||
| 	tag_id3_import_text(tag, "TSOA", TAG_ALBUM_SORT, handler); | ||||
|  | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT, | ||||
| 			    TAG_ALBUM_ARTIST_SORT, handler, handler_ctx); | ||||
| 			    TAG_ALBUM_ARTIST_SORT, handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_ORIGINAL_RELEASE_DATE, TAG_ORIGINAL_DATE, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, "TPE3", TAG_PERFORMER, | ||||
| 			    handler, handler_ctx); | ||||
| 	tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx); | ||||
| 			    handler); | ||||
| 	tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler); | ||||
| 	tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT, | ||||
| 			       handler, handler_ctx); | ||||
| 			       handler); | ||||
| 	tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC, | ||||
| 			    handler, handler_ctx); | ||||
| 			    handler); | ||||
|  | ||||
| 	tag_id3_import_musicbrainz(tag, handler, handler_ctx); | ||||
| 	tag_id3_import_ufid(tag, handler, handler_ctx); | ||||
| 	tag_id3_import_musicbrainz(tag, handler); | ||||
| 	tag_id3_import_ufid(tag, handler); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Tag> | ||||
| tag_id3_import(struct id3_tag *tag) | ||||
| { | ||||
| 	TagBuilder tag_builder; | ||||
| 	scan_id3_tag(tag, add_tag_handler, &tag_builder); | ||||
| 	AddTagHandler h(tag_builder); | ||||
| 	scan_id3_tag(tag, h); | ||||
| 	return tag_builder.empty() | ||||
| 		? nullptr | ||||
| 		: tag_builder.CommitNew(); | ||||
| } | ||||
|  | ||||
| bool | ||||
| tag_id3_scan(InputStream &is, | ||||
| 	     const TagHandler &handler, void *handler_ctx) | ||||
| tag_id3_scan(InputStream &is, TagHandler &handler) noexcept | ||||
| { | ||||
| 	UniqueId3Tag tag; | ||||
|  | ||||
| @@ -355,6 +347,6 @@ tag_id3_scan(InputStream &is, | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	scan_id3_tag(tag.get(), handler, handler_ctx); | ||||
| 	scan_id3_tag(tag.get(), handler); | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -25,13 +25,12 @@ | ||||
| #include <memory> | ||||
|  | ||||
| class InputStream; | ||||
| struct TagHandler; | ||||
| class TagHandler; | ||||
| struct Tag; | ||||
| struct id3_tag; | ||||
|  | ||||
| bool | ||||
| tag_id3_scan(InputStream &is, | ||||
| 	     const TagHandler &handler, void *handler_ctx); | ||||
| tag_id3_scan(InputStream &is, TagHandler &handler) noexcept; | ||||
|  | ||||
| std::unique_ptr<Tag> | ||||
| tag_id3_import(id3_tag *); | ||||
| @@ -41,7 +40,6 @@ tag_id3_import(id3_tag *); | ||||
|  * | ||||
|  */ | ||||
| void | ||||
| scan_id3_tag(id3_tag *tag, | ||||
| 	     const TagHandler &handler, void *handler_ctx); | ||||
| scan_id3_tag(id3_tag *tag, TagHandler &handler) noexcept; | ||||
|  | ||||
| #endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann