Skip to content

Commit c1c9d7f

Browse files
committed
support both Amazon Elastic Transcoder and Qiniu AVthumb
1 parent 98450c2 commit c1c9d7f

File tree

9 files changed

+169
-62
lines changed

9 files changed

+169
-62
lines changed

dj_elastictranscoder/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.9.6'
1+
__version__ = '1.0.0'

dj_elastictranscoder/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from django.contrib import admin
2+
23
from .models import EncodeJob
34

5+
46
class EncodeJobAdmin(admin.ModelAdmin):
57
list_display = ('id', 'state', 'message')
68
list_filters = ('state',)
9+
710
admin.site.register(EncodeJob, EncodeJobAdmin)

dj_elastictranscoder/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.db import models
22
from django.contrib.contenttypes.models import ContentType
3+
34
import django
45
if django.get_version() >= '1.8':
56
from django.contrib.contenttypes.fields import GenericForeignKey

dj_elastictranscoder/signals.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.dispatch import Signal
22

3-
transcode_onprogress = Signal(providing_args=["job", "message"])
4-
transcode_onerror = Signal(providing_args=["job", "message"])
5-
transcode_oncomplete = Signal(providing_args=["job", "message"])
3+
4+
transcode_onprogress = Signal(providing_args=['job', 'job_response'])
5+
transcode_oncomplete = Signal(providing_args=['job', 'job_response'])
6+
transcode_onerror = Signal(providing_args=['job', 'job_response'])

dj_elastictranscoder/transcoder.py

+86-30
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,113 @@
1-
from boto3.session import Session
2-
3-
from django.conf import settings
41
from django.contrib.contenttypes.models import ContentType
52

63
from .models import EncodeJob
4+
from .utils import get_setting_or_raise
75

86

97
class Transcoder(object):
108

11-
def __init__(self, pipeline_id, region=None, access_key_id=None, secret_access_key=None):
12-
self.pipeline_id = pipeline_id
9+
def start_job(self, obj, transcode_kwargs, message=''):
10+
raise NotImplementedError()
1311

14-
if not region:
15-
region = getattr(settings, 'AWS_REGION', None)
16-
self.aws_region = region
1712

13+
class AWSTranscoder(Transcoder):
14+
15+
def __init__(self, access_key_id=None, secret_access_key=None, pipeline_id=None, region=None):
1816
if not access_key_id:
19-
access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
20-
self.aws_access_key_id = access_key_id
17+
access_key_id = get_setting_or_raise('AWS_ACCESS_KEY_ID')
18+
self.access_key_id = access_key_id
2119

2220
if not secret_access_key:
23-
secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
24-
self.aws_secret_access_key = secret_access_key
21+
secret_access_key = get_setting_or_raise('AWS_SECRET_ACCESS_KEY')
22+
self.secret_access_key = secret_access_key
2523

26-
if self.aws_access_key_id is None:
27-
assert False, 'Please provide AWS_ACCESS_KEY_ID'
24+
if not pipeline_id:
25+
pipeline_id = get_setting_or_raise('AWS_TRANSCODER_PIPELINE_ID')
26+
self.pipeline_id = pipeline_id
2827

29-
if self.aws_secret_access_key is None:
30-
assert False, 'Please provide AWS_SECRET_ACCESS_KEY'
28+
if not region:
29+
region = get_setting_or_raise('AWS_REGION')
30+
self.region = region
3131

32-
if self.aws_region is None:
33-
assert False, 'Please provide AWS_REGION'
32+
from boto3.session import Session
3433

3534
boto_session = Session(
36-
aws_access_key_id=self.aws_access_key_id,
37-
aws_secret_access_key=self.aws_secret_access_key,
38-
region_name=self.aws_region,
35+
aws_access_key_id=self.access_key_id,
36+
aws_secret_access_key=self.secret_access_key,
37+
region_name=self.region,
3938
)
4039
self.client = boto_session.client('elastictranscoder')
4140

42-
def encode(self, input_name, outputs, **kwargs):
43-
self.message = self.client.create_job(
44-
PipelineId=self.pipeline_id,
45-
Input=input_name,
46-
Outputs=outputs,
47-
**kwargs
48-
)
41+
def start_job(self, obj, transcode_kwargs, message=''):
42+
"""
43+
https://boto3.readthedocs.io/en/latest/reference/services/elastictranscoder.html#ElasticTranscoder.Client.create_job
44+
"""
45+
46+
if 'PipelineId' not in transcode_kwargs:
47+
transcode_kwargs['PipelineId'] = self.pipeline_id
48+
49+
ret = self.client.create_job(**transcode_kwargs)
4950

50-
def create_job_for_object(self, obj):
5151
content_type = ContentType.objects.get_for_model(obj)
52+
job = EncodeJob()
53+
job.id = ret['Job']['Id']
54+
job.content_type = content_type
55+
job.object_id = obj.pk
56+
job.message = message
57+
job.save()
58+
5259

60+
class QiniuTranscoder(Transcoder):
61+
62+
def __init__(
63+
self,
64+
access_key=None,
65+
secret_key=None,
66+
pipeline_id=None,
67+
bucket_name=None,
68+
notify_url=None,
69+
):
70+
if not access_key:
71+
access_key = get_setting_or_raise('QINIU_ACCESS_KEY')
72+
self.access_key = access_key
73+
74+
if not secret_key:
75+
secret_key = get_setting_or_raise('QINIU_SECRET_KEY')
76+
self.secret_key = secret_key
77+
78+
if not pipeline_id:
79+
pipeline_id = get_setting_or_raise('QINIU_TRANSCODE_PIPELINE_ID')
80+
self.pipeline_id = pipeline_id
81+
82+
if not bucket_name:
83+
bucket_name = get_setting_or_raise('QINIU_TRANSCODE_BUCKET_NAME')
84+
self.bucket_name = bucket_name
85+
86+
if not notify_url:
87+
notify_url = get_setting_or_raise('QINIU_TRANSCODE_NOTIFY_URL')
88+
self.notify_url = notify_url
89+
90+
from qiniu import Auth
91+
92+
self.client = Auth(self.access_key, self.secret_key)
93+
94+
def start_job(self, obj, transcode_kwargs, message=''):
95+
"""
96+
https://developer.qiniu.com/dora/manual/1248/audio-and-video-transcoding-avthumb
97+
"""
98+
99+
from qiniu import PersistentFop
100+
101+
if 'force' not in transcode_kwargs:
102+
transcode_kwargs['force'] = 1
103+
104+
pfop = PersistentFop(self.client, self.bucket_name, self.pipeline_id, self.notify_url)
105+
ret, info = pfop.execute(**transcode_kwargs)
106+
107+
content_type = ContentType.objects.get_for_model(obj)
53108
job = EncodeJob()
54-
job.id = self.message['Job']['Id']
109+
job.id = ret['persistentId']
55110
job.content_type = content_type
56111
job.object_id = obj.pk
112+
job.message = message
57113
job.save()

dj_elastictranscoder/urls.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
except ImportError:
44
from django.conf.urls.defaults import url, patterns # Support for Django < 1.4
55

6-
urlpatterns = patterns('dj_elastictranscoder.views',
7-
url(r'^endpoint/$', 'endpoint'),
6+
urlpatterns = patterns(
7+
'dj_elastictranscoder.views',
8+
url(r'^endpoint/$', 'aws_endpoint'),
9+
url(r'^aws_endpoint/$', 'aws_endpoint', name='aws_endpoint'),
10+
url(r'^qiniu_endpoint/$', 'qiniu_endpoint', name='qiniu_endpoint'),
811
)

dj_elastictranscoder/utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.conf import settings
2+
from django.core.exceptions import ImproperlyConfigured
3+
4+
5+
def get_setting_or_raise(setting_name):
6+
try:
7+
value = getattr(settings, setting_name)
8+
except AttributeError:
9+
raise ImproperlyConfigured('Please provide {0}'.format(setting_name))
10+
return value

dj_elastictranscoder/views.py

+55-23
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
22

3+
from django.core.mail import mail_admins
34
from django.http import HttpResponse, HttpResponseBadRequest
45
from django.views.decorators.csrf import csrf_exempt
5-
from django.core.mail import mail_admins
6+
from django.views.decorators.http import require_http_methods
67

78
from .models import EncodeJob
89
from .signals import (
@@ -11,8 +12,9 @@
1112
transcode_oncomplete
1213
)
1314

15+
1416
@csrf_exempt
15-
def endpoint(request):
17+
def aws_endpoint(request):
1618
"""
1719
Receive SNS notification
1820
"""
@@ -22,7 +24,6 @@ def endpoint(request):
2224
except ValueError:
2325
return HttpResponseBadRequest('Invalid JSON')
2426

25-
2627
# handle SNS subscription
2728
if data['Type'] == 'SubscriptionConfirmation':
2829
subscribe_url = data['SubscribeURL']
@@ -34,34 +35,65 @@ def endpoint(request):
3435
mail_admins('Please confirm SNS subscription', subscribe_body)
3536
return HttpResponse('OK')
3637

37-
38-
#
39-
try:
40-
message = json.loads(data['Message'])
41-
except ValueError:
42-
assert False, data['Message']
38+
# handle job response
39+
message = json.loads(data['Message'])
40+
state = message['state']
41+
42+
job = EncodeJob.objects.get(pk=message['jobId'])
4343

44-
#
45-
if message['state'] == 'PROGRESSING':
46-
job = EncodeJob.objects.get(pk=message['jobId'])
47-
job.message = 'Progress'
44+
# https://docs.aws.amazon.com/elastictranscoder/latest/developerguide/notifications.html
45+
if state == 'PROGRESSING':
4846
job.state = 1
4947
job.save()
50-
51-
transcode_onprogress.send(sender=None, job=job, message=message)
52-
elif message['state'] == 'COMPLETED':
53-
job = EncodeJob.objects.get(pk=message['jobId'])
54-
job.message = 'Success'
48+
transcode_onprogress.send(sender=None, job=job, job_response=data)
49+
elif state == 'COMPLETED':
5550
job.state = 4
5651
job.save()
57-
58-
transcode_oncomplete.send(sender=None, job=job, message=message)
59-
elif message['state'] == 'ERROR':
60-
job = EncodeJob.objects.get(pk=message['jobId'])
52+
transcode_oncomplete.send(sender=None, job=job, job_response=data)
53+
elif state == 'ERROR':
6154
job.message = message['messageDetails']
6255
job.state = 2
6356
job.save()
57+
transcode_onerror.send(sender=None, job=job, job_response=data)
58+
else:
59+
raise RuntimeError('Invalid state')
60+
61+
return HttpResponse('Done')
62+
63+
64+
@csrf_exempt
65+
@require_http_methods(['POST', ])
66+
def qiniu_endpoint(request):
67+
"""
68+
Receive Qiniu notification
69+
"""
70+
71+
try:
72+
data = json.loads(request.body)
73+
except ValueError:
74+
return HttpResponseBadRequest('Invalid JSON')
75+
76+
code = data['code']
77+
desc = data['desc']
78+
job_id = data['id']
6479

65-
transcode_onerror.send(sender=None, job=job, message=message)
80+
job = EncodeJob.objects.get(pk=job_id)
81+
82+
# https://developer.qiniu.com/dora/manual/1294/persistent-processing-status-query-prefop
83+
if code in (1, 2): # Progressing
84+
job.state = 1
85+
job.save()
86+
transcode_onprogress.send(sender=None, job=job, job_response=data)
87+
elif code == 0: # Complete
88+
job.state = 4
89+
job.save()
90+
transcode_oncomplete.send(sender=None, job=job, job_response=data)
91+
elif code == 3 or code == 4: # Error
92+
job.message = desc
93+
job.state = 2
94+
job.save()
95+
transcode_onerror.send(sender=None, job=job, job_response=data)
96+
else:
97+
raise RuntimeError('Invalid code')
6698

6799
return HttpResponse('Done')

setup.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
include_package_data=True,
1616
zip_safe=False,
1717
install_requires=[
18-
"django >= 1.3, < 1.9",
1918
"boto3 >= 1.1",
20-
"South >= 0.8",
19+
"django >= 1.3, < 1.9",
20+
"qiniu >= 7.0.8",
21+
"south >= 0.8",
2122
],
2223
classifiers=[
2324
"Intended Audience :: Developers",
@@ -31,5 +32,5 @@
3132
"Environment :: Web Environment",
3233
"Framework :: Django",
3334
],
34-
keywords='django,aws,elastic,transcoder',
35+
keywords='django,aws,elastic,transcoder,qiniu,audio',
3536
)

0 commit comments

Comments
 (0)