diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index e443007b5a58ba45dee55c33b28b5e4a21f11032..2fb7b496c33f68f2ee0761719ebf2412693a17c1 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -589,7 +589,12 @@ CELERY_BEAT_SCHEDULE = {
     },
     "federation.refresh_nodeinfo_known_nodes": {
         "task": "federation.refresh_nodeinfo_known_nodes",
-        "schedule": crontab(minute="0", hour="*"),
+        "schedule": crontab(
+            **env.dict(
+                "SCHEDULE_FEDERATION_REFRESH_NODEINFO_KNOWN_NODES",
+                default={"minute": "0", "hour": "*"},
+            )
+        ),
         "options": {"expires": 60 * 60},
     },
 }
diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py
index 6f9ea23cecac6a997decfdf701b2cfada3fec731..f2ade81814411399d51d0674031f497af39f0490 100644
--- a/api/funkwhale_api/playlists/views.py
+++ b/api/funkwhale_api/playlists/views.py
@@ -24,7 +24,7 @@ class PlaylistViewSet(
     queryset = (
         models.Playlist.objects.all()
         .select_related("user__actor")
-        .annotate(tracks_count=Count("playlist_tracks"))
+        .annotate(tracks_count=Count("playlist_tracks", distinct=True))
         .with_covers()
         .with_duration()
     )
diff --git a/api/tests/playlists/test_views.py b/api/tests/playlists/test_views.py
index 1c2b0f19eea43fce59dcf0ed166badd8e5603265..2be64b2bb864ef4b4fbbb5bb98ede9fd25b9ac7a 100644
--- a/api/tests/playlists/test_views.py
+++ b/api/tests/playlists/test_views.py
@@ -25,6 +25,18 @@ def test_serializer_includes_tracks_count(factories, logged_in_api_client):
     assert response.data["tracks_count"] == 1
 
 
+def test_serializer_includes_tracks_count_986(factories, logged_in_api_client):
+    playlist = factories["playlists.Playlist"]()
+    plt = factories["playlists.PlaylistTrack"](playlist=playlist)
+    factories["music.Upload"].create_batch(
+        3, track=plt.track, library__privacy_level="everyone", import_status="finished"
+    )
+    url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
+    response = logged_in_api_client.get(url)
+
+    assert response.data["tracks_count"] == 1
+
+
 def test_serializer_includes_is_playable(factories, logged_in_api_client):
     playlist = factories["playlists.Playlist"]()
     factories["playlists.PlaylistTrack"](playlist=playlist)
diff --git a/changes/changelog.d/986.bugfix b/changes/changelog.d/986.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..b18d8ec8d9ff3bdb8063578d6fc1fcfcdbf84149
--- /dev/null
+++ b/changes/changelog.d/986.bugfix
@@ -0,0 +1 @@
+Fixed invalid displayed number of tracks in playlist (#986)
diff --git a/front/src/views/content/libraries/FilesTable.vue b/front/src/views/content/libraries/FilesTable.vue
index fd0348a6e016a80b1b48c8097aeccaaa2304c39e..47294fe5723d2ccefcb998b4889f5e00b334e224 100644
--- a/front/src/views/content/libraries/FilesTable.vue
+++ b/front/src/views/content/libraries/FilesTable.vue
@@ -120,7 +120,9 @@
         <template slot="row-cells" slot-scope="scope">
           <template v-if="scope.obj.track">
             <td>
-              <span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(25) }}</span>
+              <router-link :to="{name: 'library.tracks.detail', params: {id: scope.obj.track.id }}" :title="scope.obj.track.title">
+                {{ scope.obj.track.title|truncate(25) }}
+              </router-link>
             </td>
             <td>
               <span