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