├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── usage.rst └── zencoder.rst ├── setup.py ├── test ├── fixtures │ ├── account_create.json │ ├── account_details.json │ ├── input_details.json │ ├── input_progress.json │ ├── job_create.json │ ├── job_create_live.json │ ├── job_details.json │ ├── job_list.json │ ├── job_list_limit.json │ ├── job_progress.json │ ├── output_details.json │ ├── output_progress.json │ ├── report_all.json │ ├── report_all_date.json │ ├── report_live.json │ └── report_vod.json ├── test_accounts.py ├── test_inputs.py ├── test_jobs.py ├── test_outputs.py ├── test_reports.py ├── test_util.py └── test_zencoder.py └── zencoder ├── __init__.py └── core.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | MANIFEST 4 | dist/ 5 | env/ 6 | build/ 7 | venv/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "pypy" 8 | install: pip install -e . 9 | # command to run tests 10 | script: nosetests 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Alex Schworer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Zencoder 3 | -------- 4 | 5 | [![Build Status](https://travis-ci.org/zencoder/zencoder-py.svg?branch=master)](https://travis-ci.org/zencoder/zencoder-py) 6 | 7 | A Python module for interacting with the [Zencoder](http://zencoder.com) API. 8 | 9 | ### Getting Started 10 | 11 | Install from PyPI 12 | 13 | $ pip install zencoder 14 | 15 | Import zencoder 16 | 17 | ```python 18 | from zencoder import Zencoder 19 | ``` 20 | 21 | Create an instance of the Zencoder client. This will accept an API key and version. If not API key is set, it will look for a `ZENCODER_API_KEY` environment variable. API version defaults to 'v2'. 22 | 23 | ```python 24 | # If you want to specify an API key when creating a client 25 | client = Zencoder('API_KEY') 26 | 27 | # If you have the environment variable set 28 | client = Zencoder() 29 | ``` 30 | 31 | ## [Jobs](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs) 32 | 33 | Create a [new job](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-Create_a_Job). 34 | 35 | ```python 36 | client.job.create('s3://bucket/key.mp4') 37 | client.job.create('s3://bucket/key.mp4', 38 | outputs=[{'label': 'vp8 for the web', 39 | 'url': 's3://bucket/key_output.webm'}]) 40 | ``` 41 | 42 | This returns a `zencoder.Response` object. The body includes a Job ID, and one or more Output IDs (one for every output file created). 43 | 44 | ```python 45 | response = client.job.create('s3://bucket/key.mp4') 46 | response.code # 201 47 | response.body['id'] # 12345 48 | ``` 49 | 50 | [List jobs](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-List_Jobs). 51 | 52 | By default the jobs listing is paginated with 50 jobs per page and sorted by ID in descending order. You can pass two parameters to control the paging: `page` and `per_page`. 53 | 54 | ```python 55 | client.job.list(per_page=10) 56 | client.job.list(per_page=10, page=2) 57 | ``` 58 | 59 | Get [details](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-Get_Job_Details) about a job. 60 | 61 | The number passed to `details` is the ID of a Zencoder job. 62 | 63 | ```python 64 | client.job.details(1) 65 | ``` 66 | 67 | Get [progress](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-Job_Progress) on a job. 68 | 69 | The number passed to `progress` is the ID of a Zencoder job. 70 | 71 | ```python 72 | client.job.progress(1) 73 | ``` 74 | 75 | [Resubmit](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-Resubmit_a_Job) a job 76 | 77 | The number passed to `resubmit` is the ID of a Zencoder job. 78 | 79 | ```python 80 | client.job.resubmit(1) 81 | ``` 82 | 83 | [Cancel](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Jobs-Cancel_a_Job) a job 84 | 85 | The number passed to `cancel` is the ID of a Zencoder job. 86 | 87 | ```python 88 | client.job.cancel(1) 89 | ``` 90 | 91 | ## [Inputs](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Inputs) 92 | 93 | Get [details](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Inputs-Get_Input_Details) about an input. 94 | 95 | The number passed to `details` is the ID of a Zencoder input. 96 | 97 | ```python 98 | client.input.details(1) 99 | ``` 100 | 101 | Get [progress](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Inputs-Update_Input_Progress) for an input. 102 | 103 | The number passed to `progress` is the ID of a Zencoder input. 104 | 105 | ```python 106 | client.input.progress(1) 107 | ``` 108 | ## [Outputs](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Outputs) 109 | 110 | Get [details](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Outputs-Get_Output_Details) about an output. 111 | 112 | The number passed to `details` is the ID of a Zencoder output. 113 | 114 | ```python 115 | client.output.details(1) 116 | ``` 117 | 118 | Get [progress](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Outputs-Update_Output_Progress) for an output. 119 | 120 | The number passed to `progress` is the ID of a Zencoder output. 121 | 122 | ```python 123 | client.output.progress(1) 124 | ``` 125 | 126 | ## [Reports](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Reports) 127 | 128 | Reports are great for getting usage data for your account. All default to 30 days from yesterday with no [grouping](https://support.brightcove.com/zencoder-encoding-settings-job#grouping), but this can be altered. These will return `422 Unprocessable Entity` if the date format is incorrect or the range is greater than 2 months. 129 | 130 | Get [all usage](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Reports-Get_Usage_for_VOD___Live) (Live + VOD). 131 | 132 | ```python 133 | import datetime 134 | client.report.all() 135 | client.report.all(grouping="foo") 136 | client.report.all(start_date=datetime.date(2011, 10, 30), 137 | end_date=datetime.date(2011, 11, 24)) 138 | client.report.all(start_date=datetime.date(2011, 10, 30), 139 | end_date=datetime.date(2011, 11, 24), 140 | grouping="foo") 141 | ``` 142 | 143 | Get [VOD usage](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Reports-Get_Usage_for_VOD). 144 | 145 | ```python 146 | import datetime 147 | client.report.vod() 148 | client.report.vod(grouping="foo") 149 | client.report.vod(start_date=datetime.date(2011, 10, 30), 150 | end_date=datetime.date(2011, 11, 24)) 151 | client.report.vod(start_date=datetime.date(2011, 10, 30), 152 | end_date=datetime.date(2011, 11, 24), 153 | grouping="foo") 154 | ``` 155 | 156 | Get [Live usage](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Reports-Get_Usage_for_Live). 157 | 158 | ```python 159 | import datetime 160 | client.report.live() 161 | client.report.live(grouping="foo") 162 | client.report.live(start_date=datetime.date(2011, 10, 30), 163 | end_date=datetime.date(2011, 11, 24)) 164 | client.report.live(start_date=datetime.date(2011, 10, 30), 165 | end_date=datetime.date(2011, 11, 24), 166 | grouping="foo") 167 | ``` 168 | 169 | ## [Accounts](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Accounts) 170 | 171 | Create a [new account](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Accounts-Create_an_Account). A unique email address and terms of service are required, but you can also specify a password (and confirmation) along with whether or not you want to subscribe to the Zencoder newsletter. New accounts will be created under the Test (Free) plan. 172 | 173 | No API Key is required. 174 | 175 | ```python 176 | client.account.create('foo@example.com', tos=1) 177 | client.account.create('foo@example.com', tos=1, 178 | options={'password': 'abcd1234', 179 | 'password_confirmation': 'abcd1234', 180 | 'affiliate_code': 'foo'}) 181 | ``` 182 | 183 | Get [details](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Accounts-Get_Account_Details) about the current account. 184 | 185 | ```python 186 | client.account.details() 187 | ``` 188 | 189 | Turn [integration mode](https://brightcovelearning.github.io/Brightcove-API-References/zencoder-api/v2/doc/index.html#api-Accounts-Turn_On_Integration_Mode) on (all jobs are test jobs). 190 | 191 | ```python 192 | client.account.integration() 193 | ``` 194 | 195 | Turn off integration mode, which means your account is live (and you'll be billed for jobs). 196 | 197 | ```python 198 | client.account.live() 199 | ``` 200 | 201 | ## Tests 202 | 203 | The tests use the `mock` library to stub in response data from the API. Run tests individually: 204 | 205 | $ python test/test_jobs.py 206 | 207 | Or use `nose`: 208 | 209 | $ nosetests 210 | 211 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/zencoder-py.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zencoder-py.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/zencoder-py" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/zencoder-py" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # zencoder-py documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jul 18 21:05:59 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'zencoder-py' 44 | copyright = u'2012, Alex Schworer' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.6' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.6.5' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'zencoder-pydoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'zencoder-py.tex', u'zencoder-py Documentation', 187 | u'Alex Schworer', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'zencoder-py', u'zencoder-py Documentation', 217 | [u'Alex Schworer'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'zencoder-py', u'zencoder-py Documentation', 231 | u'Alex Schworer', 'zencoder-py', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. zencoder-py documentation master file, created by 2 | sphinx-quickstart on Wed Jul 18 21:05:59 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to zencoder-py's documentation! 7 | ======================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 4 13 | 14 | usage 15 | zencoder 16 | 17 | Introduction: 18 | 19 | ``zencoder`` is a Python module for the `Zencoder API`_. 20 | 21 | Official Zencoder API Docs: https://app.zencoder.com/docs 22 | 23 | ``zencoder-py`` Github: http://github.com/zencoder/zencoder-py 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | 32 | .. _Zencoder API: https://app.zencoder.com/docs 33 | 34 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Create an instance of `Zencoder`:: 5 | 6 | from zencoder import Zencoder 7 | zen = Zencoder('abc123') # enter your api key 8 | 9 | Submit a job to Zencoder:: 10 | 11 | # creates an encoding job with the defaults 12 | job = zen.job.create('http://input-file/movie.avi') 13 | print job.code 14 | print job.body 15 | print job.body['id'] 16 | 17 | Return output progress:: 18 | 19 | # get the transcode progress of the first output 20 | progress = zen.output.progress(job.body['outputs'][0]['id']) 21 | print progress.body 22 | 23 | Create a new job with multiple outputs:: 24 | 25 | # configure your outputs with dictionaries 26 | iphone = { 27 | 'label': 'iPhone', 28 | 'url': 's3://output-bucket/output-file-1.mp4', 29 | 'width': 480, 30 | 'height': 320 31 | } 32 | web = { 33 | 'label': 'web', 34 | 'url': 's3://output-bucket/output-file.vp8', 35 | 'video_codec':, 'vp8' 36 | } 37 | 38 | # the outputs kwarg requires an iterable 39 | outputs = (iphone, web) 40 | another_job = zen.job.create(input_url, outputs=outputs) 41 | 42 | -------------------------------------------------------------------------------- /docs/zencoder.rst: -------------------------------------------------------------------------------- 1 | zencoder 2 | ======== 3 | 4 | .. autoclass:: zencoder.core.Zencoder 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | .. autoclass:: zencoder.core.Account 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | .. autoclass:: zencoder.core.Job 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | .. autoclass:: zencoder.core.Output 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | .. autoclass:: zencoder.core.Response 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | .. autoclass:: zencoder.core.HTTPBackend 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | setup(name='zencoder', 7 | version='0.6.5', 8 | description='Integration library for Zencoder', 9 | author='Alex Schworer', 10 | author_email='alex.schworer@gmail.com', 11 | url='http://github.com/schworer/zencoder-py', 12 | license="MIT License", 13 | install_requires=['requests>=1.0'], 14 | tests_require=['mock', 'nose'], 15 | packages=['zencoder'], 16 | platforms='any', 17 | classifiers=[ 18 | 'Development Status :: 4 - Beta', 19 | 'Intended Audience :: Developers', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Programming Language :: Python :: 2.6', 24 | 'Programming Language :: Python :: 2.7', 25 | 'Programming Language :: Python :: 3.3', 26 | 'Programming Language :: Python :: 3.4', 27 | 'Topic :: Software Development :: Libraries :: Python Modules' 28 | ] 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /test/fixtures/account_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "abcd1234", 3 | "password": "foo" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/account_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_state": "active", 3 | "plan": "Growth", 4 | "minutes_used": 12549, 5 | "minutes_included": 25000, 6 | "billing_state": "active", 7 | "integration_mode":true 8 | } -------------------------------------------------------------------------------- /test/fixtures/input_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "audio_sample_rate": 44100, 3 | "frame_rate": 30, 4 | "job_id": 45497494, 5 | "channels": "2", 6 | "audio_bitrate_in_kbps": 50, 7 | "height": 720, 8 | "audio_codec": "aac", 9 | "duration_in_ms": 5067, 10 | "url": "http://s3.amazonaws.com/zencodertesting/test.mov", 11 | "file_size_in_bytes": 922620, 12 | "width": 1280, 13 | "format": "mpeg4", 14 | "state": "finished", 15 | "total_bitrate_in_kbps": 1452, 16 | "video_bitrate_in_kbps": 1402, 17 | "id": 45475483, 18 | "video_codec": "h264", 19 | "privacy": false 20 | } -------------------------------------------------------------------------------- /test/fixtures/input_progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "processing", 3 | "current_event": "Downloading", 4 | "current_event_progress": "32.34567345", 5 | "progress": "45.2353255" 6 | } -------------------------------------------------------------------------------- /test/fixtures/job_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "outputs": [ 3 | { 4 | "label": null, 5 | "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/7a9f3b6947c27305079fb105dbfc529e/34356e4d54f0c8fb9c3273203937e795.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=Tp9WVinpXKE%2FPrP2M08r54U4EQ0%3D&Expires=1367817210", 6 | "id": 93461812 7 | } 8 | ], 9 | "id": 45492475 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/job_create_live.json: -------------------------------------------------------------------------------- 1 | { 2 | "stream_url": "rtmp://foo:1935/live", 3 | "stream_name": "bar", 4 | "outputs": [ 5 | { 6 | "label": null, 7 | "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com", 8 | "id": 97931084 9 | } 10 | ], 11 | "id": 47010105 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/job_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "job": { 3 | "submitted_at": "2013-05-04T21:36:39-07:00", 4 | "state": "finished", 5 | "privacy": false, 6 | "input_media_file": { 7 | "video_codec": "h264", 8 | "frame_rate": 30, 9 | "channels": "2", 10 | "audio_codec": "aac", 11 | "audio_bitrate_in_kbps": 50, 12 | "state": "finished", 13 | "format": "mpeg4", 14 | "audio_sample_rate": 44100, 15 | "privacy": false, 16 | "height": 720, 17 | "error_message": null, 18 | "url": "s3://test-bucket/test.mov", 19 | "video_bitrate_in_kbps": 1402, 20 | "md5_checksum": null, 21 | "duration_in_ms": 5067, 22 | "test": false, 23 | "id": 45469002, 24 | "finished_at": "2013-05-04T21:36:46-07:00", 25 | "updated_at": "2013-05-04T21:37:12-07:00", 26 | "created_at": "2013-05-04T21:36:39-07:00", 27 | "total_bitrate_in_kbps": 1452, 28 | "width": 1280, 29 | "error_class": null, 30 | "file_size_bytes": 922620 31 | }, 32 | "test": false, 33 | "id": 45491013, 34 | "finished_at": "2013-05-04T21:37:12-07:00", 35 | "updated_at": "2013-05-04T21:37:12-07:00", 36 | "created_at": "2013-05-04T21:36:39-07:00", 37 | "thumbnails": [], 38 | "output_media_files": [ 39 | { 40 | "video_codec": "h264", 41 | "frame_rate": 30, 42 | "channels": "2", 43 | "audio_codec": "aac", 44 | "audio_bitrate_in_kbps": 90, 45 | "state": "finished", 46 | "format": "mpeg4", 47 | "audio_sample_rate": 44100, 48 | "label": null, 49 | "privacy": false, 50 | "height": 720, 51 | "error_message": null, 52 | "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/fc7f7df4f3eacd6fe4ee88cab28732de/dfc2f1b4eb49ea9ab914c84de6d392fb.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=lAc18iXd4ta1Ct0JyazKwYSwdOk%3D&Expires=1367815032", 53 | "video_bitrate_in_kbps": 1440, 54 | "md5_checksum": null, 55 | "duration_in_ms": 5130, 56 | "test": false, 57 | "id": 93457943, 58 | "finished_at": "2013-05-04T21:37:12-07:00", 59 | "updated_at": "2013-05-04T21:37:12-07:00", 60 | "created_at": "2013-05-04T21:36:39-07:00", 61 | "total_bitrate_in_kbps": 1530, 62 | "width": 1280, 63 | "error_class": null, 64 | "file_size_bytes": 973430 65 | } 66 | ], 67 | "pass_through": null 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/fixtures/job_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "job": { 4 | "submitted_at": "2013-05-05T01:30:15-05:00", 5 | "state": "finished", 6 | "privacy": false, 7 | "stream": { 8 | "state": "finished", 9 | "height": 720, 10 | "url": "rtmp://live40.us-va.zencoder.io:1935/live", 11 | "duration": 13.3956291675568, 12 | "name": "7177a51b45ccb2b594f890f99fef1fdc", 13 | "test": false, 14 | "id": 22915, 15 | "finished_at": "2013-05-05T01:34:26-05:00", 16 | "updated_at": "2013-05-05T01:35:14-05:00", 17 | "created_at": "2013-05-05T01:30:15-05:00", 18 | "total_bitrate_in_kbps": 1024, 19 | "region": "us-virgina", 20 | "width": 1280, 21 | "protocol": "rtmp" 22 | }, 23 | "input_media_file": { 24 | "video_codec": "h264", 25 | "frame_rate": 30, 26 | "channels": "2", 27 | "audio_codec": "aac", 28 | "audio_bitrate_in_kbps": 131, 29 | "state": "finished", 30 | "format": "flash video", 31 | "audio_sample_rate": 44100, 32 | "privacy": false, 33 | "height": 720, 34 | "error_message": null, 35 | "url": "rtmp://live40.us-va.zencoder.io:1935/live/republish/7177a51b45ccb2b594f890f99fef1fdc", 36 | "video_bitrate_in_kbps": 1228, 37 | "md5_checksum": null, 38 | "duration_in_ms": 11090, 39 | "test": false, 40 | "id": 45472922, 41 | "finished_at": "2013-05-05T01:34:32-05:00", 42 | "updated_at": "2013-05-05T01:34:42-05:00", 43 | "created_at": "2013-05-05T01:30:15-05:00", 44 | "total_bitrate_in_kbps": 1359, 45 | "width": 1280, 46 | "error_class": null, 47 | "file_size_bytes": 304313 48 | }, 49 | "test": false, 50 | "id": 45494934, 51 | "finished_at": "2013-05-05T01:34:42-05:00", 52 | "updated_at": "2013-05-05T01:34:42-05:00", 53 | "created_at": "2013-05-05T01:30:15-05:00", 54 | "thumbnails": [], 55 | "output_media_files": [ 56 | { 57 | "video_codec": null, 58 | "frame_rate": null, 59 | "channels": null, 60 | "audio_codec": null, 61 | "audio_bitrate_in_kbps": null, 62 | "state": "finished", 63 | "format": null, 64 | "audio_sample_rate": null, 65 | "label": "hls_master", 66 | "privacy": false, 67 | "height": null, 68 | "error_message": null, 69 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/master.m3u8", 70 | "video_bitrate_in_kbps": null, 71 | "md5_checksum": null, 72 | "duration_in_ms": null, 73 | "test": false, 74 | "id": 93468543, 75 | "finished_at": "2013-05-05T01:34:35-05:00", 76 | "updated_at": "2013-05-05T01:34:43-05:00", 77 | "created_at": "2013-05-05T01:30:15-05:00", 78 | "total_bitrate_in_kbps": null, 79 | "width": null, 80 | "error_class": null, 81 | "file_size_bytes": 199 82 | }, 83 | { 84 | "video_codec": "h264", 85 | "frame_rate": 30, 86 | "channels": "2", 87 | "audio_codec": "aac", 88 | "audio_bitrate_in_kbps": 128, 89 | "state": "finished", 90 | "format": "mpeg-ts", 91 | "audio_sample_rate": 44100, 92 | "label": "hls_600", 93 | "privacy": false, 94 | "height": 360, 95 | "error_message": null, 96 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/600/index.m3u8", 97 | "video_bitrate_in_kbps": 764, 98 | "md5_checksum": null, 99 | "duration_in_ms": 11100, 100 | "test": false, 101 | "id": 93468540, 102 | "finished_at": "2013-05-05T01:34:39-05:00", 103 | "updated_at": "2013-05-05T01:34:43-05:00", 104 | "created_at": "2013-05-05T01:30:15-05:00", 105 | "total_bitrate_in_kbps": 892, 106 | "width": 640, 107 | "error_class": null, 108 | "file_size_bytes": 1217265 109 | }, 110 | { 111 | "video_codec": "h264", 112 | "frame_rate": 30, 113 | "channels": "2", 114 | "audio_codec": "aac", 115 | "audio_bitrate_in_kbps": 128, 116 | "state": "finished", 117 | "format": "mpeg-ts", 118 | "audio_sample_rate": 44100, 119 | "label": "hls_300", 120 | "privacy": false, 121 | "height": 270, 122 | "error_message": null, 123 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/300/index.m3u8", 124 | "video_bitrate_in_kbps": 400, 125 | "md5_checksum": null, 126 | "duration_in_ms": 11100, 127 | "test": false, 128 | "id": 93468539, 129 | "finished_at": "2013-05-05T01:34:40-05:00", 130 | "updated_at": "2013-05-05T01:34:43-05:00", 131 | "created_at": "2013-05-05T01:30:15-05:00", 132 | "total_bitrate_in_kbps": 528, 133 | "width": 480, 134 | "error_class": null, 135 | "file_size_bytes": 708537 136 | }, 137 | { 138 | "video_codec": "h264", 139 | "frame_rate": 30, 140 | "channels": "2", 141 | "audio_codec": "aac", 142 | "audio_bitrate_in_kbps": 128, 143 | "state": "finished", 144 | "format": "mpeg-ts", 145 | "audio_sample_rate": 44100, 146 | "label": "hls_1200", 147 | "privacy": false, 148 | "height": 720, 149 | "error_message": null, 150 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/1200/index.m3u8", 151 | "video_bitrate_in_kbps": 1484, 152 | "md5_checksum": null, 153 | "duration_in_ms": 11100, 154 | "test": false, 155 | "id": 93468542, 156 | "finished_at": "2013-05-05T01:34:40-05:00", 157 | "updated_at": "2013-05-05T01:34:43-05:00", 158 | "created_at": "2013-05-05T01:30:15-05:00", 159 | "total_bitrate_in_kbps": 1612, 160 | "width": 1280, 161 | "error_class": null, 162 | "file_size_bytes": 2223629 163 | }, 164 | { 165 | "video_codec": "h264", 166 | "frame_rate": 30, 167 | "channels": "2", 168 | "audio_codec": "aac", 169 | "audio_bitrate_in_kbps": 124, 170 | "state": "finished", 171 | "format": "flash video", 172 | "audio_sample_rate": 44100, 173 | "label": "rtmp_300", 174 | "privacy": false, 175 | "height": 270, 176 | "error_message": null, 177 | "url": "rtmp://rtmp.live.zencdn.net/live/15ec8791b4d951a6053de2799170ec93_77507_300@107413", 178 | "video_bitrate_in_kbps": 343, 179 | "md5_checksum": null, 180 | "duration_in_ms": 11160, 181 | "test": false, 182 | "id": 93468534, 183 | "finished_at": "2013-05-05T01:34:41-05:00", 184 | "updated_at": "2013-05-05T01:34:43-05:00", 185 | "created_at": "2013-05-05T01:30:15-05:00", 186 | "total_bitrate_in_kbps": 467, 187 | "width": 480, 188 | "error_class": null, 189 | "file_size_bytes": 667832 190 | }, 191 | { 192 | "video_codec": "h264", 193 | "frame_rate": 30, 194 | "channels": "2", 195 | "audio_codec": "aac", 196 | "audio_bitrate_in_kbps": 124, 197 | "state": "finished", 198 | "format": "flash video", 199 | "audio_sample_rate": 44100, 200 | "label": "rtmp_600", 201 | "privacy": false, 202 | "height": 360, 203 | "error_message": null, 204 | "url": "rtmp://rtmp.live.zencdn.net/live/cd5766181ed19b81195db56092b4e500_77507_600@107415", 205 | "video_bitrate_in_kbps": 690, 206 | "md5_checksum": null, 207 | "duration_in_ms": 11160, 208 | "test": false, 209 | "id": 93468535, 210 | "finished_at": "2013-05-05T01:34:41-05:00", 211 | "updated_at": "2013-05-05T01:34:43-05:00", 212 | "created_at": "2013-05-05T01:30:15-05:00", 213 | "total_bitrate_in_kbps": 814, 214 | "width": 640, 215 | "error_class": null, 216 | "file_size_bytes": 1152631 217 | }, 218 | { 219 | "video_codec": "h264", 220 | "frame_rate": 30, 221 | "channels": "2", 222 | "audio_codec": "aac", 223 | "audio_bitrate_in_kbps": 124, 224 | "state": "finished", 225 | "format": "flash video", 226 | "audio_sample_rate": 44100, 227 | "label": "rtmp_1200", 228 | "privacy": false, 229 | "height": 720, 230 | "error_message": null, 231 | "url": "rtmp://rtmp.live.zencdn.net/live/a83a306ea10a266e027c3186bff701b3_77507_1200@107417", 232 | "video_bitrate_in_kbps": 1352, 233 | "md5_checksum": null, 234 | "duration_in_ms": 11160, 235 | "test": false, 236 | "id": 93468537, 237 | "finished_at": "2013-05-05T01:34:42-05:00", 238 | "updated_at": "2013-05-05T01:34:43-05:00", 239 | "created_at": "2013-05-05T01:30:15-05:00", 240 | "total_bitrate_in_kbps": 1476, 241 | "width": 1280, 242 | "error_class": null, 243 | "file_size_bytes": 2076855 244 | } 245 | ], 246 | "pass_through": null 247 | } 248 | }, 249 | { 250 | "job": { 251 | "submitted_at": "2013-05-05T01:19:53-05:00", 252 | "state": "finished", 253 | "privacy": false, 254 | "input_media_file": { 255 | "video_codec": "h264", 256 | "frame_rate": 30, 257 | "channels": "2", 258 | "audio_codec": "aac", 259 | "audio_bitrate_in_kbps": 50, 260 | "state": "finished", 261 | "format": "mpeg4", 262 | "audio_sample_rate": 44100, 263 | "privacy": false, 264 | "height": 720, 265 | "error_message": null, 266 | "url": "http://s3.amazonaws.com/zencodertesting/test.mov", 267 | "video_bitrate_in_kbps": 1402, 268 | "md5_checksum": null, 269 | "duration_in_ms": 5067, 270 | "test": false, 271 | "id": 45472534, 272 | "finished_at": "2013-05-05T01:20:02-05:00", 273 | "updated_at": "2013-05-05T01:21:14-05:00", 274 | "created_at": "2013-05-05T01:19:54-05:00", 275 | "total_bitrate_in_kbps": 1452, 276 | "width": 1280, 277 | "error_class": null, 278 | "file_size_bytes": 922620 279 | }, 280 | "test": false, 281 | "id": 45494545, 282 | "finished_at": "2013-05-05T01:21:14-05:00", 283 | "updated_at": "2013-05-05T01:21:14-05:00", 284 | "created_at": "2013-05-05T01:19:53-05:00", 285 | "thumbnails": [], 286 | "output_media_files": [ 287 | { 288 | "video_codec": "h264", 289 | "frame_rate": 30, 290 | "channels": "2", 291 | "audio_codec": "aac", 292 | "audio_bitrate_in_kbps": 90, 293 | "state": "finished", 294 | "format": "mpeg4", 295 | "audio_sample_rate": 44100, 296 | "label": null, 297 | "privacy": false, 298 | "height": 720, 299 | "error_message": null, 300 | "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/b22ad0c6353f86333126866d43cc898f/4f097ddbcee6c587cc640a6e99af2594.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=QL0oZnKKivzEXlvSX3ealXDTI%2Bc%3D&Expires=1367821273", 301 | "video_bitrate_in_kbps": 1440, 302 | "md5_checksum": null, 303 | "duration_in_ms": 5130, 304 | "test": false, 305 | "id": 93467532, 306 | "finished_at": "2013-05-05T01:21:13-05:00", 307 | "updated_at": "2013-05-05T01:21:14-05:00", 308 | "created_at": "2013-05-05T01:19:53-05:00", 309 | "total_bitrate_in_kbps": 1530, 310 | "width": 1280, 311 | "error_class": null, 312 | "file_size_bytes": 973430 313 | } 314 | ], 315 | "pass_through": null 316 | } 317 | }, 318 | { 319 | "job": { 320 | "submitted_at": "2013-05-05T01:21:21-05:00", 321 | "state": "finished", 322 | "privacy": false, 323 | "input_media_file": { 324 | "video_codec": "h264", 325 | "frame_rate": 30, 326 | "channels": "2", 327 | "audio_codec": "aac", 328 | "audio_bitrate_in_kbps": 50, 329 | "state": "finished", 330 | "format": "mpeg4", 331 | "audio_sample_rate": 44100, 332 | "privacy": false, 333 | "height": 720, 334 | "error_message": null, 335 | "url": "http://s3.amazonaws.com/zencodertesting/test.mov", 336 | "video_bitrate_in_kbps": 1402, 337 | "md5_checksum": null, 338 | "duration_in_ms": 5067, 339 | "test": false, 340 | "id": 45472497, 341 | "finished_at": "2013-05-05T01:21:28-05:00", 342 | "updated_at": "2013-05-05T01:22:29-05:00", 343 | "created_at": "2013-05-05T01:18:42-05:00", 344 | "total_bitrate_in_kbps": 1452, 345 | "width": 1280, 346 | "error_class": null, 347 | "file_size_bytes": 922620 348 | }, 349 | "test": false, 350 | "id": 45494508, 351 | "finished_at": "2013-05-05T01:22:29-05:00", 352 | "updated_at": "2013-05-05T01:22:29-05:00", 353 | "created_at": "2013-05-05T01:18:42-05:00", 354 | "thumbnails": [], 355 | "output_media_files": [ 356 | { 357 | "video_codec": "h264", 358 | "frame_rate": 30, 359 | "channels": "2", 360 | "audio_codec": "aac", 361 | "audio_bitrate_in_kbps": 90, 362 | "state": "finished", 363 | "format": "mpeg4", 364 | "audio_sample_rate": 44100, 365 | "label": null, 366 | "privacy": false, 367 | "height": 720, 368 | "error_message": null, 369 | "url": "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20130505/0ae6d5cbb960964a4714944cbc3e8bd9/7e082e00c717e2e6e32923500d3f43da.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=PAAACDb22AiJOkxaq4h4pOIZWaQ%3D&Expires=1367821349", 370 | "video_bitrate_in_kbps": 1440, 371 | "md5_checksum": null, 372 | "duration_in_ms": 5130, 373 | "test": false, 374 | "id": 93467424, 375 | "finished_at": "2013-05-05T01:22:29-05:00", 376 | "updated_at": "2013-05-05T01:22:29-05:00", 377 | "created_at": "2013-05-05T01:18:42-05:00", 378 | "total_bitrate_in_kbps": 1530, 379 | "wi£dth": 1280, 380 | "error_class": null, 381 | "file_size_bytes": 973430 382 | } 383 | ], 384 | "pass_through": null 385 | } 386 | } 387 | ] -------------------------------------------------------------------------------- /test/fixtures/job_list_limit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "job": { 4 | "submitted_at": "2013-05-05T01:30:15-05:00", 5 | "state": "finished", 6 | "privacy": false, 7 | "stream": { 8 | "state": "finished", 9 | "height": 720, 10 | "url": "rtmp://live40.us-va.zencoder.io:1935/live", 11 | "duration": 13.3956291675568, 12 | "name": "7177a51b45ccb2b594f890f99fef1fdc", 13 | "test": false, 14 | "id": 22915, 15 | "finished_at": "2013-05-05T01:34:26-05:00", 16 | "updated_at": "2013-05-05T01:35:14-05:00", 17 | "created_at": "2013-05-05T01:30:15-05:00", 18 | "total_bitrate_in_kbps": 1024, 19 | "region": "us-virgina", 20 | "width": 1280, 21 | "protocol": "rtmp" 22 | }, 23 | "input_media_file": { 24 | "video_codec": "h264", 25 | "frame_rate": 30, 26 | "channels": "2", 27 | "audio_codec": "aac", 28 | "audio_bitrate_in_kbps": 131, 29 | "state": "finished", 30 | "format": "flash video", 31 | "audio_sample_rate": 44100, 32 | "privacy": false, 33 | "height": 720, 34 | "error_message": null, 35 | "url": "rtmp://live40.us-va.zencoder.io:1935/live/republish/7177a51b45ccb2b594f890f99fef1fdc", 36 | "video_bitrate_in_kbps": 1228, 37 | "md5_checksum": null, 38 | "duration_in_ms": 11090, 39 | "test": false, 40 | "id": 45472922, 41 | "finished_at": "2013-05-05T01:34:32-05:00", 42 | "updated_at": "2013-05-05T01:34:42-05:00", 43 | "created_at": "2013-05-05T01:30:15-05:00", 44 | "total_bitrate_in_kbps": 1359, 45 | "width": 1280, 46 | "error_class": null, 47 | "file_size_bytes": 304313 48 | }, 49 | "test": false, 50 | "id": 45494934, 51 | "finished_at": "2013-05-05T01:34:42-05:00", 52 | "updated_at": "2013-05-05T01:34:42-05:00", 53 | "created_at": "2013-05-05T01:30:15-05:00", 54 | "thumbnails": [], 55 | "output_media_files": [ 56 | { 57 | "video_codec": null, 58 | "frame_rate": null, 59 | "channels": null, 60 | "audio_codec": null, 61 | "audio_bitrate_in_kbps": null, 62 | "state": "finished", 63 | "format": null, 64 | "audio_sample_rate": null, 65 | "label": "hls_master", 66 | "privacy": false, 67 | "height": null, 68 | "error_message": null, 69 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/master.m3u8", 70 | "video_bitrate_in_kbps": null, 71 | "md5_checksum": null, 72 | "duration_in_ms": null, 73 | "test": false, 74 | "id": 93468543, 75 | "finished_at": "2013-05-05T01:34:35-05:00", 76 | "updated_at": "2013-05-05T01:34:43-05:00", 77 | "created_at": "2013-05-05T01:30:15-05:00", 78 | "total_bitrate_in_kbps": null, 79 | "width": null, 80 | "error_class": null, 81 | "file_size_bytes": 199 82 | }, 83 | { 84 | "video_codec": "h264", 85 | "frame_rate": 30, 86 | "channels": "2", 87 | "audio_codec": "aac", 88 | "audio_bitrate_in_kbps": 128, 89 | "state": "finished", 90 | "format": "mpeg-ts", 91 | "audio_sample_rate": 44100, 92 | "label": "hls_600", 93 | "privacy": false, 94 | "height": 360, 95 | "error_message": null, 96 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/600/index.m3u8", 97 | "video_bitrate_in_kbps": 764, 98 | "md5_checksum": null, 99 | "duration_in_ms": 11100, 100 | "test": false, 101 | "id": 93468540, 102 | "finished_at": "2013-05-05T01:34:39-05:00", 103 | "updated_at": "2013-05-05T01:34:43-05:00", 104 | "created_at": "2013-05-05T01:30:15-05:00", 105 | "total_bitrate_in_kbps": 892, 106 | "width": 640, 107 | "error_class": null, 108 | "file_size_bytes": 1217265 109 | }, 110 | { 111 | "video_codec": "h264", 112 | "frame_rate": 30, 113 | "channels": "2", 114 | "audio_codec": "aac", 115 | "audio_bitrate_in_kbps": 128, 116 | "state": "finished", 117 | "format": "mpeg-ts", 118 | "audio_sample_rate": 44100, 119 | "label": "hls_300", 120 | "privacy": false, 121 | "height": 270, 122 | "error_message": null, 123 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/300/index.m3u8", 124 | "video_bitrate_in_kbps": 400, 125 | "md5_checksum": null, 126 | "duration_in_ms": 11100, 127 | "test": false, 128 | "id": 93468539, 129 | "finished_at": "2013-05-05T01:34:40-05:00", 130 | "updated_at": "2013-05-05T01:34:43-05:00", 131 | "created_at": "2013-05-05T01:30:15-05:00", 132 | "total_bitrate_in_kbps": 528, 133 | "width": 480, 134 | "error_class": null, 135 | "file_size_bytes": 708537 136 | }, 137 | { 138 | "video_codec": "h264", 139 | "frame_rate": 30, 140 | "channels": "2", 141 | "audio_codec": "aac", 142 | "audio_bitrate_in_kbps": 128, 143 | "state": "finished", 144 | "format": "mpeg-ts", 145 | "audio_sample_rate": 44100, 146 | "label": "hls_1200", 147 | "privacy": false, 148 | "height": 720, 149 | "error_message": null, 150 | "url": "http://hls.live.zencdn.net/hls/live/207996/77507/58d23f338d42277bbd74c6281627cea7/1200/index.m3u8", 151 | "video_bitrate_in_kbps": 1484, 152 | "md5_checksum": null, 153 | "duration_in_ms": 11100, 154 | "test": false, 155 | "id": 93468542, 156 | "finished_at": "2013-05-05T01:34:40-05:00", 157 | "updated_at": "2013-05-05T01:34:43-05:00", 158 | "created_at": "2013-05-05T01:30:15-05:00", 159 | "total_bitrate_in_kbps": 1612, 160 | "width": 1280, 161 | "error_class": null, 162 | "file_size_bytes": 2223629 163 | }, 164 | { 165 | "video_codec": "h264", 166 | "frame_rate": 30, 167 | "channels": "2", 168 | "audio_codec": "aac", 169 | "audio_bitrate_in_kbps": 124, 170 | "state": "finished", 171 | "format": "flash video", 172 | "audio_sample_rate": 44100, 173 | "label": "rtmp_300", 174 | "privacy": false, 175 | "height": 270, 176 | "error_message": null, 177 | "url": "rtmp://rtmp.live.zencdn.net/live/15ec8791b4d951a6053de2799170ec93_77507_300@107413", 178 | "video_bitrate_in_kbps": 343, 179 | "md5_checksum": null, 180 | "duration_in_ms": 11160, 181 | "test": false, 182 | "id": 93468534, 183 | "finished_at": "2013-05-05T01:34:41-05:00", 184 | "updated_at": "2013-05-05T01:34:43-05:00", 185 | "created_at": "2013-05-05T01:30:15-05:00", 186 | "total_bitrate_in_kbps": 467, 187 | "width": 480, 188 | "error_class": null, 189 | "file_size_bytes": 667832 190 | }, 191 | { 192 | "video_codec": "h264", 193 | "frame_rate": 30, 194 | "channels": "2", 195 | "audio_codec": "aac", 196 | "audio_bitrate_in_kbps": 124, 197 | "state": "finished", 198 | "format": "flash video", 199 | "audio_sample_rate": 44100, 200 | "label": "rtmp_600", 201 | "privacy": false, 202 | "height": 360, 203 | "error_message": null, 204 | "url": "rtmp://rtmp.live.zencdn.net/live/cd5766181ed19b81195db56092b4e500_77507_600@107415", 205 | "video_bitrate_in_kbps": 690, 206 | "md5_checksum": null, 207 | "duration_in_ms": 11160, 208 | "test": false, 209 | "id": 93468535, 210 | "finished_at": "2013-05-05T01:34:41-05:00", 211 | "updated_at": "2013-05-05T01:34:43-05:00", 212 | "created_at": "2013-05-05T01:30:15-05:00", 213 | "total_bitrate_in_kbps": 814, 214 | "width": 640, 215 | "error_class": null, 216 | "file_size_bytes": 1152631 217 | }, 218 | { 219 | "video_codec": "h264", 220 | "frame_rate": 30, 221 | "channels": "2", 222 | "audio_codec": "aac", 223 | "audio_bitrate_in_kbps": 124, 224 | "state": "finished", 225 | "format": "flash video", 226 | "audio_sample_rate": 44100, 227 | "label": "rtmp_1200", 228 | "privacy": false, 229 | "height": 720, 230 | "error_message": null, 231 | "url": "rtmp://rtmp.live.zencdn.net/live/a83a306ea10a266e027c3186bff701b3_77507_1200@107417", 232 | "video_bitrate_in_kbps": 1352, 233 | "md5_checksum": null, 234 | "duration_in_ms": 11160, 235 | "test": false, 236 | "id": 93468537, 237 | "finished_at": "2013-05-05T01:34:42-05:00", 238 | "updated_at": "2013-05-05T01:34:43-05:00", 239 | "created_at": "2013-05-05T01:30:15-05:00", 240 | "total_bitrate_in_kbps": 1476, 241 | "width": 1280, 242 | "error_class": null, 243 | "file_size_bytes": 2076855 244 | } 245 | ], 246 | "pass_through": null 247 | } 248 | } 249 | ] -------------------------------------------------------------------------------- /test/fixtures/job_progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "processing", 3 | "input": { 4 | "state": "finished", 5 | "id": 45474984 6 | }, 7 | "progress": 40.5, 8 | "outputs": [ 9 | { 10 | "state": "processing", 11 | "id": 93474209, 12 | "current_event_progress": 0, 13 | "progress": 15, 14 | "current_event": "Transcoding" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/output_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "audio_bitrate_in_kbps": 74, 3 | "audio_codec": "aac", 4 | "audio_sample_rate": 48000, 5 | "channels": "2", 6 | "duration_in_ms": 24892, 7 | "file_size_in_bytes": 1215110, 8 | "format": "mpeg4", 9 | "frame_rate": 29.97, 10 | "height": 352, 11 | "id": 13339, 12 | "label": null, 13 | "state": "finished", 14 | "total_bitrate_in_kbps": 387, 15 | "url": "https://example.com/file.mp4", 16 | "video_bitrate_in_kbps": 313, 17 | "video_codec": "h264", 18 | "width": 624, 19 | "md5_checksum": "7f106918e02a69466afa0ee014174143" 20 | } -------------------------------------------------------------------------------- /test/fixtures/output_progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "processing", 3 | "current_event": "Transcoding", 4 | "current_event_progress": 45.32525, 5 | "progress": 32.34567345 6 | } -------------------------------------------------------------------------------- /test/fixtures/report_all.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": { 3 | "live": { 4 | "encoded_hours": 5, 5 | "stream_hours": 5 6 | }, 7 | "vod": { 8 | "encoded_minutes": 6, 9 | "billable_minutes": 8 10 | } 11 | }, 12 | "statistics": { 13 | "live": { 14 | "length": 2 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/report_all_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "statistics": { 3 | "vod": [ 4 | { 5 | "encoded_minutes": 5, 6 | "billable_minutes": 0, 7 | "grouping": null, 8 | "collected_on": "2013-05-13" 9 | } 10 | ], 11 | "live": [ 12 | { 13 | "total_billable_hours": 2, 14 | "stream_hours": 1, 15 | "billable_stream_hours": 0, 16 | "encoded_hours": 2, 17 | "grouping": null, 18 | "collected_on": "2013-05-13", 19 | "billable_encoded_hours": 0, 20 | "total_hours": 2 21 | } 22 | ] 23 | }, 24 | "total": { 25 | "vod": { 26 | "encoded_minutes": 5, 27 | "billable_minutes": 0 28 | }, 29 | "live": { 30 | "total_billable_hours": 2, 31 | "stream_hours": 1, 32 | "billable_stream_hours": 0, 33 | "encoded_hours": 2, 34 | "billable_encoded_hours": 0, 35 | "total_hours": 2 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/fixtures/report_live.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": { 3 | "encoded_hours": 5, 4 | "stream_hours": 5 5 | }, 6 | "statistics": { 7 | "length": 5 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/report_vod.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": { 3 | "encoded_minutes": 6, 4 | "billable_minutes": 8 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/test_accounts.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from mock import patch 3 | 4 | from test_util import TEST_API_KEY, load_response 5 | from zencoder import Zencoder 6 | 7 | class TestAccounts(unittest.TestCase): 8 | def setUp(self): 9 | self.zen = Zencoder(api_key=TEST_API_KEY) 10 | 11 | @patch("requests.Session.post") 12 | def test_account_create(self, post): 13 | post.return_value = load_response(201, 'fixtures/account_create.json') 14 | 15 | response = self.zen.account.create('test@example.com', tos=1) 16 | 17 | self.assertEquals(response.code, 201) 18 | self.assertEquals(response.body['password'], 'foo') 19 | self.assertEquals(response.body['api_key'], 'abcd1234') 20 | 21 | @patch("requests.Session.get") 22 | def test_account_details(self, get): 23 | get.return_value = load_response(200, 'fixtures/account_details.json') 24 | resp = self.zen.account.details() 25 | 26 | self.assertEquals(resp.code, 200) 27 | self.assertEquals(resp.body['account_state'], 'active') 28 | self.assertEquals(resp.body['minutes_used'], 12549) 29 | 30 | @patch("requests.Session.put") 31 | def test_account_integration(self, put): 32 | put.return_value = load_response(204) 33 | 34 | resp = self.zen.account.integration() 35 | 36 | self.assertEquals(resp.code, 204) 37 | self.assertEquals(resp.body, None) 38 | 39 | @patch("requests.Session.put") 40 | def test_account_live_unauthorized(self, put): 41 | put.return_value = load_response(402) 42 | 43 | resp = self.zen.account.live() 44 | self.assertEquals(resp.code, 402) 45 | 46 | @patch("requests.Session.put") 47 | def test_account_live_authorized(self, put): 48 | put.return_value = load_response(204) 49 | 50 | resp = self.zen.account.live() 51 | self.assertEquals(resp.code, 204) 52 | 53 | if __name__ == "__main__": 54 | unittest.main() 55 | 56 | -------------------------------------------------------------------------------- /test/test_inputs.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from zencoder import Zencoder 3 | 4 | from mock import patch 5 | 6 | from test_util import TEST_API_KEY, load_response 7 | from zencoder import Zencoder 8 | 9 | class TestInputs(unittest.TestCase): 10 | def setUp(self): 11 | self.zen = Zencoder(api_key=TEST_API_KEY) 12 | 13 | @patch("requests.Session.get") 14 | def test_input_details(self, get): 15 | get.return_value = load_response(200, 'fixtures/input_details.json') 16 | 17 | resp = self.zen.input.details(15432) 18 | self.assertEquals(resp.code, 200) 19 | self.assertTrue(resp.body['id'] > 0) 20 | 21 | @patch("requests.Session.get") 22 | def test_input_progress(self, get): 23 | get.return_value = load_response(200, 'fixtures/input_progress.json') 24 | 25 | resp = self.zen.input.progress(14325) 26 | self.assertEquals(resp.code, 200) 27 | self.assertEquals(resp.body['state'], 'processing') 28 | 29 | if __name__ == "__main__": 30 | unittest.main() 31 | 32 | -------------------------------------------------------------------------------- /test/test_jobs.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from mock import patch 3 | 4 | from test_util import TEST_API_KEY, load_response 5 | from zencoder import Zencoder 6 | 7 | class TestJobs(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.zen = Zencoder(api_key=TEST_API_KEY) 11 | 12 | @patch("requests.Session.post") 13 | def test_job_create(self, post): 14 | post.return_value = load_response(201, 'fixtures/job_create.json') 15 | 16 | resp = self.zen.job.create('s3://zencodertesting/test.mov') 17 | 18 | self.assertEquals(resp.code, 201) 19 | self.assertTrue(resp.body['id'] > 0) 20 | self.assertEquals(len(resp.body['outputs']), 1) 21 | 22 | @patch("requests.Session.post") 23 | def test_job_create_list(self, post): 24 | post.return_value = load_response(201, 'fixtures/job_create_live.json') 25 | 26 | resp = self.zen.job.create(live_stream=True) 27 | 28 | self.assertEquals(resp.code, 201) 29 | self.assertTrue(resp.body['id'] > 0) 30 | self.assertEquals(len(resp.body['outputs']), 1) 31 | 32 | @patch("requests.Session.get") 33 | def test_job_details(self, get): 34 | get.return_value = load_response(200, 'fixtures/job_details.json') 35 | 36 | resp = self.zen.job.details(1234) 37 | self.assertEquals(resp.code, 200) 38 | self.assertTrue(resp.body['job']['id'] > 0) 39 | self.assertEquals(len(resp.body['job']['output_media_files']), 1) 40 | 41 | @patch("requests.Session.get") 42 | def test_job_progress(self, get): 43 | get.return_value = load_response(200, 'fixtures/job_progress.json') 44 | 45 | resp = self.zen.job.progress(12345) 46 | self.assertEquals(resp.code, 200) 47 | self.assertEquals(resp.body['state'], 'processing') 48 | 49 | @patch("requests.Session.put") 50 | def test_job_cancel(self, put): 51 | put.return_value = load_response(204) 52 | 53 | resp = self.zen.job.cancel(5555) 54 | self.assertEquals(resp.code, 204) 55 | self.assertEquals(resp.body, None) 56 | 57 | @patch("requests.Session.put") 58 | def test_job_resubmit(self, put): 59 | put.return_value = load_response(204) 60 | 61 | resp = self.zen.job.resubmit(5555) 62 | self.assertEquals(resp.code, 204) 63 | self.assertEquals(resp.body, None) 64 | 65 | @patch("requests.Session.get") 66 | def test_job_list(self, get): 67 | get.return_value = load_response(200, 'fixtures/job_list.json') 68 | 69 | resp = self.zen.job.list() 70 | self.assertEquals(resp.code, 200) 71 | self.assertEquals(len(resp.body), 3) 72 | 73 | @patch("requests.Session.get") 74 | def test_job_list_limit(self, get): 75 | get.return_value = load_response(200, 'fixtures/job_list_limit.json') 76 | 77 | resp = self.zen.job.list(per_page=1) 78 | self.assertEquals(resp.code, 200) 79 | self.assertEquals(len(resp.body), 1) 80 | 81 | @patch("requests.Session.put") 82 | def test_job_finish(self, put): 83 | put.return_value = load_response(204) 84 | 85 | resp = self.zen.job.finish(99999) 86 | self.assertEquals(resp.code, 204) 87 | 88 | if __name__ == "__main__": 89 | unittest.main() 90 | 91 | -------------------------------------------------------------------------------- /test/test_outputs.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from zencoder import Zencoder 3 | 4 | from mock import patch 5 | 6 | from test_util import TEST_API_KEY, load_response 7 | from zencoder import Zencoder 8 | 9 | class TestOutputs(unittest.TestCase): 10 | def setUp(self): 11 | self.zen = Zencoder(api_key=TEST_API_KEY) 12 | 13 | @patch("requests.Session.get") 14 | def test_output_details(self, get): 15 | get.return_value = load_response(200, 'fixtures/output_details.json') 16 | 17 | resp = self.zen.output.details(22222) 18 | self.assertEquals(resp.code, 200) 19 | self.assertTrue(resp.body['id'] > 0) 20 | 21 | @patch("requests.Session.get") 22 | def test_output_progress(self, get): 23 | get.return_value = load_response(200, 'fixtures/output_progress.json') 24 | 25 | resp = self.zen.output.progress(123456) 26 | self.assertEquals(resp.code, 200) 27 | self.assertEquals(resp.body['state'], 'processing') 28 | 29 | if __name__ == "__main__": 30 | unittest.main() 31 | 32 | -------------------------------------------------------------------------------- /test/test_reports.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from mock import patch 3 | 4 | from test_util import TEST_API_KEY, load_response 5 | from zencoder import Zencoder 6 | 7 | import datetime 8 | 9 | class TestReports(unittest.TestCase): 10 | def setUp(self): 11 | self.zen = Zencoder(api_key=TEST_API_KEY) 12 | 13 | @patch("requests.Session.get") 14 | def test_reports_vod(self, get): 15 | get.return_value = load_response(200, 'fixtures/report_vod.json') 16 | 17 | resp = self.zen.report.vod() 18 | 19 | self.assertEquals(resp.code, 200) 20 | self.assertEquals(resp.body['total']['encoded_minutes'], 6) 21 | self.assertEquals(resp.body['total']['billable_minutes'], 8) 22 | 23 | @patch("requests.Session.get") 24 | def test_reports_live(self, get): 25 | get.return_value = load_response(200, 'fixtures/report_live.json') 26 | 27 | resp = self.zen.report.live() 28 | 29 | self.assertEquals(resp.code, 200) 30 | self.assertEquals(resp.body['total']['stream_hours'], 5) 31 | self.assertEquals(resp.body['total']['encoded_hours'], 5) 32 | self.assertEquals(resp.body['statistics']['length'], 5) 33 | 34 | @patch("requests.Session.get") 35 | def test_reports_all(self, get): 36 | get.return_value = load_response(200, 'fixtures/report_all.json') 37 | 38 | resp = self.zen.report.all() 39 | 40 | self.assertEquals(resp.code, 200) 41 | 42 | self.assertEquals(resp.body['total']['live']['stream_hours'], 5) 43 | self.assertEquals(resp.body['total']['live']['encoded_hours'], 5) 44 | self.assertEquals(resp.body['total']['vod']['encoded_minutes'], 6) 45 | self.assertEquals(resp.body['total']['vod']['billable_minutes'], 8) 46 | self.assertEquals(resp.body['statistics']['live']['length'], 2) 47 | 48 | @patch("requests.Session.get") 49 | def test_reports_all_date_filter(self, get): 50 | get.return_value = load_response(200, 'fixtures/report_all_date.json') 51 | 52 | start = datetime.date(2013, 5, 13) 53 | end = datetime.date(2013, 5, 13) 54 | resp = self.zen.report.all(start_date=start, end_date=end) 55 | 56 | self.assertEquals(resp.code, 200) 57 | 58 | self.assertEquals(resp.body['statistics']['vod'][0]['encoded_minutes'], 5) 59 | self.assertEquals(resp.body['statistics']['vod'][0]['billable_minutes'], 0) 60 | self.assertEquals(resp.body['statistics']['live'][0]['stream_hours'], 1) 61 | self.assertEquals(resp.body['statistics']['live'][0]['total_hours'], 2) 62 | 63 | self.assertEquals(resp.body['total']['vod']['encoded_minutes'], 5) 64 | self.assertEquals(resp.body['total']['vod']['billable_minutes'], 0) 65 | self.assertEquals(resp.body['total']['live']['stream_hours'], 1) 66 | self.assertEquals(resp.body['total']['live']['total_hours'], 2) 67 | 68 | if __name__ == "__main__": 69 | unittest.main() 70 | 71 | -------------------------------------------------------------------------------- /test/test_util.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import json 3 | import os 4 | 5 | TEST_API_KEY = 'abcd123' 6 | 7 | MockResponse = namedtuple("Response", "status_code, json, content") 8 | 9 | CUR_DIR = os.path.split(__file__)[0] 10 | 11 | def load_response(code, fixture=None): 12 | if fixture: 13 | with open(os.path.join(CUR_DIR, fixture), 'r') as f: 14 | content = f.read() 15 | else: 16 | content = None 17 | 18 | return MockResponse(code, lambda: json.loads(content), content) 19 | 20 | -------------------------------------------------------------------------------- /test/test_zencoder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from zencoder import Zencoder 4 | import zencoder 5 | 6 | class TestZencoder(unittest.TestCase): 7 | def setUp(self): 8 | """ Initialize Zencoder for testing """ 9 | pass 10 | 11 | def test_api_key(self): 12 | """ initialize zencoder object and test api key """ 13 | api_key = 'testapikey' 14 | zc = Zencoder(api_key=api_key) 15 | self.assertEquals(zc.api_key, api_key) 16 | 17 | def test_api_key_env_var(self): 18 | """ tests the ZENOCODER_API_KEY environment var """ 19 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 20 | zc = Zencoder() 21 | self.assertEquals(zc.api_key, 'abcd123') 22 | 23 | def test_default_api_version(self): 24 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 25 | zc = Zencoder() 26 | self.assertEquals(zc.base_url, 'https://app.zencoder.com/api/v2/') 27 | 28 | def test_set_api_version(self): 29 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 30 | zc = Zencoder(api_version='v1') 31 | self.assertEquals(zc.base_url, 'https://app.zencoder.com/api/v1/') 32 | 33 | def test_set_api_edge_version(self): 34 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 35 | zc = Zencoder(api_version='edge') 36 | self.assertEquals(zc.base_url, 'https://app.zencoder.com/api/') 37 | 38 | def test_set_base_url(self): 39 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 40 | zc = Zencoder(base_url='https://localhost:800/foo/') 41 | self.assertEquals(zc.base_url, 'https://localhost:800/foo/') 42 | 43 | def test_set_base_url_and_version_fails(self): 44 | os.environ['ZENCODER_API_KEY'] = 'abcd123' 45 | 46 | self.assertRaises(zencoder.core.ZencoderError, 47 | Zencoder, 48 | base_url='https://localhost:800/foo/', 49 | api_version='v3') 50 | 51 | def test_set_timeout(self): 52 | api_key = 'testapikey' 53 | zc = Zencoder(api_key=api_key, timeout=999) 54 | 55 | self.assertEquals(zc.job.requests_params['timeout'], 999) 56 | 57 | def test_set_proxies(self): 58 | api_key = 'testapikey' 59 | proxies = { 60 | 'https': 'https://10.10.1.10:1080' 61 | } 62 | zc = Zencoder(api_key=api_key, proxies=proxies) 63 | 64 | self.assertEquals(zc.job.requests_params['proxies'], proxies) 65 | 66 | def test_set_verify_false(self): 67 | api_key = 'testapikey' 68 | zc = Zencoder(api_key=api_key, verify=False) 69 | 70 | self.assertEquals(zc.job.requests_params['verify'], False) 71 | 72 | def test_set_cert_path(self): 73 | api_key = 'testapikey' 74 | cert = '/path/to/cert.pem' 75 | zc = Zencoder(api_key=api_key, cert=cert) 76 | 77 | self.assertEquals(zc.job.requests_params['cert'], cert) 78 | 79 | if __name__ == "__main__": 80 | unittest.main() 81 | 82 | -------------------------------------------------------------------------------- /zencoder/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Zencoder 2 | from .core import ZencoderResponseError 3 | from .core import Account 4 | from .core import __version__ 5 | 6 | __title__ = 'zencoder' 7 | __author__ = 'Alex Schworer' 8 | 9 | -------------------------------------------------------------------------------- /zencoder/core.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from datetime import datetime 4 | 5 | # Note: I've seen this pattern for dealing with json in different versions of 6 | # python in a lot of modules -- if there's a better way, I'd love to use it. 7 | try: 8 | # python 2.6 and greater 9 | import json 10 | except ImportError: 11 | try: 12 | # python 2.5 13 | import simplejson 14 | json = simplejson 15 | except ImportError: 16 | # if we're in django or Google AppEngine land 17 | # use this as a last resort 18 | from django.utils import simplejson 19 | json = simplejson 20 | 21 | __version__ = '0.6.5' 22 | 23 | class ZencoderError(Exception): 24 | pass 25 | 26 | class ZencoderResponseError(Exception): 27 | def __init__(self, http_response, content): 28 | self.http_response = http_response 29 | self.content = content 30 | 31 | class HTTPBackend(object): 32 | """ Abstracts out an HTTP backend. Required argument are ``base_url`` and 33 | ``api_key``. """ 34 | def __init__(self, 35 | base_url, 36 | api_key, 37 | resource_name=None, 38 | timeout=None, 39 | test=False, 40 | version=None, 41 | proxies=None, 42 | cert=None, 43 | verify=True): 44 | 45 | self.base_url = base_url 46 | 47 | if resource_name: 48 | self.base_url = self.base_url + resource_name 49 | 50 | self.http = requests.Session() 51 | 52 | # set requests additional settings. 53 | # `None` is default for all of these settings. 54 | self.requests_params = { 55 | 'timeout': timeout, 56 | 'proxies': proxies, 57 | 'cert': cert, 58 | 'verify': verify 59 | } 60 | 61 | self.api_key = api_key 62 | self.test = test 63 | self.version = version 64 | 65 | # sets request headers for the entire session 66 | self.http.headers.update(self.headers) 67 | 68 | @property 69 | def headers(self): 70 | """ Returns default headers, by setting the Content-Type, Accepts, 71 | User-Agent and API Key headers. """ 72 | 73 | headers = { 74 | 'Content-Type': 'application/json', 75 | 'Accept': 'application/json', 76 | 'Zencoder-Api-Key': self.api_key, 77 | 'User-Agent': 'zencoder-py v{0}'.format(__version__) 78 | } 79 | 80 | return headers 81 | 82 | def delete(self, url, params=None): 83 | """ Executes an HTTP DELETE request for the given URL. 84 | 85 | ``params`` should be a dictionary 86 | """ 87 | response = self.http.delete(url, 88 | params=params, 89 | **self.requests_params) 90 | return self.process(response) 91 | 92 | def get(self, url, data=None): 93 | """ Executes an HTTP GET request for the given URL. 94 | 95 | ``data`` should be a dictionary of url parameters 96 | """ 97 | response = self.http.get(url, 98 | headers=self.headers, 99 | params=data, 100 | **self.requests_params) 101 | return self.process(response) 102 | 103 | def post(self, url, body=None): 104 | """ Executes an HTTP POST request for the given URL. """ 105 | response = self.http.post(url, 106 | headers=self.headers, 107 | data=body, 108 | **self.requests_params) 109 | 110 | return self.process(response) 111 | 112 | def put(self, url, data=None, body=None): 113 | """ Executes an HTTP PUT request for the given URL. """ 114 | response = self.http.put(url, 115 | headers=self.headers, 116 | data=body, 117 | params=data, 118 | **self.requests_params) 119 | 120 | return self.process(response) 121 | 122 | def process(self, response): 123 | """ Returns HTTP backend agnostic ``Response`` data. """ 124 | 125 | try: 126 | code = response.status_code 127 | 128 | # 204 - No Content 129 | if code == 204: 130 | body = None 131 | # add an error message to 402 errors 132 | elif code == 402: 133 | body = { 134 | "message": "Payment Required", 135 | "status": "error" 136 | } 137 | else: 138 | body = response.json() 139 | 140 | return Response(code, body, response.content, response) 141 | except ValueError: 142 | raise ZencoderResponseError(response, response.content) 143 | 144 | class Zencoder(object): 145 | """ This is the entry point to the Zencoder API. You must have a valid 146 | ``api_key``. 147 | 148 | You can pass in the api_key as an argument, or set ``ZENCODER_API_KEY`` 149 | as an environment variable, and it will use that, if ``api_key`` is 150 | unspecified. 151 | 152 | Set ``api_version='edge'`` to get the Zencoder development API. 153 | (defaults to 'v2') 154 | 155 | ``timeout``, ``proxies`` and ``verify`` can be set to control the 156 | underlying HTTP requests that are made. 157 | """ 158 | def __init__(self, 159 | api_key=None, 160 | api_version=None, 161 | base_url=None, 162 | timeout=None, 163 | test=False, 164 | proxies=None, 165 | cert=None, 166 | verify=True): 167 | 168 | if base_url and api_version: 169 | raise ZencoderError('Cannot set both `base_url` and `api_version`.') 170 | 171 | if base_url: 172 | self.base_url = base_url 173 | else: 174 | self.base_url = 'https://app.zencoder.com/api/' 175 | 176 | if not api_version: 177 | api_version = 'v2' 178 | 179 | if api_version != 'edge': 180 | self.base_url = '{0}{1}/'.format(self.base_url, api_version) 181 | 182 | if not api_key: 183 | try: 184 | self.api_key = os.environ['ZENCODER_API_KEY'] 185 | except KeyError: 186 | raise ZencoderError('ZENCODER_API_KEY not set') 187 | else: 188 | self.api_key = api_key 189 | 190 | self.test = test 191 | 192 | args = (self.base_url, self.api_key) 193 | 194 | kwargs = dict(timeout=timeout, 195 | test=self.test, 196 | version=api_version, 197 | proxies=proxies, 198 | cert=cert, 199 | verify=verify) 200 | 201 | self.job = Job(*args, **kwargs) 202 | self.account = Account(*args, **kwargs) 203 | self.output = Output(*args, **kwargs) 204 | self.input = Input(*args, **kwargs) 205 | self.report = None 206 | if api_version == 'v2': 207 | self.report = Report(*args, **kwargs) 208 | 209 | class Response(object): 210 | """ The Response object stores the details of an API request. 211 | 212 | `Response.body` contains the loaded JSON response from the API. 213 | """ 214 | def __init__(self, code, body, raw_body, raw_response): 215 | self.code = code 216 | self.body = body 217 | self.raw_body = raw_body 218 | self.raw_response = raw_response 219 | 220 | class Account(HTTPBackend): 221 | """ Contains all API methods relating to Accounts. 222 | 223 | https://app.zencoder.com/docs/api/inputs 224 | 225 | """ 226 | def __init__(self, *args, **kwargs): 227 | kwargs['resource_name'] = 'account' 228 | super(Account, self).__init__(*args, **kwargs) 229 | 230 | def create(self, email, tos=1, options=None): 231 | """ Creates an account with Zencoder, no API Key necessary. 232 | 233 | https://app.zencoder.com/docs/api/accounts/create 234 | 235 | """ 236 | data = {'email': email, 237 | 'terms_of_service': str(tos)} 238 | if options: 239 | data.update(options) 240 | 241 | return self.post(self.base_url, body=json.dumps(data)) 242 | 243 | def details(self): 244 | """ Gets account details. 245 | 246 | https://app.zencoder.com/docs/api/accounts/show 247 | 248 | """ 249 | return self.get(self.base_url) 250 | 251 | def integration(self): 252 | """ Puts the account into integration mode. 253 | 254 | https://app.zencoder.com/docs/api/accounts/integration 255 | 256 | """ 257 | return self.put(self.base_url + '/integration') 258 | 259 | def live(self): 260 | """ Puts the account into live mode. 261 | 262 | https://app.zencoder.com/docs/api/accounts/integration 263 | 264 | """ 265 | return self.put(self.base_url + '/live') 266 | 267 | class Output(HTTPBackend): 268 | """ Contains all API methods relating to Outputs. 269 | 270 | https://app.zencoder.com/docs/api/outputs 271 | 272 | """ 273 | def __init__(self, *args, **kwargs): 274 | kwargs['resource_name'] = 'outputs' 275 | super(Output, self).__init__(*args, **kwargs) 276 | 277 | def progress(self, output_id): 278 | """ Returns the progress for the given ``output_id``. 279 | 280 | https://app.zencoder.com/docs/api/outputs/progress 281 | 282 | """ 283 | return self.get(self.base_url + '/%s/progress' % str(output_id)) 284 | 285 | def details(self, output_id): 286 | """ Returns the details of the given ``output_id``. 287 | 288 | https://app.zencoder.com/docs/api/outputs/show 289 | 290 | """ 291 | return self.get(self.base_url + '/%s' % str(output_id)) 292 | 293 | class Input(HTTPBackend): 294 | """ Contains all API methods relating to Inputs. 295 | 296 | https://app.zencoder.com/docs/api/inputs 297 | 298 | """ 299 | def __init__(self, *args, **kwargs): 300 | kwargs['resource_name'] = 'inputs' 301 | super(Input, self).__init__(*args, **kwargs) 302 | 303 | def progress(self, input_id): 304 | """ Returns the progress of the given ``input_id``. 305 | 306 | https://app.zencoder.com/docs/api/inputs/progress 307 | 308 | """ 309 | return self.get(self.base_url + '/%s/progress' % str(input_id)) 310 | 311 | def details(self, input_id): 312 | """ Returns the detials of the given ``input_id``. 313 | 314 | https://app.zencoder.com/docs/api/inputs/show 315 | 316 | """ 317 | return self.get(self.base_url + '/%s' % str(input)) 318 | 319 | class Job(HTTPBackend): 320 | """ Contains all API methods relating to transcoding Jobs. 321 | 322 | https://app.zencoder.com/docs/api/jobs 323 | 324 | """ 325 | def __init__(self, *args, **kwargs): 326 | kwargs['resource_name'] = 'jobs' 327 | super(Job, self).__init__(*args, **kwargs) 328 | 329 | def create(self, input=None, live_stream=False, outputs=None, options=None): 330 | """ Creates a transcoding job. Here are some examples:: 331 | 332 | job.create('s3://zencodertesting/test.mov') 333 | job.create(live_stream=True) 334 | job.create(input='http://example.com/input.mov', 335 | outputs=({'label': 'test output'},)) 336 | 337 | https://app.zencoder.com/docs/api/jobs/create 338 | 339 | """ 340 | data = {"input": input, "test": self.test} 341 | if outputs: 342 | data['outputs'] = outputs 343 | 344 | if options: 345 | data.update(options) 346 | 347 | if live_stream: 348 | data['live_stream'] = live_stream 349 | 350 | return self.post(self.base_url, body=json.dumps(data)) 351 | 352 | def list(self, page=1, per_page=50): 353 | """ Lists Jobs. 354 | 355 | https://app.zencoder.com/docs/api/jobs/list 356 | 357 | """ 358 | data = {"page": page, 359 | "per_page": per_page} 360 | return self.get(self.base_url, data=data) 361 | 362 | def details(self, job_id): 363 | """ Returns details of the given ``job_id``. 364 | 365 | https://app.zencoder.com/docs/api/jobs/show 366 | 367 | """ 368 | return self.get(self.base_url + '/%s' % str(job_id)) 369 | 370 | def progress(self, job_id): 371 | """ Returns the progress of the given ``job_id``. 372 | 373 | https://app.zencoder.com/docs/api/jobs/progress 374 | 375 | """ 376 | return self.get(self.base_url + '/%s/progress' % str(job_id)) 377 | 378 | def resubmit(self, job_id): 379 | """ Resubmits the given ``job_id``. 380 | 381 | https://app.zencoder.com/docs/api/jobs/resubmit 382 | 383 | """ 384 | url = self.base_url + '/%s/resubmit' % str(job_id) 385 | return self.put(url) 386 | 387 | def cancel(self, job_id): 388 | """ Cancels the given ``job_id``. 389 | 390 | https://app.zencoder.com/docs/api/jobs/cancel 391 | 392 | """ 393 | if self.version == 'v1': 394 | verb = self.get 395 | else: 396 | verb = self.put 397 | 398 | url = self.base_url + '/%s/cancel' % str(job_id) 399 | return verb(url) 400 | 401 | def delete(self, job_id): 402 | """ Deletes the given ``job_id``. 403 | 404 | WARNING: This method is aliased to `Job.cancel` -- it is deprecated in 405 | API version 2 and greater. 406 | """ 407 | return self.cancel(job_id) 408 | 409 | def finish(self, job_id): 410 | """ Finishes the live stream for ``job_id``. 411 | 412 | https://app.zencoder.com/docs/api/jobs/finish 413 | 414 | """ 415 | return self.put(self.base_url + '/%s/finish' % str(job_id)) 416 | 417 | class Report(HTTPBackend): 418 | def __init__(self, *args, **kwargs): 419 | """ Contains all API methods relating to Reports. 420 | 421 | https://app.zencoder.com/docs/api/reports 422 | 423 | """ 424 | kwargs['resource_name'] = 'reports' 425 | super(Report, self).__init__(*args, **kwargs) 426 | 427 | def __format(self, start_date=None, end_date=None, grouping=None): 428 | data = {'api_key': self.api_key} 429 | 430 | date_format = '%Y-%m-%d' 431 | if start_date: 432 | data['from'] = start_date.strftime(date_format) 433 | 434 | if end_date: 435 | data['to'] = end_date.strftime(date_format) 436 | 437 | if grouping: 438 | data['grouping'] = grouping 439 | 440 | return data 441 | 442 | def minutes(self, start_date=None, end_date=None, grouping=None): 443 | """ Gets a detailed Report of encoded minutes and billable minutes for a 444 | date range. 445 | 446 | **Warning**: ``start_date`` and ``end_date`` must be ``datetime.date`` objects. 447 | 448 | Example:: 449 | import datetime 450 | start = datetime.date(2012, 12, 31) 451 | end = datetime.today() 452 | data = z.report.minutes(start, end) 453 | 454 | 455 | https://app.zencoder.com/docs/api/reports/minutes 456 | 457 | """ 458 | 459 | data = self.__format(start_date, end_date) 460 | 461 | url = self.base_url + '/minutes' 462 | return self.get(url, data=data) 463 | 464 | def vod(self, start_date=None, end_date=None, grouping=None): 465 | """ Returns a report of VOD usage. 466 | 467 | https://app.zencoder.com/docs/api/reports/vod 468 | 469 | """ 470 | data = self.__format(start_date, end_date, grouping) 471 | 472 | url = self.base_url + '/vod' 473 | return self.get(url, data=data) 474 | 475 | def live(self, start_date=None, end_date=None, grouping=None): 476 | """ Returns a report of Live usage. 477 | 478 | https://app.zencoder.com/docs/api/reports/vod 479 | 480 | """ 481 | data = self.__format(start_date, end_date, grouping) 482 | 483 | url = self.base_url + '/live' 484 | return self.get(url, data=data) 485 | 486 | def all(self, start_date=None, end_date=None, grouping=None): 487 | """ Returns a report of both VOD and Live usage. 488 | 489 | https://app.zencoder.com/docs/api/reports/all 490 | 491 | """ 492 | data = self.__format(start_date, end_date, grouping) 493 | 494 | url = self.base_url + '/all' 495 | return self.get(url, data=data) 496 | 497 | --------------------------------------------------------------------------------