From ddea5f182570f9d558e195fcfe55ba533e547a12 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Sun, 18 Feb 2018 23:46:15 +0100
Subject: [PATCH] Now store track file mimetype in database

---
 .../migrations/0018_auto_20180218_1554.py     | 28 +++++++++++++++
 .../migrations/0019_populate_mimetypes.py     | 34 +++++++++++++++++++
 api/funkwhale_api/music/models.py             |  6 ++++
 api/funkwhale_api/music/serializers.py        |  9 ++++-
 api/funkwhale_api/music/utils.py              |  8 +++++
 api/tests/music/test_models.py                | 15 ++++++++
 6 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
 create mode 100644 api/funkwhale_api/music/migrations/0019_populate_mimetypes.py

diff --git a/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
new file mode 100644
index 00000000..c4529879
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.0.2 on 2018-02-18 15:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0017_auto_20171227_1728'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='trackfile',
+            name='mimetype',
+            field=models.CharField(blank=True, max_length=200, null=True),
+        ),
+        migrations.AlterField(
+            model_name='importjob',
+            name='source',
+            field=models.CharField(max_length=500),
+        ),
+        migrations.AlterField(
+            model_name='importjob',
+            name='status',
+            field=models.CharField(choices=[('pending', 'Pending'), ('finished', 'Finished'), ('errored', 'Errored'), ('skipped', 'Skipped')], default='pending', max_length=30),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
new file mode 100644
index 00000000..127aa5e6
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import os
+
+from django.db import migrations, models
+from funkwhale_api.music.utils import guess_mimetype
+
+
+def populate_mimetype(apps, schema_editor):
+    TrackFile = apps.get_model("music", "TrackFile")
+
+    for tf in TrackFile.objects.filter(audio_file__isnull=False, mimetype__isnull=True).only('audio_file'):
+        try:
+            tf.mimetype = guess_mimetype(tf.audio_file)
+        except Exception as e:
+            print('Error on track file {}: {}'.format(tf.pk, e))
+            continue
+        print('Track file {}: {}'.format(tf.pk, tf.mimetype))
+        tf.save(update_fields=['mimetype'])
+
+
+def rewind(apps, schema_editor):
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0018_auto_20180218_1554'),
+    ]
+
+    operations = [
+        migrations.RunPython(populate_mimetype, rewind),
+    ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index f8373ab4..3ebd0741 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -18,6 +18,7 @@ from versatileimagefield.fields import VersatileImageField
 from funkwhale_api import downloader
 from funkwhale_api import musicbrainz
 from . import importers
+from . import utils
 
 
 class APIModelMixin(models.Model):
@@ -364,6 +365,7 @@ class TrackFile(models.Model):
     source = models.URLField(null=True, blank=True)
     duration = models.IntegerField(null=True, blank=True)
     acoustid_track_id = models.UUIDField(null=True, blank=True)
+    mimetype = models.CharField(null=True, blank=True, max_length=200)
 
     def download_file(self):
         # import the track file, since there is not any
@@ -393,6 +395,10 @@ class TrackFile(models.Model):
             self.track.full_name,
             os.path.splitext(self.audio_file.name)[-1])
 
+    def save(self, **kwargs):
+        if not self.mimetype and self.audio_file:
+            self.mimetype = utils.guess_mimetype(self.audio_file)
+        return super().save(**kwargs)
 
 class ImportBatch(models.Model):
     IMPORT_BATCH_SOURCES = [
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 506893a4..41de30f1 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -28,7 +28,14 @@ class TrackFileSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.TrackFile
-        fields = ('id', 'path', 'duration', 'source', 'filename', 'track')
+        fields = (
+            'id',
+            'path',
+            'duration',
+            'source',
+            'filename',
+            'mimetype',
+            'track')
 
     def get_path(self, o):
         url = o.path
diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py
index 32b1aeb4..0e4318e5 100644
--- a/api/funkwhale_api/music/utils.py
+++ b/api/funkwhale_api/music/utils.py
@@ -1,7 +1,9 @@
+import magic
 import re
 
 from django.db.models import Q
 
+
 def normalize_query(query_string,
                     findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
                     normspace=re.compile(r'\s{2,}').sub):
@@ -15,6 +17,7 @@ def normalize_query(query_string,
     '''
     return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
 
+
 def get_query(query_string, search_fields):
     ''' Returns a query, that is a combination of Q objects. That combination
         aims to search keywords within a model by testing the given search fields.
@@ -35,3 +38,8 @@ def get_query(query_string, search_fields):
         else:
             query = query & or_query
     return query
+
+
+def guess_mimetype(f):
+    b = min(100000, f.size)
+    return magic.from_buffer(f.read(b), mime=True)
diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py
index 16541546..2eb1f276 100644
--- a/api/tests/music/test_models.py
+++ b/api/tests/music/test_models.py
@@ -1,9 +1,12 @@
+import os
 import pytest
 
 from funkwhale_api.music import models
 from funkwhale_api.music import importers
 from funkwhale_api.music import tasks
 
+DATA_DIR = os.path.dirname(os.path.abspath(__file__))
+
 
 def test_can_store_release_group_id_on_album(factories):
     album = factories['music.Album']()
@@ -48,3 +51,15 @@ def test_import_job_is_bound_to_track_file(factories, mocker):
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
     assert job.track_file.track == track
+
+@pytest.mark.parametrize('extention,mimetype', [
+    ('ogg', 'audio/ogg'),
+    ('mp3', 'audio/mpeg'),
+])
+def test_audio_track_mime_type(extention, mimetype, factories):
+
+    name = '.'.join(['test', extention])
+    path = os.path.join(DATA_DIR, name)
+    tf = factories['music.TrackFile'](audio_file__from_path=path)
+
+    assert tf.mimetype == mimetype
-- 
GitLab