27b6d00e5314f43782e9a3470f8246c1c9dbb906
[audio.git] / apps / archive / tasks.py
1 from datetime import datetime
2 import errno
3 import mimetypes
4 import os
5 import os.path
6 import pipes
7 import subprocess
8 from tempfile import NamedTemporaryFile
9 from time import sleep
10
11 #from celery.decorators import task
12 from celery.task import Task
13 from fabric import api
14 from mutagen import File
15 from mutagen import id3
16
17 import mutagen
18
19 from archive.constants import status
20 from archive.models import Audiobook
21 from archive.settings import (BUILD_PATH, COVER_IMAGE,
22     UPLOAD_HOST, UPLOAD_USER, UPLOAD_PASSWORD, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO)
23 from archive.utils import ExistingFile
24
25 api.env.host_string = UPLOAD_HOST
26 api.env.user = UPLOAD_USER
27 api.env.password = UPLOAD_PASSWORD
28
29 class AudioFormatTask(Task):
30     abstract = True
31
32     @classmethod
33     def set_status(cls, audiobook, status):
34         Audiobook.objects.filter(pk=audiobook.pk).update(
35             **{'%s_status' % cls.ext: status})
36
37     @staticmethod
38     def encode(in_path, out_path):
39         pass
40
41     @classmethod
42     def set_tags(cls, audiobook, file_name):
43         audio = File(file_name)
44         for k, v in getattr(audiobook, "%s_tags" % cls.ext)['tags'].items():
45             audio[k] = v
46         audio.save()
47
48     @classmethod
49     def save(cls, audiobook, file_name):
50         field = "%s_file" % cls.ext
51         getattr(audiobook, field).save(
52             "%d.%s" % (audiobook.pk, cls.ext),
53             ExistingFile(file_name),
54             save=False
55             )
56         Audiobook.objects.filter(pk=audiobook.pk).update(
57             **{field: getattr(audiobook, field)})
58
59     @classmethod
60     def published(cls, audiobook):
61         kwargs = {
62             "%s_published_tags" % cls.ext: 
63                     getattr(audiobook, "%s_tags" % cls.ext),
64             "%s_tags" % cls.ext: None,
65             "%s_published" % cls.ext: datetime.now(),
66             '%s_status' % cls.ext: None,
67         }
68         Audiobook.objects.filter(pk=audiobook.pk).update(**kwargs)
69
70     @classmethod
71     def put(cls, audiobook):
72         tags = getattr(audiobook, "%s_tags" % cls.ext)
73         prefix, slug = tags['url'].rstrip('/').rsplit('/', 1)
74         name = tags['name']
75         path = getattr(audiobook, "%s_file" % cls.ext).path
76         api.put(path, UPLOAD_PATH)
77         command = UPLOAD_CMD + (u' %s %s %s > output.txt' % (
78             pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))),
79             pipes.quote(slug),
80             pipes.quote(name)
81             )).encode('utf-8')
82         if UPLOAD_SUDO:
83             api.sudo(command, user=UPLOAD_SUDO, shell=False)
84         else:
85             api.run(command)
86
87     def run(self, aid):
88         audiobook = Audiobook.objects.get(id=aid)
89         self.set_status(audiobook, status.ENCODING)
90
91         try:
92             os.makedirs(BUILD_PATH)
93         except OSError as e:
94             if e.errno == errno.EEXIST:
95                 pass
96             else:
97                 raise
98
99         out_file = NamedTemporaryFile(delete=False, prefix='audiobook-', suffix='.%s' % self.ext, dir=BUILD_PATH)
100         out_file.close()
101         self.encode(audiobook.source_file.path, out_file.name)
102         self.set_status(audiobook, status.TAGGING)
103         self.set_tags(audiobook, out_file.name)
104         self.save(audiobook, out_file.name)
105         self.set_status(audiobook, status.SENDING)
106
107         self.put(audiobook)
108
109         self.published(audiobook)
110
111
112 class Mp3Task(AudioFormatTask):
113     ext = 'mp3'
114
115     # these shouldn't be staticmethods
116     def id3_text(tag, text):
117         return tag(encoding=1, text=text)
118     def id3_url(tag, text):
119         return tag(url=text)
120     def id3_comment(tag, text, lang=u'pol'):
121         return tag(encoding=1, lang=lang, desc=u'', text=text)
122     def id3_sha1(tag, text, what=u''):
123         return tag(owner='http://wolnelektury.pl?%s' % what, data=text)
124
125     TAG_MAP = {
126         'album': (id3_text, id3.TALB),
127         'albumartist': (id3_text, id3.TPE2),
128         'artist': (id3_text, id3.TPE1),
129         'conductor': (id3_text, id3.TPE3),
130         'copyright': (id3_text, id3.TCOP),
131         'date': (id3_text, id3.TDRC),
132         'genre': (id3_text, id3.TCON),
133         'language': (id3_text, id3.TLAN),
134         'organization': (id3_text, id3.TPUB),
135         'title': (id3_text, id3.TIT2),
136         'comment': (id3_comment, id3.COMM, 'pol'),
137         'contact': (id3_url, id3.WOAF),
138         'license': (id3_url, id3.WCOP),
139         'flac_sha1': (id3_sha1, id3.PRIV, 'flac_sha1'),
140     }
141
142     @staticmethod
143     def encode(in_path, out_path):
144         # 44.1kHz 64kbps mono MP3
145         subprocess.check_call(['ffmpeg', 
146             '-i', in_path,
147             '-ar', '44100',
148             '-ab', '64k',
149             '-ac', '1',
150             '-y',
151             out_path
152             ])
153
154     @classmethod
155     def set_tags(cls, audiobook, file_name):
156         audio = id3.ID3(file_name)
157         for k, v in audiobook.mp3_tags['tags'].items():
158             factory_tuple = cls.TAG_MAP[k]
159             factory, tagtype = factory_tuple[:2]
160             audio.add(factory(tagtype, v, *factory_tuple[2:]))
161
162         if COVER_IMAGE:
163             mime = mimetypes.guess_type(COVER_IMAGE)
164             f = open(COVER_IMAGE)
165             audio.add(id3.APIC(encoding=0, mime=mime, type=3, desc=u'', data=f.read()))
166             f.close()
167
168         audio.save()
169
170
171 class OggTask(AudioFormatTask):
172     ext = 'ogg'
173
174     @staticmethod
175     def encode(in_path, out_path):
176         # 44.1kHz 64kbps mono Ogg Vorbis
177         subprocess.check_call(['oggenc', 
178             in_path,
179             '--discard-comments',
180             '--resample', '44100',
181             '--downmix',
182             '-b', '64',
183             '-o', out_path
184             ])