<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/atom.xsl" type="text/xsl"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-US"
	><title>Sacha Chua - category - streaming</title>
	<subtitle>Emacs, sketches, and life</subtitle>
	<link rel="self" type="application/atom+xml" href="https://sachachua.com/blog/category/streaming/feed/atom/index.xml" />
  <link rel="alternate" type="text/html" href="https://sachachua.com/blog/category/streaming" />
  <id>https://sachachua.com/blog/category/streaming/feed/atom/index.xml</id>
  <generator uri="https://11ty.dev">11ty</generator>
	<updated>2024-01-04T15:57:25Z</updated>
<entry>
		<title type="html">Quick notes on livestreaming to YouTube with FFmpeg on a Lenovo X230T</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2024/01/quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2024-01-04T15:57:25Z</updated>
    <published>2024-01-04T15:57:25Z</published>
    <category term="video" />
<category term="youtube" />
<category term="streaming" />
<category term="ffmpeg" />
<category term="yay-emacs" />
		<id>https://sachachua.com/blog/2024/01/quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t/</id>
		<content type="html"><![CDATA[<div class="update" id="org78e2751">
<p>
<span class="timestamp-wrapper"><span class="timestamp">[2024-01-05 Fri]</span></span>: Updated scripts
</p>

</div>

<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2024-01-03-03%20Quick%20thoughts%20on%20livestreaming%20%23sharing%20%23video%20%23streaming%20%23community.png" data-src="https://sketches.sachachua.com/static/2024-01-03-03%20Quick%20thoughts%20on%20livestreaming%20%23sharing%20%23video%20%23streaming%20%23community.png" data-title="2024-01-03-03 Quick thoughts on livestreaming #sharing #video #streaming #community.png" data-w="2808" data-h="3744"><picture>
      <img src="https://sketches.sachachua.com/static/2024-01-03-03%20Quick%20thoughts%20on%20livestreaming%20%23sharing%20%23video%20%23streaming%20%23community.png" width="2808" height="3744" alt="2024-01-03-03 Quick thoughts on livestreaming #sharing #video #streaming #community.png" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2024-01-03-03 Quick thoughts on livestreaming #sharing #video #streaming #community.png</figcaption>
    </picture></a></div>
<p></p>

<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>Text from the sketch</strong></summary>
<p>
Quick thoughts on livestreaming
</p>

<p>
Why:
</p>

<ul class="org-ul">
<li>work out loud</li>
<li>share tips</li>
<li>share more</li>
<li>spark conversations</li>
<li>(also get questions about things)</li>
</ul>

<p>
Doable with ffmpeg on my X230T:
</p>

<ul class="org-ul">
<li>streaming from my laptop</li>
<li>lapel mic + system audio,</li>
<li>second screen for monitoring</li>
</ul>

<p>
Ideas for next time:
</p>

<ul class="org-ul">
<li>Overall notes in Emacs with outline, org-timer timestamped notes; capture to this file</li>
<li>Elisp to start/stop the stream → find old code</li>
<li>Use the Yeti? Better sound</li>
<li>tee to a local recording</li>
<li>grab screenshot from SuperNote mirror?</li>
</ul>

<p>
Live streaming info density:
</p>

<ul class="org-ul">
<li>High: Emacs News review, package/workflow demo</li>
<li>Narrating a blog post to make it a video</li>
<li>Categorizing Emacs News, exploring packages</li>
<li>Low: Figuring things out</li>
</ul>

<p>
YouTube can do closed captions for livestreams, although accuracy is
low. Videos take a while to be ready to download.
</p>


</details>
<div id="outline-container-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-experimenting-with-working-out-loud" class="outline-2">
<h3 id="quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-experimenting-with-working-out-loud">Experimenting with working out loud</h3>
<div class="outline-text-2" id="text-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-experimenting-with-working-out-loud">
<p>
I wanted to write <a href="https://emacsconf.org/2023/report">a report on EmacsConf 2023</a> so that we could share it
with speakers, volunteers, participants, donors, related organizations
like the Free Software Foundation, and other communities. I
experimented with livestreaming via YouTube while I worked on the
conference highlights.
</p>

<p>
It's a little over an hour long and probably very boring, but it was
nice of people to drop by and say hello.
</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/KXKA-3JFb14?si=2V5XQmQSUte0ZWyF" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<p>
The main parts are:
</p>

<ul class="org-ul">
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h0m00s">0:00</a>: reading through other conference reports for inspiration</li>
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h6m54s">6:54</a>: writing an overview of the talks</li>
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h13m10s">13:10</a>: adding quotes for specific talks</li>
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h25m00s">25:00</a>: writing about the overall conference</li>
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h32m00s">32:00</a>: squeezing in more highlights</li>
<li><a href="https://www.youtube.com/watch?v=KXKA-3JFb14&t=0h49m00s">49:00</a>: fiddling with the formatting and the export</li>
</ul>

<p>
It mostly worked out, aside from a brief moment of "uhhh, I'm
looking at our private conf.org file on stream". Fortunately, the
e-mail addresses that were showed were the public ones.
</p>
</div>
</div>
<div id="outline-container-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-technical-details" class="outline-2">
<h3 id="quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-technical-details">Technical details</h3>
<div class="outline-text-2" id="text-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-technical-details">
<p>
Setup:
</p>

<ul class="org-ul">
<li><p>
I set up environment variables and screen resolution:
</p>

<div class="org-src-container">
<pre class="src src-sh" id="org8d1d9a1">  <span class="org-comment-delimiter"># </span><span class="org-comment">From pacmd list-sources | egrep '^\s+name'</span>
  <span class="org-variable-name">LAPEL</span>=alsa_input.usb-Jieli_Technology_USB_Composite_Device_433035383239312E-00.mono-fallback <span class="org-comment-delimiter">#</span>
  <span class="org-variable-name">YETI</span>=alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo
  <span class="org-variable-name">SYSTEM</span>=alsa_output.pci-0000_00_1b.0.analog-stereo.monitor
  <span class="org-comment-delimiter"># </span><span class="org-comment">MIC=$LAPEL</span>
  <span class="org-comment-delimiter"># </span><span class="org-comment">AUDIO_WEIGHTS="1 1"</span>
  <span class="org-variable-name">MIC</span>=$<span class="org-variable-name">YETI</span>
  <span class="org-variable-name">AUDIO_WEIGHTS</span>=<span class="org-string">"0.5 0.5"</span>
  <span class="org-variable-name">OFFSET</span>=+1920,430
  <span class="org-variable-name">SIZE</span>=1280x720
  <span class="org-variable-name">SCREEN</span>=LVDS-1  <span class="org-comment-delimiter"># </span><span class="org-comment">from xrandr</span>
  xrandr &#45;&#45;output $<span class="org-variable-name">SCREEN</span> &#45;&#45;mode 1280x720
</pre>
</div>
</li>
<li>I switch to a larger size and a light theme. I also turn <a href="https://github.com/minad/consult">consult</a> previews off to minimize the risk of leaking data through buffer previews.
<details open=""><summary>my-emacsconf-prepare-for-screenshots: Set the resolution, change to a light theme, and make the text bigger.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-emacsconf-prepare-for-screenshots</span> ()
  (<span class="org-keyword">interactive</span>)
  (shell-command <span class="org-string">"xrandr &#45;&#45;output LVDS-1 &#45;&#45;mode 1280x720"</span>)
  (modus-themes-load-theme <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">modus-operandi</span>)
  (my-hl-sexp-update-overlay)
  (set-face-attribute <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">default</span> nil <span class="org-builtin">:height</span> 170)
  (keycast-mode))
</pre></div></details></li>
</ul>

<p>
Testing:
</p>


<div class="org-src-container">
<pre class="src src-sh">ffmpeg -f x11grab -video_size $<span class="org-variable-name">SIZE</span> -i :0.0$<span class="org-variable-name">OFFSET</span> -y /tmp/test.png; display /tmp/test.png
ffmpeg -f pulse -i $<span class="org-variable-name">MIC</span> -f pulse -i $<span class="org-variable-name">SYSTEM</span> -filter_complex <span class="org-variable-name">amix</span>=<span class="org-variable-name">inputs</span>=2:<span class="org-variable-name">weights</span>=$<span class="org-variable-name">AUDIO_WEIGHTS</span>:<span class="org-variable-name">duration</span>=longest:<span class="org-variable-name">normalize</span>=0 -y /tmp/test.mp3; mpv /tmp/test.mp3
<span class="org-variable-name">DATE</span>=$(date <span class="org-string">"+%Y-%m-%d-%H-%M-%S"</span>)
ffmpeg -f x11grab -framerate 30 -video_size $<span class="org-variable-name">SIZE</span> -i :0.0$<span class="org-variable-name">OFFSET</span> -f pulse -i $<span class="org-variable-name">MIC</span> -f pulse -i $<span class="org-variable-name">SYSTEM</span> -filter_complex <span class="org-string">"amix=inputs=2:weights=$AUDIO_WEIGHTS:duration=longest:normalize=0"</span> -c:v libx264 -preset fast -maxrate 690k -bufsize 2000k -g 60 -vf <span class="org-variable-name">format</span>=yuv420p -c:a aac -b:a 96k -y -flags +global_header <span class="org-string">"/home/sacha/recordings/$DATE.flv"</span> -f flv
</pre>
</div>


<p>
Streaming:
</p>


<div class="org-src-container">
<pre class="src src-sh"><span class="org-variable-name">DATE</span>=$(date <span class="org-string">"+%Y-%m-%d-%H-%M-%S"</span>)
ffmpeg -f x11grab -framerate 30 -video_size $<span class="org-variable-name">SIZE</span> -i :0.0$<span class="org-variable-name">OFFSET</span> -f pulse -i $<span class="org-variable-name">MIC</span> -f pulse -i $<span class="org-variable-name">SYSTEM</span> -filter_complex <span class="org-string">"amix=inputs=2:weights=$AUDIO_WEIGHTS:duration=longest:normalize=0[audio]"</span> -c:v libx264 -preset fast -maxrate 690k -bufsize 2000k -g 60 -vf <span class="org-variable-name">format</span>=yuv420p -c:a aac -b:a 96k -y -f tee -map 0:v -map <span class="org-string">'[audio]'</span> -flags +global_header  <span class="org-string">"/home/sacha/recordings/$DATE.flv|[f=flv]rtmp://a.rtmp.youtube.com/live2/$YOUTUBE_KEY"</span>
</pre>
</div>


<p>
To restore my previous setup:
</p>

<p>
</p><details open=""><summary>my-emacsconf-back-to-normal: Go back to a more regular setup.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-emacsconf-back-to-normal</span> ()
  (<span class="org-keyword">interactive</span>)
  (shell-command <span class="org-string">"xrandr &#45;&#45;output LVDS-1 &#45;&#45;mode 1366x768"</span>)
  (modus-themes-load-theme <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">modus-vivendi</span>)
  (my-hl-sexp-update-overlay)
  (set-face-attribute <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">default</span> nil <span class="org-builtin">:height</span> 115)
  (keycast-mode -1))
</pre></div></details>
<p></p>
</div>
</div>
<div id="outline-container-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-ideas-for-next-steps" class="outline-2">
<h3 id="quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-ideas-for-next-steps">Ideas for next steps</h3>
<div class="outline-text-2" id="text-quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t-ideas-for-next-steps">
<p>
I can think of a few workflow tweaks that might be fun:
</p>

<ul class="org-ul">
<li>a stream notes buffer on the right side of the screen for context
information, timestamped notes to make editing/review easier (maybe
using <code>org-timer</code>), etc. I experimented with some streaming-related
code in my config, so I can dust that off and see what that's like.
I also want to have an <code>org-capture</code> template for it so that I can add
notes from anywhere.</li>
<li>a quick way to <a href="https://sachachua.com/blog/2024/01/using-puppeteer-to-grab-an-image-from-the-supernote-s-screen-mirror/">add a screenshot from my Supernote</a> to my Org files</li>
</ul>

<p>
I think I'll try going through an informal presentation or Emacs News as my next livestream experiment, since that's probably higher information density.
</p>
</div>
</div>
<div><a href="https://sachachua.com/blog/2024/01/quick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t/index.org">View org source for this post</a></div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2024%2F01%2Fquick-notes-on-livestreaming-to-youtube-with-ffmpeg-on-a-lenovo-x230t%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Getting live speech into Emacs with Deepgram's streaming API</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2023/12/live-speech-with-deepgram/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2023-12-19T15:11:56Z</updated>
    <published>2023-12-19T15:11:56Z</published>
    <category term="speechtotext" />
<category term="emacs" />
<category term="speech" />
<category term="streaming" />
		<id>https://sachachua.com/blog/2023/12/live-speech-with-deepgram/</id>
		<content type="html"><![CDATA[<div class="update" id="org1a865c9">
<ul class="org-ul">
<li><span class="timestamp-wrapper"><span class="timestamp">[2023-12-26 Tue]</span></span>: Reorganized code to call a list of functions and pass the recognition results. Added Etherpad. Took out the mode; will just use the functions. Related: <a href="https://sachachua.com/blog/2023/12/yay-i-can-get-live-speech-recognition-results-from-emacs-to-etherpad/">getting live speech from Emacs into Etherpad</a></li>
</ul>

</div>

<p>
This is a quick demonstration of using <a href="https://developers.deepgram.com/reference/streaming">Deepgram's streaming API</a> to do
speech recognition live. It isn't as accurate as OpenAI Whisper but
since Whisper doesn't have a streaming API, it'll do for now. I can
correct misrecognized words manually. I tend to talk really quickly,
so it displays the words per minute in my modeline. I put the words
into an Org Mode buffer so I can toggle headings with avy and cycle
visibility. When I'm done, it saves the text, JSON, and WAV for
further processing. I think it'll be handy to have a quick way to take
live notes during interviews or when I'm thinking out loud. Could be
fun!
</p>

<p>
<video controls=""><source src="https://sachachua.com/blog/2023/12/live-speech-with-deepgram/2023-12-19-live-speech.webm" type="video/webm"><a href="https://sachachua.com/blog/2023/12/live-speech-with-deepgram/2023-12-19-live-speech.webm">Download the video</a></video>
</p>

<p>
I'm still getting some weirdness when the mode turns on when I don't
expect it, so that's something to look into. Maybe I won't use it as a
mode for now. I'll just use <code>my-live-speech-start</code> and
<code>my-live-speech-stop</code>.
</p>
<div id="outline-container-org2fdb909" class="outline-2">
<h3 id="org2fdb909">General code</h3>
<div class="outline-text-2" id="text-org2fdb909">

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-buffer</span> <span class="org-string">"*Speech*"</span>)
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-process</span> nil)
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-output-buffer</span> <span class="org-string">"*Speech JSON*"</span>)

(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-functions</span>
  <span class="org-highlight-quoted-quote">'</span>(my-live-speech-display-in-speech-buffer
    my-live-speech-display-wpm
    my-live-speech-append-to-etherpad)
  <span class="org-doc">"Functions to call with one argument, the recognition results."</span>)

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-start</span> ()
  <span class="org-doc">"Turn on live captions."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">with-current-buffer</span> (get-buffer-create my-live-speech-buffer)
    (<span class="org-keyword">unless</span> (process-live-p my-live-speech-process)
      (<span class="org-keyword">let</span> ((default-directory <span class="org-string">"~/proj/deepgram-live"</span>))
        (message <span class="org-string">"%s"</span> default-directory)
        (<span class="org-keyword">with-current-buffer</span> (get-buffer-create my-live-speech-output-buffer)
          (erase-buffer))
        (<span class="org-keyword">setq</span> my-live-speech-recent-words nil
              my-live-speech-wpm-string <span class="org-string">"READY "</span>)
        (<span class="org-keyword">setq</span> my-deepgram-process
              (make-process
               <span class="org-builtin">:command</span> <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"bash"</span> <span class="org-string">"run.sh"</span>)
               <span class="org-builtin">:name</span> <span class="org-string">"speech"</span>
               <span class="org-builtin">:filter</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-live-speech-json-filter</span>
               <span class="org-builtin">:sentinel</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-live-speech-process-sentinel</span>
               <span class="org-builtin">:buffer</span> my-live-speech-output-buffer)))
      (org-mode))
    (display-buffer (current-buffer))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-stop</span> ()
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">if</span> (process-live-p my-live-speech-process)
      (kill-process my-live-speech-process))
  (<span class="org-keyword">setq</span> my-live-speech-wpm-string nil))

<span class="org-comment-delimiter">;; </span><span class="org-comment">(define-minor-mode my-live-speech-mode</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment"> "Show live speech and display WPM.</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">Need to check how to reliably turn this on and off."</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment"> :global t :group 'sachac</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment"> (if my-live-speech-mode</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">     (my-live-speech-start)</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">   (my-live-speech-stop)</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">   (setq my-live-speech-wpm-string nil)))</span>

<span class="org-comment-delimiter">;; </span><span class="org-comment">based on subed-mpv::client-filter</span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-handle-json</span> (line-object)
  <span class="org-doc">"Process the JSON object in LINE."</span>
  (run-hook-with-args <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-live-speech-functions</span> (json-parse-string line <span class="org-builtin">:object-type</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">alist</span>)))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-process-sentinel</span> (proc event)
  (<span class="org-keyword">when</span> (string-match <span class="org-string">"finished"</span> event)
    (my-live-speech-stop)
    <span class="org-comment-delimiter">;</span><span class="org-comment">(my-live-speech-mode -1)</span>
    ))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-json-filter</span> (proc string)
  (<span class="org-keyword">when</span> (buffer-live-p (process-buffer proc))
    (<span class="org-keyword">with-current-buffer</span> (process-buffer proc)
      (<span class="org-keyword">let*</span> ((proc-mark (process-mark proc))
             (moving (= (point) proc-mark)))
        <span class="org-comment-delimiter">;;  </span><span class="org-comment">insert the output</span>
        (<span class="org-keyword">save-excursion</span>
          (goto-char proc-mark)
          (insert string)
          (set-marker proc-mark (point)))
        (<span class="org-keyword">if</span> moving (goto-char proc-mark))
        <span class="org-comment-delimiter">;; </span><span class="org-comment">process and remove all complete lines of JSON (lines are complete if ending with \n)</span>
        (<span class="org-keyword">let</span> ((pos (point-min)))
          (<span class="org-keyword">while</span> (<span class="org-keyword">progn</span> (goto-char pos)
                        (end-of-line)
                        (equal (following-char) ?\n))
            (<span class="org-keyword">let*</span> ((end (point))
                   (line (buffer-substring pos end)))
              (delete-region pos (+ end 1))
              (<span class="org-keyword">with-current-buffer</span> (get-buffer my-live-speech-buffer)
                (my-live-speech-handle-json line)))))))))
</pre>
</div>


<p>
Python code based on <a href="https://developers.deepgram.com/docs/getting-started-with-the-streaming-test-suite">the Deepgram streaming test suite</a>:
</p>

<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>Very rough app.py</strong></summary>

<div class="org-src-container">
<pre class="src src-python"><span class="org-comment-delimiter"># </span><span class="org-comment">Based on streaming-test-suite</span>
<span class="org-comment-delimiter"># </span><span class="org-comment">https://developers.deepgram.com/docs/getting-started-with-the-streaming-test-suite</span>

<span class="org-keyword">import</span> pyaudio
<span class="org-keyword">import</span> asyncio
<span class="org-keyword">import</span> json
<span class="org-keyword">import</span> os
<span class="org-keyword">import</span> websockets
<span class="org-keyword">from</span> datetime <span class="org-keyword">import</span> datetime
<span class="org-keyword">import</span> wave
<span class="org-keyword">import</span> sys

<span class="org-variable-name">startTime</span> <span class="org-operator">=</span> datetime.now()
<span class="org-variable-name">key</span> <span class="org-operator">=</span> os.environ[<span class="org-string">'DEEPGRAM_API_KEY'</span>]
<span class="org-variable-name">live_json</span> <span class="org-operator">=</span> os.environ.get(<span class="org-string">'LIVE_CAPTIONS_JSON'</span>, <span class="org-constant">True</span>)
<span class="org-variable-name">all_mic_data</span> <span class="org-operator">=</span> []
<span class="org-variable-name">all_transcripts</span> <span class="org-operator">=</span> []
<span class="org-variable-name">all_words</span> <span class="org-operator">=</span> []
<span class="org-variable-name">FORMAT</span> <span class="org-operator">=</span> pyaudio.paInt16
<span class="org-variable-name">CHANNELS</span> <span class="org-operator">=</span> 1
<span class="org-variable-name">RATE</span> <span class="org-operator">=</span> 16000
<span class="org-variable-name">CHUNK</span> <span class="org-operator">=</span> 8000

<span class="org-variable-name">audio_queue</span> <span class="org-operator">=</span> asyncio.Queue()
<span class="org-variable-name">REALTIME_RESOLUTION</span> <span class="org-operator">=</span> 0.250
<span class="org-variable-name">SAMPLE_SIZE</span> <span class="org-operator">=</span> 0

<span class="org-keyword">def</span> <span class="org-function-name">save_info</span>():
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">global</span> SAMPLE_SIZE
<span class="org-highlight-indentation"> </span>   <span class="org-variable-name">base</span> <span class="org-operator">=</span> startTime.strftime(<span class="org-string">'%Y%m%d%H%M'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-variable-name">wave_file_path</span> <span class="org-operator">=</span> os.path.abspath(f<span class="org-string">"</span>{base}<span class="org-string">.wav"</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-variable-name">wave_file</span> <span class="org-operator">=</span> wave.<span class="org-builtin">open</span>(wave_file_path, <span class="org-string">"wb"</span>)
<span class="org-highlight-indentation"> </span>   wave_file.setnchannels(CHANNELS)
<span class="org-highlight-indentation"> </span>   wave_file.setsampwidth(SAMPLE_SIZE)
<span class="org-highlight-indentation"> </span>   wave_file.setframerate(RATE)
<span class="org-highlight-indentation"> </span>   wave_file.writeframes(b<span class="org-string">""</span>.join(all_mic_data))
<span class="org-highlight-indentation"> </span>   wave_file.close()
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">with</span> <span class="org-builtin">open</span>(f<span class="org-string">"</span>{base}<span class="org-string">.txt"</span>, <span class="org-string">"w"</span>) <span class="org-keyword">as</span> f:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   f.write(<span class="org-string">"</span><span class="org-constant">\n</span><span class="org-string">"</span>.join(all_transcripts))
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">with</span> <span class="org-builtin">open</span>(f<span class="org-string">"</span>{base}<span class="org-string">.json"</span>, <span class="org-string">"w"</span>) <span class="org-keyword">as</span> f:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   f.write(json.dumps(all_words))
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">'{{"msg": "&#128994; Saved to </span>{base}<span class="org-string">.txt , </span>{base}<span class="org-string">.json , </span>{base}<span class="org-string">.wav", "base": "</span>{base}<span class="org-string">"}}'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">"&#128994; Saved to </span>{base}<span class="org-string">.txt , </span>{base}<span class="org-string">.json , </span>{base}<span class="org-string">.wav"</span>)

<span class="org-comment-delimiter"># </span><span class="org-comment">Used for microphone streaming only.</span>
<span class="org-keyword">def</span> <span class="org-function-name">mic_callback</span>(input_data, frame_count, time_info, status_flag):
<span class="org-highlight-indentation"> </span>   audio_queue.put_nowait(input_data)
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">return</span> (input_data, pyaudio.paContinue)

<span class="org-keyword">async def</span> <span class="org-function-name">run</span>(key, method<span class="org-operator">=</span><span class="org-string">"mic"</span>, <span class="org-builtin">format</span><span class="org-operator">=</span><span class="org-string">"text"</span>, <span class="org-operator">**</span>kwargs):
<span class="org-highlight-indentation"> </span>   <span class="org-variable-name">deepgram_url</span> <span class="org-operator">=</span> f<span class="org-string">'wss://api.deepgram.com/v1/listen?punctuate=true&amp;smart_format=true&amp;utterances=true&amp;encoding=linear16&amp;sample_rate=16000'</span>
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">async with</span> websockets.connect(
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   deepgram_url, extra_headers<span class="org-operator">=</span>{<span class="org-string">"Authorization"</span>: <span class="org-string">"Token {}"</span>.<span class="org-builtin">format</span>(key)}
<span class="org-highlight-indentation"> </span>   ) <span class="org-keyword">as</span> ws:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">async def</span> <span class="org-function-name">sender</span>(ws):
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">try</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">while</span> <span class="org-constant">True</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">mic_data</span> <span class="org-operator">=</span> <span class="org-keyword">await</span> audio_queue.get()
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   all_mic_data.append(mic_data)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">await</span> ws.send(mic_data)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> websockets.exceptions.ConnectionClosedOK:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">await</span> ws.send(json.dumps({<span class="org-string">"type"</span>: <span class="org-string">"CloseStream"</span>}))
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">'{"msg": "Closed."}'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">"Closed."</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">async def</span> <span class="org-function-name">receiver</span>(ws):
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">global</span> all_words
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-string">"""Print out the messages received from the server."""</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">first_message</span> <span class="org-operator">=</span> <span class="org-constant">True</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">first_transcript</span> <span class="org-operator">=</span> <span class="org-constant">True</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">transcript</span> <span class="org-operator">=</span> <span class="org-string">""</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">async for</span> msg <span class="org-keyword">in</span> ws:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">res</span> <span class="org-operator">=</span> json.loads(msg)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> first_message:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">first_message</span> <span class="org-operator">=</span> <span class="org-constant">False</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">try</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">handle local server messages</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> res.get(<span class="org-string">"msg"</span>):
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(json.dumps(res))
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(res[<span class="org-string">"msg"</span>])
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> res.get(<span class="org-string">"is_final"</span>):
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">transcript</span> <span class="org-operator">=</span> (
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   res.get(<span class="org-string">"channel"</span>, {})
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   .get(<span class="org-string">"alternatives"</span>, [{}])[0]
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   .get(<span class="org-string">"transcript"</span>, <span class="org-string">""</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   )
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> transcript <span class="org-operator">!=</span> <span class="org-string">""</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> first_transcript:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">first_transcript</span> <span class="org-operator">=</span> <span class="org-constant">False</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(json.dumps(res.get(<span class="org-string">"channel"</span>, {}).get(<span class="org-string">"alternatives"</span>, [{}])[0]))
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(transcript)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   all_transcripts.append(transcript)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">all_words</span> <span class="org-operator">=</span> all_words <span class="org-operator">+</span> res.get(<span class="org-string">"channel"</span>, {}).get(<span class="org-string">"alternatives"</span>, [{}])[0].get(<span class="org-string">"words"</span>, [])
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">if using the microphone, close stream if user says "goodbye"</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> method <span class="org-operator">==</span> <span class="org-string">"mic"</span> <span class="org-keyword">and</span> <span class="org-string">"goodbye"</span> <span class="org-keyword">in</span> transcript.lower():
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">await</span> ws.send(json.dumps({<span class="org-string">"type"</span>: <span class="org-string">"CloseStream"</span>}))
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">'{"msg": "Done."}'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">"Done."</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">handle end of stream</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> res.get(<span class="org-string">"created"</span>):
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   save_info()
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> <span class="org-type">KeyError</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">"&#128308; ERROR: Received unexpected API response! </span>{msg}<span class="org-string">"</span>)

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">Set up microphone if streaming from mic</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">async def</span> <span class="org-function-name">microphone</span>():
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">audio</span> <span class="org-operator">=</span> pyaudio.PyAudio()
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">stream</span> <span class="org-operator">=</span> audio.<span class="org-builtin">open</span>(
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">format</span><span class="org-operator">=</span>FORMAT,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   channels<span class="org-operator">=</span>CHANNELS,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   rate<span class="org-operator">=</span>RATE,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">input</span><span class="org-operator">=</span><span class="org-constant">True</span>,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   frames_per_buffer<span class="org-operator">=</span>CHUNK,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   stream_callback<span class="org-operator">=</span>mic_callback,
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   )

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   stream.start_stream()

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">global</span> SAMPLE_SIZE
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">SAMPLE_SIZE</span> <span class="org-operator">=</span> audio.get_sample_size(FORMAT)

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">while</span> stream.is_active():
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">await</span> asyncio.sleep(0.1)

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   stream.stop_stream()
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   stream.close()

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">functions</span> <span class="org-operator">=</span> [
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   asyncio.ensure_future(sender(ws)),
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   asyncio.ensure_future(receiver(ws)),
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   ]

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   functions.append(asyncio.ensure_future(microphone()))
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> live_json:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">'{"msg": "Ready."}'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">else</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(<span class="org-string">"&#128994; Ready."</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">await</span> asyncio.gather(<span class="org-operator">*</span>functions)

<span class="org-keyword">def</span> <span class="org-function-name">main</span>():
<span class="org-highlight-indentation"> </span>   <span class="org-doc">"""Entrypoint for the example."""</span>
<span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">Parse the command-line arguments.</span>
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">try</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   asyncio.run(run(key, <span class="org-string">"mic"</span>, <span class="org-string">"text"</span>))
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> websockets.exceptions.InvalidStatusCode <span class="org-keyword">as</span> e:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">'&#128308; ERROR: Could not connect to Deepgram! </span>{e.headers.get("dg<span class="org-operator">-</span>error")}<span class="org-string">'</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   f<span class="org-string">'&#128308; Please contact Deepgram Support (developers@deepgram.com) with request ID </span>{e.headers.get("dg<span class="org-operator">-</span>request<span class="org-operator">-</span><span class="org-builtin">id</span>")}<span class="org-string">'</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   )
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">return</span>
<span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> websockets.exceptions.ConnectionClosedError <span class="org-keyword">as</span> e:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">error_description</span> <span class="org-operator">=</span> f<span class="org-string">"Unknown websocket error."</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   f<span class="org-string">"&#128308; ERROR: Deepgram connection unexpectedly closed with code </span>{e.code}<span class="org-string"> and payload </span>{e.reason}<span class="org-string">"</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   )

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">if</span> e.reason <span class="org-operator">==</span> <span class="org-string">"DATA-0000"</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">error_description</span> <span class="org-operator">=</span> <span class="org-string">"The payload cannot be decoded as audio. It is either not audio data or is a codec unsupported by Deepgram."</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">elif</span> e.reason <span class="org-operator">==</span> <span class="org-string">"NET-0000"</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">error_description</span> <span class="org-operator">=</span> <span class="org-string">"The service has not transmitted a Text frame to the client within the timeout window. This may indicate an issue internally in Deepgram's systems or could be due to Deepgram not receiving enough audio data to transcribe a frame."</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">elif</span> e.reason <span class="org-operator">==</span> <span class="org-string">"NET-0001"</span>:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-variable-name">error_description</span> <span class="org-operator">=</span> <span class="org-string">"The service has not received a Binary frame from the client within the timeout window. This may indicate an internal issue in Deepgram's systems, the client's systems, or the network connecting them."</span>

<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">"&#128308; </span>{error_description}<span class="org-string">"</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">TODO: update with link to streaming troubleshooting page once available</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-comment-delimiter"># </span><span class="org-comment">print(f'&#128308; Refer to our troubleshooting suggestions: ')</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   f<span class="org-string">"&#128308; Please contact Deepgram Support (developers@deepgram.com) with the request ID listed above."</span>
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   )
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">return</span>

<span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> websockets.exceptions.ConnectionClosedOK:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">return</span>

<span class="org-highlight-indentation"> </span>   <span class="org-keyword">except</span> <span class="org-type">Exception</span> <span class="org-keyword">as</span> e:
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-builtin">print</span>(f<span class="org-string">"&#128308; ERROR: Something went wrong! </span>{e}<span class="org-string">"</span>)
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   save_info()
<span class="org-highlight-indentation"> </span>   <span class="org-highlight-indentation"> </span>   <span class="org-keyword">return</span>


<span class="org-keyword">if</span> <span class="org-builtin">__name__</span> <span class="org-operator">==</span> <span class="org-string">"__main__"</span>:
<span class="org-highlight-indentation"> </span>   sys.<span class="org-constant">exit</span>(main() <span class="org-keyword">or</span> 0)
</pre>
</div>



</details>

<p>
The Python script sends the microphone stream to Deepgram and prints
out the JSON output. The Emacs Lisp code starts an asynchronous
process and reads the JSON output, displaying the transcript and
calculating the WPM based on the words. run.sh just loads the venv for
this project (requirements.txt based on the streaming text suite) and
then runs app.py, since some of the Python library versions conflict
with other things I want to experiment with.
</p>

<p>
I also added
<code>my-live-speech-wpm-string</code> to my <code>mode-line-format</code> manually using
Customize, since I wanted it displayed on the left side instead of
getting lost when I turn <code>keycast-mode</code> on.
</p>

<p>
I'm still a little anxious about accidentally leaving a process
running, so I check with <code>ps aux | grep python3</code>. Eventually I'll
figure out how to make sure everything gets properly stopped when I'm
done.
</p>

<p>
Anyway, there it is!
</p>
</div>
</div>
<div id="outline-container-orgcaa12c0" class="outline-2">
<h3 id="orgcaa12c0">Display in speech buffer</h3>
<div class="outline-text-2" id="text-orgcaa12c0">

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-display-in-speech-buffer</span> (recognition-results)
  (<span class="org-keyword">with-current-buffer</span> (get-buffer-create my-live-speech-buffer)
    (<span class="org-keyword">let-alist</span> recognition-results
      (<span class="org-keyword">let*</span> ((pos (point))
             (at-end (eobp)))
        (goto-char (point-max))
        (<span class="org-keyword">unless</span> (eolp) (insert <span class="org-string">"\n"</span>))
        (<span class="org-keyword">when</span> .msg
          (insert .msg <span class="org-string">"\n"</span>))
        (<span class="org-keyword">when</span> .transcript
          (insert .transcript <span class="org-string">"\n"</span>))
        <span class="org-comment-delimiter">;; </span><span class="org-comment">scroll to the bottom if being displayed</span>
        (<span class="org-keyword">if</span> at-end
            (<span class="org-keyword">when</span> (get-buffer-window (current-buffer))
              (set-window-point (get-buffer-window (current-buffer)) (point)))
          (goto-char pos))))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-toggle-heading</span> ()
  <span class="org-doc">"Toggle a line as a heading."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">with-current-buffer</span> (get-buffer my-live-speech-buffer)
    (display-buffer (current-buffer))
    (<span class="org-keyword">with-selected-window</span> (get-buffer-window (get-buffer my-live-speech-buffer))
      (<span class="org-keyword">let</span> ((avy-all-windows nil))
        (avy-goto-line 1))
      (org-toggle-heading 1))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-cycle-visibility</span> ()
  <span class="org-doc">"Get a quick overview."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">with-current-buffer</span> (get-buffer my-live-speech-buffer)
    (display-buffer (current-buffer))
    (<span class="org-keyword">if</span> (eq org-cycle-global-status <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">contents</span>)
        (<span class="org-keyword">progn</span>
          (run-hook-with-args <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-cycle-pre-hook</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">all</span>)
          (org-fold-show-all <span class="org-highlight-quoted-quote">'</span>(headings blocks))
          (<span class="org-keyword">setq</span> org-cycle-global-status <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">all</span>)
          (run-hook-with-args <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-cycle-hook</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">all</span>))
      (run-hook-with-args <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-cycle-pre-hook</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">contents</span>)
      (org-cycle-content)
      (<span class="org-keyword">setq</span> org-cycle-global-status <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">contents</span>)
      (run-hook-with-args <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-cycle-hook</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">contents</span>))))
</pre>
</div>

</div>
</div>
<div id="outline-container-org45e1834" class="outline-2">
<h3 id="org45e1834">Display words per minute</h3>
<div class="outline-text-2" id="text-org45e1834">

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-wpm-window-seconds</span> 15 <span class="org-doc">"How many seconds to calculate WPM for."</span>)
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-recent-words</span> nil <span class="org-doc">"Words spoken in `</span><span class="org-doc"><span class="org-constant">my-live-speech-wpm-window-minutes</span></span><span class="org-doc">'."</span>)
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-wpm</span> nil <span class="org-doc">"Current WPM."</span>)
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-wpm-colors</span>  <span class="org-comment-delimiter">; </span><span class="org-comment">haven't figured out how to make these work yet</span>
  <span class="org-highlight-quoted-quote">'</span>((180 <span class="org-builtin">:foreground</span> <span class="org-string">"red"</span>)
    (170 <span class="org-builtin">:foreground</span> <span class="org-string">"yellow"</span>)
    (160 <span class="org-builtin">:foreground</span> <span class="org-string">"green"</span>)))
(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-wpm-string</span> nil <span class="org-doc">"Add this somewhere in `</span><span class="org-doc"><span class="org-constant">mode-line-format</span></span><span class="org-doc">'."</span>)
(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-wpm-string</span> ()
  (propertize
   (format <span class="org-string">"%d WPM "</span> my-live-speech-wpm)
   <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">face</span>
   (cdr (seq-find (<span class="org-keyword">lambda</span> (row) (&gt; my-live-speech-wpm (car row))) my-live-speech-wpm-colors))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-display-wpm</span> (recognition-results)
  (<span class="org-keyword">let-alist</span> recognition-results
    (<span class="org-keyword">when</span> .words
      <span class="org-comment-delimiter">;; </span><span class="org-comment">calculate WPM</span>
      (<span class="org-keyword">setq</span> my-live-speech-recent-words
            (append my-live-speech-recent-words .words nil))
      (<span class="org-keyword">let</span> ((threshold (- (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">end</span> (aref .words (1- (length .words))))
                          my-live-speech-wpm-window-seconds)))
        (<span class="org-keyword">setq</span> my-live-speech-recent-words
              (seq-filter
               (<span class="org-keyword">lambda</span> (o)
                 (&gt;= (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">start</span> o)
                     threshold))
               my-live-speech-recent-words))
        (<span class="org-keyword">setq</span> my-live-speech-wpm
              (/
               (length my-live-speech-recent-words)
               (/ (- (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">end</span> (aref .words (1- (length .words))))
                     (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">start</span> (car my-live-speech-recent-words)))
                  60.0)))
        (<span class="org-keyword">setq</span> my-live-speech-wpm-string (my-live-speech-wpm-string))))))
</pre>
</div>

</div>
</div>
<div id="outline-container-org5aa4c56" class="outline-2">
<h3 id="org5aa4c56">Append to EmacsConf Etherpad</h3>
<div class="outline-text-2" id="text-org5aa4c56">

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-live-speech-etherpad-id</span> nil)
(<span class="org-keyword">defun</span> <span class="org-function-name">my-live-speech-append-to-etherpad</span> (recognition-results)
  (<span class="org-keyword">when</span> my-live-speech-etherpad-id
    (emacsconf-pad-append-text my-live-speech-etherpad-id (concat <span class="org-string">" "</span> (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">transcript</span> recognition-results)))))
</pre>
</div>

</div>
</div>

<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#live-speech">Emacs configuration.</a></div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2023%2F12%2Flive-speech-with-deepgram%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Emacs Conf video tech notes: jit.si, twitch.tv, livestreamer, ffmpeg</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2015/09/emacs-conf-video-tech-notes-jit-si-twitch-tv-livestreamer-ffmpeg/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2015-09-05T02:59:44Z</updated>
    <published>2015-09-06T12:00:00Z</published>
    <category term="emacs" />
<category term="geek" />
<category term="ffmpeg" />
<category term="streaming" />
		<id>https://sachachua.com/blog/?p=28395</id>
		<content type="html"><![CDATA[<p>Last week&#8217;s Emacs Conf was fantastic. There were lots of people at the in-person event in San Francisco, and people could also watch the stream through twitch.tv and ask questions through IRC. There were remote speakers and in-person speakers, and that mix even worked for the impromptu lightning talks sprinkled throughout the day.</p>
<p>This is how the tech worked:</p>
<ul class="org-ul">
<li>Before the conference started, the organizers set up a laptop for streaming on <a href="http://twitch.tv/emacsconf">twitch.tv/emacsconf</a>. This was hooked up to the main display (a large television with speakers). They also configured the account to record and archive videos. In the free account, recorded videos are available for 14 days.</li>
<li>Remote speakers were brought in using the <a href="http://jitsi.org/">Jitsi</a> open source video conferencing system, using the public servers at <a href="http://meet.jit.si/">meet.jit.si</a>. This was on the same computer that did the twitch.tv streaming, so people watching the stream could see whatever was shared through Jitsi. Organizers read out questions from the in-person audience and from the IRC channel. The audio from Jitsi wasn&#8217;t directly available through twitch.tv, though. Instead, the audio came in as a recording from the laptop&#8217;s microphone.</li>
<li>Local speakers either used the streaming laptop to go to a specific webpage they wanted to talk about, or joined the Jitsi web conference using Google Chrome or Chromium so that they could share their screen. The organizers muted the second Jitsi client to avoid audio feedback loops.</li>
</ul>
<p>That worked out really well. There were more than a hundred remote viewers. As one of them, I can definitely rate the experience as surprisingly smooth.</p>
<p>All that&#8217;s left now is to figure out how to make a more lasting archive of the Emacs Conf videos. As it turns out, twitch.tv or online tools don&#8217;t make it easy to download stream recordings that are longer than three hours. Fortunately, <code>livestreamer</code> can handle the job. Here&#8217;s what I did to download the timestream data from one of the recordings of EmacsConf:</p>
<div class="org-src-container">
<pre class="src src-sh">livestreamer -o emacsconf-1.ts &#45;&#45;hls-segment-threads 4 http://www.twitch.tv/emacsconf/v/13421774 best
ffmpeg -i emacsconf-1.ts -acodec copy -absf aac_adtstoasc -vcodec copy emacsconf-1.mp4
</pre>
</div>
<p>I normally use Camtasia Studio to edit videos, but for some reason, it kept flaking out on me today. After the umpteenth crash, I decided to keep things simple by using <code>ffmpeg</code> to extract the relevant part of the video. To extract a segment, you can use <code>-ss</code> to specify the start time and <code>t</code> to specify the duration. Here&#8217;s a sample command:</p>
<div class="org-src-container">
<pre class="src src-sh">ffmpeg -i emacsconf-1.mp4 -ss 1:18:06.11 -t 0:03:32.29 -c:v copy -c:a copy emacsconf-engine-mode.mp4
</pre>
</div>
<p>Your version of ffmpeg might have a <code>-to</code> option, which would let you specify the end time instead of using <code>-t</code> to specify duration.</p>
<p>I&#8217;m coordinating with the other organizers to see if there&#8217;s a better way to process the videos, so that&#8217;s why we haven&#8217;t released them publicly yet. (Soon!) It would be nice to improve the audio, especially for some of the talks, and maybe it would be good to add overlays or zoom in as well. The on-site organizers captured backup videos and screen recordings, too, so we might want to edit some of those clips into the streamed recording. One of the organizers has access to better video editing tools, so we&#8217;ll try that out.</p>
<p>Anyway, those were the commands that helped me get started with command-line conversion and editing of Twitch.tv recorded videos. Hope they come in handy for other people too.</p>
<p>For more info about EmacsConf 2015, check out <a href="http://emacsconf2015.org/">http://emacsconf2015.org/</a>. There&#8217;ll probably be an announcement there once the videos are up. =)</p>
<p>Hat tip to <a href="https://www.reddit.com/r/Twitch/comments/36qtwg/is_there_any_way_of_currently_download_a_past_vod/">Reddit</a> and <a href="http://superuser.com/questions/377343/cut-part-from-video-file-from-start-position-to-end-position-with-ffmpeg">superuser.com</a> for tips.</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2015%2F09%2Femacs-conf-video-tech-notes-jit-si-twitch-tv-livestreamer-ffmpeg%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">My new Google Hangouts On Air checklist, plus upcoming Nov 29 Q&amp;A on learning</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2013/11/my-new-google-hangouts-on-air-workflow-plus-upcoming-nov-29-qa-on-learning/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2013-11-17T01:43:35Z</updated>
    <published>2013-11-18T13:00:00Z</published>
    <category term="process" />
<category term="youtube" />
<category term="streaming" />
		<id>https://sachachua.com/blog/?p=26454</id>
		<content type="html"><![CDATA[<p><a href="http://hangouts.google.com/onair">Google Hangouts On Air</a> is a quick, free way to have a videocast with up to 10 participants and as many passive watchers as you want, thanks to streaming through YouTube. The stream is about 20 seconds delayed and the commenting interface is still kinda raw, but as a quick way to set up and broadcast video chats, it’s hard to beat that.</p>
<p>I picked up a lot of great ideas from Pat Flynn’s first Q&amp;A Hangout. He used <a href="http://chatwing.com">ChatWing</a> to set up a chat room that everyone could join, and the experience was much smoother than using CommentTracker or something like that.</p>
<p>Here’s my new Google Hangouts On Air workflow for the <a href="https://sachachua.com/blog/emacs-chat">Emacs Chats</a> series I’ve been doing. Since the Emacs crowd is fairly technical, I used IRC as my chat room, with a <a href="https://webchat.freenode.net">web interface</a> for others who didn’t have an IRC client handy. (Naturally, I used ERC to chat on IRC from within Emacs.) Having other people around worked out really well, because I could take a break and ask other people’s questions. =)</p>
<p><a href="https://sachachua.com/blog/wp-content/uploads/2013/11/2013-11-03-My-new-Google-Hangouts-workflow.png"><img loading="lazy" style="background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border: 0px;" title="2013-11-03 My new Google Hangouts workflow" alt="2013-11-03 My new Google Hangouts workflow" src="https://sachachua.com/blog/wp-content/uploads/2013/11/2013-11-03-My-new-Google-Hangouts-workflow_thumb.png" width="640" height="486" border="0"></a></p>
<p>The other new thing I tried this time around was starting the broadcast really early (like, half an hour early) and setting it to share my screen with the coming-soon information, which meant that I could post the streaming URL in lots of different places.</p>
<p>I’d like to expand this to doing regular Hangouts On Air Q&amp;As or conversations. How about we use the <a href="http://sach.ac/live">sach.ac/live</a> URL to point to the next Hangout On Air I’ve scheduled? As of this writing, this will be a Q&amp;A on <a href="https://plus.google.com/events/c8i73ks3ruoi962ich22566s5tg">November 29 on learning and note-taking</a>. We’ll probably stream it over YouTube and have a chat room for discussion/Q&amp;A. Want to pick my brain? See you then!</p>
<p>(Want more one-on-one help? <a href="http://sach.ac/help">Book a Helpout session</a> – there’s a nominal charge to keep slots available instead of letting no-shows book them all.)</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2013%2F11%2Fmy-new-google-hangouts-on-air-workflow-plus-upcoming-nov-29-qa-on-learning%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry>
</feed>