diff --git a/Makefile.am b/Makefile.am
index 65ffc300c..882fd4e05 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -196,6 +196,7 @@ src_mpd_SOURCES = \
 	src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
 	src/SignalHandlers.cxx src/SignalHandlers.hxx \
 	src/DetachedSong.cxx src/DetachedSong.hxx \
+	src/LightSong.cxx src/LightSong.hxx \
 	src/Song.cxx src/Song.hxx \
 	src/SongUpdate.cxx \
 	src/SongPrint.cxx src/SongPrint.hxx \
diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx
index e7bd006f0..58e7aaa3b 100644
--- a/src/DatabaseHelpers.cxx
+++ b/src/DatabaseHelpers.cxx
@@ -19,7 +19,7 @@
 
 #include "DatabaseHelpers.hxx"
 #include "DatabasePlugin.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "tag/Tag.hxx"
 
 #include <functional>
@@ -37,9 +37,9 @@ struct StringLess {
 typedef std::set<const char *, StringLess> StringSet;
 
 static bool
-CollectTags(StringSet &set, TagType tag_type, Song &song)
+CollectTags(StringSet &set, TagType tag_type, const LightSong &song)
 {
-	const Tag *tag = &song.tag;
+	const Tag *tag = song.tag;
 
 	bool found = false;
 	for (unsigned i = 0; i < tag->num_items; ++i) {
@@ -102,11 +102,11 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
 
 static bool
 StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
-	       Song &song)
+	       const LightSong &song)
 {
 	++stats.song_count;
 
-	StatsVisitTag(stats, artists, albums, song.tag);
+	StatsVisitTag(stats, artists, albums, *song.tag);
 
 	return true;
 }
diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx
index 5f7c85a78..58742ca64 100644
--- a/src/DatabasePlaylist.cxx
+++ b/src/DatabasePlaylist.cxx
@@ -30,7 +30,7 @@
 
 static bool
 AddSong(const char *playlist_path_utf8,
-	Song &song, Error &error)
+	const LightSong &song, Error &error)
 {
 	return spl_append_song(playlist_path_utf8, map_song_detach(song),
 			       error);
diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx
index 3af44d7dd..2ded7f736 100644
--- a/src/DatabasePlugin.hxx
+++ b/src/DatabasePlugin.hxx
@@ -35,7 +35,7 @@
 struct config_param;
 struct DatabaseSelection;
 struct db_visitor;
-struct Song;
+struct LightSong;
 class Error;
 class EventLoop;
 class DatabaseListener;
@@ -94,14 +94,14 @@ public:
 	 * @param uri_utf8 the URI of the song within the music
 	 * directory (UTF-8)
 	 */
-	virtual Song *GetSong(const char *uri_utf8,
-			      Error &error) const = 0;
+	virtual const LightSong *GetSong(const char *uri_utf8,
+					 Error &error) const = 0;
 
 	/**
 	 * Mark the song object as "unused".  Call this on objects
 	 * returned by GetSong().
 	 */
-	virtual void ReturnSong(Song *song) const = 0;
+	virtual void ReturnSong(const LightSong *song) const = 0;
 
 	/**
 	 * Visit the selected entities.
diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx
index 514cf3f7b..7b5975275 100644
--- a/src/DatabasePrint.cxx
+++ b/src/DatabasePrint.cxx
@@ -26,7 +26,7 @@
 #include "Directory.hxx"
 #include "Client.hxx"
 #include "tag/Tag.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "DatabaseGlue.hxx"
 #include "DatabasePlugin.hxx"
 
@@ -52,6 +52,17 @@ PrintDirectoryFull(Client &client, const Directory &directory)
 	return true;
 }
 
+static void
+print_playlist_in_directory(Client &client,
+			    const char *directory,
+			    const char *name_utf8)
+{
+	if (directory == nullptr)
+		client_printf(client, "playlist: %s\n", name_utf8);
+	else
+		client_printf(client, "playlist: %s/%s\n",
+			      directory, name_utf8);
+}
 
 static void
 print_playlist_in_directory(Client &client,
@@ -66,25 +77,25 @@ print_playlist_in_directory(Client &client,
 }
 
 static bool
-PrintSongBrief(Client &client, const Song &song)
+PrintSongBrief(Client &client, const LightSong &song)
 {
 	song_print_uri(client, song);
 
-	if (song.tag.has_playlist)
+	if (song.tag->has_playlist)
 		/* this song file has an embedded CUE sheet */
-		print_playlist_in_directory(client, song.parent, song.uri);
+		print_playlist_in_directory(client, song.directory, song.uri);
 
 	return true;
 }
 
 static bool
-PrintSongFull(Client &client, const Song &song)
+PrintSongFull(Client &client, const LightSong &song)
 {
 	song_print_info(client, song);
 
-	if (song.tag.has_playlist)
+	if (song.tag->has_playlist)
 		/* this song file has an embedded CUE sheet */
-		print_playlist_in_directory(client, song.parent, song.uri);
+		print_playlist_in_directory(client, song.directory, song.uri);
 
 	return true;
 }
@@ -146,7 +157,7 @@ static void printSearchStats(Client &client, SearchStats *stats)
 }
 
 static bool
-stats_visitor_song(SearchStats &stats, Song &song)
+stats_visitor_song(SearchStats &stats, const LightSong &song)
 {
 	stats.numberOfSongs++;
 	stats.playTime += song.GetDuration();
@@ -195,7 +206,7 @@ printInfoForAllIn(Client &client, const char *uri_utf8,
 }
 
 static bool
-PrintSongURIVisitor(Client &client, Song &song)
+PrintSongURIVisitor(Client &client, const LightSong &song)
 {
 	song_print_uri(client, song);
 
diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx
index 0ed073285..ee1dbd57c 100644
--- a/src/DatabaseQueue.cxx
+++ b/src/DatabaseQueue.cxx
@@ -29,7 +29,7 @@
 #include <functional>
 
 static bool
-AddToQueue(Partition &partition, const Song &song, Error &error)
+AddToQueue(Partition &partition, const LightSong &song, Error &error)
 {
 	PlaylistResult result =
 		partition.playlist.AppendSong(partition.pc,
diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx
index 1dedafec0..035321252 100644
--- a/src/DatabaseSelection.cxx
+++ b/src/DatabaseSelection.cxx
@@ -31,7 +31,7 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
 }
 
 bool
-DatabaseSelection::Match(const Song &song) const
+DatabaseSelection::Match(const LightSong &song) const
 {
 	return filter == nullptr || filter->Match(song);
 }
diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx
index 3447b5eee..a39ce7afe 100644
--- a/src/DatabaseSelection.hxx
+++ b/src/DatabaseSelection.hxx
@@ -25,7 +25,7 @@
 #include <string>
 
 class SongFilter;
-struct Song;
+struct LightSong;
 
 struct DatabaseSelection {
 	/**
@@ -45,7 +45,7 @@ struct DatabaseSelection {
 			  const SongFilter *_filter=nullptr);
 
 	gcc_pure
-	bool Match(const Song &song) const;
+	bool Match(const LightSong &song) const;
 };
 
 #endif
diff --git a/src/DatabaseSong.cxx b/src/DatabaseSong.cxx
index a8f2188cc..592d38b85 100644
--- a/src/DatabaseSong.cxx
+++ b/src/DatabaseSong.cxx
@@ -31,7 +31,7 @@ DatabaseDetachSong(const char *uri, Error &error)
 	if (db == nullptr)
 		return nullptr;
 
-	Song *tmp = db->GetSong(uri, error);
+	const LightSong *tmp = db->GetSong(uri, error);
 	if (tmp == nullptr)
 		return nullptr;
 
diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx
index 6f7ff3deb..486407765 100644
--- a/src/DatabaseVisitor.hxx
+++ b/src/DatabaseVisitor.hxx
@@ -23,12 +23,12 @@
 #include <functional>
 
 struct Directory;
-struct Song;
+struct LightSong;
 struct PlaylistInfo;
 class Error;
 
 typedef std::function<bool(const Directory &, Error &)> VisitDirectory;
-typedef std::function<bool(struct Song &, Error &)> VisitSong;
+typedef std::function<bool(const LightSong &, Error &)> VisitSong;
 typedef std::function<bool(const PlaylistInfo &, const Directory &,
 			   Error &)> VisitPlaylist;
 
diff --git a/src/DetachedSong.cxx b/src/DetachedSong.cxx
index e8b75f618..4e52afb0c 100644
--- a/src/DetachedSong.cxx
+++ b/src/DetachedSong.cxx
@@ -19,13 +19,13 @@
 
 #include "config.h"
 #include "DetachedSong.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "util/UriUtil.hxx"
 #include "fs/Traits.hxx"
 
-DetachedSong::DetachedSong(const Song &other)
+DetachedSong::DetachedSong(const LightSong &other)
 	:uri(other.GetURI().c_str()),
-	 tag(other.tag),
+	 tag(*other.tag),
 	 mtime(other.mtime),
 	 start_ms(other.start_ms), end_ms(other.end_ms) {}
 
diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx
index 7d841a4c2..2ef3cdf3e 100644
--- a/src/DetachedSong.hxx
+++ b/src/DetachedSong.hxx
@@ -29,10 +29,10 @@
 
 #include <time.h>
 
-struct Song;
+struct LightSong;
 
 class DetachedSong {
-	friend DetachedSong map_song_detach(const Song &song);
+	friend DetachedSong map_song_detach(const LightSong &song);
 
 	/**
 	 * An UTF-8-encoded URI referring to the song file.  This can
@@ -62,7 +62,7 @@ class DetachedSong {
 	 */
 	unsigned end_ms;
 
-	explicit DetachedSong(const Song &other);
+	explicit DetachedSong(const LightSong &other);
 
 public:
 	explicit DetachedSong(const DetachedSong &other)
diff --git a/src/Directory.cxx b/src/Directory.cxx
index ff2c00b05..210d7cb67 100644
--- a/src/Directory.cxx
+++ b/src/Directory.cxx
@@ -24,6 +24,7 @@
 #include "DatabaseLock.hxx"
 #include "SongSort.hxx"
 #include "Song.hxx"
+#include "LightSong.hxx"
 #include "fs/Traits.hxx"
 #include "util/Alloc.hxx"
 #include "util/Error.hxx"
@@ -251,6 +252,20 @@ Directory::Sort()
 		child->Sort();
 }
 
+static LightSong
+ExportSong(const Song &src)
+{
+	LightSong dest;
+	dest.directory = src.parent->IsRoot()
+		? nullptr : src.parent->GetPath();
+	dest.uri = src.uri;
+	dest.tag = &src.tag;
+	dest.mtime = src.mtime;
+	dest.start_ms = src.start_ms;
+	dest.end_ms = src.end_ms;
+	return dest;
+}
+
 bool
 Directory::Walk(bool recursive, const SongFilter *filter,
 		VisitDirectory visit_directory, VisitSong visit_song,
@@ -261,10 +276,12 @@ Directory::Walk(bool recursive, const SongFilter *filter,
 
 	if (visit_song) {
 		Song *song;
-		directory_for_each_song(song, *this)
-			if ((filter == nullptr || filter->Match(*song)) &&
-			    !visit_song(*song, error))
+		directory_for_each_song(song, *this) {
+			const LightSong song2 = ExportSong(*song);
+			if ((filter == nullptr || filter->Match(song2)) &&
+			    !visit_song(song2, error))
 				return false;
+		}
 	}
 
 	if (visit_playlist) {
diff --git a/test/FakeSong.cxx b/src/LightSong.cxx
similarity index 79%
rename from test/FakeSong.cxx
rename to src/LightSong.cxx
index e2fae4d6e..af1e801f8 100644
--- a/test/FakeSong.cxx
+++ b/src/LightSong.cxx
@@ -17,17 +17,17 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "config.h"
-#include "Song.hxx"
-#include "directory.h"
-#include "Compiler.h"
+#include "LightSong.hxx"
+#include "tag/Tag.hxx"
 
-#include <stdlib.h>
-
-struct directory detached_root;
-
-Song *
-song_dup_detached(gcc_unused const Song *src)
+double
+LightSong::GetDuration() const
 {
-	abort();
+	if (end_ms > 0)
+		return (end_ms - start_ms) / 1000.0;
+
+	if (tag->time <= 0)
+		return 0;
+
+	return tag->time - start_ms / 1000.0;
 }
diff --git a/src/LightSong.hxx b/src/LightSong.hxx
new file mode 100644
index 000000000..b93d5876c
--- /dev/null
+++ b/src/LightSong.hxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LIGHT_SONG_HXX
+#define MPD_LIGHT_SONG_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+#include <time.h>
+
+struct Tag;
+
+/**
+ * A reference to a song file.  Unlike the other "Song" classes in the
+ * MPD code base, this one consists only of pointers.  It is supposed
+ * to be as light as possible while still providing all the
+ * information MPD has about a song file.  This class does not manage
+ * any memory, and the pointers become invalid quickly.  Only to be
+ * used to pass around during well-defined situations.
+ */
+struct LightSong {
+	/**
+	 * If this is not nullptr, then it denotes a prefix for the
+	 * #uri.  To build the full URI, join directory and uri with a
+	 * slash.
+	 */
+	const char *directory;
+
+	const char *uri;
+
+	/**
+	 * Must not be nullptr.
+	 */
+	const Tag *tag;
+
+	time_t mtime;
+
+	/**
+	 * Start of this sub-song within the file in milliseconds.
+	 */
+	unsigned start_ms;
+
+	/**
+	 * End of this sub-song within the file in milliseconds.
+	 * Unused if zero.
+	 */
+	unsigned end_ms;
+
+	gcc_pure
+	std::string GetURI() const {
+		if (directory == nullptr)
+			return std::string(uri);
+
+		std::string result(directory);
+		result.push_back('/');
+		result.append(uri);
+		return result;
+	}
+
+	gcc_pure
+	double GetDuration() const;
+};
+
+#endif
diff --git a/src/Mapper.cxx b/src/Mapper.cxx
index d41fba957..8fafce12d 100644
--- a/src/Mapper.cxx
+++ b/src/Mapper.cxx
@@ -218,7 +218,7 @@ map_detached_song_fs(const char *uri_utf8)
 }
 
 DetachedSong
-map_song_detach(const Song &song)
+map_song_detach(const LightSong &song)
 {
 	return DetachedSong(song);
 }
diff --git a/src/Mapper.hxx b/src/Mapper.hxx
index 18a5ca3fe..5c01a9aff 100644
--- a/src/Mapper.hxx
+++ b/src/Mapper.hxx
@@ -33,6 +33,7 @@
 class AllocatedPath;
 struct Directory;
 struct Song;
+struct LightSong;
 class DetachedSong;
 
 void
@@ -112,7 +113,7 @@ map_directory_child_fs(const Directory &directory, const char *name);
  */
 gcc_pure
 DetachedSong
-map_song_detach(const Song &song);
+map_song_detach(const LightSong &song);
 
 /**
  * Determines the file system path of a song.  This must not be a
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
index 0eea62e18..cbae02fef 100644
--- a/src/PlaylistEdit.cxx
+++ b/src/PlaylistEdit.cxx
@@ -29,7 +29,6 @@
 #include "PlayerControl.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
-#include "Song.hxx"
 #include "DetachedSong.hxx"
 #include "Mapper.hxx"
 #include "Idle.hxx"
diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx
index dcd0953d6..34143d8a8 100644
--- a/src/PlaylistPrint.cxx
+++ b/src/PlaylistPrint.cxx
@@ -30,12 +30,14 @@
 #include "DatabasePlugin.hxx"
 #include "Client.hxx"
 #include "InputStream.hxx"
-#include "Song.hxx"
 #include "DetachedSong.hxx"
 #include "fs/Traits.hxx"
 #include "util/Error.hxx"
 #include "thread/Cond.hxx"
 
+#define SONG_FILE "file: "
+#define SONG_TIME "Time: "
+
 void
 playlist_print_uris(Client &client, const playlist &playlist)
 {
@@ -118,7 +120,7 @@ PrintSongDetails(Client &client, const char *uri_utf8)
 	if (db == nullptr)
 		return false;
 
-	Song *song = db->GetSong(uri_utf8, IgnoreError());
+	auto *song = db->GetSong(uri_utf8, IgnoreError());
 	if (song == nullptr)
 		return false;
 
diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx
index 6d9b41b62..9a01aa954 100644
--- a/src/PlaylistSave.cxx
+++ b/src/PlaylistSave.cxx
@@ -22,7 +22,6 @@
 #include "PlaylistFile.hxx"
 #include "PlaylistError.hxx"
 #include "Playlist.hxx"
-#include "Song.hxx"
 #include "DetachedSong.hxx"
 #include "Mapper.hxx"
 #include "Idle.hxx"
diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx
index 5d61c7f71..5d3011988 100644
--- a/src/PlaylistSong.cxx
+++ b/src/PlaylistSong.cxx
@@ -29,7 +29,6 @@
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
 #include "DetachedSong.hxx"
-#include "Song.hxx"
 
 #include <assert.h>
 #include <string.h>
diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx
index 55b2e9f0a..755589786 100644
--- a/src/PlaylistUpdate.cxx
+++ b/src/PlaylistUpdate.cxx
@@ -21,7 +21,7 @@
 #include "Playlist.hxx"
 #include "DatabaseGlue.hxx"
 #include "DatabasePlugin.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "DetachedSong.hxx"
 #include "tag/Tag.hxx"
 #include "Idle.hxx"
@@ -35,7 +35,7 @@ UpdatePlaylistSong(const Database &db, DetachedSong &song)
 		   from the Database */
 		return false;
 
-	Song *original = db.GetSong(song.GetURI(), IgnoreError());
+	const LightSong *original = db.GetSong(song.GetURI(), IgnoreError());
 	if (original == nullptr)
 		/* not found - shouldn't happen, because the update
 		   thread should ensure that all stale Song instances
@@ -49,7 +49,7 @@ UpdatePlaylistSong(const Database &db, DetachedSong &song)
 	}
 
 	song.SetLastModified(original->mtime);
-	song.SetTag(original->tag);
+	song.SetTag(*original->tag);
 
 	db.ReturnSong(original);
 	return true;
diff --git a/src/Song.cxx b/src/Song.cxx
index 384307642..565b9af98 100644
--- a/src/Song.cxx
+++ b/src/Song.cxx
@@ -23,6 +23,7 @@
 #include "tag/Tag.hxx"
 #include "util/VarSize.hxx"
 #include "DetachedSong.hxx"
+#include "LightSong.hxx"
 
 #include <assert.h>
 #include <string.h>
@@ -94,14 +95,16 @@ Song::GetURI() const
 	}
 }
 
-double
-Song::GetDuration() const
+LightSong
+Song::Export() const
 {
-	if (end_ms > 0)
-		return (end_ms - start_ms) / 1000.0;
-
-	if (tag.time <= 0)
-		return 0;
-
-	return tag.time - start_ms / 1000.0;
+	LightSong dest;
+	dest.directory = parent->IsRoot()
+		? nullptr : parent->GetPath();
+	dest.uri = uri;
+	dest.tag = &tag;
+	dest.mtime = mtime;
+	dest.start_ms = start_ms;
+	dest.end_ms = end_ms;
+	return dest;
 }
diff --git a/src/Song.hxx b/src/Song.hxx
index 81f9f0c90..824fb429c 100644
--- a/src/Song.hxx
+++ b/src/Song.hxx
@@ -29,9 +29,7 @@
 #include <assert.h>
 #include <time.h>
 
-#define SONG_FILE	"file: "
-#define SONG_TIME	"Time: "
-
+struct LightSong;
 struct Directory;
 class DetachedSong;
 
@@ -112,7 +110,7 @@ struct Song {
 	std::string GetURI() const;
 
 	gcc_pure
-	double GetDuration() const;
+	LightSong Export() const;
 };
 
 #endif
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index dccbab925..594ac3abc 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "SongFilter.hxx"
 #include "Song.hxx"
+#include "LightSong.hxx"
 #include "DetachedSong.hxx"
 #include "tag/Tag.hxx"
 #include "util/ASCII.hxx"
@@ -137,7 +138,19 @@ SongFilter::Item::Match(const Tag &_tag) const
 }
 
 bool
-SongFilter::Item::Match(const Song &song) const
+SongFilter::Item::Match(const DetachedSong &song) const
+{
+	if (tag == LOCATE_TAG_BASE_TYPE)
+		return uri_is_child_or_same(value.c_str(), song.GetURI());
+
+	if (tag == LOCATE_TAG_FILE_TYPE)
+		return StringMatch(song.GetURI());
+
+	return Match(song.GetTag());
+}
+
+bool
+SongFilter::Item::Match(const LightSong &song) const
 {
 	if (tag == LOCATE_TAG_BASE_TYPE) {
 		const auto uri = song.GetURI();
@@ -149,19 +162,7 @@ SongFilter::Item::Match(const Song &song) const
 		return StringMatch(uri.c_str());
 	}
 
-	return Match(song.tag);
-}
-
-bool
-SongFilter::Item::Match(const DetachedSong &song) const
-{
-	if (tag == LOCATE_TAG_BASE_TYPE)
-		return uri_is_child_or_same(value.c_str(), song.GetURI());
-
-	if (tag == LOCATE_TAG_FILE_TYPE)
-		return StringMatch(song.GetURI());
-
-	return Match(song.GetTag());
+	return Match(*song.tag);
 }
 
 SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
@@ -207,7 +208,7 @@ SongFilter::Parse(unsigned argc, char *argv[], bool fold_case)
 }
 
 bool
-SongFilter::Match(const Song &song) const
+SongFilter::Match(const DetachedSong &song) const
 {
 	for (const auto &i : items)
 		if (!i.Match(song))
@@ -217,7 +218,7 @@ SongFilter::Match(const Song &song) const
 }
 
 bool
-SongFilter::Match(const DetachedSong &song) const
+SongFilter::Match(const LightSong &song) const
 {
 	for (const auto &i : items)
 		if (!i.Match(song))
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index d53c98357..74d7187c9 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -38,6 +38,7 @@
 struct Tag;
 struct TagItem;
 struct Song;
+struct LightSong;
 class DetachedSong;
 
 class SongFilter {
@@ -80,10 +81,10 @@ public:
 		bool Match(const Tag &tag) const;
 
 		gcc_pure
-		bool Match(const Song &song) const;
+		bool Match(const DetachedSong &song) const;
 
 		gcc_pure
-		bool Match(const DetachedSong &song) const;
+		bool Match(const LightSong &song) const;
 	};
 
 private:
@@ -107,10 +108,10 @@ public:
 	bool Match(const Tag &tag) const;
 
 	gcc_pure
-	bool Match(const Song &song) const;
+	bool Match(const DetachedSong &song) const;
 
 	gcc_pure
-	bool Match(const DetachedSong &song) const;
+	bool Match(const LightSong &song) const;
 
 	const std::list<Item> &GetItems() const {
 		return items;
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
index 67b622356..810518c21 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "SongPrint.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "DetachedSong.hxx"
 #include "Directory.hxx"
 #include "TimePrint.hxx"
@@ -28,6 +28,8 @@
 #include "Client.hxx"
 #include "util/UriUtil.hxx"
 
+#define SONG_FILE "file: "
+
 static void
 song_print_uri(Client &client, const char *uri)
 {
@@ -40,11 +42,11 @@ song_print_uri(Client &client, const char *uri)
 }
 
 void
-song_print_uri(Client &client, const Song &song)
+song_print_uri(Client &client, const LightSong &song)
 {
-	if (song.parent != nullptr && !song.parent->IsRoot()) {
+	if (song.directory != nullptr) {
 		client_printf(client, "%s%s/%s\n", SONG_FILE,
-			      song.parent->GetPath(), song.uri);
+			      song.directory, song.uri);
 	} else
 		song_print_uri(client, song.uri);
 }
@@ -56,7 +58,7 @@ song_print_uri(Client &client, const DetachedSong &song)
 }
 
 void
-song_print_info(Client &client, const Song &song)
+song_print_info(Client &client, const LightSong &song)
 {
 	song_print_uri(client, song);
 
@@ -74,7 +76,7 @@ song_print_info(Client &client, const Song &song)
 	if (song.mtime > 0)
 		time_print(client, "Last-Modified", song.mtime);
 
-	tag_print(client, song.tag);
+	tag_print(client, *song.tag);
 }
 
 void
diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx
index 7bbf6e19c..16a9ee6ff 100644
--- a/src/SongPrint.hxx
+++ b/src/SongPrint.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_SONG_PRINT_HXX
 #define MPD_SONG_PRINT_HXX
 
-struct Song;
+struct LightSong;
 class DetachedSong;
 class Client;
 
@@ -28,10 +28,10 @@ void
 song_print_info(Client &client, const DetachedSong &song);
 
 void
-song_print_info(Client &client, const Song &song);
+song_print_info(Client &client, const LightSong &song);
 
 void
-song_print_uri(Client &client, const Song &song);
+song_print_uri(Client &client, const LightSong &song);
 
 void
 song_print_uri(Client &client, const DetachedSong &song);
diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx
index 8c5499bc2..55143d278 100644
--- a/src/SongSticker.cxx
+++ b/src/SongSticker.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "SongSticker.hxx"
 #include "StickerDatabase.hxx"
+#include "LightSong.hxx"
 #include "Song.hxx"
 #include "Directory.hxx"
 
@@ -29,14 +30,14 @@
 #include <string.h>
 
 std::string
-sticker_song_get_value(const Song &song, const char *name)
+sticker_song_get_value(const LightSong &song, const char *name)
 {
 	const auto uri = song.GetURI();
 	return sticker_load_value("song", uri.c_str(), name);
 }
 
 bool
-sticker_song_set_value(const Song &song,
+sticker_song_set_value(const LightSong &song,
 		       const char *name, const char *value)
 {
 	const auto uri = song.GetURI();
@@ -44,21 +45,21 @@ sticker_song_set_value(const Song &song,
 }
 
 bool
-sticker_song_delete(const Song &song)
+sticker_song_delete(const LightSong &song)
 {
 	const auto uri = song.GetURI();
 	return sticker_delete("song", uri.c_str());
 }
 
 bool
-sticker_song_delete_value(const Song &song, const char *name)
+sticker_song_delete_value(const LightSong &song, const char *name)
 {
 	const auto uri = song.GetURI();
 	return sticker_delete_value("song", uri.c_str(), name);
 }
 
 struct sticker *
-sticker_song_get(const Song &song)
+sticker_song_get(const LightSong &song)
 {
 	const auto uri = song.GetURI();
 	return sticker_load("song", uri.c_str());
@@ -69,7 +70,7 @@ struct sticker_song_find_data {
 	const char *base_uri;
 	size_t base_uri_length;
 
-	void (*func)(Song &song, const char *value,
+	void (*func)(const LightSong &song, const char *value,
 		     void *user_data);
 	void *user_data;
 };
@@ -86,12 +87,12 @@ sticker_song_find_cb(const char *uri, const char *value, void *user_data)
 
 	Song *song = data->directory->LookupSong(uri + data->base_uri_length);
 	if (song != nullptr)
-		data->func(*song, value, data->user_data);
+		data->func(song->Export(), value, data->user_data);
 }
 
 bool
 sticker_song_find(Directory &directory, const char *name,
-		  void (*func)(Song &song, const char *value,
+		  void (*func)(const LightSong &song, const char *value,
 			       void *user_data),
 		  void *user_data)
 {
diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx
index b626e63e3..2f977bd21 100644
--- a/src/SongSticker.hxx
+++ b/src/SongSticker.hxx
@@ -24,7 +24,7 @@
 
 #include <string>
 
-struct Song;
+struct LightSong;
 struct Directory;
 struct sticker;
 
@@ -34,28 +34,28 @@ struct sticker;
  */
 gcc_pure
 std::string
-sticker_song_get_value(const Song &song, const char *name);
+sticker_song_get_value(const LightSong &song, const char *name);
 
 /**
  * Sets a sticker value in the specified song.  Overwrites existing
  * values.
  */
 bool
-sticker_song_set_value(const Song &song,
+sticker_song_set_value(const LightSong &song,
 		       const char *name, const char *value);
 
 /**
  * Deletes a sticker from the database.  All values are deleted.
  */
 bool
-sticker_song_delete(const Song &song);
+sticker_song_delete(const LightSong &song);
 
 /**
  * Deletes a sticker value.  Does nothing if the sticker did not
  * exist.
  */
 bool
-sticker_song_delete_value(const Song &song, const char *name);
+sticker_song_delete_value(const LightSong &song, const char *name);
 
 /**
  * Loads the sticker for the specified song.
@@ -64,7 +64,7 @@ sticker_song_delete_value(const Song &song, const char *name);
  * @return a sticker object, or NULL on error or if there is no sticker
  */
 sticker *
-sticker_song_get(const Song &song);
+sticker_song_get(const LightSong &song);
 
 /**
  * Finds stickers with the specified name below the specified
@@ -79,7 +79,7 @@ sticker_song_get(const Song &song);
  */
 bool
 sticker_song_find(Directory &directory, const char *name,
-		  void (*func)(Song &song, const char *value,
+		  void (*func)(const LightSong &song, const char *value,
 			       void *user_data),
 		  void *user_data);
 
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
index 14d3abcf0..4f5ea8be3 100644
--- a/src/TagPrint.cxx
+++ b/src/TagPrint.cxx
@@ -21,9 +21,10 @@
 #include "TagPrint.hxx"
 #include "tag/Tag.hxx"
 #include "tag/TagSettings.h"
-#include "Song.hxx"
 #include "Client.hxx"
 
+#define SONG_TIME "Time: "
+
 void tag_print_types(Client &client)
 {
 	int i;
diff --git a/src/TagSave.cxx b/src/TagSave.cxx
index 24058906e..3a291e115 100644
--- a/src/TagSave.cxx
+++ b/src/TagSave.cxx
@@ -20,7 +20,8 @@
 #include "config.h"
 #include "TagSave.hxx"
 #include "tag/Tag.hxx"
-#include "Song.hxx"
+
+#define SONG_TIME "Time: "
 
 void
 tag_save(FILE *file, const Tag &tag)
diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx
index cc57fbe94..bed7a92ab 100644
--- a/src/UpdateRemove.cxx
+++ b/src/UpdateRemove.cxx
@@ -24,6 +24,7 @@
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 #include "Song.hxx"
+#include "LightSong.hxx"
 #include "Main.hxx"
 #include "Instance.hxx"
 #include "Log.hxx"
@@ -57,7 +58,7 @@ song_remove_event(void)
 #ifdef ENABLE_SQLITE
 	/* if the song has a sticker, remove it */
 	if (sticker_enabled())
-		sticker_song_delete(*removed_song);
+		sticker_song_delete(removed_song->Export());
 #endif
 
 	{
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index 93a13140b..4272dee69 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -39,7 +39,7 @@ struct sticker_song_find_data {
 };
 
 static void
-sticker_song_find_print_cb(Song &song, const char *value,
+sticker_song_find_print_cb(const LightSong &song, const char *value,
 			   void *user_data)
 {
 	struct sticker_song_find_data *data =
@@ -59,7 +59,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
 
 	/* get song song_id key */
 	if (argc == 5 && strcmp(argv[1], "get") == 0) {
-		Song *song = db->GetSong(argv[3], error);
+		const LightSong *song = db->GetSong(argv[3], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
@@ -76,7 +76,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
 		return CommandResult::OK;
 	/* list song song_id */
 	} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
-		Song *song = db->GetSong(argv[3], error);
+		const LightSong *song = db->GetSong(argv[3], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
@@ -90,7 +90,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
 		return CommandResult::OK;
 	/* set song song_id id key */
 	} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
-		Song *song = db->GetSong(argv[3], error);
+		const LightSong *song = db->GetSong(argv[3], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
@@ -106,7 +106,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
 	/* delete song song_id [key] */
 	} else if ((argc == 4 || argc == 5) &&
 		   strcmp(argv[1], "delete") == 0) {
-		Song *song = db->GetSong(argv[3], error);
+		const LightSong *song = db->GetSong(argv[3], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
diff --git a/src/db/LazyDatabase.cxx b/src/db/LazyDatabase.cxx
index 0718c3dcd..6a01ffb82 100644
--- a/src/db/LazyDatabase.cxx
+++ b/src/db/LazyDatabase.cxx
@@ -51,7 +51,7 @@ LazyDatabase::Close()
 	}
 }
 
-Song *
+const LightSong *
 LazyDatabase::GetSong(const char *uri, Error &error) const
 {
 	return EnsureOpen(error)
@@ -60,7 +60,7 @@ LazyDatabase::GetSong(const char *uri, Error &error) const
 }
 
 void
-LazyDatabase::ReturnSong(Song *song) const
+LazyDatabase::ReturnSong(const LightSong *song) const
 {
 	assert(open);
 
diff --git a/src/db/LazyDatabase.hxx b/src/db/LazyDatabase.hxx
index 7f97aa40d..f718ecb3f 100644
--- a/src/db/LazyDatabase.hxx
+++ b/src/db/LazyDatabase.hxx
@@ -41,9 +41,9 @@ public:
 
 	virtual void Close() override;
 
-	virtual Song *GetSong(const char *uri_utf8,
-			      Error &error) const override;
-	virtual void ReturnSong(Song *song) const;
+	virtual const LightSong *GetSong(const char *uri_utf8,
+					 Error &error) const override;
+	virtual void ReturnSong(const LightSong *song) const;
 
 	virtual bool Visit(const DatabaseSelection &selection,
 			   VisitDirectory visit_directory,
diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
index e5e9ac76f..f65e4f3d0 100644
--- a/src/db/ProxyDatabasePlugin.cxx
+++ b/src/db/ProxyDatabasePlugin.cxx
@@ -24,11 +24,12 @@
 #include "DatabaseSelection.hxx"
 #include "DatabaseError.hxx"
 #include "Directory.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "SongFilter.hxx"
 #include "Compiler.h"
 #include "ConfigData.hxx"
 #include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
 #include "protocol/Ack.hxx"
@@ -44,6 +45,25 @@
 #include <string>
 #include <list>
 
+class ProxySong : public LightSong {
+	Tag tag2;
+
+public:
+	explicit ProxySong(const mpd_song *song);
+};
+
+class AllocatedProxySong : public ProxySong {
+	mpd_song *const song;
+
+public:
+	explicit AllocatedProxySong(mpd_song *_song)
+		:ProxySong(_song), song(_song) {}
+
+	~AllocatedProxySong() {
+		mpd_song_free(song);
+	}
+};
+
 class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
 	DatabaseListener &listener;
 
@@ -79,9 +99,9 @@ public:
 
 	virtual bool Open(Error &error) override;
 	virtual void Close() override;
-	virtual Song *GetSong(const char *uri_utf8,
+	virtual const LightSong *GetSong(const char *uri_utf8,
 				     Error &error) const override;
-	virtual void ReturnSong(Song *song) const;
+	virtual void ReturnSong(const LightSong *song) const;
 
 	virtual bool Visit(const DatabaseSelection &selection,
 			   VisitDirectory visit_directory,
@@ -144,6 +164,38 @@ static constexpr struct {
 	{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
 };
 
+static void
+Copy(TagBuilder &tag, TagType d_tag,
+     const struct mpd_song *song, enum mpd_tag_type s_tag)
+{
+
+	for (unsigned i = 0;; ++i) {
+		const char *value = mpd_song_get_tag(song, s_tag, i);
+		if (value == nullptr)
+			break;
+
+		tag.AddItem(d_tag, value);
+	}
+}
+
+ProxySong::ProxySong(const mpd_song *song)
+{
+	directory = nullptr;
+	uri = mpd_song_get_uri(song);
+	tag = &tag2;
+	mtime = mpd_song_get_last_modified(song);
+	start_ms = mpd_song_get_start(song) * 1000;
+	end_ms = mpd_song_get_end(song) * 1000;
+
+	TagBuilder tag_builder;
+	tag_builder.SetTime(mpd_song_get_duration(song));
+
+	for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
+		Copy(tag_builder, i->d, song, i->s);
+
+	tag_builder.Commit(tag2);
+}
+
 gcc_const
 static enum mpd_tag_type
 Convert(TagType tag_type)
@@ -424,10 +476,7 @@ ProxyDatabase::OnIdle()
 	SocketMonitor::ScheduleRead();
 }
 
-static Song *
-Convert(const struct mpd_song *song);
-
-Song *
+const LightSong *
 ProxyDatabase::GetSong(const char *uri, Error &error) const
 {
 	// TODO: eliminate the const_cast
@@ -452,18 +501,17 @@ ProxyDatabase::GetSong(const char *uri, Error &error) const
 		return nullptr;
 	}
 
-	Song *song2 = Convert(song);
-	mpd_song_free(song);
-	return song2;
+	return new AllocatedProxySong(song);
 }
 
 void
-ProxyDatabase::ReturnSong(Song *song) const
+ProxyDatabase::ReturnSong(const LightSong *_song) const
 {
-	assert(song != nullptr);
-	assert(song->parent == nullptr);
+	assert(_song != nullptr);
 
-	song->Free();
+	AllocatedProxySong *song = (AllocatedProxySong *)
+		const_cast<LightSong *>(_song);
+	delete song;
 }
 
 static bool
@@ -493,60 +541,23 @@ Visit(struct mpd_connection *connection, Directory &root,
 	return true;
 }
 
-static void
-Copy(TagBuilder &tag, TagType d_tag,
-     const struct mpd_song *song, enum mpd_tag_type s_tag)
-{
-
-	for (unsigned i = 0;; ++i) {
-		const char *value = mpd_song_get_tag(song, s_tag, i);
-		if (value == nullptr)
-			break;
-
-		tag.AddItem(d_tag, value);
-	}
-}
-
-static Song *
-Convert(const struct mpd_song *song)
-{
-	Song *s = Song::NewFile(mpd_song_get_uri(song), nullptr);
-
-	s->mtime = mpd_song_get_last_modified(song);
-	s->start_ms = mpd_song_get_start(song) * 1000;
-	s->end_ms = mpd_song_get_end(song) * 1000;
-
-	TagBuilder tag;
-	tag.SetTime(mpd_song_get_duration(song));
-
-	for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
-		Copy(tag, i->d, song, i->s);
-
-	tag.Commit(s->tag);
-
-	return s;
-}
-
 gcc_pure
 static bool
-Match(const SongFilter *filter, const Song &song)
+Match(const SongFilter *filter, const LightSong &song)
 {
 	return filter == nullptr || filter->Match(song);
 }
 
 static bool
 Visit(const SongFilter *filter,
-      const struct mpd_song *song,
+      const mpd_song *_song,
       VisitSong visit_song, Error &error)
 {
 	if (!visit_song)
 		return true;
 
-	Song *s = Convert(song);
-	bool success = !Match(filter, *s) || visit_song(*s, error);
-	s->Free();
-
-	return success;
+	const ProxySong song(_song);
+	return !Match(filter, song) || visit_song(song, error);
 }
 
 static bool
@@ -664,12 +675,10 @@ SearchSongs(struct mpd_connection *connection,
 	bool result = true;
 	struct mpd_song *song;
 	while (result && (song = mpd_recv_song(connection)) != nullptr) {
-		Song *song2 = Convert(song);
-		mpd_song_free(song);
+		AllocatedProxySong song2(song);
 
-		result = !Match(selection.filter, *song2) ||
-			visit_song(*song2, error);
-		song2->Free();
+		result = !Match(selection.filter, song2) ||
+			visit_song(song2, error);
 	}
 
 	mpd_response_finish(connection);
diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx
index c33db3831..3d947c042 100644
--- a/src/db/SimpleDatabasePlugin.cxx
+++ b/src/db/SimpleDatabasePlugin.cxx
@@ -22,6 +22,7 @@
 #include "DatabaseSelection.hxx"
 #include "DatabaseHelpers.hxx"
 #include "Directory.hxx"
+#include "Song.hxx"
 #include "SongFilter.hxx"
 #include "DatabaseSave.hxx"
 #include "DatabaseLock.hxx"
@@ -193,29 +194,34 @@ SimpleDatabase::Close()
 	delete root;
 }
 
-Song *
+const LightSong *
 SimpleDatabase::GetSong(const char *uri, Error &error) const
 {
 	assert(root != nullptr);
+	assert(borrowed_song_count == 0);
 
 	db_lock();
-	Song *song = root->LookupSong(uri);
+	const Song *song = root->LookupSong(uri);
 	db_unlock();
-	if (song == nullptr)
+	if (song == nullptr) {
 		error.Format(db_domain, DB_NOT_FOUND,
 			     "No such song: %s", uri);
+		return nullptr;
+	}
+
+	light_song = song->Export();
+
 #ifndef NDEBUG
-	else
-		++borrowed_song_count;
+	++borrowed_song_count;
 #endif
 
-	return song;
+	return &light_song;
 }
 
 void
-SimpleDatabase::ReturnSong(gcc_unused Song *song) const
+SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const
 {
-	assert(song != nullptr);
+	assert(song == &light_song);
 
 #ifndef NDEBUG
 	assert(borrowed_song_count > 0);
@@ -247,9 +253,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
 	if (directory == nullptr) {
 		if (visit_song) {
 			Song *song = root->LookupSong(selection.uri.c_str());
-			if (song != nullptr)
-				return !selection.Match(*song) ||
-					visit_song(*song, error);
+			if (song != nullptr) {
+				const LightSong song2 = song->Export();
+				return !selection.Match(song2) ||
+					visit_song(song2, error);
+			}
 		}
 
 		error.Set(db_domain, DB_NOT_FOUND, "No such directory");
diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx
index d51174194..509b91e4e 100644
--- a/src/db/SimpleDatabasePlugin.hxx
+++ b/src/db/SimpleDatabasePlugin.hxx
@@ -22,6 +22,7 @@
 
 #include "DatabasePlugin.hxx"
 #include "fs/AllocatedPath.hxx"
+#include "LightSong.hxx"
 #include "Compiler.h"
 
 #include <cassert>
@@ -36,6 +37,11 @@ class SimpleDatabase : public Database {
 
 	time_t mtime;
 
+	/**
+	 * A buffer for GetSong().
+	 */
+	mutable LightSong light_song;
+
 #ifndef NDEBUG
 	mutable unsigned borrowed_song_count;
 #endif
@@ -60,9 +66,9 @@ public:
 	virtual bool Open(Error &error) override;
 	virtual void Close() override;
 
-	virtual Song *GetSong(const char *uri_utf8,
-			      Error &error) const override;
-	virtual void ReturnSong(Song *song) const;
+	virtual const LightSong *GetSong(const char *uri_utf8,
+					 Error &error) const override;
+	virtual void ReturnSong(const LightSong *song) const;
 
 	virtual bool Visit(const DatabaseSelection &selection,
 			   VisitDirectory visit_directory,
diff --git a/src/db/UpnpDatabasePlugin.cxx b/src/db/UpnpDatabasePlugin.cxx
index dbf04f818..0768488a3 100644
--- a/src/db/UpnpDatabasePlugin.cxx
+++ b/src/db/UpnpDatabasePlugin.cxx
@@ -31,7 +31,7 @@
 #include "DatabaseError.hxx"
 #include "PlaylistVector.hxx"
 #include "Directory.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "ConfigData.hxx"
 #include "tag/TagBuilder.hxx"
 #include "tag/TagTable.hxx"
@@ -49,6 +49,31 @@
 
 static const char *const rootid = "0";
 
+class UpnpSong : public LightSong {
+	std::string uri2;
+
+	Tag tag2;
+
+public:
+	explicit UpnpSong(UPnPDirObject &&object)
+		:uri2(std::move(object.url)), tag2(std::move(object.tag)) {
+		directory = nullptr;
+		uri = uri2.c_str();
+		tag = &tag2;
+		mtime = 0;
+		start_ms = end_ms = 0;
+	}
+
+	UpnpSong(UPnPDirObject &&object, const char *_uri)
+		:uri2(_uri), tag2(std::move(object.tag)) {
+		directory = nullptr;
+		uri = uri2.c_str();
+		tag = &tag2;
+		mtime = 0;
+		start_ms = end_ms = 0;
+	}
+};
+
 class UpnpDatabase : public Database {
 	LibUPnP *m_lib;
 	UPnPDeviceDirectory *m_superdir;
@@ -61,9 +86,9 @@ public:
 
 	virtual bool Open(Error &error) override;
 	virtual void Close() override;
-	virtual Song *GetSong(const char *uri_utf8,
-			      Error &error) const override;
-	virtual void ReturnSong(Song *song) const;
+	virtual const LightSong *GetSong(const char *uri_utf8,
+					 Error &error) const override;
+	virtual void ReturnSong(const LightSong *song) const;
 
 	virtual bool Visit(const DatabaseSelection &selection,
 			   VisitDirectory visit_directory,
@@ -187,34 +212,20 @@ UpnpDatabase::Close()
 }
 
 void
-UpnpDatabase::ReturnSong(Song *song) const
+UpnpDatabase::ReturnSong(const LightSong *_song) const
 {
-	assert(song != nullptr);
+	assert(_song != nullptr);
 
-	song->Free();
-}
-
-// If uri is empty, we use the object's url instead. This happens
-// when the target of a Visit() is a song, which  only happens when
-// "add"ing AFAIK. Visit() calls us with a null uri so that the url
-// appropriate for fetching is used instead.
-static Song *
-upnpItemToSong(UPnPDirObject &&dirent, const char *uri)
-{
-	if (*uri == 0)
-		uri = dirent.url.c_str();
-
-	Song *s = Song::NewFile(uri, nullptr);
-	s->tag = std::move(dirent.tag);
-	return s;
+	UpnpSong *song = (UpnpSong *)const_cast<LightSong *>(_song);
+	delete song;
 }
 
 // Get song info by path. We can receive either the id path, or the titles
 // one
-Song *
+const LightSong *
 UpnpDatabase::GetSong(const char *uri, Error &error) const
 {
-	Song *song = nullptr;
+	UpnpSong *song = nullptr;
 	auto vpath = stringToTokens(uri, "/", true);
 	if (vpath.size() >= 2) {
 		ContentDirectoryService server;
@@ -232,7 +243,8 @@ UpnpDatabase::GetSong(const char *uri, Error &error) const
 				      error))
 				return nullptr;
 		}
-		song = upnpItemToSong(std::move(dirent), "");
+
+		song = new UpnpSong(std::move(dirent));
 	}
 	if (song == nullptr)
 		error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri);
@@ -357,12 +369,9 @@ visitSong(UPnPDirObject &&meta, const char *path,
 {
 	if (!visit_song)
 		return true;
-	Song *s = upnpItemToSong(std::move(meta), path);
-	if (!selection.Match(*s))
-		return true;
-	bool success = visit_song(*s, error);
-	s->Free();
-	return success;
+
+	const UpnpSong song(std::move(meta), path);
+	return !selection.Match(song) || visit_song(song, error);
 }
 
 /**
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx
index 7c988a539..24eb26077 100644
--- a/src/playlist/AsxPlaylistPlugin.cxx
+++ b/src/playlist/AsxPlaylistPlugin.cxx
@@ -21,7 +21,6 @@
 #include "AsxPlaylistPlugin.hxx"
 #include "PlaylistPlugin.hxx"
 #include "MemorySongEnumerator.hxx"
-#include "Song.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/ASCII.hxx"
 #include "util/Error.hxx"
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
index 253ff7ad2..550a4630e 100644
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ b/src/playlist/RssPlaylistPlugin.cxx
@@ -21,7 +21,6 @@
 #include "RssPlaylistPlugin.hxx"
 #include "PlaylistPlugin.hxx"
 #include "MemorySongEnumerator.hxx"
-#include "Song.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/ASCII.hxx"
 #include "util/Error.hxx"
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
index b0282b5da..bf68acd3b 100644
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/SoundCloudPlaylistPlugin.cxx
@@ -23,7 +23,6 @@
 #include "MemorySongEnumerator.hxx"
 #include "ConfigData.hxx"
 #include "InputStream.hxx"
-#include "Song.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/StringUtil.hxx"
 #include "util/Error.hxx"
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index ade6b5345..60c20e4ae 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -23,7 +23,7 @@
 #include "DatabaseSelection.hxx"
 #include "DatabaseListener.hxx"
 #include "Directory.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
 #include "PlaylistVector.hxx"
 #include "ConfigGlobal.hxx"
 #include "ConfigData.hxx"
@@ -65,11 +65,11 @@ DumpDirectory(const Directory &directory, Error &)
 }
 
 static bool
-DumpSong(Song &song, Error &)
+DumpSong(const LightSong &song, Error &)
 {
 	cout << "S ";
-	if (song.parent != nullptr && !song.parent->IsRoot())
-		cout << song.parent->path << "/";
+	if (song.directory != nullptr)
+		cout << song.directory << "/";
 	cout << song.uri << endl;
 	return true;
 }