diff --git a/src/core/library/Indexer.cpp b/src/core/library/Indexer.cpp
index 39d761f7c..382c7663b 100644
--- a/src/core/library/Indexer.cpp
+++ b/src/core/library/Indexer.cpp
@@ -607,9 +607,8 @@ void Indexer::SyncCleanup() {
     this->dbConnection.Execute("DELETE FROM meta_values WHERE id NOT IN (SELECT DISTINCT(meta_value_id) FROM track_meta)");
     this->dbConnection.Execute("DELETE FROM meta_keys WHERE id NOT IN (SELECT DISTINCT(meta_key_id) FROM meta_values)");
 
-    /* orphaned playlist tracks (local tracks only. let tracks from external
-    sources continue to exist */
-    this->dbConnection.Execute("DELETE FROM playlist_tracks WHERE source_id=0 AND track_external_id NOT IN (SELECT DISTINCT external_id FROM tracks WHERE source_id == 0)");
+    /* NOTE: we used to remove orphaned local library tracks here, but we don't anymore because
+    the indexer generates stable external ids by hashing various file and metadata fields */
 
     /* orphaned playlist tracks from source plugins that do not have stable
     ids need to be cleaned up. */
@@ -629,7 +628,6 @@ void Indexer::SyncCleanup() {
         }
     }
 
-
     /* optimize and shrink */
     this->dbConnection.Execute("VACUUM");
 }
diff --git a/src/core/library/track/IndexerTrack.cpp b/src/core/library/track/IndexerTrack.cpp
index 31d9888c8..0388a8b07 100644
--- a/src/core/library/track/IndexerTrack.cpp
+++ b/src/core/library/track/IndexerTrack.cpp
@@ -43,9 +43,6 @@
 #include <core/io/DataStreamFactory.h>
 
 #include <boost/lexical_cast.hpp>
-#include <boost/uuid/uuid.hpp>
-#include <boost/uuid/uuid_generators.hpp>
-#include <boost/uuid/uuid_io.hpp>
 #include <unordered_map>
 
 using namespace musik::core;
@@ -62,7 +59,6 @@ using namespace musik::core;
 
 static std::mutex trackWriteLock;
 static std::unordered_map<std::string, int64_t> metadataIdCache;
-static auto uuids = boost::uuids::random_generator();
 
 void IndexerTrack::ResetIdCache() {
     metadataIdCache.clear();
@@ -477,6 +473,20 @@ static size_t hash32(const char* str) {
     return h;
 }
 
+static std::string createTrackExternalId(IndexerTrack& track) {
+    size_t hash1 = (size_t) hash32(track.GetValue("filename").c_str());
+
+    size_t hash2 = (size_t) hash32(
+        (track.GetValue("title") +
+        track.GetValue("album") +
+        track.GetValue("artist") +
+        track.GetValue("album_artist") +
+        track.GetValue("filesize") +
+        track.GetValue("duration")).c_str());
+
+    return std::string("local-") + std::to_string(hash1) + "-" + std::to_string(hash2);
+}
+
 int64_t IndexerTrack::SaveAlbum(db::Connection& dbConnection) {
     std::string album = this->GetValue("album");
     std::string value = album + "-" + this->GetValue("album_artist");
@@ -619,7 +629,7 @@ bool IndexerTrack::Save(db::Connection &dbConnection, std::string libraryDirecto
     }
 
     if (this->GetValue("external_id") == "") {
-        this->SetValue("external_id", boost::uuids::to_string(uuids()).c_str());
+        this->SetValue("external_id", createTrackExternalId(*this).c_str());
     }
 
     /* remove existing relations -- we're going to update them with fresh data */