From ac57e2df132d4ea05dacee642de74d057bd98f5d Mon Sep 17 00:00:00 2001
From: Karl Kroening <karlk@kralnet.us>
Date: Sun, 20 May 2018 01:13:07 -0700
Subject: [PATCH 1/5] Add input/output support in `run` command; update docs

---
 .gitignore                  |   1 +
 doc/html/genindex.html      |  22 ++++++++++
 doc/html/index.html         |  83 +++++++++++++++++++++++++++++++++---
 doc/html/objects.inv        | Bin 398 -> 427 bytes
 doc/html/searchindex.js     |   2 +-
 ffmpeg/_ffmpeg.py           |  13 +++++-
 ffmpeg/_probe.py            |  16 +++----
 ffmpeg/_run.py              |  67 +++++++++++++++++++++++------
 ffmpeg/tests/test_ffmpeg.py |  67 +++++++++++++++++++++++------
 requirements-base.txt       |   1 +
 requirements.txt            |   8 ++++
 setup.py                    |   2 +-
 12 files changed, 238 insertions(+), 44 deletions(-)
 mode change 100755 => 100644 ffmpeg/_probe.py

diff --git a/.gitignore b/.gitignore
index 3179ebc..780f20e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ dist/
 ffmpeg/tests/sample_data/out*.mp4
 ffmpeg_python.egg-info/
 venv*
+build/
diff --git a/doc/html/genindex.html b/doc/html/genindex.html
index c39ba30..4e258ee 100644
--- a/doc/html/genindex.html
+++ b/doc/html/genindex.html
@@ -50,12 +50,14 @@
 <div class="genindex-jumpbox">
  <a href="#C"><strong>C</strong></a>
  | <a href="#D"><strong>D</strong></a>
+ | <a href="#E"><strong>E</strong></a>
  | <a href="#F"><strong>F</strong></a>
  | <a href="#G"><strong>G</strong></a>
  | <a href="#H"><strong>H</strong></a>
  | <a href="#I"><strong>I</strong></a>
  | <a href="#M"><strong>M</strong></a>
  | <a href="#O"><strong>O</strong></a>
+ | <a href="#P"><strong>P</strong></a>
  | <a href="#R"><strong>R</strong></a>
  | <a href="#S"><strong>S</strong></a>
  | <a href="#T"><strong>T</strong></a>
@@ -67,6 +69,8 @@
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="index.html#ffmpeg.colorchannelmixer">colorchannelmixer() (in module ffmpeg)</a>
+</li>
+      <li><a href="index.html#ffmpeg.compile">compile() (in module ffmpeg)</a>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
@@ -89,6 +93,14 @@
   </ul></td>
 </tr></table>
 
+<h2 id="E">E</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="index.html#ffmpeg.Error">Error</a>
+</li>
+  </ul></td>
+</tr></table>
+
 <h2 id="F">F</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
@@ -97,6 +109,8 @@
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="index.html#ffmpeg.filter_">filter_() (in module ffmpeg)</a>
+</li>
+      <li><a href="index.html#ffmpeg.filter_multi_output">filter_multi_output() (in module ffmpeg)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -151,6 +165,14 @@
   </ul></td>
 </tr></table>
 
+<h2 id="P">P</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="index.html#ffmpeg.probe">probe() (in module ffmpeg)</a>
+</li>
+  </ul></td>
+</tr></table>
+
 <h2 id="R">R</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
diff --git a/doc/html/index.html b/doc/html/index.html
index c7717a6..ba91983 100644
--- a/doc/html/index.html
+++ b/doc/html/index.html
@@ -313,6 +313,21 @@ and arguments to ffmpeg verbatim.</p>
 <p><code class="docutils literal"><span class="pre">ffmpeg.input('in.mp4').filter_('hflip').output('out.mp4').run()</span></code></p>
 </dd></dl>
 
+<dl class="function">
+<dt id="ffmpeg.filter_multi_output">
+<code class="descclassname">ffmpeg.</code><code class="descname">filter_multi_output</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>filter_name</em>, <em>*args</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.filter_multi_output" title="Permalink to this definition">¶</a></dt>
+<dd><p>Apply custom filter with one or more outputs.</p>
+<p>This is the same as <code class="docutils literal"><span class="pre">filter_</span></code> except that the filter can produce more than one output.</p>
+<p>To reference an output stream, use either the <code class="docutils literal"><span class="pre">.stream</span></code> operator or bracket shorthand:</p>
+<p class="rubric">Example</p>
+<p><code class="docutils literal"><span class="pre">`</span>
+<span class="pre">split</span> <span class="pre">=</span> <span class="pre">ffmpeg.input('in.mp4').filter_multi_output('split')</span>
+<span class="pre">split0</span> <span class="pre">=</span> <span class="pre">split.stream(0)</span>
+<span class="pre">split1</span> <span class="pre">=</span> <span class="pre">split[1]</span>
+<span class="pre">ffmpeg.concat(split0,</span> <span class="pre">split1).output('out.mp4').run()</span>
+<span class="pre">`</span></code></p>
+</dd></dl>
+
 <dl class="function">
 <dt id="ffmpeg.hflip">
 <code class="descclassname">ffmpeg.</code><code class="descname">hflip</code><span class="sig-paren">(</span><em>stream</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.hflip" title="Permalink to this definition">¶</a></dt>
@@ -479,6 +494,9 @@ for single input image.</li>
 <dt id="ffmpeg.input">
 <code class="descclassname">ffmpeg.</code><code class="descname">input</code><span class="sig-paren">(</span><em>filename</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.input" title="Permalink to this definition">¶</a></dt>
 <dd><p>Input file URL (ffmpeg <code class="docutils literal"><span class="pre">-i</span></code> option)</p>
+<p>Any supplied kwargs are passed to ffmpeg verbatim (e.g. <code class="docutils literal"><span class="pre">t=20</span></code>,
+<code class="docutils literal"><span class="pre">f='mp4'</span></code>, <code class="docutils literal"><span class="pre">acodec='pcm'</span></code>, etc.).</p>
+<p>To tell ffmpeg to read from stdin, use <code class="docutils literal"><span class="pre">pipe:</span></code> as the filename.</p>
 <p>Official documentation: <a class="reference external" href="https://ffmpeg.org/ffmpeg.html#Main-options">Main options</a></p>
 </dd></dl>
 
@@ -490,8 +508,17 @@ for single input image.</li>
 
 <dl class="function">
 <dt id="ffmpeg.output">
-<code class="descclassname">ffmpeg.</code><code class="descname">output</code><span class="sig-paren">(</span><em>stream</em>, <em>filename</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.output" title="Permalink to this definition">¶</a></dt>
+<code class="descclassname">ffmpeg.</code><code class="descname">output</code><span class="sig-paren">(</span><em>*streams_and_filename</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.output" title="Permalink to this definition">¶</a></dt>
 <dd><p>Output file URL</p>
+<dl class="docutils">
+<dt>Syntax:</dt>
+<dd><cite>ffmpeg.output(stream1[, stream2, stream3…], filename, **ffmpeg_args)</cite></dd>
+</dl>
+<p>If multiple streams are provided, they are mapped to the same
+output.</p>
+<p>Any supplied kwargs are passed to ffmpeg verbatim (e.g. <code class="docutils literal"><span class="pre">t=20</span></code>,
+<code class="docutils literal"><span class="pre">f='mp4'</span></code>, <code class="docutils literal"><span class="pre">acodec='pcm'</span></code>, etc.).</p>
+<p>To tell ffmpeg to write to stdout, use <code class="docutils literal"><span class="pre">pipe:</span></code> as the filename.</p>
 <p>Official documentation: <a class="reference external" href="https://ffmpeg.org/ffmpeg.html#Synopsis">Synopsis</a></p>
 </dd></dl>
 
@@ -502,24 +529,53 @@ for single input image.</li>
 <p>Official documentation: <a class="reference external" href="https://ffmpeg.org/ffmpeg.html#Main-options">Main options</a></p>
 </dd></dl>
 
+<dl class="function">
+<dt id="ffmpeg.compile">
+<code class="descclassname">ffmpeg.</code><code class="descname">compile</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>cmd=u'ffmpeg'</em>, <em>overwrite_output=False</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.compile" title="Permalink to this definition">¶</a></dt>
+<dd><p>Build command-line for invoking ffmpeg.</p>
+<p>The <a class="reference internal" href="#ffmpeg.run" title="ffmpeg.run"><code class="xref py py-meth docutils literal"><span class="pre">run()</span></code></a> function uses this to build the commnad line
+arguments and should work in most cases, but calling this function
+directly is useful for debugging or if you need to invoke ffmpeg
+manually for whatever reason.</p>
+<p>This is the same as calling <a class="reference internal" href="#ffmpeg.get_args" title="ffmpeg.get_args"><code class="xref py py-meth docutils literal"><span class="pre">get_args()</span></code></a> except that it also
+includes the <code class="docutils literal"><span class="pre">ffmpeg</span></code> command as the first argument.</p>
+</dd></dl>
+
+<dl class="exception">
+<dt id="ffmpeg.Error">
+<em class="property">exception </em><code class="descclassname">ffmpeg.</code><code class="descname">Error</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>stdout</em>, <em>stderr</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.Error" title="Permalink to this definition">¶</a></dt>
+<dd><p>Bases: <code class="xref py py-class docutils literal"><span class="pre">exceptions.Exception</span></code></p>
+</dd></dl>
+
 <dl class="function">
 <dt id="ffmpeg.get_args">
 <code class="descclassname">ffmpeg.</code><code class="descname">get_args</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>overwrite_output=False</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.get_args" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get command-line arguments for ffmpeg.</p>
+<dd><p>Build command-line arguments to be passed to ffmpeg.</p>
 </dd></dl>
 
 <dl class="function">
 <dt id="ffmpeg.run">
-<code class="descclassname">ffmpeg.</code><code class="descname">run</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>cmd=u'ffmpeg'</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.run" title="Permalink to this definition">¶</a></dt>
-<dd><p>Run ffmpeg on node graph.</p>
+<code class="descclassname">ffmpeg.</code><code class="descname">run</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>cmd=u'ffmpeg'</em>, <em>capture_stdout=False</em>, <em>capture_stderr=False</em>, <em>input=None</em>, <em>quiet=False</em>, <em>overwrite_output=False</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.run" title="Permalink to this definition">¶</a></dt>
+<dd><p>Ivoke ffmpeg for the supplied node graph.</p>
 <table class="docutils field-list" frame="void" rules="none">
 <col class="field-name" />
 <col class="field-body" />
 <tbody valign="top">
-<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>**kwargs</strong> – keyword-arguments passed to <code class="docutils literal"><span class="pre">get_args()</span></code> (e.g. <code class="docutils literal"><span class="pre">overwrite_output=True</span></code>).</td>
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
+<li><strong>capture_stdout</strong> – if True, capture stdout (to be used with
+<code class="docutils literal"><span class="pre">pipe:</span></code> ffmpeg outputs).</li>
+<li><strong>capture_stderr</strong> – if True, capture stderr.</li>
+<li><strong>quiet</strong> – shorthand for setting <code class="docutils literal"><span class="pre">capture_stdout</span></code> and <code class="docutils literal"><span class="pre">capture_stderr</span></code>.</li>
+<li><strong>input</strong> – text to be sent to stdin (to be used with <code class="docutils literal"><span class="pre">pipe:</span></code>
+ffmpeg inputs)</li>
+<li><strong>**kwargs</strong> – keyword-arguments passed to <code class="docutils literal"><span class="pre">get_args()</span></code> (e.g.
+<code class="docutils literal"><span class="pre">overwrite_output=True</span></code>).</li>
+</ul>
+</td>
 </tr>
 </tbody>
 </table>
+<p>Returns: (out, err) tuple containing captured stdout and stderr data.</p>
 </dd></dl>
 
 <dl class="function">
@@ -527,6 +583,23 @@ for single input image.</li>
 <code class="descclassname">ffmpeg.</code><code class="descname">view</code><span class="sig-paren">(</span><em>stream_spec</em>, <em>**kwargs</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.view" title="Permalink to this definition">¶</a></dt>
 <dd></dd></dl>
 
+<dl class="function">
+<dt id="ffmpeg.probe">
+<code class="descclassname">ffmpeg.</code><code class="descname">probe</code><span class="sig-paren">(</span><em>filename</em><span class="sig-paren">)</span><a class="headerlink" href="#ffmpeg.probe" title="Permalink to this definition">¶</a></dt>
+<dd><p>Run ffprobe on the specified file and return a JSON representation of the output.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Raises:</th><td class="field-body"><a class="reference internal" href="#ffmpeg.Error" title="ffmpeg.Error"><code class="xref py py-class docutils literal"><span class="pre">ffmpeg.Error</span></code></a> – if ffprobe returns a non-zero exit code,
+an <a class="reference internal" href="#ffmpeg.Error" title="ffmpeg.Error"><code class="xref py py-class docutils literal"><span class="pre">Error</span></code></a> is returned with a generic error message.
+The stderr output can be retrieved by accessing the
+<code class="docutils literal"><span class="pre">stderr</span></code> property of the exception.</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
 </div>
 <div class="section" id="indices-and-tables">
 <h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">¶</a></h1>
diff --git a/doc/html/objects.inv b/doc/html/objects.inv
index 49e13ed09c61f4724332794f7da26e5aa7e62699..1072aecde4a2efc3029fc6e2136fd1873ccda6a9 100644
GIT binary patch
delta 313
zcmV-90mlB01FHj&dw-Rb!D_=W42JJ{3d43yS+BjXu*0CR2MA6gCkERxirglB`Z@8+
zIv7+v*+`!(>9;Loe1t*8<>0YT4%C~np@(@lvUe}VR==vcG8?#iCqi0rm^+AUF{u50
z#2wn9L+VBy0yuAH*uqQ--Ge9}ZLUMchEB5#C5$4lCw*yg7Jsu0v#bJR9RoGbus%7q
z4JM9BmSX^JG!2so9*wgyO~xd<4;!R~dY}P*I+IoVgbH{Da(WT77qSe`$Q%h<nIe>e
zh?{_;PzWaLMc`R{zh?^<5qQP?JN1K>1_+x)P23-xZo#Q~uWr_jQy=TL`o((eR_Kl>
z8SA+4h~c14+cK)!OxTbgxXDxKcwCVx52=6h#ihPE%7LF{zLm@q=scSKMig6tzl#5)
L2dKUQZAQzNXb_U#

delta 284
zcmV+%0ptFw1C9fbdw-48&1!@&5C`zPPchJ5UAk8vV4;V?LLWe)PBf4&#)%*M^i_j&
z7Ydz|GWq?T3~ID?5qpSh<wBP%!Uq`N;c^++*|%IP-=Qq@0WWQ^f!aZNkJgz5X^-mU
zTQ<SN^*Q{K@~vVSr4U81BQ-Z+5nIMZwgYV}Bh@0?BiD-hQGbL7ZH;WUA)&|_ACqh-
zhty*o5^n+7ZnKrq3CXHeY-cijZblTM<V9{oj>7Snj1$30{MIIevj{#ya8dm;Qjh+q
zz{p)?Rf87FE1cEC!RLNk{IV{zJ9-kiV;NT-g0J9yT%p;7@!Mys1HtXKlVbbV|L7Oj
i@?6Ld{L05f?iD<u+75%)M6PB0o!9TG$KnS}CyJ|u&yF$x

diff --git a/doc/html/searchindex.js b/doc/html/searchindex.js
index cfde87c..d86eec7 100644
--- a/doc/html/searchindex.js
+++ b/doc/html/searchindex.js
@@ -1 +1 @@
-Search.setIndex({docnames:["index"],envversion:52,filenames:["index.rst"],objects:{"":{ffmpeg:[0,0,0,"-"]},ffmpeg:{colorchannelmixer:[0,1,1,""],concat:[0,1,1,""],crop:[0,1,1,""],drawbox:[0,1,1,""],drawtext:[0,1,1,""],filter_:[0,1,1,""],get_args:[0,1,1,""],hflip:[0,1,1,""],hue:[0,1,1,""],input:[0,1,1,""],merge_outputs:[0,1,1,""],output:[0,1,1,""],overlay:[0,1,1,""],overwrite_output:[0,1,1,""],run:[0,1,1,""],setpts:[0,1,1,""],trim:[0,1,1,""],vflip:[0,1,1,""],view:[0,1,1,""],zoompan:[0,1,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:function"},terms:{"case":0,"default":0,"function":0,"return":0,"true":0,For:0,PTS:0,The:0,These:0,Used:0,accept:0,action:0,activ:0,adjust:0,after:0,alia:0,all:0,allow:0,alpha:0,also:0,alwai:0,angl:0,anoth:0,appli:0,arab:0,area:0,arg:0,argument:0,around:0,ascent:0,asetpt:0,ask:0,aspect:0,atom:0,attempt:0,audio:0,author:0,automat:0,avoid:0,axi:0,background:0,baselin:0,basetim:0,befor:0,behavior:0,behind:0,below:0,between:0,black:0,blend:0,border:0,bordercolor:0,borderw:0,both:0,box:0,boxborderw:0,boxcolor:0,bright:0,call:0,can:0,chang:0,channel:0,charact:0,check:0,chroma:0,clip:0,cmd:0,codec:0,color:0,colorchannelmix:0,com:0,combin:0,command:0,common:0,compil:0,concat:0,concaten:0,configur:0,confus:0,constant:0,construct:0,consult:0,contain:0,continu:0,convert:0,coord:0,coordin:0,corner:0,correctli:0,correspond:0,count:0,crop:0,crop_bitmap:0,custom:0,dar:0,degre:0,deprec:0,descent:0,detail:0,dictionari:0,differ:0,directli:0,disabl:0,displai:0,distanc:0,document:0,draw:0,drawbox:0,drawn:0,drawtext:0,drop:0,due:0,durat:0,dure:0,dynam:0,each:0,edg:0,effect:0,either:0,empti:0,emul:0,enabl:0,encod:0,encount:0,end:0,end_fram:0,end_pt:0,endal:0,eof:0,eof_act:0,equival:0,error:0,escape_text:0,eval:0,evalu:0,even:0,exactli:0,exampl:0,except:0,expand:0,expans:0,explicitli:0,expr:0,express:0,fail:0,fallback:0,fals:0,famili:0,file:0,filenam:0,filter:0,filter_:0,filter_nam:0,first:0,fix:0,fix_bound:0,flag:0,flip:0,fmpeg:0,follow:0,font:0,fontcolor:0,fontcolor_expr:0,fontconfig:0,fontfil:0,fontsiz:0,forc:0,force_autohint:0,format:0,fps:0,frame:0,frame_num:0,from:0,ft_load_:0,ft_load_flag:0,gbrp:0,gener:0,get:0,get_arg:0,github:0,given:0,glyph:0,graph:0,greater:0,grid:0,handl:0,has:0,have:0,hd720:0,height:0,heigth:0,hflip:0,higher:0,highest:0,horizont:0,hour:0,how:0,hsub:0,http:0,hue:0,huge:0,ignore_global_advance_width:0,ignore_transform:0,imag:0,immedi:0,implement:0,includ:0,incom:0,index:0,inform:0,init:0,initi:0,input:0,instead:0,interpret:0,invalid:0,invert:0,its:0,join:0,just:0,kept:0,keyword:0,kkroen:0,kwarg:0,label:0,last:0,layout:0,left:0,level:0,libfontconfig:0,libfreetyp:0,libfribidi:0,librari:0,line:0,line_h:0,line_spac:0,linear_design:0,list:0,load:0,longest:0,lowest:0,luma:0,mai:0,main:0,main_h:0,main_parent_nod:0,main_w:0,mandatori:0,mani:0,manual:0,map:0,max:0,max_glyph_a:0,max_glyph_d:0,max_glyph_h:0,max_glyph_w:0,maximum:0,mean:0,merge_output:0,microsecond:0,min:0,miss:0,mix:0,mode:0,modifi:0,modul:0,monochrom:0,more:0,mp4:0,must:0,name:0,nan:0,necessari:0,need:0,neg:0,no_autohint:0,no_bitmap:0,no_hint:0,no_recurs:0,no_scal:0,node:0,none:0,normal:0,number:0,obtain:0,offici:0,offset:0,onc:0,one:0,onli:0,option:0,order:0,orient:0,other:0,otherwis:0,out:0,outlin:0,output:0,over:0,overlai:0,overlaid:0,overlay_parent_nod:0,overrid:0,overwrit:0,overwrite_output:0,pack:0,pad:0,page:0,pan:0,paramet:0,partial:0,pass:0,path:0,pedant:0,pixel:0,place:0,planar:0,pleas:0,point:0,posit:0,preced:0,present:0,process:0,pts:0,radian:0,rand:0,random:0,rang:0,rate:0,ratio:0,read:0,reason:0,refer:0,rel:0,relat:0,reload:0,render:0,repeat:0,repeatlast:0,represent:0,resolut:0,respect:0,result:0,revers:0,rgb:0,right:0,run:0,same:0,sampl:0,san:0,sar:0,satur:0,search:0,second:0,secondari:0,section:0,see:0,segment:0,select:0,sequenc:0,set:0,setpt:0,shadow:0,shadowcolor:0,shadowi:0,shadowx:0,shape:0,shorter:0,shortest:0,should:0,silenc:0,singl:0,size:0,sloppi:0,space:0,special:0,specifi:0,standard:0,start:0,start_fram:0,start_numb:0,start_pt:0,stream:0,stream_spec:0,strftime:0,string:0,subpart:0,subsampl:0,suffix:0,suppli:0,support:0,sure:0,synchron:0,synopsi:0,syntax:0,system:0,tab:0,tabsiz:0,take:0,tc24hmax:0,termin:0,text:0,text_h:0,text_shap:0,text_w:0,textfil:0,than:0,them:0,thi:0,thick:0,through:0,thrown:0,time:0,timebas:0,timecod:0,timecode_r:0,timestamp:0,togeth:0,top:0,track:0,trim:0,type:0,unit:0,unknown:0,unsaf:0,until:0,updat:0,upper:0,upward:0,url:0,use:0,used:0,user:0,using:0,utf:0,util:0,valu:0,variabl:0,variou:0,verbatim:0,vertic:0,vertical_layout:0,vflip:0,video:0,view:0,visibl:0,vsub:0,wai:0,well:0,when:0,where:0,which:0,white:0,width:0,within:0,without:0,work:0,wrap:0,write:0,you:0,yuv420:0,yuv422:0,yuv422p:0,yuv444:0,zoom:0,zoompan:0},titles:["ffmpeg-python: Python bindings for FFmpeg"],titleterms:{bind:0,ffmpeg:0,indic:0,python:0,tabl:0}})
\ No newline at end of file
+Search.setIndex({docnames:["index"],envversion:52,filenames:["index.rst"],objects:{"":{ffmpeg:[0,0,0,"-"]},ffmpeg:{Error:[0,1,1,""],colorchannelmixer:[0,2,1,""],compile:[0,2,1,""],concat:[0,2,1,""],crop:[0,2,1,""],drawbox:[0,2,1,""],drawtext:[0,2,1,""],filter_:[0,2,1,""],filter_multi_output:[0,2,1,""],get_args:[0,2,1,""],hflip:[0,2,1,""],hue:[0,2,1,""],input:[0,2,1,""],merge_outputs:[0,2,1,""],output:[0,2,1,""],overlay:[0,2,1,""],overwrite_output:[0,2,1,""],probe:[0,2,1,""],run:[0,2,1,""],setpts:[0,2,1,""],trim:[0,2,1,""],vflip:[0,2,1,""],view:[0,2,1,""],zoompan:[0,2,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","exception","Python exception"],"2":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:exception","2":"py:function"},terms:{"case":0,"default":0,"function":0,"return":0,"true":0,For:0,PTS:0,The:0,These:0,Used:0,accept:0,access:0,acodec:0,action:0,activ:0,adjust:0,after:0,alia:0,all:0,allow:0,alpha:0,also:0,alwai:0,angl:0,ani:0,anoth:0,appli:0,arab:0,area:0,arg:0,argument:0,around:0,ascent:0,asetpt:0,ask:0,aspect:0,atom:0,attempt:0,audio:0,author:0,automat:0,avoid:0,axi:0,background:0,base:0,baselin:0,basetim:0,befor:0,behavior:0,behind:0,below:0,between:0,black:0,blend:0,border:0,bordercolor:0,borderw:0,both:0,box:0,boxborderw:0,boxcolor:0,bracket:0,bright:0,build:0,call:0,can:0,captur:0,capture_stderr:0,capture_stdout:0,chang:0,channel:0,charact:0,check:0,chroma:0,clip:0,cmd:0,code:0,codec:0,color:0,colorchannelmix:0,com:0,combin:0,command:0,commnad:0,common:0,compil:0,concat:0,concaten:0,configur:0,confus:0,constant:0,construct:0,consult:0,contain:0,continu:0,convert:0,coord:0,coordin:0,corner:0,correctli:0,correspond:0,count:0,crop:0,crop_bitmap:0,custom:0,dar:0,data:0,debug:0,degre:0,deprec:0,descent:0,detail:0,dictionari:0,differ:0,directli:0,disabl:0,displai:0,distanc:0,document:0,draw:0,drawbox:0,drawn:0,drawtext:0,drop:0,due:0,durat:0,dure:0,dynam:0,each:0,edg:0,effect:0,either:0,empti:0,emul:0,enabl:0,encod:0,encount:0,end:0,end_fram:0,end_pt:0,endal:0,eof:0,eof_act:0,equival:0,err:0,error:0,escape_text:0,etc:0,eval:0,evalu:0,even:0,exactli:0,exampl:0,except:0,exit:0,expand:0,expans:0,explicitli:0,expr:0,express:0,fail:0,fallback:0,fals:0,famili:0,ffmpeg_arg:0,ffprobe:0,file:0,filenam:0,filter:0,filter_:0,filter_multi_output:0,filter_nam:0,first:0,fix:0,fix_bound:0,flag:0,flip:0,fmpeg:0,follow:0,font:0,fontcolor:0,fontcolor_expr:0,fontconfig:0,fontfil:0,fontsiz:0,forc:0,force_autohint:0,format:0,fps:0,frame:0,frame_num:0,from:0,ft_load_:0,ft_load_flag:0,gbrp:0,gener:0,get_arg:0,github:0,given:0,glyph:0,graph:0,greater:0,grid:0,handl:0,has:0,have:0,hd720:0,height:0,heigth:0,hflip:0,higher:0,highest:0,horizont:0,hour:0,how:0,hsub:0,http:0,hue:0,huge:0,ignore_global_advance_width:0,ignore_transform:0,imag:0,immedi:0,implement:0,includ:0,incom:0,index:0,inform:0,init:0,initi:0,input:0,instead:0,interpret:0,invalid:0,invert:0,invok:0,its:0,ivok:0,join:0,json:0,just:0,kept:0,keyword:0,kkroen:0,kwarg:0,label:0,last:0,layout:0,left:0,level:0,libfontconfig:0,libfreetyp:0,libfribidi:0,librari:0,line:0,line_h:0,line_spac:0,linear_design:0,list:0,load:0,longest:0,lowest:0,luma:0,mai:0,main:0,main_h:0,main_parent_nod:0,main_w:0,mandatori:0,mani:0,manual:0,map:0,max:0,max_glyph_a:0,max_glyph_d:0,max_glyph_h:0,max_glyph_w:0,maximum:0,mean:0,merge_output:0,messag:0,microsecond:0,min:0,miss:0,mix:0,mode:0,modifi:0,modul:0,monochrom:0,more:0,most:0,mp4:0,multipl:0,must:0,name:0,nan:0,necessari:0,need:0,neg:0,no_autohint:0,no_bitmap:0,no_hint:0,no_recurs:0,no_scal:0,node:0,non:0,none:0,normal:0,number:0,obtain:0,offici:0,offset:0,onc:0,one:0,onli:0,oper:0,option:0,order:0,orient:0,other:0,otherwis:0,out:0,outlin:0,output:0,over:0,overlai:0,overlaid:0,overlay_parent_nod:0,overrid:0,overwrit:0,overwrite_output:0,pack:0,pad:0,page:0,pan:0,paramet:0,partial:0,pass:0,path:0,pcm:0,pedant:0,pipe:0,pixel:0,place:0,planar:0,pleas:0,point:0,posit:0,preced:0,present:0,probe:0,process:0,produc:0,properti:0,provid:0,pts:0,quiet:0,radian:0,rais:0,rand:0,random:0,rang:0,rate:0,ratio:0,read:0,reason:0,refer:0,rel:0,relat:0,reload:0,render:0,repeat:0,repeatlast:0,represent:0,resolut:0,respect:0,result:0,retriev:0,revers:0,rgb:0,right:0,run:0,same:0,sampl:0,san:0,sar:0,satur:0,search:0,second:0,secondari:0,section:0,see:0,segment:0,select:0,sent:0,sequenc:0,set:0,setpt:0,shadow:0,shadowcolor:0,shadowi:0,shadowx:0,shape:0,shorter:0,shortest:0,shorthand:0,should:0,silenc:0,singl:0,size:0,sloppi:0,space:0,special:0,specifi:0,split0:0,split1:0,split:0,standard:0,start:0,start_fram:0,start_numb:0,start_pt:0,stderr:0,stdin:0,stdout:0,stream1:0,stream2:0,stream3:0,stream:0,stream_spec:0,streams_and_filenam:0,strftime:0,string:0,subpart:0,subsampl:0,suffix:0,suppli:0,support:0,sure:0,synchron:0,synopsi:0,syntax:0,system:0,tab:0,tabsiz:0,take:0,tc24hmax:0,tell:0,termin:0,text:0,text_h:0,text_shap:0,text_w:0,textfil:0,than:0,thei:0,them:0,thi:0,thick:0,through:0,thrown:0,time:0,timebas:0,timecod:0,timecode_r:0,timestamp:0,togeth:0,top:0,track:0,trim:0,tupl:0,type:0,unit:0,unknown:0,unsaf:0,until:0,updat:0,upper:0,upward:0,url:0,use:0,used:0,useful:0,user:0,uses:0,using:0,utf:0,util:0,valu:0,variabl:0,variou:0,verbatim:0,vertic:0,vertical_layout:0,vflip:0,video:0,view:0,visibl:0,vsub:0,wai:0,well:0,whatev:0,when:0,where:0,which:0,white:0,width:0,within:0,without:0,work:0,wrap:0,write:0,you:0,yuv420:0,yuv422:0,yuv422p:0,yuv444:0,zero:0,zoom:0,zoompan:0},titles:["ffmpeg-python: Python bindings for FFmpeg"],titleterms:{bind:0,ffmpeg:0,indic:0,python:0,tabl:0}})
\ No newline at end of file
diff --git a/ffmpeg/_ffmpeg.py b/ffmpeg/_ffmpeg.py
index 7ea3be2..7928930 100644
--- a/ffmpeg/_ffmpeg.py
+++ b/ffmpeg/_ffmpeg.py
@@ -16,6 +16,11 @@ from .nodes import (
 def input(filename, **kwargs):
     """Input file URL (ffmpeg ``-i`` option)
 
+    Any supplied kwargs are passed to ffmpeg verbatim (e.g. ``t=20``,
+    ``f='mp4'``, ``acodec='pcm'``, etc.).
+
+    To tell ffmpeg to read from stdin, use ``pipe:`` as the filename.
+
     Official documentation: `Main options <https://ffmpeg.org/ffmpeg.html#Main-options>`__
     """
     kwargs['filename'] = filename
@@ -57,7 +62,13 @@ def output(*streams_and_filename, **kwargs):
     Syntax:
         `ffmpeg.output(stream1[, stream2, stream3...], filename, **ffmpeg_args)`
 
-        If multiple streams are provided, they are mapped to the same output.
+    If multiple streams are provided, they are mapped to the same
+    output.
+
+    Any supplied kwargs are passed to ffmpeg verbatim (e.g. ``t=20``,
+    ``f='mp4'``, ``acodec='pcm'``, etc.).
+
+    To tell ffmpeg to write to stdout, use ``pipe:`` as the filename.
 
     Official documentation: `Synopsis <https://ffmpeg.org/ffmpeg.html#Synopsis>`__
     """
diff --git a/ffmpeg/_probe.py b/ffmpeg/_probe.py
old mode 100755
new mode 100644
index ea3a52c..a6a8c8b
--- a/ffmpeg/_probe.py
+++ b/ffmpeg/_probe.py
@@ -1,29 +1,25 @@
 import json
 import subprocess
-
-
-class ProbeException(Exception):
-    def __init__(self, stderr_output):
-        super(ProbeException, self).__init__('ffprobe error')
-        self.stderr_output = stderr_output
+from ._run import Error
 
 
 def probe(filename):
     """Run ffprobe on the specified file and return a JSON representation of the output.
 
     Raises:
-        ProbeException: if ffprobe returns a non-zero exit code, a ``ProbeException`` is returned with a generic error
-            message.  The stderr output can be retrieved by accessing the ``stderr_output`` property of the exception.
+        :class:`ffmpeg.Error`: if ffprobe returns a non-zero exit code,
+            an :class:`Error` is returned with a generic error message.
+            The stderr output can be retrieved by accessing the
+            ``stderr`` property of the exception.
     """
     args = ['ffprobe', '-show_format', '-show_streams', '-of', 'json', filename]
     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, err = p.communicate()
     if p.returncode != 0:
-        raise ProbeException(err)
+        raise Error(err)
     return json.loads(out.decode('utf-8'))
 
 
 __all__ = [
     'probe',
-    'ProbeException',
 ]
diff --git a/ffmpeg/_run.py b/ffmpeg/_run.py
index cec8bfb..46ec2e1 100644
--- a/ffmpeg/_run.py
+++ b/ffmpeg/_run.py
@@ -1,13 +1,13 @@
 from __future__ import unicode_literals
 
-from builtins import str
-from past.builtins import basestring
 from .dag import get_outgoing_edges, topo_sort
-from functools import reduce
 from ._utils import basestring
+from builtins import str
+from functools import reduce
+from past.builtins import basestring
 import copy
 import operator
-import subprocess as _subprocess
+import subprocess
 
 from ._ffmpeg import (
     input,
@@ -23,6 +23,14 @@ from .nodes import (
 )
 
 
+class Error(Exception):
+    def __init__(self, stream_spec, stdout, stderr):
+        super(Error, self).__init__('ffmpeg error (see stderr output for detail)')
+        self.stream_spec = stream_spec
+        self.stdout = stdout
+        self.stderr = stderr
+
+
 def _convert_kwargs_to_cmd_line_args(kwargs):
     args = []
     for k in sorted(kwargs.keys()):
@@ -80,8 +88,9 @@ def _allocate_filter_stream_names(filter_nodes, outgoing_edge_maps, stream_name_
         for upstream_label, downstreams in list(outgoing_edge_map.items()):
             if len(downstreams) > 1:
                 # TODO: automatically insert `splits` ahead of time via graph transformation.
-                raise ValueError('Encountered {} with multiple outgoing edges with same upstream label {!r}; a '
-                                 '`split` filter is probably required'.format(upstream_node, upstream_label))
+                raise ValueError(
+                    'Encountered {} with multiple outgoing edges with same upstream label {!r}; a '
+                    '`split` filter is probably required'.format(upstream_node, upstream_label))
             stream_name_map[upstream_node, upstream_label] = 's{}'.format(stream_count)
             stream_count += 1
 
@@ -122,7 +131,7 @@ def _get_output_args(node, stream_name_map):
 
 @output_operator()
 def get_args(stream_spec, overwrite_output=False):
-    """Get command-line arguments for ffmpeg."""
+    """Build command-line arguments to be passed to ffmpeg."""
     nodes = get_stream_spec_nodes(stream_spec)
     args = []
     # TODO: group nodes together, e.g. `-i somefile -r somerate`.
@@ -144,27 +153,57 @@ def get_args(stream_spec, overwrite_output=False):
 
 
 @output_operator()
-def compile(stream_spec, cmd='ffmpeg', **kwargs):
-    """Build command-line for ffmpeg."""
+def compile(stream_spec, cmd='ffmpeg', overwrite_output=False):
+    """Build command-line for invoking ffmpeg.
+
+    The :meth:`run` function uses this to build the commnad line
+    arguments and should work in most cases, but calling this function
+    directly is useful for debugging or if you need to invoke ffmpeg
+    manually for whatever reason.
+
+    This is the same as calling :meth:`get_args` except that it also
+    includes the ``ffmpeg`` command as the first argument.
+    """
     if isinstance(cmd, basestring):
         cmd = [cmd]
     elif type(cmd) != list:
         cmd = list(cmd)
-    return cmd + get_args(stream_spec, **kwargs)
+    return cmd + get_args(stream_spec, overwrite_output=overwrite_output)
 
 
 @output_operator()
-def run(stream_spec, cmd='ffmpeg', **kwargs):
-    """Run ffmpeg on node graph.
+def run(
+        stream_spec, cmd='ffmpeg', capture_stdout=False, capture_stderr=False, input=None,
+        quiet=False, overwrite_output=False):
+    """Ivoke ffmpeg for the supplied node graph.
 
     Args:
-        **kwargs: keyword-arguments passed to ``get_args()`` (e.g. ``overwrite_output=True``).
+        capture_stdout: if True, capture stdout (to be used with
+            ``pipe:`` ffmpeg outputs).
+        capture_stderr: if True, capture stderr.
+        quiet: shorthand for setting ``capture_stdout`` and ``capture_stderr``.
+        input: text to be sent to stdin (to be used with ``pipe:``
+            ffmpeg inputs)
+        **kwargs: keyword-arguments passed to ``get_args()`` (e.g.
+            ``overwrite_output=True``).
+
+    Returns: (out, err) tuple containing captured stdout and stderr data.
     """
-    _subprocess.check_call(compile(stream_spec, cmd, **kwargs))
+    args = compile(stream_spec, cmd, overwrite_output=overwrite_output)
+    stdin_stream = subprocess.PIPE if input else None
+    stdout_stream = subprocess.PIPE if capture_stdout or quiet else None
+    stderr_stream = subprocess.PIPE if capture_stderr or quiet else None
+    p = subprocess.Popen(args, stdin=stdin_stream, stdout=stdout_stream, stderr=stderr_stream)
+    out, err = p.communicate(input)
+    retcode = p.poll()
+    if retcode:
+        raise Error(stream_spec, out, err)
+    return out, err
 
 
 __all__ = [
     'compile',
+    'Error',
     'get_args',
     'run',
 ]
diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py
index c7805fa..2a322b4 100644
--- a/ffmpeg/tests/test_ffmpeg.py
+++ b/ffmpeg/tests/test_ffmpeg.py
@@ -111,6 +111,10 @@ def test_global_args():
     assert out_file.get_args() == ['-i', 'dummy.mp4', 'dummy2.mp4', '-progress', 'someurl']
 
 
+def _get_simple_example():
+    return ffmpeg.input(TEST_INPUT_FILE1).output(TEST_OUTPUT_FILE1)
+
+
 def _get_complex_filter_example():
     split = (ffmpeg
         .input(TEST_INPUT_FILE1)
@@ -313,33 +317,72 @@ def test_compile():
 
 def test_run():
     stream = _get_complex_filter_example()
-    ffmpeg.run(stream)
+    out, err = ffmpeg.run(stream)
+    assert out is None
+    assert err is None
 
 
-def test_run_multi_output():
+@pytest.mark.parametrize('capture_stdout', [True, False])
+@pytest.mark.parametrize('capture_stderr', [True, False])
+def test_run__capture_out(mocker, capture_stdout, capture_stderr):
+    mocker.patch.object(ffmpeg._run, 'compile', return_value=['echo', 'test'])
+    stream = _get_simple_example()
+    out, err = ffmpeg.run(stream, capture_stdout=capture_stdout, capture_stderr=capture_stderr)
+    if capture_stdout:
+        assert out == 'test\n'
+    else:
+        assert out is None
+    if capture_stderr:
+        assert err == ''
+    else:
+        assert err is None
+
+
+def test_run__input_output(mocker):
+    mocker.patch.object(ffmpeg._run, 'compile', return_value=['cat'])
+    stream = _get_simple_example()
+    out, err = ffmpeg.run(stream, input='test', capture_stdout=True)
+    assert out == 'test'
+    assert err is None
+
+
+@pytest.mark.parametrize('capture_stdout', [True, False])
+@pytest.mark.parametrize('capture_stderr', [True, False])
+def test_run__error(mocker, capture_stdout, capture_stderr):
+    mocker.patch.object(ffmpeg._run, 'compile', return_value=['ffmpeg'])
+    stream = _get_complex_filter_example()
+    with pytest.raises(ffmpeg.Error) as excinfo:
+        out, err = ffmpeg.run(stream, capture_stdout=capture_stdout, capture_stderr=capture_stderr)
+    out = excinfo.value.stdout
+    err = excinfo.value.stderr
+    if capture_stdout:
+        assert out == ''
+    else:
+        assert out is None
+    if capture_stderr:
+        assert err.startswith('ffmpeg version')
+    else:
+        assert err is None
+
+
+def test_run__multi_output():
     in_ = ffmpeg.input(TEST_INPUT_FILE1)
     out1 = in_.output(TEST_OUTPUT_FILE1)
     out2 = in_.output(TEST_OUTPUT_FILE2)
     ffmpeg.run([out1, out2], overwrite_output=True)
 
 
-def test_run_dummy_cmd():
+def test_run__dummy_cmd():
     stream = _get_complex_filter_example()
     ffmpeg.run(stream, cmd='true')
 
 
-def test_run_dummy_cmd_list():
+def test_run__dummy_cmd_list():
     stream = _get_complex_filter_example()
     ffmpeg.run(stream, cmd=['true', 'ignored'])
 
 
-def test_run_failing_cmd():
-    stream = _get_complex_filter_example()
-    with pytest.raises(subprocess.CalledProcessError):
-        ffmpeg.run(stream, cmd='false')
-
-
-def test_custom_filter():
+def test_filter__custom():
     stream = ffmpeg.input('dummy.mp4')
     stream = ffmpeg.filter_(stream, 'custom_filter', 'a', 'b', kwarg1='c')
     stream = ffmpeg.output(stream, 'dummy2.mp4')
@@ -351,7 +394,7 @@ def test_custom_filter():
     ]
 
 
-def test_custom_filter_fluent():
+def test_filter__custom_fluent():
     stream = (ffmpeg
         .input('dummy.mp4')
         .filter_('custom_filter', 'a', 'b', kwarg1='c')
diff --git a/requirements-base.txt b/requirements-base.txt
index b5e8ae3..6afd152 100644
--- a/requirements-base.txt
+++ b/requirements-base.txt
@@ -1,5 +1,6 @@
 future
 pytest
+pytest-mock
 pytest-runner
 sphinx
 tox
diff --git a/requirements.txt b/requirements.txt
index 84710e2..dfec4a1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,18 +1,26 @@
 alabaster==0.7.10
+apipkg==1.4
 Babel==2.5.1
 certifi==2017.7.27.1
 chardet==3.0.4
 docutils==0.14
+execnet==1.5.0
+funcsigs==1.0.2
 future==0.16.0
 idna==2.6
 imagesize==0.7.1
 Jinja2==2.9.6
 MarkupSafe==1.0
+mock==2.0.0
+pbr==4.0.3
 pluggy==0.5.2
 py==1.4.34
 Pygments==2.2.0
 pytest==3.2.3
+pytest-forked==0.2
+pytest-mock==1.10.0
 pytest-runner==3.0
+pytest-xdist==1.22.2
 pytz==2017.3
 requests==2.18.4
 six==1.11.0
diff --git a/setup.py b/setup.py
index 6ee7bb0..3e4ee38 100644
--- a/setup.py
+++ b/setup.py
@@ -57,7 +57,7 @@ setup(
     name='ffmpeg-python',
     packages=['ffmpeg'],
     setup_requires=['pytest-runner'],
-    tests_require=['pytest'],
+    tests_require=['pytest', 'pytest-mock'],
     version=version,
     description='Python bindings for FFmpeg - with support for complex filtering',
     author='Karl Kroening',

From 4558c25ceddfb365fa9a5a2aceb25bd63e67118b Mon Sep 17 00:00:00 2001
From: Karl Kroening <karlk@kralnet.us>
Date: Sun, 20 May 2018 01:17:54 -0700
Subject: [PATCH 2/5] Update tox.ini

---
 tox.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tox.ini b/tox.ini
index c187ba3..f86ec4b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,3 +11,4 @@ commands = py.test -vv
 deps =
     future
     pytest
+    pytest-mock

From 940b05f3fcf46f60eb02ebdabb58289db033dfae Mon Sep 17 00:00:00 2001
From: Karl Kroening <karlk@kralnet.us>
Date: Sun, 20 May 2018 01:21:30 -0700
Subject: [PATCH 3/5] Fix ffprobe exception test

---
 ffmpeg/tests/test_ffmpeg.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py
index 2a322b4..7ab48a5 100644
--- a/ffmpeg/tests/test_ffmpeg.py
+++ b/ffmpeg/tests/test_ffmpeg.py
@@ -491,7 +491,7 @@ def test_ffprobe():
 
 
 def test_ffprobe_exception():
-    with pytest.raises(ffmpeg.ProbeException) as excinfo:
+    with pytest.raises(ffmpeg.Error) as excinfo:
         ffmpeg.probe(BOGUS_INPUT_FILE)
     assert str(excinfo.value) == 'ffprobe error'
-    assert b'No such file or directory' in excinfo.value.stderr_output
+    assert 'No such file or directory'.encode() in excinfo.value.stderr

From 9a487e8603bf881519cd04afe34c1272adfa7d15 Mon Sep 17 00:00:00 2001
From: Karl Kroening <karlk@kralnet.us>
Date: Sun, 20 May 2018 01:29:59 -0700
Subject: [PATCH 4/5] Fix exception params

---
 ffmpeg/_probe.py            |  2 +-
 ffmpeg/_run.py              |  7 +++----
 ffmpeg/tests/test_ffmpeg.py | 33 +++++++++++++++++----------------
 3 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/ffmpeg/_probe.py b/ffmpeg/_probe.py
index a6a8c8b..d3658fd 100644
--- a/ffmpeg/_probe.py
+++ b/ffmpeg/_probe.py
@@ -16,7 +16,7 @@ def probe(filename):
     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, err = p.communicate()
     if p.returncode != 0:
-        raise Error(err)
+        raise Error('ffprobe', out, err)
     return json.loads(out.decode('utf-8'))
 
 
diff --git a/ffmpeg/_run.py b/ffmpeg/_run.py
index 46ec2e1..c5a2b52 100644
--- a/ffmpeg/_run.py
+++ b/ffmpeg/_run.py
@@ -24,9 +24,8 @@ from .nodes import (
 
 
 class Error(Exception):
-    def __init__(self, stream_spec, stdout, stderr):
-        super(Error, self).__init__('ffmpeg error (see stderr output for detail)')
-        self.stream_spec = stream_spec
+    def __init__(self, cmd, stdout, stderr):
+        super(Error, self).__init__('{} error (see stderr output for detail)'.format(cmd))
         self.stdout = stdout
         self.stderr = stderr
 
@@ -197,7 +196,7 @@ def run(
     out, err = p.communicate(input)
     retcode = p.poll()
     if retcode:
-        raise Error(stream_spec, out, err)
+        raise Error('ffmpeg', out, err)
     return out, err
 
 
diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py
index 7ab48a5..06d00c7 100644
--- a/ffmpeg/tests/test_ffmpeg.py
+++ b/ffmpeg/tests/test_ffmpeg.py
@@ -101,7 +101,7 @@ def test_stream_repr():
     assert repr(dummy_out) == 'dummy()[{!r}] <{}>'.format(dummy_out.label, dummy_out.node.short_hash)
 
 
-def test_get_args_simple():
+def test__get_args__simple():
     out_file = ffmpeg.input('dummy.mp4').output('dummy2.mp4')
     assert out_file.get_args() == ['-i', 'dummy.mp4', 'dummy2.mp4']
 
@@ -138,7 +138,7 @@ def _get_complex_filter_example():
     )
 
 
-def test_get_args_complex_filter():
+def test__get_args__complex_filter():
     out = _get_complex_filter_example()
     args = ffmpeg.get_args(out)
     assert args == ['-i', TEST_INPUT_FILE1,
@@ -309,13 +309,13 @@ def test_filter_text_arg_str_escape():
 #    subprocess.check_call(['ffmpeg', '-version'])
 
 
-def test_compile():
+def test__compile():
     out_file = ffmpeg.input('dummy.mp4').output('dummy2.mp4')
     assert out_file.compile() == ['ffmpeg', '-i', 'dummy.mp4', 'dummy2.mp4']
     assert out_file.compile(cmd='ffmpeg.old') == ['ffmpeg.old', '-i', 'dummy.mp4', 'dummy2.mp4']
 
 
-def test_run():
+def test__run():
     stream = _get_complex_filter_example()
     out, err = ffmpeg.run(stream)
     assert out is None
@@ -324,7 +324,7 @@ def test_run():
 
 @pytest.mark.parametrize('capture_stdout', [True, False])
 @pytest.mark.parametrize('capture_stderr', [True, False])
-def test_run__capture_out(mocker, capture_stdout, capture_stderr):
+def test__run__capture_out(mocker, capture_stdout, capture_stderr):
     mocker.patch.object(ffmpeg._run, 'compile', return_value=['echo', 'test'])
     stream = _get_simple_example()
     out, err = ffmpeg.run(stream, capture_stdout=capture_stdout, capture_stderr=capture_stderr)
@@ -338,7 +338,7 @@ def test_run__capture_out(mocker, capture_stdout, capture_stderr):
         assert err is None
 
 
-def test_run__input_output(mocker):
+def test__run__input_output(mocker):
     mocker.patch.object(ffmpeg._run, 'compile', return_value=['cat'])
     stream = _get_simple_example()
     out, err = ffmpeg.run(stream, input='test', capture_stdout=True)
@@ -348,11 +348,12 @@ def test_run__input_output(mocker):
 
 @pytest.mark.parametrize('capture_stdout', [True, False])
 @pytest.mark.parametrize('capture_stderr', [True, False])
-def test_run__error(mocker, capture_stdout, capture_stderr):
+def test__run__error(mocker, capture_stdout, capture_stderr):
     mocker.patch.object(ffmpeg._run, 'compile', return_value=['ffmpeg'])
     stream = _get_complex_filter_example()
     with pytest.raises(ffmpeg.Error) as excinfo:
         out, err = ffmpeg.run(stream, capture_stdout=capture_stdout, capture_stderr=capture_stderr)
+    assert str(excinfo.value) == 'ffmpeg error (see stderr output for detail)'
     out = excinfo.value.stdout
     err = excinfo.value.stderr
     if capture_stdout:
@@ -365,24 +366,24 @@ def test_run__error(mocker, capture_stdout, capture_stderr):
         assert err is None
 
 
-def test_run__multi_output():
+def test__run__multi_output():
     in_ = ffmpeg.input(TEST_INPUT_FILE1)
     out1 = in_.output(TEST_OUTPUT_FILE1)
     out2 = in_.output(TEST_OUTPUT_FILE2)
     ffmpeg.run([out1, out2], overwrite_output=True)
 
 
-def test_run__dummy_cmd():
+def test__run__dummy_cmd():
     stream = _get_complex_filter_example()
     ffmpeg.run(stream, cmd='true')
 
 
-def test_run__dummy_cmd_list():
+def test__run__dummy_cmd_list():
     stream = _get_complex_filter_example()
     ffmpeg.run(stream, cmd=['true', 'ignored'])
 
 
-def test_filter__custom():
+def test__filter__custom():
     stream = ffmpeg.input('dummy.mp4')
     stream = ffmpeg.filter_(stream, 'custom_filter', 'a', 'b', kwarg1='c')
     stream = ffmpeg.output(stream, 'dummy2.mp4')
@@ -394,7 +395,7 @@ def test_filter__custom():
     ]
 
 
-def test_filter__custom_fluent():
+def test__filter__custom_fluent():
     stream = (ffmpeg
         .input('dummy.mp4')
         .filter_('custom_filter', 'a', 'b', kwarg1='c')
@@ -408,7 +409,7 @@ def test_filter__custom_fluent():
     ]
 
 
-def test_merge_outputs():
+def test__merge_outputs():
     in_ = ffmpeg.input('in.mp4')
     out1 = in_.output('out1.mp4')
     out2 = in_.output('out2.mp4')
@@ -484,14 +485,14 @@ def test_pipe():
     assert out_data == in_data[start_frame*frame_size:]
 
 
-def test_ffprobe():
+def test__probe():
     data = ffmpeg.probe(TEST_INPUT_FILE1)
     assert set(data.keys()) == {'format', 'streams'}
     assert data['format']['duration'] == '7.036000'
 
 
-def test_ffprobe_exception():
+def test__probe__exception():
     with pytest.raises(ffmpeg.Error) as excinfo:
         ffmpeg.probe(BOGUS_INPUT_FILE)
-    assert str(excinfo.value) == 'ffprobe error'
+    assert str(excinfo.value) == 'ffprobe error (see stderr output for detail)'
     assert 'No such file or directory'.encode() in excinfo.value.stderr

From 6a2d3381b79d5a8feca2c11733d0e80388c6e28e Mon Sep 17 00:00:00 2001
From: Karl Kroening <karlk@kralnet.us>
Date: Sun, 20 May 2018 01:36:46 -0700
Subject: [PATCH 5/5] Fix tests

---
 ffmpeg/tests/test_ffmpeg.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py
index 06d00c7..25aedd1 100644
--- a/ffmpeg/tests/test_ffmpeg.py
+++ b/ffmpeg/tests/test_ffmpeg.py
@@ -329,11 +329,11 @@ def test__run__capture_out(mocker, capture_stdout, capture_stderr):
     stream = _get_simple_example()
     out, err = ffmpeg.run(stream, capture_stdout=capture_stdout, capture_stderr=capture_stderr)
     if capture_stdout:
-        assert out == 'test\n'
+        assert out == 'test\n'.encode()
     else:
         assert out is None
     if capture_stderr:
-        assert err == ''
+        assert err == ''.encode()
     else:
         assert err is None
 
@@ -341,8 +341,8 @@ def test__run__capture_out(mocker, capture_stdout, capture_stderr):
 def test__run__input_output(mocker):
     mocker.patch.object(ffmpeg._run, 'compile', return_value=['cat'])
     stream = _get_simple_example()
-    out, err = ffmpeg.run(stream, input='test', capture_stdout=True)
-    assert out == 'test'
+    out, err = ffmpeg.run(stream, input='test'.encode(), capture_stdout=True)
+    assert out == 'test'.encode()
     assert err is None
 
 
@@ -357,11 +357,11 @@ def test__run__error(mocker, capture_stdout, capture_stderr):
     out = excinfo.value.stdout
     err = excinfo.value.stderr
     if capture_stdout:
-        assert out == ''
+        assert out == ''.encode()
     else:
         assert out is None
     if capture_stderr:
-        assert err.startswith('ffmpeg version')
+        assert err.decode().startswith('ffmpeg version')
     else:
         assert err is None