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