diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 4759d7aab2b51b76281782498b47e510bda168c2..a445e102cfc5ef07487379243cf089db0217d0c6 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -412,7 +412,12 @@ CELERY_BEAT_SCHEDULE = {
         "task": "federation.clean_music_cache",
         "schedule": crontab(hour="*/2"),
         "options": {"expires": 60 * 2},
-    }
+    },
+    "music.clean_transcoding_cache": {
+        "task": "music.clean_transcoding_cache",
+        "schedule": crontab(hour="*"),
+        "options": {"expires": 60 * 2},
+    },
 }
 
 JWT_AUTH = {
diff --git a/api/funkwhale_api/music/dynamic_preferences_registry.py b/api/funkwhale_api/music/dynamic_preferences_registry.py
index d41225775ba4f4b93a22bf91075063be4ba51366..0a7d781aa65f523ff0da13d8677216709d930f8c 100644
--- a/api/funkwhale_api/music/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/music/dynamic_preferences_registry.py
@@ -17,3 +17,18 @@ class MaxTracks(types.BooleanPreference):
         "load on the server."
     )
     default = True
+
+
+@global_preferences_registry.register
+class MusicCacheDuration(types.IntPreference):
+    show_in_api = True
+    section = music
+    name = "transcoding_cache_duration"
+    default = 60 * 24 * 7
+    verbose_name = "Transcoding cache duration"
+    help_text = (
+        "How much minutes do you want to keep a copy of transcoded tracks"
+        "locally? Transcoded files that were not listened in this interval "
+        "will be erased and retranscoded from the remote on the next listening."
+    )
+    field_kwargs = {"required": False}
diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py
index 9571f978516535b357665a6672235be8abbd3b47..0ec3fdb227a61ab77c88d994fb3a66e495495554 100644
--- a/api/funkwhale_api/music/factories.py
+++ b/api/funkwhale_api/music/factories.py
@@ -95,6 +95,18 @@ class UploadFactory(factory.django.DjangoModelFactory):
         )
 
 
+@registry.register
+class UploadVersionFactory(factory.django.DjangoModelFactory):
+    upload = factory.SubFactory(UploadFactory, bitrate=200000)
+    bitrate = factory.SelfAttribute("upload.bitrate")
+    mimetype = "audio/mpeg"
+    audio_file = factory.django.FileField()
+    size = 2000000
+
+    class Meta:
+        model = "music.UploadVersion"
+
+
 @registry.register
 class WorkFactory(factory.django.DjangoModelFactory):
     mbid = factory.Faker("uuid4")
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index d96471b961c5fcfe0efa5bf1809f0e93f7106fb8..7008b12fc51ebc094b69876f4823ee1ab5c94261 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -1,4 +1,5 @@
 import collections
+import datetime
 import logging
 import os
 
@@ -10,7 +11,7 @@ from django.dispatch import receiver
 from musicbrainzngs import ResponseError
 from requests.exceptions import RequestException
 
-from funkwhale_api.common import channels
+from funkwhale_api.common import channels, preferences
 from funkwhale_api.federation import routes
 from funkwhale_api.federation import library as lb
 from funkwhale_api.taskapp import celery
@@ -526,3 +527,19 @@ def broadcast_import_status_update_to_owner(old_status, new_status, upload, **kw
             },
         },
     )
+
+
+@celery.app.task(name="music.clean_transcoding_cache")
+def clean_transcoding_cache():
+    delay = preferences.get("music__transcoding_cache_duration")
+    if delay < 1:
+        return  # cache clearing disabled
+    limit = timezone.now() - datetime.timedelta(minutes=delay)
+    candidates = (
+        models.UploadVersion.objects.filter(
+            (Q(accessed_date__lt=limit) | Q(accessed_date=None))
+        )
+        .only("audio_file", "id")
+        .order_by("id")
+    )
+    return candidates.delete()
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index efa0e801f42dfe6c785a35ca4ee70d63dfd8be82..10d9fba7517dcaba213f8b4b40c8cb340a49fa4a 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -546,3 +546,20 @@ def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock
     scan.refresh_from_db()
 
     assert scan.status == "finished"
+
+
+def test_clean_transcoding_cache(preferences, now, factories):
+    preferences['music__transcoding_cache_duration'] = 60
+    u1 = factories['music.UploadVersion'](
+        accessed_date=now - datetime.timedelta(minutes=61)
+    )
+    u2 = factories['music.UploadVersion'](
+        accessed_date=now - datetime.timedelta(minutes=59)
+    )
+
+    tasks.clean_transcoding_cache()
+
+    u2.refresh_from_db()
+
+    with pytest.raises(u1.__class__.DoesNotExist):
+        u1.refresh_from_db()
\ No newline at end of file