{"id":229,"date":"2017-09-09T00:38:09","date_gmt":"2017-09-09T06:38:09","guid":{"rendered":"http:\/\/www.blog.jay-greco.com\/wp\/?p=229"},"modified":"2017-09-09T00:38:09","modified_gmt":"2017-09-09T06:38:09","slug":"downloading-saving-a-nest-cam-live-stream-using-a-raspberry-pi-debian-linux","status":"publish","type":"post","link":"http:\/\/www.blog.jay-greco.com\/wp\/?p=229","title":{"rendered":"Downloading &#038; Saving a Nest Cam Live Stream Using a Raspberry Pi + Debian Linux"},"content":{"rendered":"<p>Tonight, I stumbled on an interesting <a href=\"https:\/\/www.reddit.com\/r\/TropicalWeather\/comments\/6yx703\/live_nest_cameras_in_miami_florida_for_hurricane\/\">post on Reddit<\/a>. It linked to two Nest Cams livestreaming the landfall of hurricane Irma from a Miami condo. I popped the streams open on my phone (the hurricane had not hit yet, and the sky was mostly clear) and thought about the fact that I was probably going to fall asleep before the power went out and killed the stream.<\/p>\n<p>Being an incredibly good-looking and overconfident dweeb, I then thought about the fact that there&#8217;s got to be a good way to rip and save a Nest Cam livestream so that I could watch it tomorrow, maybe post a time-lapse, and gain all of the Karma. After all, its already been done with tons of other streams from various sites. It seemed like a decent Friday night hackathon in the making, and would at least solve the problem of me falling asleep lest I fail.<\/p>\n<p>I decided I wanted something that was (1) automatic, and (2) running in the background so that I didn&#8217;t need to stay up all night or keep my computer on. I ran through the list of options in my head:<\/p>\n<ol>\n<li>Just stream them in a browser window and use screen capture (lame and fails both #1 and #2),<\/li>\n<li>Use one of the millions of Chrome or Firefox plugins that allows for saving of streams (extra lame and still fails #2),<\/li>\n<li>Use some sort of stream-ripping software built for Linux so I could load it on my always-running Pi (not lame, but impossible for me to find something that worked after looking for an hour or so), or<\/li>\n<li>Hack it and do it myself.<\/li>\n<\/ol>\n<p>If you haven&#8217;t guessed already, I went with #4. I&#8217;m going to show you how I figured it out and how to do it yourself. This assumes some basic knowledge of Linux command line and shell scripting.<\/p>\n<p>First thing first, I loaded one of the Nest Cam streams using the links provided on Reddit. The video livestream itself sits inside a Nest-branded HTML page that does this really annoying thing where it auto-pauses and pops over a Nest advertisement every once in a while. If I wasn&#8217;t going to rip it out already, I would have been sufficiently annoyed by this to figure out how to get to the base stream.<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"231\" data-permalink=\"http:\/\/www.blog.jay-greco.com\/wp\/?attachment_id=231\" data-orig-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?fit=994%2C720\" data-orig-size=\"994,720\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Screen Shot 2017-09-08 at 11.32.03 PM\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?fit=300%2C217\" data-large-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?fit=525%2C380\" class=\"alignnone size-full wp-image-231\" src=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?resize=525%2C380\" alt=\"\" width=\"525\" height=\"380\" srcset=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?w=994 994w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?resize=300%2C217 300w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.32.03-PM.png?resize=768%2C556 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/a><\/p>\n<p>I poked around inside the page source using the Safari dev tools to see if I could find any obvious stream container or link, but didn&#8217;t see anything. I did find a more minimal stream that is formatted for Twitter but it still does the popover thing. Boo.\u00a0I also poked around in the javascript (warning: there&#8217;s a lot) to see if the stream was being lazy-fetched from any obvious source. Again, nothing. Boo.<\/p>\n<p>I decided to use the Timelines tool to see what&#8217;s being loaded on the network. I recorded for a few seconds and saw what was clearly a periodic fetch taking place. There&#8217;s an XHR request going out approximately every 4 seconds. It&#8217;s loading a <code>media_xxxxxxxx_123.ts<\/code> file and a <code>chunklist_xxxxxxxx.m3u8<\/code> file after each request. This is definitely an MPEG-2 stream, with the chunklist serving as a manifest for the media.ts file.\u00a0Bingo!<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"232\" data-permalink=\"http:\/\/www.blog.jay-greco.com\/wp\/?attachment_id=232\" data-orig-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?fit=1440%2C447\" data-orig-size=\"1440,447\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Screen Shot 2017-09-08 at 11.46.00 PM\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?fit=300%2C93\" data-large-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?fit=525%2C163\" class=\"alignnone size-full wp-image-232\" src=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?resize=525%2C163\" alt=\"\" width=\"525\" height=\"163\" srcset=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?w=1440 1440w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?resize=300%2C93 300w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?resize=768%2C238 768w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?resize=1024%2C318 1024w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/a><\/p>\n<p>.m3u8 files are commonly used to define video streams, and so I knew I was on the right track. Right-clicking on the m3u8 file and choosing &#8220;Copy Link Address&#8221; and pasting it into the Safari address bar yielded a base-level video stream with no extra junk (*cough*) on top of it. It looks like Nest streams their livestream content from <code>stream-bravo.dropcam.com<\/code>\u00a0or from <code>stream-delta.dropcam.com<\/code>. (Both are currently using\u00a0Wowza Streaming Engine 4 Subscription Edition 4.7.1 build20635)<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"233\" data-permalink=\"http:\/\/www.blog.jay-greco.com\/wp\/?attachment_id=233\" data-orig-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?fit=1440%2C900\" data-orig-size=\"1440,900\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Screen Shot 2017-09-08 at 11.54.10 PM\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?fit=300%2C188\" data-large-file=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?fit=525%2C328\" class=\"alignnone size-full wp-image-233\" src=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?resize=525%2C328\" alt=\"\" width=\"525\" height=\"328\" srcset=\"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?w=1440 1440w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?resize=300%2C188 300w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?resize=768%2C480 768w, https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.54.10-PM.png?resize=1024%2C640 1024w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/a><\/p>\n<p>The next step was saving the stream using this URL. Time to break out the Pi! I figured I could use ffmpeg to do this, and after a quick google search, my assumptions were confirmed. <a href=\"https:\/\/stackoverflow.com\/questions\/32528595\/ffmpeg-mp4-from-http-live-streaming-m3u8-file\">This<\/a> StackOverflow question gave me what I needed, except I wanted to ensure that the ffmpeg command was always running (in the event the stream broke up and was restarted, a network issue occurred, etc).<\/p>\n<p>For those of you who just want to save a Nest Cam stream to disk using Raspbian\/Raspberry Pi\/Debian\/Other Linux, this is the command that will do it for you (you need <code>ffmpeg<\/code>\u00a0installed in order to use this): <code>ffmpeg -i http:\/\/your_stream_chunklist_link.m3u8 -c copy -bsf:a aac_adtstoasc \/path\/to\/output\/file.mp4<\/code>. For example, this is the command I used to save the stream I was watching to my home directory: <code>ffmpeg -i https:\/\/stream-delta.dropcam.com\/nexus_aac\/a8a645a10ef24a50b250c14a08b02ef9\/chunklist_w719996219.m3u8 -c copy -bsf:a aac_adtstoasc Stream.mp4<\/code><\/p>\n<p>In order to make sure that ffmpeg was always restarted in case of any issues, I whipped up the following shell script (named <code>runStream.sh<\/code>) to be run as a cronjob:<\/p>\n<pre>#!\/bin\/bash\r\n#make-run.sh\r\n#make sure a process is always running.\r\n\r\nprocess=LivingRoom\r\nnow=$(date +%Y%m%d%H%M%S)\r\nmakerun=\"ffmpeg -i https:\/\/stream-delta.dropcam.com\/nexus_aac\/a8a645a10ef24a50b250c14a08b02ef9\/chunklist_w719996219.m3u8 -c copy -bsf:a aac_adtstoasc \/media\/HDD\/Stream_$now.mp4\"\r\n\r\nif ps ax | grep -v grep | grep $process &gt; \/dev\/null\r\nthen\r\n exit\r\nelse\r\n $makerun &amp;\r\nfi\r\n\r\nexit<\/pre>\n<p>The script checks to see if the ffmpeg command is running using <code>ps ax<\/code>\u00a0and <code>grep<\/code>. If it is, there is no need to start it, so it exits. If it isn&#8217;t, the script is started using the <code>makerun<\/code>\u00a0shell command. Note the <code>$now<\/code>\u00a0variable at the end of the filename: it automatically appends a puncuation-less timestamp to each video file, so that the previous file is not lost when ffmpeg is automatically restarted.<\/p>\n<p>The last thing to do was to make the script executable using <code>chmod +x runStream.sh<\/code>\u00a0and add it to the crontab using <code>crontab -e<\/code>. I set it to run every minute (can&#8217;t miss any of the action!) using the following crontab:<\/p>\n<pre># m h\u00a0 dom mon dow \u00a0 command\r\n* * * * * \/home\/pi\/runStream.sh<\/pre>\n<p>After saving the changes and waiting a minute, I saw the first video file pop up. After running for a few hours, the auto-restart was a great idea, because it&#8217;s kicked in several times (likely due to haphazard internet because there a HURRICANE).<\/p>\n<p>Stay safe out there, Florida. It&#8217;s going to get crazy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tonight, I stumbled on an interesting post on Reddit. It linked to two Nest Cams livestreaming the landfall of hurricane Irma from a Miami condo. I popped the streams open on my phone (the hurricane had not hit yet, and the sky was mostly clear) and thought about the fact that I was probably going &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/www.blog.jay-greco.com\/wp\/?p=229\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Downloading &#038; Saving a Nest Cam Live Stream Using a Raspberry Pi + Debian Linux&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":232,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"quote","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[9,18],"tags":[87,89,92,88,90,86,91,25],"class_list":["post-229","post","type-post","status-publish","format-quote","has-post-thumbnail","hentry","category-projects","category-reverse-engineering","tag-cam","tag-dropcam","tag-ffmpeg","tag-google","tag-live-stream","tag-nest","tag-nest-cam","tag-reverse-engineering","post_format-post-format-quote"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.blog.jay-greco.com\/wp\/wp-content\/uploads\/2017\/09\/Screen-Shot-2017-09-08-at-11.46.00-PM.png?fit=1440%2C447","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p755Zm-3H","jetpack-related-posts":[],"_links":{"self":[{"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/229"}],"collection":[{"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=229"}],"version-history":[{"count":2,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/229\/revisions"}],"predecessor-version":[{"id":234,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/229\/revisions\/234"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=\/wp\/v2\/media\/232"}],"wp:attachment":[{"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=229"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.blog.jay-greco.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}