From ededd2f9b143796372627ab8de9de32d20e83f6a Mon Sep 17 00:00:00 2001
From: Dan Williams <dan@workshop.ws>
Date: Wed, 10 Oct 2018 16:20:17 +0100
Subject: [PATCH 1/6] Django 2.0+ Comaptible

---
 dj_elastictranscoder/migrations/0001_initial.py |  2 +-
 dj_elastictranscoder/urls.py                    | 12 +++++-------
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/dj_elastictranscoder/migrations/0001_initial.py b/dj_elastictranscoder/migrations/0001_initial.py
index db0199f..0eccc2f 100644
--- a/dj_elastictranscoder/migrations/0001_initial.py
+++ b/dj_elastictranscoder/migrations/0001_initial.py
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
                 ('message', models.TextField()),
                 ('created_at', models.DateTimeField(auto_now_add=True)),
                 ('last_modified', models.DateTimeField(auto_now=True)),
-                ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
+                ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.deletion.CASCADE)),
             ],
         ),
     ]
diff --git a/dj_elastictranscoder/urls.py b/dj_elastictranscoder/urls.py
index 3b6d4a3..a9229f1 100644
--- a/dj_elastictranscoder/urls.py
+++ b/dj_elastictranscoder/urls.py
@@ -1,8 +1,6 @@
-try:
-    from django.conf.urls import url, patterns
-except ImportError:
-    from django.conf.urls.defaults import url, patterns  # Support for Django < 1.4
+from django.urls import path
 
-urlpatterns = patterns('dj_elastictranscoder.views',
-    url(r'^endpoint/$', 'endpoint'),
-)
+from dj_elastictranscoder.views import endpoint
+
+
+urlpatterns = [path("endpoint/", endpoint)]

From 381afc8341cc5bad99c128f0d2467f783ace9ce7 Mon Sep 17 00:00:00 2001
From: Dan Williams <dan@workshop.ws>
Date: Wed, 10 Oct 2018 16:20:41 +0100
Subject: [PATCH 2/6] Add SNS notification verification to subscription
 endpoint

---
 dj_elastictranscoder/admin.py      |   7 +-
 dj_elastictranscoder/models.py     |  18 +--
 dj_elastictranscoder/transcoder.py |  26 ++--
 dj_elastictranscoder/views.py      | 199 ++++++++++++++++++++++++-----
 setup.py                           |   3 +-
 5 files changed, 193 insertions(+), 60 deletions(-)

diff --git a/dj_elastictranscoder/admin.py b/dj_elastictranscoder/admin.py
index c7ad991..cf98399 100644
--- a/dj_elastictranscoder/admin.py
+++ b/dj_elastictranscoder/admin.py
@@ -1,7 +1,10 @@
 from django.contrib import admin
 from .models import EncodeJob
 
+
 class EncodeJobAdmin(admin.ModelAdmin):
-    list_display = ('id', 'state', 'message')
-    list_filters = ('state',)
+    list_display = ("id", "state", "message")
+    list_filters = ("state",)
+
+
 admin.site.register(EncodeJob, EncodeJobAdmin)
diff --git a/dj_elastictranscoder/models.py b/dj_elastictranscoder/models.py
index 8422e07..dbe36bb 100644
--- a/dj_elastictranscoder/models.py
+++ b/dj_elastictranscoder/models.py
@@ -1,22 +1,18 @@
 from django.db import models
+from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
-import django
-if django.get_version() >= '1.8':
-    from django.contrib.contenttypes.fields import GenericForeignKey
-else:
-    from django.contrib.contenttypes.generic import GenericForeignKey
 
 
 class EncodeJob(models.Model):
     STATE_CHOICES = (
-        (0, 'Submitted'),
-        (1, 'Progressing'),
-        (2, 'Error'),
-        (3, 'Warning'),
-        (4, 'Complete'),
+        (0, "Submitted"),
+        (1, "Progressing"),
+        (2, "Error"),
+        (3, "Warning"),
+        (4, "Complete"),
     )
     id = models.CharField(max_length=100, primary_key=True)
-    content_type = models.ForeignKey(ContentType)
+    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
     object_id = models.PositiveIntegerField()
     state = models.PositiveIntegerField(choices=STATE_CHOICES, default=0, db_index=True)
     content_object = GenericForeignKey()
diff --git a/dj_elastictranscoder/transcoder.py b/dj_elastictranscoder/transcoder.py
index 6d77f75..c27d765 100644
--- a/dj_elastictranscoder/transcoder.py
+++ b/dj_elastictranscoder/transcoder.py
@@ -7,51 +7,49 @@
 
 
 class Transcoder(object):
-
-    def __init__(self, pipeline_id, region=None, access_key_id=None, secret_access_key=None):
+    def __init__(
+        self, pipeline_id, region=None, access_key_id=None, secret_access_key=None
+    ):
         self.pipeline_id = pipeline_id
 
         if not region:
-            region = getattr(settings, 'AWS_REGION', None)
+            region = getattr(settings, "AWS_REGION", None)
         self.aws_region = region
 
         if not access_key_id:
-            access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
+            access_key_id = getattr(settings, "AWS_ACCESS_KEY_ID", None)
         self.aws_access_key_id = access_key_id
 
         if not secret_access_key:
-            secret_access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
+            secret_access_key = getattr(settings, "AWS_SECRET_ACCESS_KEY", None)
         self.aws_secret_access_key = secret_access_key
 
         if self.aws_access_key_id is None:
-            assert False, 'Please provide AWS_ACCESS_KEY_ID'
+            assert False, "Please provide AWS_ACCESS_KEY_ID"
 
         if self.aws_secret_access_key is None:
-            assert False, 'Please provide AWS_SECRET_ACCESS_KEY'
+            assert False, "Please provide AWS_SECRET_ACCESS_KEY"
 
         if self.aws_region is None:
-            assert False, 'Please provide AWS_REGION'
+            assert False, "Please provide AWS_REGION"
 
         boto_session = Session(
             aws_access_key_id=self.aws_access_key_id,
             aws_secret_access_key=self.aws_secret_access_key,
             region_name=self.aws_region,
         )
-        self.client = boto_session.client('elastictranscoder')
+        self.client = boto_session.client("elastictranscoder")
 
     def encode(self, input_name, outputs, **kwargs):
         self.message = self.client.create_job(
-            PipelineId=self.pipeline_id,
-            Input=input_name,
-            Outputs=outputs,
-            **kwargs
+            PipelineId=self.pipeline_id, Input=input_name, Outputs=outputs, **kwargs
         )
 
     def create_job_for_object(self, obj):
         content_type = ContentType.objects.get_for_model(obj)
 
         job = EncodeJob()
-        job.id = self.message['Job']['Id']
+        job.id = self.message["Job"]["Id"]
         job.content_type = content_type
         job.object_id = obj.pk
         job.save()
diff --git a/dj_elastictranscoder/views.py b/dj_elastictranscoder/views.py
index 9ff0f51..dfb23ea 100644
--- a/dj_elastictranscoder/views.py
+++ b/dj_elastictranscoder/views.py
@@ -1,67 +1,204 @@
 import json
+from base64 import b64decode
+from urllib.request import urlopen
 
+from django.core.mail import mail_admins
 from django.http import HttpResponse, HttpResponseBadRequest
 from django.views.decorators.csrf import csrf_exempt
-from django.core.mail import mail_admins
+from django.views.decorators.http import require_http_methods
+
+from M2Crypto import X509
+from M2Crypto.Err import M2CryptoError
 
 from .models import EncodeJob
-from .signals import (
-    transcode_onprogress,
-    transcode_onerror,
-    transcode_oncomplete
-)
+from .signals import transcode_oncomplete, transcode_onerror, transcode_onprogress
+
+SNS_MESSAGE_TYPE_SUB_NOTIFICATION = "SubscriptionConfirmation"
+SNS_MESSAGE_TYPE_NOTIFICATION = "Notification"
+SNS_MESSAGE_TYPE_UNSUB_NOTIFICATION = "UnsubscribeConfirmation"
+
+
+def canonical_message_builder(content, format):
+    """ 
+    Builds the canonical message to be verified.
+    Sorts the fields as a requirement from AWS
+    Args:
+        content (dict): Parsed body of the response
+        format (list): List of the fields that need to go into the message
+    Returns (str):
+        canonical message
+    """
+    m = ""
+
+    for field in sorted(format):
+        try:
+            m += field + "\n" + content[field] + "\n"
+        except KeyError:
+            # Build with what you have
+            pass
+
+    return str(m).encode()
+
+
+def verify_sns_notification(request, content):
+    """ 
+    Takes a notification request from Amazon push service SNS and verifies the origin of the notification.
+    Kudos to Artur Rodrigues for suggesting M2Crypto: http://goo.gl/KAgPPc
+    Args:
+        request (HTTPRequest): The request object that is passed to the view function
+    Returns (bool):
+        True if he message passes the verification, False otherwise
+    Raises:
+        ValueError: If the body of the response couldn't be parsed
+        M2CryptoError: If an error raises during the verification process
+        URLError: If the SigningCertURL couldn't be opened
+    """
+    cert = None
+    pubkey = None
+    canonical_message = None
+    canonical_sub_unsub_format = [
+        "Message",
+        "MessageId",
+        "SubscribeURL",
+        "Timestamp",
+        "Token",
+        "TopicArn",
+        "Type",
+    ]
+    canonical_notification_format = [
+        "Message",
+        "MessageId",
+        "Subject",
+        "Timestamp",
+        "TopicArn",
+        "Type",
+    ]
+
+    decoded_signature = b64decode(content["Signature"])
+
+    # Depending on the message type, the canonical message format varies: http://goo.gl/oSrJl8
+    if (
+        request.META.get("HTTP_X_AMZ_SNS_MESSAGE_TYPE", None)
+        == SNS_MESSAGE_TYPE_SUB_NOTIFICATION
+        or request.META.get("HTTP_X_AMZ_SNS_MESSAGE_TYPE", None)
+        == SNS_MESSAGE_TYPE_UNSUB_NOTIFICATION
+    ):
+
+        canonical_message = canonical_message_builder(
+            content, canonical_sub_unsub_format
+        )
+
+    elif (
+        request.META.get("HTTP_X_AMZ_SNS_MESSAGE_TYPE", None)
+        == SNS_MESSAGE_TYPE_NOTIFICATION
+    ):
+
+        canonical_message = canonical_message_builder(
+            content, canonical_notification_format
+        )
+
+    else:
+        raise ValueError(
+            "Message Type (%s) is not recognized"
+            % request.META.get("HTTP_X_AMZ_SNS_MESSAGE_TYPE", None)
+        )
+
+    # Load the certificate and extract the public key
+    cert = X509.load_cert_string(
+        urlopen(content["SigningCertURL"]).read().decode("utf-8")
+    )
+    pubkey = cert.get_pubkey()
+    pubkey.reset_context(md="sha1")
+    pubkey.verify_init()
+
+    # Feed the canonical message to sign it with the public key from the certificate
+    pubkey.verify_update(canonical_message)
+
+    # M2Crypto uses EVP_VerifyFinal() from openssl as the underlying verification function.
+    # http://goo.gl/Bk2G36: "EVP_VerifyFinal() returns 1 for a correct signature, 0 for failure and -1
+    # if some other error occurred."
+    verification_result = pubkey.verify_final(decoded_signature)
+
+    if verification_result == 1:
+        return True
+    elif verification_result == 0:
+        return False
+    else:
+        raise M2CryptoError("Some error occured while verifying the signature.")
+
 
 @csrf_exempt
+@require_http_methods(["POST"])
 def endpoint(request):
     """
     Receive SNS notification
     """
-
+    data = {}
     try:
-        data = json.loads(request.read().decode('utf-8'))
+        data = json.loads(request.body)
     except ValueError:
-        return HttpResponseBadRequest('Invalid JSON')
+        return HttpResponseBadRequest(str(e))
 
+    try:
+        if not verify_sns_notification(request, data):
+            return HttpResponseBadRequest(
+                "Unable to verify authenticity of SNS notification."
+            )
+    except (ValueError, M2CryptoError) as e:
+        return HttpResponseBadRequest(str(e))
 
-    # handle SNS subscription
-    if data['Type'] == 'SubscriptionConfirmation':
-        subscribe_url = data['SubscribeURL']
+    # Handle the SNS subscription
+    if (
+        request.META.get("HTTP_X_AMZ_SNS_MESSAGE_TYPE", None)
+        == SNS_MESSAGE_TYPE_SUB_NOTIFICATION
+    ):
+        subscribe_topic = data["TopicArn"]
+        subscribe_url = data["SubscribeURL"]
         subscribe_body = """
-        Please visit this URL below to confirm your subscription with SNS
+        Automatically confirming subscription to topic: %s
 
-        %s """ % subscribe_url
+        %s """ % (
+            subscribe_topic,
+            subscribe_url,
+        )
 
-        mail_admins('Please confirm SNS subscription', subscribe_body)
-        return HttpResponse('OK')
+        mail_admins("SNS Subscription Automatically Confirmed", subscribe_body)
+        response = urlopen(subscribe_url)
+        return HttpResponse(response.read().decode("utf-8"))
 
-    
-    #
     try:
-        message = json.loads(data['Message'])
+        message = json.loads(data["Message"])
     except ValueError:
-        assert False, data['Message']
+        assert False, data["Message"]
+
+    try:
+        # Turn the dictionary into a readable string format
+        formatted_str_message = json.dumps(message, indent=2)
+    except (TypeError, ValueError):
+        # Couldn't format it, so set it to the raw message
+        formatted_str_message = data["Message"]
 
-    #
-    if message['state'] == 'PROGRESSING':
-        job = EncodeJob.objects.get(pk=message['jobId'])
-        job.message = 'Progress'
+    if message["state"] == "PROGRESSING":
+        job = EncodeJob.objects.get(pk=message["jobId"])
+        job.message = "Progress"
         job.state = 1
         job.save()
 
         transcode_onprogress.send(sender=None, job=job, message=message)
-    elif message['state'] == 'COMPLETED':
-        job = EncodeJob.objects.get(pk=message['jobId'])
-        job.message = 'Success'
+    elif message["state"] == "COMPLETED":
+        job = EncodeJob.objects.get(pk=message["jobId"])
+        job.message = "Success"
         job.state = 4
         job.save()
 
         transcode_oncomplete.send(sender=None, job=job, message=message)
-    elif message['state'] == 'ERROR':
-        job = EncodeJob.objects.get(pk=message['jobId'])
-        job.message = message['messageDetails']
+    elif message["state"] == "ERROR":
+        job = EncodeJob.objects.get(pk=message["jobId"])
+        # add the entire message dictionary for easier debugging
+        job.message = formatted_str_message
         job.state = 2
         job.save()
 
         transcode_onerror.send(sender=None, job=job, message=message)
 
-    return HttpResponse('Done')
+    return HttpResponse("Done")
diff --git a/setup.py b/setup.py
index 13ecc92..bfbc684 100644
--- a/setup.py
+++ b/setup.py
@@ -15,9 +15,8 @@
     include_package_data=True,
     zip_safe=False,
     install_requires=[
-        "django >= 1.3, < 1.9",
+        "django >= 2.0",
         "boto3 >= 1.1",
-        "South >= 0.8",
     ],
     classifiers=[
         "Intended Audience :: Developers",

From 18a92816e1070087f4d30c03631e8dcf185e5495 Mon Sep 17 00:00:00 2001
From: Dan <dan@sortedfood.com>
Date: Thu, 11 Oct 2018 12:12:19 +0100
Subject: [PATCH 3/6] Fix bytes -> json bug

---
 dj_elastictranscoder/views.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/dj_elastictranscoder/views.py b/dj_elastictranscoder/views.py
index dfb23ea..8865e9f 100644
--- a/dj_elastictranscoder/views.py
+++ b/dj_elastictranscoder/views.py
@@ -134,9 +134,16 @@ def endpoint(request):
     Receive SNS notification
     """
     data = {}
+
+    # Request body can be bytes or string
     try:
-        data = json.loads(request.body)
-    except ValueError:
+        request_body = request.body.decode("utf-8")
+    except AttributeError:
+        request_body = request.body
+
+    try:
+        data = json.loads(request_body)
+    except ValueError as e:
         return HttpResponseBadRequest(str(e))
 
     try:

From 93bd09e31c5d4474935da4f3c3b39b9474af306f Mon Sep 17 00:00:00 2001
From: Dan <dan@sortedfood.com>
Date: Thu, 11 Oct 2018 12:12:29 +0100
Subject: [PATCH 4/6] Improve settings control and add convenience method

---
 README.md                          | 184 +++++++++++++++++++++++++++++
 README.rst                         | 138 ----------------------
 dj_elastictranscoder/transcoder.py |  20 +++-
 3 files changed, 201 insertions(+), 141 deletions(-)
 create mode 100644 README.md
 delete mode 100644 README.rst

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b280cf5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,184 @@
+Django Elastic Transcoder
+=========================
+
+`django-elastic-transcoder` is an [Django]{.title-ref} app, let you
+integrate AWS Elastic Transcoder in Django easily.
+
+What is provided in this package?
+
+-   `Transcoder` class
+-   URL endpoint for receive SNS notification
+-   Signals for PROGRESS, ERROR, COMPLETE
+-   `EncodeJob` model
+
+Workflow
+--------
+
+![image]
+
+Install
+-------
+
+First, install `dj_elastictranscode` with `pip`
+
+``` {.sourceCode .sh}
+$ pip install django-elastic-transcoder
+```
+
+Then, add `dj_elastictranscoder` to `INSTALLED_APPS`
+
+``` {.sourceCode .python}
+INSTALLED_APPS = (
+    ...
+    'dj_elastictranscoder',
+    ...
+)
+```
+
+Bind `urls.py`
+
+``` {.sourceCode .python}
+urlpatterns = patterns('',
+    ...
+    url(r'^elastictranscoder/', include('dj_elastictranscoder.urls')),
+    ...
+)
+```
+
+Migrate
+
+``` {.sourceCode .sh}
+$ ./manage.py migrate
+```
+
+Setting up AWS Elastic Transcoder
+---------------------------------
+
+1.  Create a new `Pipeline` in AWS Elastic Transcoder.
+2.  Hookup every available Transcoder Notification to an SNS Topic.
+3.  Subscribe to the SNS Topics using the protocol HTTPS/HTTP.
+4.  Ready to encode and receieve notifications on progress!
+
+Required Django settings
+------------------------
+
+Please settings up variables below to make this app works.
+
+```python
+# Optional: Provide the variable name where we can find the AWS Access Key
+# Defaults to "AWS_ACCESS_KEY"
+SNS_ACCESS_KEY_NAME = <name of the access key settings variable>
+# Optional: Provide the variable name where we can find the AWS Secret Access Key
+# Defaults to "AWS_SECRET_ACCESS_KEY"
+SNS_SECRET_KEY_NAME = <name of the secret key settings variable>
+
+AWS_ACCESS_KEY_ID = <your aws access key id>
+AWS_SECRET_ACCESS_KEY = <your aws secret access key>
+# Defaults to "eu-west-1"
+AWS_REGION = <aws region>
+```
+
+Usage
+-----
+
+For instance, encode an mp3
+
+```python
+from dj_elastictranscoder.transcoder import Transcoder
+
+input = {
+    'Key': 'input/SampleVideo_1280x720_5mb.mp4', 
+    'FrameRate': 'auto',
+    'Resolution': 'auto',
+    'AspectRatio': 'auto',
+    'Interlaced': 'false',
+    'Container': 'auto',
+}
+
+input = {
+    'Key': 'path/to/input.mp3', 
+}
+
+outputs = [{
+    'Key': 'path/to/output.mp3',
+    'PresetId': '1351620000001-300040' # for example: 128k mp3 audio preset
+}]
+
+outputs =[
+    {
+        'Key': 'HLS_2M',
+        'ThumbnailPattern': 'thumb-{count}',
+        'PresetId': '1471250797416-4bhqrz',
+        'SegmentDuration': '10',
+    },
+    {
+        'Key': 'HLS_1M',
+        'ThumbnailPattern': '',
+        'PresetId': '1351620000001-200030',
+        'SegmentDuration': '10',
+    },
+    {
+        'Key': 'HLS_400K',
+        'ThumbnailPattern': '',
+        'PresetId': '1351620000001-200050',
+        'SegmentDuration': '10',
+    },
+]
+
+output_key_prefix = 'output/'
+
+playlists=[
+    {
+        'Name': 'master',
+        'Format': 'HLSv3',
+        'OutputKeys': [
+            'HLS_2M',
+            'HLS_1M',
+            'HLS_400K',
+        ],
+    },
+]
+
+pipeline_id = '<pipeline_id>'
+
+transcoder = Transcoder(pipeline_id)
+
+transcoder.create_job_and_encode(input, outputs, obj, **{"OutputKeyPrefix": output_key_prefix, "Playlists": playlists})
+
+# Or you can start the encoding process and create an EncodeJob manually
+transcoder.encode(input, outputs, **{"OutputKeyPrefix": output_key_prefix, "Playlists": playlists})
+transcoder.create_job_for_object(obj)
+
+
+# Transcoder can also work standalone without Django
+# just pass region and required aws key/secret to Transcoder, when initiate
+
+transcoder = Transcoder(pipeline_id, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
+```
+
+Setting Up an AWS SNS Topic & Subscribing
+-----------------------------------------
+
+AWS Elastic Transcoder can send various SNS notifications to notify your application of transcoding progress, these include `PROGRESS`, `ERROR`, `WARNING` and `COMPLETE`
+
+This package provides an endpoint within your application to receieve these notifications (the notifications are a POST request from SNS). This allows you to receive updates on transcoding progress without having to check yourself. This also provides a mechanism through which you can persist the transcode state of particular objects in your database and receive information about where the transcoded files exist on Amazon S3.
+
+To get started, go to the SNS section in the AWS Console to choose a topic and subscribe with the url below.
+
+`http://<your-domain>/elastictranscoder/endpoint/`
+
+Before notification get started to work, you have to activate an SNS subscription. This is carried out automatically by the endpoint, but you will receive an email with details of the activation.
+
+After subscribing is complete, you can receive SNS notifications from your transcoding pipeline.
+
+Signals
+=======
+
+This package provides various signals for you to get notifications, and do more things with this data. You can check the signals usage in
+tests.py for more usage example.
+
+-   transcode\_onprogress
+-   transcode\_onerror
+-   transcode\_oncomplete
+
+  [image]: https://github.com/StreetVoice/django-elastic-transcoder/blob/master/docs/images/workflow.jpg
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 745a094..0000000
--- a/README.rst
+++ /dev/null
@@ -1,138 +0,0 @@
-Django Elastic Transcoder
-=========================
-
-|Build Status| |Coverage Status|
-
-``django-elastic-transcoder`` is an `Django` app, let you integrate AWS Elastic Transcoder in Django easily.
-
-What is provided in this package?
-
-- ``Transcoder`` class
-- URL endpoint for receive SNS notification
-- Signals for PROGRESS, ERROR, COMPLETE
-- ``EncodeJob`` model
-
-Workflow
------------
-
-.. image:: https://github.com/StreetVoice/django-elastic-transcoder/blob/master/docs/images/workflow.jpg
-
-
-Install
--------
-
-First, install ``dj_elastictranscode`` with ``pip``
-
-.. code:: sh
-
-    $ pip install django-elastic-transcoder
-
-Then, add ``dj_elastictranscoder`` to ``INSTALLED_APPS``
-
-.. code:: python
-
-    INSTALLED_APPS = (
-        ...
-        'dj_elastictranscoder',
-        ...
-    )
-
-Bind ``urls.py``
-
-.. code:: python
-
-    urlpatterns = patterns('',
-        ...
-        url(r'^dj_elastictranscoder/', include('dj_elastictranscoder.urls')),
-        ...
-    )
-
-Migrate
-
-.. code:: sh
-
-    $ ./manage.py migrate
-
-Setting up AWS Elastic Transcoder
----------------------------------
-
-1. Create a new ``Pipeline`` in AWS Elastic Transcoder.
-2. Hookup every Notification.
-3. Subscribe SNS Notification through HTTP
-4. You are ready to encode!
-
-
-Required Django settings
--------------------------
-
-Please settings up variables below to make this app works.
-
-.. code:: python
-
-    AWS_ACCESS_KEY_ID = <your aws access key id>
-    AWS_SECRET_ACCESS_KEY = <your aws secret access key>
-    AWS_REGION = <aws region>
-
-Usage
------
-
-For instance, encode an mp3
-
-.. code:: python
-
-    from dj_elastictranscoder.transcoder import Transcoder
-
-    input = {
-        'Key': 'path/to/input.mp3', 
-    }
-
-    outputs = [{
-        'Key': 'path/to/output.mp3',
-        'PresetId': '1351620000001-300040' # for example: 128k mp3 audio preset
-    }]
-
-    pipeline_id = '<pipeline_id>'
-
-    transcoder = Transcoder(pipeline_id)
-    transcoder.encode(input, outputs)
-
-    # your can also create a EncodeJob for object automatically
-    transcoder.create_job_for_object(obj)
-
-
-    # Transcoder can also work standalone without Django
-    # just pass region and required aws key/secret to Transcoder, when initiate
-
-    transcoder = Transcoder(pipeline_id, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
-
-
-Setting Up AWS SNS endpoint
----------------------------------
-
-AWS Elastic Transcoder can send various SNS notification to notify your application, like ``PROGRESS``, ``ERROR``, ``WARNING`` and ``COMPLETE``
-
-So this package provide a endpoint to receieve these notifications, for you to update transcode progress. without checking by your self.
-
-Go to SNS section in AWS WebConsole to choose topic and subscribe with the url below.
-
-``http://<your-domain>/dj_elastictranscoder/endpoint/``
-
-Before notification get started to work, you have to activate SNS subscription, you will receive email with activation link.
-
-After subscribe is done, you will receive SNS notification.
-
-    
-Signals
------------
-
-This package provide various signals for you to get notification, and do more things in your application. you can check the signals usage in tests.py for more usage example.
-
-* transcode_onprogress
-* transcode_onerror
-* transcode_oncomplete
-
-
-.. |Build Status| image:: https://travis-ci.org/StreetVoice/django-elastic-transcoder.png?branch=master
-   :target: https://travis-ci.org/StreetVoice/django-elastic-transcoder
-.. |Coverage Status| image:: https://coveralls.io/repos/StreetVoice/django-elastic-transcoder/badge.png?branch=master
-   :target: https://coveralls.io/r/StreetVoice/django-elastic-transcoder?branch=master
diff --git a/dj_elastictranscoder/transcoder.py b/dj_elastictranscoder/transcoder.py
index c27d765..c8ababf 100644
--- a/dj_elastictranscoder/transcoder.py
+++ b/dj_elastictranscoder/transcoder.py
@@ -6,6 +6,10 @@
 from .models import EncodeJob
 
 
+SNS_ACCESS_KEY_NAME = getattr(settings, "SNS_ACCESS_KEY_NAME", "AWS_ACCESS_KEY")
+SNS_SECRET_KEY_NAME = getattr(settings, "SNS_SECRET_KEY_NAME", "AWS_SECRET_ACCESS_KEY")
+
+
 class Transcoder(object):
     def __init__(
         self, pipeline_id, region=None, access_key_id=None, secret_access_key=None
@@ -13,15 +17,15 @@ def __init__(
         self.pipeline_id = pipeline_id
 
         if not region:
-            region = getattr(settings, "AWS_REGION", None)
+            region = getattr(settings, "AWS_REGION", "eu-west-1")
         self.aws_region = region
 
         if not access_key_id:
-            access_key_id = getattr(settings, "AWS_ACCESS_KEY_ID", None)
+            access_key_id = getattr(settings, SNS_ACCESS_KEY_NAME, None)
         self.aws_access_key_id = access_key_id
 
         if not secret_access_key:
-            secret_access_key = getattr(settings, "AWS_SECRET_ACCESS_KEY", None)
+            secret_access_key = getattr(settings, SNS_SECRET_KEY_NAME, None)
         self.aws_secret_access_key = secret_access_key
 
         if self.aws_access_key_id is None:
@@ -41,11 +45,15 @@ def __init__(
         self.client = boto_session.client("elastictranscoder")
 
     def encode(self, input_name, outputs, **kwargs):
+        """
+        """
         self.message = self.client.create_job(
             PipelineId=self.pipeline_id, Input=input_name, Outputs=outputs, **kwargs
         )
 
     def create_job_for_object(self, obj):
+        """
+        """
         content_type = ContentType.objects.get_for_model(obj)
 
         job = EncodeJob()
@@ -53,3 +61,9 @@ def create_job_for_object(self, obj):
         job.content_type = content_type
         job.object_id = obj.pk
         job.save()
+
+    def create_job_and_encode(self, input, outputs, obj, **kwargs):
+        """
+        """
+        self.encode(input, outputs, **kwargs)
+        self.create_job_for_object(obj)

From 5234a7267abb80c6c07532c9fae77a0c92a07c01 Mon Sep 17 00:00:00 2001
From: Dan <dan@sortedfood.com>
Date: Fri, 12 Oct 2018 06:49:55 +0100
Subject: [PATCH 5/6] Remove south migrations

---
 .../migrations/0001_initial.py                |  2 +-
 .../south_migrations/0001_initial.py          | 49 -------------------
 .../south_migrations/__init__.py              |  0
 3 files changed, 1 insertion(+), 50 deletions(-)
 delete mode 100644 dj_elastictranscoder/south_migrations/0001_initial.py
 delete mode 100644 dj_elastictranscoder/south_migrations/__init__.py

diff --git a/dj_elastictranscoder/migrations/0001_initial.py b/dj_elastictranscoder/migrations/0001_initial.py
index 0eccc2f..951cf3f 100644
--- a/dj_elastictranscoder/migrations/0001_initial.py
+++ b/dj_elastictranscoder/migrations/0001_initial.py
@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.CharField(max_length=100, serialize=False, primary_key=True)),
                 ('object_id', models.PositiveIntegerField()),
-                ('state', models.PositiveIntegerField(default=0, db_index=True, choices=[(0, b'Submitted'), (1, b'Progressing'), (2, b'Error'), (3, b'Warning'), (4, b'Complete')])),
+                ('state', models.PositiveIntegerField(default=0, db_index=True, choices=[(0, 'Submitted'), (1, 'Progressing'), (2, 'Error'), (3, 'Warning'), (4, 'Complete')])),
                 ('message', models.TextField()),
                 ('created_at', models.DateTimeField(auto_now_add=True)),
                 ('last_modified', models.DateTimeField(auto_now=True)),
diff --git a/dj_elastictranscoder/south_migrations/0001_initial.py b/dj_elastictranscoder/south_migrations/0001_initial.py
deleted file mode 100644
index 849422f..0000000
--- a/dj_elastictranscoder/south_migrations/0001_initial.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
-
-    def forwards(self, orm):
-        # Adding model 'EncodeJob'
-        db.create_table(u'dj_elastictranscoder_encodejob', (
-            ('id', self.gf('django.db.models.fields.CharField')(max_length=100, primary_key=True)),
-            ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
-            ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()),
-            ('state', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
-            ('message', self.gf('django.db.models.fields.TextField')()),
-            ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
-            ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
-        ))
-        db.send_create_signal(u'dj_elastictranscoder', ['EncodeJob'])
-
-
-    def backwards(self, orm):
-        # Deleting model 'EncodeJob'
-        db.delete_table(u'dj_elastictranscoder_encodejob')
-
-
-    models = {
-        u'contenttypes.contenttype': {
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
-        },
-        u'dj_elastictranscoder.encodejob': {
-            'Meta': {'object_name': 'EncodeJob'},
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
-            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'primary_key': 'True'}),
-            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
-            'message': ('django.db.models.fields.TextField', [], {}),
-            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
-            'state': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'})
-        }
-    }
-
-    complete_apps = ['dj_elastictranscoder']
\ No newline at end of file
diff --git a/dj_elastictranscoder/south_migrations/__init__.py b/dj_elastictranscoder/south_migrations/__init__.py
deleted file mode 100644
index e69de29..0000000

From ca818641417263316c8969c3f41ab6f20a5e54c2 Mon Sep 17 00:00:00 2001
From: Dan Williams <dan@workshop.ws>
Date: Mon, 15 Oct 2018 15:33:55 +0100
Subject: [PATCH 6/6] Update setup.py

---
 setup.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/setup.py b/setup.py
index bfbc684..d776bec 100644
--- a/setup.py
+++ b/setup.py
@@ -6,10 +6,10 @@
     name='django-elastic-transcoder',
     version=__version__,
     description="Django with AWS elastic transcoder",
-    long_description=open("README.rst").read(),
-    author='tzangms',
-    author_email='tzangms@streetvoice.com',
-    url='http://github.com/StreetVoice/django-elastic-transcoder',
+    long_description=open("README.md").read(),
+    author='djw27',
+    author_email='dan@workshop.ws',
+    url='http://github.com/workshoptech/django-elastic-transcoder',
     license='MIT',
     packages=find_packages(),
     include_package_data=True,
