diff --git a/config/settings/common.py b/config/settings/common.py
index 6efad404cd7b832892e9643263645b2595ae3ee0..ceee21f9467a615b976ced99fa742518ca698000 100644
--- a/config/settings/common.py
+++ b/config/settings/common.py
@@ -64,6 +64,7 @@ LOCAL_APPS = (
     'funkwhale_api.radios',
     'funkwhale_api.history',
     'funkwhale_api.playlists',
+    'funkwhale_api.providers.audiofile',
 )
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
diff --git a/funkwhale_api/music/metadata.py b/funkwhale_api/music/metadata.py
index 92914ca22413013edabed1027d88bfd7d2a614de..3fe61e652b085eb404c9c6753475d9a079507aed 100644
--- a/funkwhale_api/music/metadata.py
+++ b/funkwhale_api/music/metadata.py
@@ -1,5 +1,6 @@
 import mutagen
 
+NODEFAULT = object()
 
 class Metadata(object):
     ALIASES = {
@@ -11,8 +12,14 @@ class Metadata(object):
     def __init__(self, path):
         self._file = mutagen.File(path)
 
-    def get(self, key, single=True):
-        v = self._file[key]
+    def get(self, key, default=NODEFAULT, single=True):
+        try:
+            v = self._file[key]
+        except KeyError:
+            if default == NODEFAULT:
+                raise
+            return default
+
         # Some tags are returned as lists of string
         if single:
             return v[0]
diff --git a/funkwhale_api/providers/audiofile/__init__.py b/funkwhale_api/providers/audiofile/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..18e2c469a0aa29a634080d4efe1e3959650d4601
--- /dev/null
+++ b/funkwhale_api/providers/audiofile/__init__.py
@@ -0,0 +1,4 @@
+"""
+This module is responsible from importing existing audiofiles from the
+filesystem into funkwhale.
+"""
diff --git a/funkwhale_api/providers/audiofile/importer.py b/funkwhale_api/providers/audiofile/importer.py
new file mode 100644
index 0000000000000000000000000000000000000000..d95c120e1f7e8eec9faacc14e4b010491d321c1c
--- /dev/null
+++ b/funkwhale_api/providers/audiofile/importer.py
@@ -0,0 +1,60 @@
+import os
+import datetime
+from django.core.files import File
+
+from funkwhale_api.taskapp import celery
+from funkwhale_api.music import models, metadata
+
+
+@celery.app.task(name='audiofile.from_path')
+def from_path(path):
+    data = metadata.Metadata(path)
+
+    artist = models.Artist.objects.get_or_create(
+        name__iexact=data.get('artist'),
+        defaults={'name': data.get('artist')},
+    )[0]
+
+    release_date = None
+    try:
+        year, month, day = data.get('date', None).split('-')
+        release_date = datetime.date(
+            int(year), int(month), int(day)
+        )
+    except (ValueError, TypeError):
+        pass
+
+    album = models.Album.objects.get_or_create(
+        title__iexact=data.get('album'),
+        artist=artist,
+        defaults={
+            'title': data.get('album'),
+            'release_date': release_date,
+        },
+    )[0]
+
+    position = None
+    try:
+        position = int(data.get('tracknumber', None))
+    except ValueError:
+        pass
+    track = models.Track.objects.get_or_create(
+        title__iexact=data.get('title'),
+        album=album,
+        defaults={
+            'title': data.get('title'),
+            'position': position,
+        },
+    )[0]
+
+    if track.files.count() > 0:
+        raise ValueError('File already exists for track {}'.format(track.pk))
+
+    track_file = models.TrackFile(track=track)
+    track_file.audio_file.save(
+        os.path.basename(path),
+        File(open(path, 'rb'))
+    )
+    track_file.save()
+
+    return track_file
diff --git a/funkwhale_api/providers/audiofile/management/__init__.py b/funkwhale_api/providers/audiofile/management/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/funkwhale_api/providers/audiofile/management/commands/__init__.py b/funkwhale_api/providers/audiofile/management/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/funkwhale_api/providers/audiofile/management/commands/import_files.py b/funkwhale_api/providers/audiofile/management/commands/import_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a99156cb0c477bd371910ef1bbb0a193a2bf52b
--- /dev/null
+++ b/funkwhale_api/providers/audiofile/management/commands/import_files.py
@@ -0,0 +1,61 @@
+import glob
+from django.core.management.base import BaseCommand, CommandError
+from funkwhale_api.providers.audiofile import importer
+
+
+class Command(BaseCommand):
+    help = 'Import audio files mathinc given glob pattern'
+
+    def add_arguments(self, parser):
+        parser.add_argument('path', type=str)
+        parser.add_argument(
+            '--recursive',
+            action='store_true',
+            dest='recursive',
+            default=False,
+            help='Will match the pattern recursively (including subdirectories)',
+        )
+        parser.add_argument(
+            '--async',
+            action='store_true',
+            dest='async',
+            default=False,
+            help='Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI',
+        )
+        parser.add_argument(
+            '--noinput', '--no-input', action='store_false', dest='interactive',
+            help="Do NOT prompt the user for input of any kind.",
+        )
+
+    def handle(self, *args, **options):
+        # self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
+        matching = glob.glob(options['path'], recursive=options['recursive'])
+        self.stdout.write('This will import {} files matching this pattern: {}'.format(
+            len(matching), options['path']))
+
+        if not matching:
+            raise CommandError('No file matching pattern, aborting')
+
+        if options['interactive']:
+            message = (
+                'Are you sure you want to do this?\n\n'
+                "Type 'yes' to continue, or 'no' to cancel: "
+            )
+            if input(''.join(message)) != 'yes':
+                raise CommandError("Import cancelled.")
+
+        message = 'Importing {}...'
+        if options['async']:
+            message = 'Launching import for {}...'
+
+        for path in matching:
+            self.stdout.write(message.format(path))
+            try:
+                importer.from_path(path)
+            except Exception as e:
+                self.stdout.write('Error: {}'.format(e))
+
+        message = 'Successfully imported {} tracks'
+        if options['async']:
+            message = 'Successfully launched import for {} tracks'
+        self.stdout.write(message.format(len(matching)))
diff --git a/funkwhale_api/providers/audiofile/tests/__init__.py b/funkwhale_api/providers/audiofile/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/funkwhale_api/providers/audiofile/tests/dummy_file.ogg b/funkwhale_api/providers/audiofile/tests/dummy_file.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/funkwhale_api/providers/audiofile/tests/test_disk_import.py b/funkwhale_api/providers/audiofile/tests/test_disk_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..26532a8c93aba087d0dcd07fedaedfe9b47e23c1
--- /dev/null
+++ b/funkwhale_api/providers/audiofile/tests/test_disk_import.py
@@ -0,0 +1,34 @@
+import os
+import datetime
+import unittest
+from test_plus.test import TestCase
+
+from funkwhale_api.providers.audiofile import importer
+
+DATA_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+class TestAudioFile(TestCase):
+    def test_can_import_single_audio_file(self, *mocks):
+        metadata = {
+            'artist': ['Test artist'],
+            'album': ['Test album'],
+            'title': ['Test track'],
+            'tracknumber': ['4'],
+            'date': ['2012-08-15']
+        }
+
+        with unittest.mock.patch('mutagen.File', return_value=metadata):
+            track_file = importer.from_path(
+                os.path.join(DATA_DIR, 'dummy_file.ogg'))
+
+        self.assertEqual(
+            track_file.track.title, metadata['title'][0])
+        self.assertEqual(
+            track_file.track.position, 4)
+        self.assertEqual(
+            track_file.track.album.title, metadata['album'][0])
+        self.assertEqual(
+            track_file.track.album.release_date, datetime.date(2012, 8, 15))
+        self.assertEqual(
+            track_file.track.artist.name, metadata['artist'][0])