1 from datetime import datetime
9 from tempfile import NamedTemporaryFile
10 from time import sleep
12 from celery.task import Task
13 from django.conf import settings
14 from django.db.models import F
15 from django.contrib.auth.models import User
16 from mutagen import File
17 from mutagen import id3
19 from apiclient import api_call
20 from archive.constants import status
21 from archive.models import Audiobook
22 from archive.settings import COVER_IMAGE, UPLOAD_URL
23 from archive.utils import ExistingFile
26 class AudioFormatTask(Task):
29 class RemoteOperationError(BaseException):
33 def set_status(cls, aid, status):
34 Audiobook.objects.filter(pk=aid).update(
35 **{'%s_status' % cls.prefix: status})
38 def encode(in_path, out_path):
42 def set_tags(cls, audiobook, file_name):
43 tags = getattr(audiobook, "get_%s_tags" % cls.prefix)()['tags']
44 if not tags.get('flac_sha1'):
45 tags['flac_sha1'] = audiobook.get_source_sha1()
46 audio = File(file_name)
47 for k, v in tags.items():
52 def save(cls, audiobook, file_name):
53 field = "%s_file" % cls.prefix
54 getattr(audiobook, field).save(
55 "%d.%s" % (audiobook.pk, cls.ext),
56 ExistingFile(file_name),
59 os.chmod(getattr(audiobook, field).path, stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH)
60 Audiobook.objects.filter(pk=audiobook.pk).update(
61 **{field: getattr(audiobook, field)})
64 def published(cls, aid):
66 "%s_published_tags" % cls.prefix: F("%s_tags" % cls.prefix),
67 "%s_tags" % cls.prefix: None,
68 "%s_published" % cls.prefix: datetime.now(),
69 '%s_status' % cls.prefix: None,
71 Audiobook.objects.filter(pk=aid).update(**kwargs)
74 def put(cls, user, audiobook, path):
75 tags = getattr(audiobook, "get_%s_tags" % cls.prefix)()
80 'part_name': audiobook.part_name,
81 'part_index': audiobook.index,
82 'parts_count': audiobook.parts_count,
83 'source_sha1': audiobook.source_sha1,
85 'project_description': audiobook.project.get_description(),
86 'project_icon': audiobook.project.get_icon_url(),
88 with open(path, 'rb') as f:
89 api_call(user, UPLOAD_URL, data=data, files={
93 def run(self, uid, aid, publish=True):
95 audiobook = Audiobook.objects.get(id=aid)
96 self.audiobook = audiobook
97 self.set_status(aid, status.ENCODING)
100 user = User.objects.get(id=uid)
104 out_file = NamedTemporaryFile(
105 delete=False, prefix='%d-' % aid, suffix='.%s' % self.ext,
106 dir=settings.FILE_UPLOAD_TEMP_DIR
109 self.encode(self.get_source_file_paths(audiobook), out_file.name)
110 self.set_status(aid, status.TAGGING)
111 self.set_tags(audiobook, out_file.name)
112 self.set_status(aid, status.SENDING)
115 self.put(user, audiobook, out_file.name)
118 self.set_status(aid, None)
120 self.save(audiobook, out_file.name)
122 def get_source_file_paths(self, audiobook):
123 return [audiobook.prepare_audio()]
125 def on_failure(self, exc, task_id, args, kwargs, einfo):
126 aid = (args[0], kwargs.get('aid'))[0]
127 self.set_status(aid, None)
130 class Mp3Task(AudioFormatTask):
133 # these shouldn't be staticmethods
134 def id3_text(tag, text):
135 return tag(encoding=3, text=text)
136 def id3_url(tag, text):
138 def id3_comment(tag, text, lang='pol'):
139 return tag(encoding=3, lang=lang, desc='', text=text)
140 def id3_priv(tag, text, what=''):
141 return tag(owner='wolnelektury.pl?%s' % what, data=text.encode('utf-8'))
144 'album': (id3_text, id3.TALB),
145 'albumartist': (id3_text, id3.TPE2),
146 'artist': (id3_text, id3.TPE1),
147 'conductor': (id3_text, id3.TPE3),
148 'copyright': (id3_text, id3.TCOP),
149 'date': (id3_text, id3.TDRC),
150 'genre': (id3_text, id3.TCON),
151 'language': (id3_text, id3.TLAN),
152 'organization': (id3_text, id3.TPUB),
153 'title': (id3_text, id3.TIT2),
154 'comment': (id3_comment, id3.COMM, 'pol'),
155 'contact': (id3_url, id3.WOAF),
156 'license': (id3_url, id3.WCOP),
157 'flac_sha1': (id3_priv, id3.PRIV, 'flac_sha1'),
158 'project': (id3_priv, id3.PRIV, 'project'),
159 'funded_by': (id3_priv, id3.PRIV, 'funded_by'),
163 def encode(in_paths, out_path):
164 assert len(in_paths) == 1
165 in_path = in_paths[0]
166 # 44.1kHz 64kbps mono MP3
167 subprocess.check_call(['ffmpeg',
168 '-i', in_path.encode('utf-8'),
173 '-acodec', 'libmp3lame',
174 out_path.encode('utf-8')
178 def set_tags(cls, audiobook, file_name):
179 mp3_tags = audiobook.get_mp3_tags()['tags']
180 if not mp3_tags.get('flac_sha1'):
181 mp3_tags['flac_sha1'] = audiobook.get_source_sha1()
182 audio = id3.ID3(file_name)
183 for k, v in mp3_tags.items():
184 factory_tuple = cls.TAG_MAP[k]
185 factory, tagtype = factory_tuple[:2]
186 audio.add(factory(tagtype, v, *factory_tuple[2:]))
189 mime = mimetypes.guess_type(COVER_IMAGE)
190 with open(COVER_IMAGE, 'rb') as f:
191 audio.add(id3.APIC(encoding=0, mime=mime, type=3, desc='', data=f.read()))
196 class OggTask(AudioFormatTask):
200 def encode(in_paths, out_path):
201 assert len(in_paths) == 1
202 in_path = in_paths[0]
203 # 44.1kHz 64kbps mono Ogg Vorbis
204 subprocess.check_call(['ffmpeg',
205 '-i', in_path.encode('utf-8'),
210 '-acodec', 'libvorbis',
211 out_path.encode('utf-8')