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