new fields: part name, index, parts count
[audio.git] / apps / archive / tasks.py
old mode 100755 (executable)
new mode 100644 (file)
index 8d8df5e..a46b79c
@@ -4,13 +4,16 @@ import mimetypes
 import os
 import os.path
 import pipes
 import os
 import os.path
 import pipes
+import stat
 import subprocess
 from tempfile import NamedTemporaryFile
 from time import sleep
 
 #from celery.decorators import task
 from celery.task import Task
 import subprocess
 from tempfile import NamedTemporaryFile
 from time import sleep
 
 #from celery.decorators import task
 from celery.task import Task
+from django.db.models import F
 from fabric import api
 from fabric import api
+from fabric.network import disconnect_all
 from mutagen import File
 from mutagen import id3
 
 from mutagen import File
 from mutagen import id3
 
@@ -19,7 +22,7 @@ import mutagen
 from archive.constants import status
 from archive.models import Audiobook
 from archive.settings import (BUILD_PATH, COVER_IMAGE,
 from archive.constants import status
 from archive.models import Audiobook
 from archive.settings import (BUILD_PATH, COVER_IMAGE,
-    UPLOAD_HOST, UPLOAD_USER, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO)
+    UPLOAD_HOST, UPLOAD_USER, UPLOAD_PASSWORD, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO)
 from archive.utils import ExistingFile
 
 api.env.host_string = UPLOAD_HOST
 from archive.utils import ExistingFile
 
 api.env.host_string = UPLOAD_HOST
@@ -29,58 +32,77 @@ api.env.password = UPLOAD_PASSWORD
 class AudioFormatTask(Task):
     abstract = True
 
 class AudioFormatTask(Task):
     abstract = True
 
+    class RemoteOperationError(BaseException):
+        pass
+
     @classmethod
     @classmethod
-    def set_status(cls, audiobook, status):
-        setattr(audiobook, '%s_status' % cls.ext, status)
-        audiobook.save()
+    def set_status(cls, aid, status):
+        Audiobook.objects.filter(pk=aid).update(
+            **{'%s_status' % cls.ext: status})
 
     @staticmethod
     def encode(in_path, out_path):
 
     @staticmethod
     def encode(in_path, out_path):
-        pass
+        raise NotImplemented
 
     @classmethod
     def set_tags(cls, audiobook, file_name):
 
     @classmethod
     def set_tags(cls, audiobook, file_name):
+        tags = getattr(audiobook, "%s_tags" % cls.ext)['tags']
+        if not tags.get('flac_sha1'):
+            tags['flac_sha1'] = audiobook.get_source_sha1()
         audio = File(file_name)
         audio = File(file_name)
-        for k, v in getattr(audiobook, "%s_tags" % cls.ext)['tags'].items():
+        for k, v in tags.items():
             audio[k] = v
         audio.save()
 
     @classmethod
     def save(cls, audiobook, file_name):
             audio[k] = v
         audio.save()
 
     @classmethod
     def save(cls, audiobook, file_name):
-        getattr(audiobook, "%s_file" % cls.ext).save(
+        field = "%s_file" % cls.ext
+        getattr(audiobook, field).save(
             "%d.%s" % (audiobook.pk, cls.ext),
             "%d.%s" % (audiobook.pk, cls.ext),
-            ExistingFile(file_name)
+            ExistingFile(file_name),
+            save=False
             )
             )
+        os.chmod(getattr(audiobook, field).path, stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH)
+        Audiobook.objects.filter(pk=audiobook.pk).update(
+            **{field: getattr(audiobook, field)})
 
     @classmethod
 
     @classmethod
-    def published(cls, audiobook):
-        setattr(audiobook, "%s_published_tags" % cls.ext,
-            getattr(audiobook, "%s_tags" % cls.ext))
-        setattr(audiobook, "%s_tags" % cls.ext, None)
-        setattr(audiobook, "%s_published" % cls.ext, datetime.now())
-        cls.set_status(audiobook, None)
+    def published(cls, aid):
+        kwargs = {
+            "%s_published_tags" % cls.ext: F("%s_tags" % cls.ext),
+            "%s_tags" % cls.ext: None,
+            "%s_published" % cls.ext: datetime.now(),
+            '%s_status' % cls.ext: None,
+        }
+        Audiobook.objects.filter(pk=aid).update(**kwargs)
 
     @classmethod
 
     @classmethod
-    def put(cls, audiobook):
+    def put(cls, audiobook, path):
         tags = getattr(audiobook, "%s_tags" % cls.ext)
         prefix, slug = tags['url'].rstrip('/').rsplit('/', 1)
         name = tags['name']
         tags = getattr(audiobook, "%s_tags" % cls.ext)
         prefix, slug = tags['url'].rstrip('/').rsplit('/', 1)
         name = tags['name']
-        path = getattr(audiobook, "%s_file" % cls.ext).path
-        api.put(path, UPLOAD_PATH)
-        command = UPLOAD_CMD + (u' %s %s %s > output.txt' % (
+        command = UPLOAD_CMD + (u' %s %s %s %s %s %s > output.txt' % (
             pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))),
             pipes.quote(slug),
             pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))),
             pipes.quote(slug),
-            pipes.quote(name)
+            pipes.quote(name),
+            pipes.quote(audiobook.part_name),
+            audiobook.index,
+            audiobook.parts_count,
             )).encode('utf-8')
             )).encode('utf-8')
-        print command
-        if UPLOAD_SUDO:
-            api.sudo(command, user=UPLOAD_SUDO, shell=False)
-        else:
-            api.run(command)
+        try:
+            api.put(path, UPLOAD_PATH)
+            if UPLOAD_SUDO:
+                api.sudo(command, user=UPLOAD_SUDO, shell=False)
+            else:
+                api.run(command)
+            disconnect_all()
+        except SystemExit, e:
+            raise cls.RemoteOperationError
 
 
-    def run(self, aid):
+    def run(self, aid, publish=True):
+        aid = int(aid)
         audiobook = Audiobook.objects.get(id=aid)
         audiobook = Audiobook.objects.get(id=aid)
-        self.set_status(audiobook, status.ENCODING)
+        self.set_status(aid, status.ENCODING)
 
         try:
             os.makedirs(BUILD_PATH)
 
         try:
             os.makedirs(BUILD_PATH)
@@ -90,18 +112,24 @@ class AudioFormatTask(Task):
             else:
                 raise
 
             else:
                 raise
 
-        out_file = NamedTemporaryFile(delete=False, prefix='audiobook-', suffix='.%s' % self.ext, dir=BUILD_PATH)
+        out_file = NamedTemporaryFile(delete=False, prefix='%d-' % aid, suffix='.%s' % self.ext, dir=BUILD_PATH)
         out_file.close()
         self.encode(audiobook.source_file.path, out_file.name)
         out_file.close()
         self.encode(audiobook.source_file.path, out_file.name)
-        self.set_status(audiobook, status.TAGGING)
+        self.set_status(aid, status.TAGGING)
         self.set_tags(audiobook, out_file.name)
         self.set_tags(audiobook, out_file.name)
-        self.save(audiobook, out_file.name)
-        self.set_status(audiobook, status.SENDING)
+        self.set_status(aid, status.SENDING)
 
 
-        self.put(audiobook)
+        if publish:
+            self.put(audiobook, out_file.name)
+            self.published(aid)
+        else:
+            self.set_status(aid, None)
 
 
-        self.published(audiobook)
-        audiobook.save()
+        self.save(audiobook, out_file.name)
+
+    def on_failure(self, exc, task_id, args, kwargs, einfo):
+        aid = (args[0], kwargs.get('aid'))[0]
+        self.set_status(aid, None)
 
 
 class Mp3Task(AudioFormatTask):
 
 
 class Mp3Task(AudioFormatTask):
@@ -114,8 +142,8 @@ class Mp3Task(AudioFormatTask):
         return tag(url=text)
     def id3_comment(tag, text, lang=u'pol'):
         return tag(encoding=1, lang=lang, desc=u'', text=text)
         return tag(url=text)
     def id3_comment(tag, text, lang=u'pol'):
         return tag(encoding=1, lang=lang, desc=u'', text=text)
-    def id3_sha1(tag, text, what=u''):
-        return tag(owner='http://wolnelektury.pl?%s' % what, data=text)
+    def id3_priv(tag, text, what=u''):
+        return tag(owner='wolnelektury.pl?%s' % what, data=text.encode('utf-8'))
 
     TAG_MAP = {
         'album': (id3_text, id3.TALB),
 
     TAG_MAP = {
         'album': (id3_text, id3.TALB),
@@ -131,25 +159,31 @@ class Mp3Task(AudioFormatTask):
         'comment': (id3_comment, id3.COMM, 'pol'),
         'contact': (id3_url, id3.WOAF),
         'license': (id3_url, id3.WCOP),
         'comment': (id3_comment, id3.COMM, 'pol'),
         'contact': (id3_url, id3.WOAF),
         'license': (id3_url, id3.WCOP),
-        'flac_sha1': (id3_sha1, id3.PRIV, 'flac_sha1'),
+        'flac_sha1': (id3_priv, id3.PRIV, 'flac_sha1'),
+        'project': (id3_priv, id3.PRIV, 'project'),
+        'funded_by': (id3_priv, id3.PRIV, 'funded_by'),
     }
 
     @staticmethod
     def encode(in_path, out_path):
         # 44.1kHz 64kbps mono MP3
         subprocess.check_call(['ffmpeg', 
     }
 
     @staticmethod
     def encode(in_path, out_path):
         # 44.1kHz 64kbps mono MP3
         subprocess.check_call(['ffmpeg', 
-            '-i', in_path,
+            '-i', in_path.encode('utf-8'),
             '-ar', '44100',
             '-ab', '64k',
             '-ac', '1',
             '-y',
             '-ar', '44100',
             '-ab', '64k',
             '-ac', '1',
             '-y',
-            out_path
+            '-acodec', 'libmp3lame',
+            out_path.encode('utf-8')
             ])
 
     @classmethod
     def set_tags(cls, audiobook, file_name):
             ])
 
     @classmethod
     def set_tags(cls, audiobook, file_name):
+        mp3_tags = audiobook.mp3_tags['tags']
+        if not mp3_tags.get('flac_sha1'):
+            mp3_tags['flac_sha1'] = audiobook.get_source_sha1()
         audio = id3.ID3(file_name)
         audio = id3.ID3(file_name)
-        for k, v in audiobook.mp3_tags['tags'].items():
+        for k, v in mp3_tags.items():
             factory_tuple = cls.TAG_MAP[k]
             factory, tagtype = factory_tuple[:2]
             audio.add(factory(tagtype, v, *factory_tuple[2:]))
             factory_tuple = cls.TAG_MAP[k]
             factory, tagtype = factory_tuple[:2]
             audio.add(factory(tagtype, v, *factory_tuple[2:]))
@@ -169,11 +203,12 @@ class OggTask(AudioFormatTask):
     @staticmethod
     def encode(in_path, out_path):
         # 44.1kHz 64kbps mono Ogg Vorbis
     @staticmethod
     def encode(in_path, out_path):
         # 44.1kHz 64kbps mono Ogg Vorbis
-        subprocess.check_call(['oggenc', 
-            in_path,
-            '--discard-comments',
-            '--resample', '44100',
-            '--downmix',
-            '-b', '64',
-            '-o', out_path
+        subprocess.check_call(['ffmpeg', 
+            '-i', in_path.encode('utf-8'),
+            '-ar', '44100',
+            '-ab', '64k',
+            '-ac', '1',
+            '-y',
+            '-acodec', 'libvorbis',
+            out_path.encode('utf-8')
             ])
             ])