From c07bacb73eec4b963ec53c067f23385dad246fb6 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Fri, 3 May 2013 12:28:40 -0700 Subject: [PATCH 01/15] Add classifiers to zencoder-py package --- setup.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0d23c91..647c82d 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,18 @@ url='http://github.com/schworer/zencoder-py', license="MIT License", install_requires=['httplib2'], - packages=['zencoder'] + packages=['zencoder'], + platforms='any', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] ) From cc1c1afda6dc996c74799a976c914829205568da Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Fri, 3 May 2013 12:28:51 -0700 Subject: [PATCH 02/15] Use setuptools instead of distutils --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 647c82d..1bff370 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ - -from distutils.core import setup +try: + from setuptools import setup +except ImportError: + from distutils.core import setup setup(name='zencoder', version='0.5.2', From e65f7b3afe66f38bcc884ecf7196050839b7c240 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Fri, 3 May 2013 16:01:12 -0700 Subject: [PATCH 03/15] add version, author and title attrs to zencoder package --- zencoder/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zencoder/__init__.py b/zencoder/__init__.py index 7d72e80..0772d4e 100644 --- a/zencoder/__init__.py +++ b/zencoder/__init__.py @@ -1,4 +1,9 @@ -from core import Zencoder -from core import ZencoderResponseError +from .core import Zencoder +from .core import ZencoderResponseError + +from .core import Account + +__version__ = '0.5.2' +__title__ = 'zencoder' +__author__ = 'Alex Schworer' -from core import Account From 8999dda3e14deae7dea57838ab04e9648782792e Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Fri, 17 May 2013 17:06:49 -0700 Subject: [PATCH 04/15] Change HTTP library from httplib2 to requests. --- setup.py | 2 +- zencoder/core.py | 123 ++++++++++++++++++----------------------------- 2 files changed, 47 insertions(+), 78 deletions(-) diff --git a/setup.py b/setup.py index 1bff370..28998a8 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ author_email='alex.schworer@gmail.com', url='http://github.com/schworer/zencoder-py', license="MIT License", - install_requires=['httplib2'], + install_requires=['requests>=1.0'], packages=['zencoder'], platforms='any', classifiers=[ diff --git a/zencoder/core.py b/zencoder/core.py index e7703b4..20c36ca 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -1,5 +1,5 @@ import os -import httplib2 +import requests from urllib import urlencode from datetime import datetime @@ -30,37 +30,35 @@ def __init__(self, http_response, content): self.content = content class HTTPBackend(object): - """ - Abstracts out an HTTP backend, but defaults to httplib2. Required arguments - are `base_url` and `api_key`. + """ Abstracts out an HTTP backend. Required argument are `base_url` and + `api_key`. """ + def __init__(self, + base_url, + api_key, + resource_name=None, + timeout=None, + test=False, + version=None): - .. note:: - While `as_xml` is provided as a keyword argument, XML or input or output - is not supported. - """ - def __init__(self, base_url, api_key, as_xml=False, resource_name=None, timeout=None, test=False, version=None): self.base_url = base_url + if resource_name: self.base_url = self.base_url + resource_name - self.http = httplib2.Http(timeout=timeout) - self.as_xml = as_xml + self.http = requests.Session() + + self.as_xml = False self.api_key = api_key self.test = test self.version = version - def content_length(self, body): - """ - Returns the content length as an int for the given `body` data. Used by - PUT and POST requests to set the Content-Length header. - """ - return str(len(body)) if body else "0" + # sets request headers for the entire session + self.http.headers.update(self.headers) @property def headers(self): - """ Returns default headers, by setting the Content-Type and Accepts - headers. - """ + """ Returns default headers, by setting the Content-Type, Accepts, + User-Agent and API Key headers.""" content_type = 'xml' if self.as_xml else 'json' headers = { @@ -85,35 +83,14 @@ def encode(self, data): else: raise NotImplementedError('Encoding as XML is not supported.') - def decode(self, raw_body): - """ - Returns the JSON-encoded `raw_body` and decodes it to a `dict` (using - `json.loads`). - - .. note:: - Decoding as XML is not supported. - """ - if not self.as_xml: - # only parse json when it exists, else just return None - if not raw_body or raw_body == ' ': - return None - else: - return json.loads(raw_body) - else: - raise NotImplementedError('Decoding as XML is not supported.') - def delete(self, url, params=None): """ Executes an HTTP DELETE request for the given URL - params should be a urllib.urlencoded string + params should be a dictionary """ - if params: - url = '?'.join([url, params]) - - response, content = self.http.request(url, method="DELETE", - headers=self.headers) - return self.process(response, content) + response = self.http.delete(url, params=params) + return self.process(response) def get(self, url, data=None): """ @@ -125,66 +102,58 @@ def get(self, url, data=None): params = urlencode(data) url = '?'.join([url, params]) - response, content = self.http.request(url, method="GET", - headers=self.headers) - return self.process(response, content) + response = self.http.get(url, headers=self.headers, params=data) + return self.process(response) def post(self, url, body=None): """ Executes an HTTP POST request for the given URL """ - headers = self.headers - headers['Content-Length'] = self.content_length(body) - response, content = self.http.request(url, method="POST", - body=body, - headers=self.headers) + response = self.http.post(url, data=body, headers=self.headers) - return self.process(response, content) + return self.process(response) def put(self, url, data=None, body=None): """ Executes an HTTP PUT request for the given URL """ - headers = self.headers - headers['Content-Length'] = self.content_length(body) + response = self.http.put(url, params=data, data=body, headers=self.headers) - if data: - params = urlencode(data) - url = '?'.join([url, params]) + return self.process(response) - response, content = self.http.request(url, method="PUT", - body=body, - headers=headers) - - return self.process(response, content) - - def process(self, http_response, content): - """ - Returns HTTP backend agnostic Response data - """ + def process(self, response): + """ Returns HTTP backend agnostic Response data. """ try: - code = http_response.status - body = self.decode(content) - response = Response(code, body, content, http_response) - - return response + code = response.status_code + + # 204 - No Content + if code == 204: + body = None + # add an error message to 402 errors + elif code == 402: + body = { + "message": "Payment Required", + "status": "error" + } + else: + body = response.json() + return Response(code, body, response.content, response) except ValueError: - raise ZencoderResponseError(http_response, content) + raise ZencoderResponseError(response, content) class Zencoder(object): """ This is the entry point to the Zencoder API """ def __init__(self, api_key=None, api_version=None, as_xml=False, timeout=None, test=False): """ - Initializes Zencoder. You must have a valid API_KEY. + Initializes Zencoder. You must have a valid `api_key`. You can pass in the api_key as an argument, or set `ZENCODER_API_KEY` as an environment variable, and it will use - that, if api_key is unspecified. + that, if `api_key` is unspecified. Set api_version='edge' to get the Zencoder development API. (defaults to 'v2') - Set as_xml=True to get back xml data instead of the default json. """ if not api_version: api_version = 'v2' From 1e824cc4f64595e178447d38f7d295720e25ac62 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Fri, 17 May 2013 17:22:31 -0700 Subject: [PATCH 05/15] Remove unneeded test --- test/test_zencoder.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_zencoder.py b/test/test_zencoder.py index 565d306..02c461f 100644 --- a/test/test_zencoder.py +++ b/test/test_zencoder.py @@ -34,12 +34,6 @@ def test_set_api_edge_version(self): zc = Zencoder(api_version='edge') self.assertEquals(zc.base_url, 'https://app.zencoder.com/api/') - def test_zero_content_length(self): - os.environ['ZENCODER_API_KEY'] = 'abcd123' - zc = Zencoder() - content = None - self.assertEquals(zc.job.content_length(content), "0") - if __name__ == "__main__": unittest.main() From 6b2f475eb40129d1246bcf5a53d4d64aff10996d Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sat, 18 May 2013 18:52:01 -0700 Subject: [PATCH 06/15] Remove references to XML --- zencoder/core.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/zencoder/core.py b/zencoder/core.py index 20c36ca..34640bd 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -47,7 +47,6 @@ def __init__(self, self.http = requests.Session() - self.as_xml = False self.api_key = api_key self.test = test self.version = version @@ -59,11 +58,10 @@ def __init__(self, def headers(self): """ Returns default headers, by setting the Content-Type, Accepts, User-Agent and API Key headers.""" - content_type = 'xml' if self.as_xml else 'json' headers = { - 'Content-Type': 'application/{0}'.format(content_type), - 'Accept': 'application/{0}'.format(content_type), + 'Content-Type': 'application/json', + 'Accept': 'application/json', 'Zencoder-Api-Key': self.api_key, 'User-Agent': 'zencoder-py v{0}'.format(LIB_VERSION) } @@ -74,14 +72,8 @@ def encode(self, data): """ Encodes data as JSON (by calling `json.dumps`), so that it can be passed onto the Zencoder API. - - .. note:: - Encoding as XML is not supported. """ - if not self.as_xml: - return json.dumps(data) - else: - raise NotImplementedError('Encoding as XML is not supported.') + return json.dumps(data) def delete(self, url, params=None): """ @@ -145,7 +137,7 @@ def process(self, response): class Zencoder(object): """ This is the entry point to the Zencoder API """ - def __init__(self, api_key=None, api_version=None, as_xml=False, timeout=None, test=False): + def __init__(self, api_key=None, api_version=None, timeout=None, test=False): """ Initializes Zencoder. You must have a valid `api_key`. @@ -171,9 +163,8 @@ def __init__(self, api_key=None, api_version=None, as_xml=False, timeout=None, t self.api_key = api_key self.test = test - self.as_xml = as_xml - args = (self.base_url, self.api_key, self.as_xml) + args = (self.base_url, self.api_key) kwargs = dict(timeout=timeout, test=self.test, version=api_version) self.job = Job(*args, **kwargs) self.account = Account(*args, **kwargs) @@ -183,10 +174,7 @@ def __init__(self, api_key=None, api_version=None, as_xml=False, timeout=None, t self.report = Report(*args, **kwargs) class Response(object): - """ - The Response object stores the details of an API request in an XML/JSON - agnostic way. - """ + """ The Response object stores the details of an API request. """ def __init__(self, code, body, raw_body, raw_response): self.code = code self.body = body @@ -256,13 +244,9 @@ def details(self, output_id): return self.get(self.base_url + '/%s' % str(output_id)) class Job(HTTPBackend): - """ - Contains all API methods relating to transcoding Jobs. - """ + """ Contains all API methods relating to transcoding Jobs. """ def __init__(self, *args, **kwargs): - """ - Initializes a job object - """ + """ Initializes a job object. """ kwargs['resource_name'] = 'jobs' super(Job, self).__init__(*args, **kwargs) From 23d5da230653227e768397aa8707da46982d1171 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sat, 18 May 2013 18:59:41 -0700 Subject: [PATCH 07/15] Test python 3.3 and pypy --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 89e71b1..dffafd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: python python: - "2.6" - "2.7" + - "3.3" + - "pypy" install: pip install -e . # command to run tests -script: python test/test_zencoder.py \ No newline at end of file +script: python test/test_zencoder.py + From 397fcf4d3d50092ac0a47e149e03a330751ac8e0 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sat, 18 May 2013 19:19:58 -0700 Subject: [PATCH 08/15] Remove dependency on `urllib.urlencode` --- zencoder/core.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/zencoder/core.py b/zencoder/core.py index 34640bd..331a6af 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -1,6 +1,5 @@ import os import requests -from urllib import urlencode from datetime import datetime LIB_VERSION = '0.5.2' @@ -90,10 +89,6 @@ def get(self, url, data=None): data should be a dictionary of url parameters """ - if data: - params = urlencode(data) - url = '?'.join([url, params]) - response = self.http.get(url, headers=self.headers, params=data) return self.process(response) From 0d9fdbccbb6b447bf41f991432e8065bcc1afa98 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sat, 18 May 2013 19:20:19 -0700 Subject: [PATCH 09/15] Remove `encode` method and use `json.dumps` directly. --- zencoder/core.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/zencoder/core.py b/zencoder/core.py index 331a6af..35f0730 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -67,13 +67,6 @@ def headers(self): return headers - def encode(self, data): - """ - Encodes data as JSON (by calling `json.dumps`), so that it can be - passed onto the Zencoder API. - """ - return json.dumps(data) - def delete(self, url, params=None): """ Executes an HTTP DELETE request for the given URL @@ -194,7 +187,7 @@ def create(self, email, tos=1, options=None): if options: data.update(options) - return self.post(self.base_url, body=self.encode(data)) + return self.post(self.base_url, body=json.dumps(data)) def details(self): """ @@ -262,7 +255,7 @@ def create(self, input, outputs=None, options=None): if options: data.update(options) - return self.post(self.base_url, body=self.encode(data)) + return self.post(self.base_url, body=json.dumps(data)) def list(self, page=1, per_page=50): """ From e574bbb74103273fd51c802b2e598de18e2f7f49 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sun, 19 May 2013 16:16:06 -0700 Subject: [PATCH 10/15] Pass `response.content` into `ZencoderResponseError` --- zencoder/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zencoder/core.py b/zencoder/core.py index 35f0730..813e756 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -121,7 +121,7 @@ def process(self, response): return Response(code, body, response.content, response) except ValueError: - raise ZencoderResponseError(response, content) + raise ZencoderResponseError(response, response.content) class Zencoder(object): """ This is the entry point to the Zencoder API """ @@ -207,7 +207,6 @@ def live(self): """ Puts your account into live mode. """ - return self.put(self.base_url + '/live') class Output(HTTPBackend): From 0479f96c729846c4a480a46895348b5abe92bf68 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Sun, 19 May 2013 16:44:00 -0700 Subject: [PATCH 11/15] Implement unit tests with `mock` --- .travis.yml | 2 +- setup.py | 2 +- test/fixtures/account_create.json | 4 + test/fixtures/account_details.json | 8 + test/fixtures/job_create.json | 10 + test/fixtures/job_details.json | 69 +++++ test/fixtures/job_list.json | 387 +++++++++++++++++++++++++++++ test/fixtures/job_list_limit.json | 249 +++++++++++++++++++ test/fixtures/job_progress.json | 17 ++ test/fixtures/output_details.json | 20 ++ test/fixtures/output_progress.json | 6 + test/test_accounts.py | 55 ++++ test/test_jobs.py | 73 ++++++ test/test_outputs.py | 31 +++ test/test_util.py | 19 ++ 15 files changed, 950 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/account_create.json create mode 100644 test/fixtures/account_details.json create mode 100644 test/fixtures/job_create.json create mode 100644 test/fixtures/job_details.json create mode 100644 test/fixtures/job_list.json create mode 100644 test/fixtures/job_list_limit.json create mode 100644 test/fixtures/job_progress.json create mode 100644 test/fixtures/output_details.json create mode 100644 test/fixtures/output_progress.json create mode 100644 test/test_accounts.py create mode 100644 test/test_jobs.py create mode 100644 test/test_outputs.py create mode 100644 test/test_util.py diff --git a/.travis.yml b/.travis.yml index dffafd4..f57c1e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,5 @@ python: - "pypy" install: pip install -e . # command to run tests -script: python test/test_zencoder.py +script: nosetests diff --git a/setup.py b/setup.py index 28998a8..1e95e49 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ url='http://github.com/schworer/zencoder-py', license="MIT License", install_requires=['requests>=1.0'], + tests_require=['mock', 'nose'], packages=['zencoder'], platforms='any', classifiers=[ @@ -19,7 +20,6 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Software Development :: Libraries :: Python Modules' diff --git a/test/fixtures/account_create.json b/test/fixtures/account_create.json new file mode 100644 index 0000000..657131f --- /dev/null +++ b/test/fixtures/account_create.json @@ -0,0 +1,4 @@ +{ + "api_key": "abcd1234", + "password": "foo" +} diff --git a/test/fixtures/account_details.json b/test/fixtures/account_details.json new file mode 100644 index 0000000..72d230c --- /dev/null +++ b/test/fixtures/account_details.json @@ -0,0 +1,8 @@ +{ + "account_state": "active", + "plan": "Growth", + "minutes_used": 12549, + "minutes_included": 25000, + "billing_state": "active", + "integration_mode":true +} \ No newline at end of file diff --git a/test/fixtures/job_create.json b/test/fixtures/job_create.json new file mode 100644 index 0000000..db41f7d --- /dev/null +++ b/test/fixtures/job_create.json @@ -0,0 +1,10 @@ + { + "outputs": [ + { + "label": null, + "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/7a9f3b6947c27305079fb105dbfc529e/34356e4d54f0c8fb9c3273203937e795.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=Tp9WVinpXKE%2FPrP2M08r54U4EQ0%3D&Expires=1367817210", + "id": 93461812 + } + ], + "id": 45492475 +} diff --git a/test/fixtures/job_details.json b/test/fixtures/job_details.json new file mode 100644 index 0000000..6b25bd7 --- /dev/null +++ b/test/fixtures/job_details.json @@ -0,0 +1,69 @@ + { + "job": { + "submitted_at": "2013-05-04T21:36:39-07:00", + "state": "finished", + "privacy": false, + "input_media_file": { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 50, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "privacy": false, + "height": 720, + "error_message": null, + "url": "s3://test-bucket/test.mov", + "video_bitrate_in_kbps": 1402, + "md5_checksum": null, + "duration_in_ms": 5067, + "test": false, + "id": 45469002, + "finished_at": "2013-05-04T21:36:46-07:00", + "updated_at": "2013-05-04T21:37:12-07:00", + "created_at": "2013-05-04T21:36:39-07:00", + "total_bitrate_in_kbps": 1452, + "width": 1280, + "error_class": null, + "file_size_bytes": 922620 + }, + "test": false, + "id": 45491013, + "finished_at": "2013-05-04T21:37:12-07:00", + "updated_at": "2013-05-04T21:37:12-07:00", + "created_at": "2013-05-04T21:36:39-07:00", + "thumbnails": [], + "output_media_files": [ + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 90, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "label": null, + "privacy": false, + "height": 720, + "error_message": null, + "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/fc7f7df4f3eacd6fe4ee88cab28732de/dfc2f1b4eb49ea9ab914c84de6d392fb.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=lAc18iXd4ta1Ct0JyazKwYSwdOk%3D&Expires=1367815032", + "video_bitrate_in_kbps": 1440, + "md5_checksum": null, + "duration_in_ms": 5130, + "test": false, + "id": 93457943, + "finished_at": "2013-05-04T21:37:12-07:00", + "updated_at": "2013-05-04T21:37:12-07:00", + "created_at": "2013-05-04T21:36:39-07:00", + "total_bitrate_in_kbps": 1530, + "width": 1280, + "error_class": null, + "file_size_bytes": 973430 + } + ], + "pass_through": null + } +} diff --git a/test/fixtures/job_list.json b/test/fixtures/job_list.json new file mode 100644 index 0000000..47aedf1 --- /dev/null +++ b/test/fixtures/job_list.json @@ -0,0 +1,387 @@ +[ + { + "job": { + "submitted_at": "2013-05-05T01:30:15-05:00", + "state": "finished", + "privacy": false, + "stream": { + "state": "finished", + "height": 720, + "url": "rtmp://live40.us-va.zencoder.io:1935/live", + "duration": 13.3956291675568, + "name": "7177a51b45ccb2b594f890f99fef1fdc", + "test": false, + "id": 22915, + "finished_at": "2013-05-05T01:34:26-05:00", + "updated_at": "2013-05-05T01:35:14-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1024, + "region": "us-virgina", + "width": 1280, + "protocol": "rtmp" + }, + "input_media_file": { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 131, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "privacy": false, + "height": 720, + "error_message": null, + "url": "rtmp://live40.us-va.zencoder.io:1935/live/republish/7177a51b45ccb2b594f890f99fef1fdc", + "video_bitrate_in_kbps": 1228, + "md5_checksum": null, + "duration_in_ms": 11090, + "test": false, + "id": 45472922, + "finished_at": "2013-05-05T01:34:32-05:00", + "updated_at": "2013-05-05T01:34:42-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1359, + "width": 1280, + "error_class": null, + "file_size_bytes": 304313 + }, + "test": false, + "id": 45494934, + "finished_at": "2013-05-05T01:34:42-05:00", + "updated_at": "2013-05-05T01:34:42-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "thumbnails": [], + "output_media_files": [ + { + "video_codec": null, + "frame_rate": null, + "channels": null, + "audio_codec": null, + "audio_bitrate_in_kbps": null, + "state": "finished", + "format": null, + "audio_sample_rate": null, + "label": "hls_master", + "privacy": false, + "height": null, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/master.m3u8", + "video_bitrate_in_kbps": null, + "md5_checksum": null, + "duration_in_ms": null, + "test": false, + "id": 93468543, + "finished_at": "2013-05-05T01:34:35-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": null, + "width": null, + "error_class": null, + "file_size_bytes": 199 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_600", + "privacy": false, + "height": 360, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/600/index.m3u8", + "video_bitrate_in_kbps": 764, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468540, + "finished_at": "2013-05-05T01:34:39-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 892, + "width": 640, + "error_class": null, + "file_size_bytes": 1217265 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_300", + "privacy": false, + "height": 270, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/300/index.m3u8", + "video_bitrate_in_kbps": 400, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468539, + "finished_at": "2013-05-05T01:34:40-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 528, + "width": 480, + "error_class": null, + "file_size_bytes": 708537 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_1200", + "privacy": false, + "height": 720, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/1200/index.m3u8", + "video_bitrate_in_kbps": 1484, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468542, + "finished_at": "2013-05-05T01:34:40-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1612, + "width": 1280, + "error_class": null, + "file_size_bytes": 2223629 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_300", + "privacy": false, + "height": 270, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/15ec8791b4d951a6053de2799170ec93_77507_300@107413", + "video_bitrate_in_kbps": 343, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468534, + "finished_at": "2013-05-05T01:34:41-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 467, + "width": 480, + "error_class": null, + "file_size_bytes": 667832 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_600", + "privacy": false, + "height": 360, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/cd5766181ed19b81195db56092b4e500_77507_600@107415", + "video_bitrate_in_kbps": 690, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468535, + "finished_at": "2013-05-05T01:34:41-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 814, + "width": 640, + "error_class": null, + "file_size_bytes": 1152631 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_1200", + "privacy": false, + "height": 720, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/a83a306ea10a266e027c3186bff701b3_77507_1200@107417", + "video_bitrate_in_kbps": 1352, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468537, + "finished_at": "2013-05-05T01:34:42-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1476, + "width": 1280, + "error_class": null, + "file_size_bytes": 2076855 + } + ], + "pass_through": null + } + }, + { + "job": { + "submitted_at": "2013-05-05T01:19:53-05:00", + "state": "finished", + "privacy": false, + "input_media_file": { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 50, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "privacy": false, + "height": 720, + "error_message": null, + "url": "http://s3.amazonaws.com/zencodertesting/test.mov", + "video_bitrate_in_kbps": 1402, + "md5_checksum": null, + "duration_in_ms": 5067, + "test": false, + "id": 45472534, + "finished_at": "2013-05-05T01:20:02-05:00", + "updated_at": "2013-05-05T01:21:14-05:00", + "created_at": "2013-05-05T01:19:54-05:00", + "total_bitrate_in_kbps": 1452, + "width": 1280, + "error_class": null, + "file_size_bytes": 922620 + }, + "test": false, + "id": 45494545, + "finished_at": "2013-05-05T01:21:14-05:00", + "updated_at": "2013-05-05T01:21:14-05:00", + "created_at": "2013-05-05T01:19:53-05:00", + "thumbnails": [], + "output_media_files": [ + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 90, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "label": null, + "privacy": false, + "height": 720, + "error_message": null, + "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/b22ad0c6353f86333126866d43cc898f/4f097ddbcee6c587cc640a6e99af2594.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=QL0oZnKKivzEXlvSX3ealXDTI%2Bc%3D&Expires=1367821273", + "video_bitrate_in_kbps": 1440, + "md5_checksum": null, + "duration_in_ms": 5130, + "test": false, + "id": 93467532, + "finished_at": "2013-05-05T01:21:13-05:00", + "updated_at": "2013-05-05T01:21:14-05:00", + "created_at": "2013-05-05T01:19:53-05:00", + "total_bitrate_in_kbps": 1530, + "width": 1280, + "error_class": null, + "file_size_bytes": 973430 + } + ], + "pass_through": null + } + }, + { + "job": { + "submitted_at": "2013-05-05T01:21:21-05:00", + "state": "finished", + "privacy": false, + "input_media_file": { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 50, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "privacy": false, + "height": 720, + "error_message": null, + "url": "http://s3.amazonaws.com/zencodertesting/test.mov", + "video_bitrate_in_kbps": 1402, + "md5_checksum": null, + "duration_in_ms": 5067, + "test": false, + "id": 45472497, + "finished_at": "2013-05-05T01:21:28-05:00", + "updated_at": "2013-05-05T01:22:29-05:00", + "created_at": "2013-05-05T01:18:42-05:00", + "total_bitrate_in_kbps": 1452, + "width": 1280, + "error_class": null, + "file_size_bytes": 922620 + }, + "test": false, + "id": 45494508, + "finished_at": "2013-05-05T01:22:29-05:00", + "updated_at": "2013-05-05T01:22:29-05:00", + "created_at": "2013-05-05T01:18:42-05:00", + "thumbnails": [], + "output_media_files": [ + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 90, + "state": "finished", + "format": "mpeg4", + "audio_sample_rate": 44100, + "label": null, + "privacy": false, + "height": 720, + "error_message": null, + "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/0ae6d5cbb960964a4714944cbc3e8bd9/7e082e00c717e2e6e32923500d3f43da.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=PAAACDb22AiJOkxaq4h4pOIZWaQ%3D&Expires=1367821349", + "video_bitrate_in_kbps": 1440, + "md5_checksum": null, + "duration_in_ms": 5130, + "test": false, + "id": 93467424, + "finished_at": "2013-05-05T01:22:29-05:00", + "updated_at": "2013-05-05T01:22:29-05:00", + "created_at": "2013-05-05T01:18:42-05:00", + "total_bitrate_in_kbps": 1530, + "wi£dth": 1280, + "error_class": null, + "file_size_bytes": 973430 + } + ], + "pass_through": null + } + } +] \ No newline at end of file diff --git a/test/fixtures/job_list_limit.json b/test/fixtures/job_list_limit.json new file mode 100644 index 0000000..b51652b --- /dev/null +++ b/test/fixtures/job_list_limit.json @@ -0,0 +1,249 @@ +[ + { + "job": { + "submitted_at": "2013-05-05T01:30:15-05:00", + "state": "finished", + "privacy": false, + "stream": { + "state": "finished", + "height": 720, + "url": "rtmp://live40.us-va.zencoder.io:1935/live", + "duration": 13.3956291675568, + "name": "7177a51b45ccb2b594f890f99fef1fdc", + "test": false, + "id": 22915, + "finished_at": "2013-05-05T01:34:26-05:00", + "updated_at": "2013-05-05T01:35:14-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1024, + "region": "us-virgina", + "width": 1280, + "protocol": "rtmp" + }, + "input_media_file": { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 131, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "privacy": false, + "height": 720, + "error_message": null, + "url": "rtmp://live40.us-va.zencoder.io:1935/live/republish/7177a51b45ccb2b594f890f99fef1fdc", + "video_bitrate_in_kbps": 1228, + "md5_checksum": null, + "duration_in_ms": 11090, + "test": false, + "id": 45472922, + "finished_at": "2013-05-05T01:34:32-05:00", + "updated_at": "2013-05-05T01:34:42-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1359, + "width": 1280, + "error_class": null, + "file_size_bytes": 304313 + }, + "test": false, + "id": 45494934, + "finished_at": "2013-05-05T01:34:42-05:00", + "updated_at": "2013-05-05T01:34:42-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "thumbnails": [], + "output_media_files": [ + { + "video_codec": null, + "frame_rate": null, + "channels": null, + "audio_codec": null, + "audio_bitrate_in_kbps": null, + "state": "finished", + "format": null, + "audio_sample_rate": null, + "label": "hls_master", + "privacy": false, + "height": null, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/master.m3u8", + "video_bitrate_in_kbps": null, + "md5_checksum": null, + "duration_in_ms": null, + "test": false, + "id": 93468543, + "finished_at": "2013-05-05T01:34:35-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": null, + "width": null, + "error_class": null, + "file_size_bytes": 199 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_600", + "privacy": false, + "height": 360, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/600/index.m3u8", + "video_bitrate_in_kbps": 764, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468540, + "finished_at": "2013-05-05T01:34:39-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 892, + "width": 640, + "error_class": null, + "file_size_bytes": 1217265 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_300", + "privacy": false, + "height": 270, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/300/index.m3u8", + "video_bitrate_in_kbps": 400, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468539, + "finished_at": "2013-05-05T01:34:40-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 528, + "width": 480, + "error_class": null, + "file_size_bytes": 708537 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 128, + "state": "finished", + "format": "mpeg-ts", + "audio_sample_rate": 44100, + "label": "hls_1200", + "privacy": false, + "height": 720, + "error_message": null, + "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/1200/index.m3u8", + "video_bitrate_in_kbps": 1484, + "md5_checksum": null, + "duration_in_ms": 11100, + "test": false, + "id": 93468542, + "finished_at": "2013-05-05T01:34:40-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1612, + "width": 1280, + "error_class": null, + "file_size_bytes": 2223629 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_300", + "privacy": false, + "height": 270, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/15ec8791b4d951a6053de2799170ec93_77507_300@107413", + "video_bitrate_in_kbps": 343, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468534, + "finished_at": "2013-05-05T01:34:41-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 467, + "width": 480, + "error_class": null, + "file_size_bytes": 667832 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_600", + "privacy": false, + "height": 360, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/cd5766181ed19b81195db56092b4e500_77507_600@107415", + "video_bitrate_in_kbps": 690, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468535, + "finished_at": "2013-05-05T01:34:41-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 814, + "width": 640, + "error_class": null, + "file_size_bytes": 1152631 + }, + { + "video_codec": "h264", + "frame_rate": 30, + "channels": "2", + "audio_codec": "aac", + "audio_bitrate_in_kbps": 124, + "state": "finished", + "format": "flash video", + "audio_sample_rate": 44100, + "label": "rtmp_1200", + "privacy": false, + "height": 720, + "error_message": null, + "url": "rtmp://rtmp.live.zencdn.net/live/a83a306ea10a266e027c3186bff701b3_77507_1200@107417", + "video_bitrate_in_kbps": 1352, + "md5_checksum": null, + "duration_in_ms": 11160, + "test": false, + "id": 93468537, + "finished_at": "2013-05-05T01:34:42-05:00", + "updated_at": "2013-05-05T01:34:43-05:00", + "created_at": "2013-05-05T01:30:15-05:00", + "total_bitrate_in_kbps": 1476, + "width": 1280, + "error_class": null, + "file_size_bytes": 2076855 + } + ], + "pass_through": null + } + } +] \ No newline at end of file diff --git a/test/fixtures/job_progress.json b/test/fixtures/job_progress.json new file mode 100644 index 0000000..0dfdc90 --- /dev/null +++ b/test/fixtures/job_progress.json @@ -0,0 +1,17 @@ +{ + "state": "processing", + "input": { + "state": "finished", + "id": 45474984 + }, + "progress": 40.5, + "outputs": [ + { + "state": "processing", + "id": 93474209, + "current_event_progress": 0, + "progress": 15, + "current_event": "Transcoding" + } + ] +} diff --git a/test/fixtures/output_details.json b/test/fixtures/output_details.json new file mode 100644 index 0000000..239afa3 --- /dev/null +++ b/test/fixtures/output_details.json @@ -0,0 +1,20 @@ +{ + "audio_bitrate_in_kbps": 74, + "audio_codec": "aac", + "audio_sample_rate": 48000, + "channels": "2", + "duration_in_ms": 24892, + "file_size_in_bytes": 1215110, + "format": "mpeg4", + "frame_rate": 29.97, + "height": 352, + "id": 13339, + "label": null, + "state": "finished", + "total_bitrate_in_kbps": 387, + "url": "https://example.com/file.mp4", + "video_bitrate_in_kbps": 313, + "video_codec": "h264", + "width": 624, + "md5_checksum": "7f106918e02a69466afa0ee014174143" +} \ No newline at end of file diff --git a/test/fixtures/output_progress.json b/test/fixtures/output_progress.json new file mode 100644 index 0000000..54da2f6 --- /dev/null +++ b/test/fixtures/output_progress.json @@ -0,0 +1,6 @@ +{ + "state": "processing", + "current_event": "Transcoding", + "current_event_progress": 45.32525, + "progress": 32.34567345 +} \ No newline at end of file diff --git a/test/test_accounts.py b/test/test_accounts.py new file mode 100644 index 0000000..36debf8 --- /dev/null +++ b/test/test_accounts.py @@ -0,0 +1,55 @@ +import unittest +from mock import patch + +from test_util import TEST_API_KEY, load_response +from zencoder import Zencoder + +class TestAccounts(unittest.TestCase): + def setUp(self): + self.zen = Zencoder(api_key=TEST_API_KEY) + + @patch("requests.Session.post") + def test_account_create(self, post): + post.return_value = load_response(201, 'fixtures/account_create.json') + + response = self.zen.account.create('test@example.com', tos=1) + + self.assertEquals(response.code, 201) + self.assertEquals(response.body['password'], 'foo') + self.assertEquals(response.body['api_key'], 'abcd1234') + + @patch("requests.Session.get") + def test_account_details(self, get): + get.return_value = load_response(200, 'fixtures/account_details.json') + resp = self.zen.account.details() + + self.assertEquals(resp.code, 200) + self.assertEquals(resp.body['account_state'], 'active') + self.assertEquals(resp.body['minutes_used'], 12549) + + @patch("requests.Session.put") + def test_account_integration(self, put): + put.return_value = load_response(204) + + resp = self.zen.account.integration() + + self.assertEquals(resp.code, 204) + self.assertEquals(resp.body, None) + + @patch("requests.Session.put") + def test_account_live_unauthorized(self, put): + put.return_value = load_response(402) + + resp = self.zen.account.live() + self.assertEquals(resp.code, 402) + + @patch("requests.Session.put") + def test_account_live_authorized(self, put): + put.return_value = load_response(204) + + resp = self.zen.account.live() + self.assertEquals(resp.code, 204) + +if __name__ == "__main__": + unittest.main() + diff --git a/test/test_jobs.py b/test/test_jobs.py new file mode 100644 index 0000000..3333549 --- /dev/null +++ b/test/test_jobs.py @@ -0,0 +1,73 @@ +import unittest +from mock import patch + +from test_util import TEST_API_KEY, load_response +from zencoder import Zencoder + +class TestJobs(unittest.TestCase): + + def setUp(self): + self.zen = Zencoder(api_key=TEST_API_KEY) + + @patch("requests.Session.post") + def test_job_create(self, post): + post.return_value = load_response(201, 'fixtures/job_create.json') + + resp = self.zen.job.create('s3://zencodertesting/test.mov') + + self.assertEquals(resp.code, 201) + self.assertGreater(resp.body['id'], 0) + self.assertEquals(len(resp.body['outputs']), 1) + + @patch("requests.Session.get") + def test_job_details(self, get): + get.return_value = load_response(200, 'fixtures/job_details.json') + + resp = self.zen.job.details(1234) + self.assertEquals(resp.code, 200) + self.assertGreater(resp.body['job']['id'], 0) + self.assertEquals(len(resp.body['job']['output_media_files']), 1) + + @patch("requests.Session.get") + def test_job_progress(self, get): + get.return_value = load_response(200, 'fixtures/job_progress.json') + + resp = self.zen.job.progress(12345) + self.assertEquals(resp.code, 200) + self.assertEquals(resp.body['state'], 'processing') + + @patch("requests.Session.put") + def test_job_cancel(self, put): + put.return_value = load_response(204) + + resp = self.zen.job.cancel(5555) + self.assertEquals(resp.code, 204) + self.assertEquals(resp.body, None) + + @patch("requests.Session.put") + def test_job_resubmit(self, put): + put.return_value = load_response(204) + + resp = self.zen.job.resubmit(5555) + self.assertEquals(resp.code, 204) + self.assertEquals(resp.body, None) + + @patch("requests.Session.get") + def test_job_list(self, get): + get.return_value = load_response(200, 'fixtures/job_list.json') + + resp = self.zen.job.list() + self.assertEquals(resp.code, 200) + self.assertEquals(len(resp.body), 3) + + @patch("requests.Session.get") + def test_job_list_limit(self, get): + get.return_value = load_response(200, 'fixtures/job_list_limit.json') + + resp = self.zen.job.list(per_page=1) + self.assertEquals(resp.code, 200) + self.assertEquals(len(resp.body), 1) + +if __name__ == "__main__": + unittest.main() + diff --git a/test/test_outputs.py b/test/test_outputs.py new file mode 100644 index 0000000..509b7e5 --- /dev/null +++ b/test/test_outputs.py @@ -0,0 +1,31 @@ +import unittest +from zencoder import Zencoder + +from mock import patch + +from test_util import TEST_API_KEY, load_response +from zencoder import Zencoder + +class TestOutputs(unittest.TestCase): + def setUp(self): + self.zen = Zencoder(api_key=TEST_API_KEY) + + @patch("requests.Session.get") + def test_output_details(self, get): + get.return_value = load_response(200, 'fixtures/output_details.json') + + resp = self.zen.output.details(22222) + self.assertEquals(resp.code, 200) + self.assertGreater(resp.body['id'], 0) + + @patch("requests.Session.get") + def test_output_progress(self, get): + get.return_value = load_response(200, 'fixtures/output_progress.json') + + resp = self.zen.output.progress(123456) + self.assertEquals(resp.code, 200) + self.assertEquals(resp.body['state'], 'processing') + +if __name__ == "__main__": + unittest.main() + diff --git a/test/test_util.py b/test/test_util.py new file mode 100644 index 0000000..f985ca9 --- /dev/null +++ b/test/test_util.py @@ -0,0 +1,19 @@ +from collections import namedtuple +import json +import os + +TEST_API_KEY = 'abcd123' + +MockResponse = namedtuple("Response", "status_code, json, content") + +CUR_DIR = os.path.split(__file__)[0] + +def load_response(code, fixture=None): + if fixture: + with open(os.path.join(CUR_DIR, fixture), 'r') as f: + content = f.read() + else: + content = None + + return MockResponse(code, lambda: json.loads(content), content) + From fb1d90f6f86af77afccfd7e172e4ef3e8ff5de83 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Mon, 20 May 2013 15:24:36 -0700 Subject: [PATCH 12/15] Use assertTrue instead of assertGreater in tests. The `unittest` module in py2.6 does not have assertGreater. --- test/test_jobs.py | 4 ++-- test/test_outputs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_jobs.py b/test/test_jobs.py index 3333549..6bb317b 100644 --- a/test/test_jobs.py +++ b/test/test_jobs.py @@ -16,7 +16,7 @@ def test_job_create(self, post): resp = self.zen.job.create('s3://zencodertesting/test.mov') self.assertEquals(resp.code, 201) - self.assertGreater(resp.body['id'], 0) + self.assertTrue(resp.body['id'] > 0) self.assertEquals(len(resp.body['outputs']), 1) @patch("requests.Session.get") @@ -25,7 +25,7 @@ def test_job_details(self, get): resp = self.zen.job.details(1234) self.assertEquals(resp.code, 200) - self.assertGreater(resp.body['job']['id'], 0) + self.assertTrue(resp.body['job']['id'] > 0) self.assertEquals(len(resp.body['job']['output_media_files']), 1) @patch("requests.Session.get") diff --git a/test/test_outputs.py b/test/test_outputs.py index 509b7e5..e0628e3 100644 --- a/test/test_outputs.py +++ b/test/test_outputs.py @@ -16,7 +16,7 @@ def test_output_details(self, get): resp = self.zen.output.details(22222) self.assertEquals(resp.code, 200) - self.assertGreater(resp.body['id'], 0) + self.assertTrue(resp.body['id'] > 0) @patch("requests.Session.get") def test_output_progress(self, get): From 754b3b4163f5db9bc370286767f0c8b0c410ec67 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Mon, 20 May 2013 15:39:21 -0700 Subject: [PATCH 13/15] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 053f68f..b714ca1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # Zencoder +[![Build Status](https://secure.travis-ci.org/schworer/zencoder-py.png)](http://travis-ci.org/schworer/zencoder-py) A Python module for the [Zencoder](http://zencoder.com) API. ## Installation Install from PyPI using `easy_install` or `pip`. + pip install zencoder + ## Dependencies -`zencoder-py` depends on [httplib2](http://code.google.com/p/httplib2/), and uses the `json` or `simplejson` module. +`zencoder-py` depends on [requests](http://python-requests.org), and uses the `json` or `simplejson` module. ## Usage @@ -60,5 +63,4 @@ Docs are in progress, and hosted at Read the Docs: http://zencoder.rtfd.org * [Josh Kennedy](http://github.com/kennedyj) * [Issac Kelly](http://github.com/issackelly) -[![Build Status](https://secure.travis-ci.org/schworer/zencoder-py.png)](http://travis-ci.org/schworer/zencoder-py) From a6479eb8e31b2e0e14d3042a301f92145364d3a6 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Mon, 20 May 2013 17:55:31 -0700 Subject: [PATCH 14/15] Add test for `Job.finish` --- test/test_jobs.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_jobs.py b/test/test_jobs.py index 6bb317b..28386cd 100644 --- a/test/test_jobs.py +++ b/test/test_jobs.py @@ -68,6 +68,13 @@ def test_job_list_limit(self, get): self.assertEquals(resp.code, 200) self.assertEquals(len(resp.body), 1) + @patch("requests.Session.put") + def test_job_finish(self, put): + put.return_value = load_response(204) + + resp = self.zen.job.finish(99999) + self.assertEquals(resp.code, 204) + if __name__ == "__main__": unittest.main() From 05d0d18ba4c0def34a2d1d01afbab7779bddeae3 Mon Sep 17 00:00:00 2001 From: Alex Schworer Date: Tue, 21 May 2013 13:37:21 -0700 Subject: [PATCH 15/15] Add `live_stream` kwarg to Job.create --- test/fixtures/job_create_live.json | 12 ++++++++++++ test/test_jobs.py | 10 ++++++++++ zencoder/core.py | 5 ++++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/job_create_live.json diff --git a/test/fixtures/job_create_live.json b/test/fixtures/job_create_live.json new file mode 100644 index 0000000..56a57e0 --- /dev/null +++ b/test/fixtures/job_create_live.json @@ -0,0 +1,12 @@ +{ + "stream_url": "rtmp://foo:1935/live", + "stream_name": "bar", + "outputs": [ + { + "label": null, + "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com", + "id": 97931084 + } + ], + "id": 47010105 +} diff --git a/test/test_jobs.py b/test/test_jobs.py index 28386cd..f32d57c 100644 --- a/test/test_jobs.py +++ b/test/test_jobs.py @@ -19,6 +19,16 @@ def test_job_create(self, post): self.assertTrue(resp.body['id'] > 0) self.assertEquals(len(resp.body['outputs']), 1) + @patch("requests.Session.post") + def test_job_create_list(self, post): + post.return_value = load_response(201, 'fixtures/job_create_live.json') + + resp = self.zen.job.create(live_stream=True) + + self.assertEquals(resp.code, 201) + self.assertTrue(resp.body['id'] > 0) + self.assertEquals(len(resp.body['outputs']), 1) + @patch("requests.Session.get") def test_job_details(self, get): get.return_value = load_response(200, 'fixtures/job_details.json') diff --git a/zencoder/core.py b/zencoder/core.py index 0c17cb8..e34f158 100644 --- a/zencoder/core.py +++ b/zencoder/core.py @@ -242,7 +242,7 @@ def create(self, input=None, live_stream=False, outputs=None, options=None): Creates a transcoding job. @param input: the input url as string - @param live_stream: starts an RTMP Live Stream + @param live_stream: starts a Live Stream job (input must be None) @param outputs: a list of output dictionaries @param options: a dictionary of job options """ @@ -253,6 +253,9 @@ def create(self, input=None, live_stream=False, outputs=None, options=None): if options: data.update(options) + if live_stream: + data['live_stream'] = live_stream + return self.post(self.base_url, body=json.dumps(data)) def list(self, page=1, per_page=50):