{"id":16902037,"url":"https://github.com/jpf/dial-a-cat","last_synced_at":"2025-10-06T03:16:33.733Z","repository":{"id":9675710,"uuid":"11618839","full_name":"jpf/dial-a-cat","owner":"jpf","description":"855-MEOW-JAM","archived":false,"fork":false,"pushed_at":"2018-06-10T03:16:40.000Z","size":274,"stargazers_count":71,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-11T08:08:37.241Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OpenEdge ABL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jpf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-07-23T20:47:35.000Z","updated_at":"2025-05-26T05:32:39.000Z","dependencies_parsed_at":"2022-09-01T03:51:04.356Z","dependency_job_id":null,"html_url":"https://github.com/jpf/dial-a-cat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jpf/dial-a-cat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpf%2Fdial-a-cat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpf%2Fdial-a-cat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpf%2Fdial-a-cat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpf%2Fdial-a-cat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpf","download_url":"https://codeload.github.com/jpf/dial-a-cat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpf%2Fdial-a-cat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278552255,"owners_count":26005532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T18:03:37.322Z","updated_at":"2025-10-06T03:16:33.701Z","avatar_url":"https://github.com/jpf.png","language":"OpenEdge ABL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 1-855-MEOW JAM: Sending Cat Pictures Over The Phone Via Space Age Technology\n\n\u003cimg width=\"800\" height=\"265\" src=\"https://www.twilio.com/blog/wp-content/uploads/2013/10/1336023490071-800x265.png\" alt=\"A space age cat\"\u003e\n\n**Note!** The text below was originally posted on the Twilio blog as\nthe post \"[1-855-MEOW JAM: Sending Cat Pictures Over The Phone Via Space Age Technology](https://www.twilio.com/blog/2013/10/1-855-meow-jam-sending-cat-pictures-over-the-phone-via-space-age-technology.html)\"\n\nOn July 20, 1969, at 20:18 UTC, Neil Armstrong was the first human to\nset foot on the Moon. Video of Neil descending the ladder of the lunar\nmodule was broadcast to earth at 10 frames a second via an image\ntransmission method known as Slow-scan Television, or “SSTV”.\n\n\u003ca title=\"By National Aeronautics and Space Administration (NASA\u0026#039;s Apollo 11 Multimedia webpage) [Public domain], via Wikimedia Commons\" href=\"https://commons.wikimedia.org/wiki/File%3AApollo_11_first_step.jpg\"\u003e\u003cimg width=\"256\" alt=\"Apollo 11 first step\" src=\"https://upload.wikimedia.org/wikipedia/commons/1/1e/Apollo_11_first_step.jpg\"/\u003e\u003c/a\u003e\n\nToday I’ll be showing you how to use the space-age technology of\nSSTV and your telephone to do something critical to human progress:\nlook at pictures of cats.\n\nWe’ll be using an SSTV transmission mode called “[Martin M2](http://en.wikipedia.org/wiki/Slow-scan_television#Modes)”. Martin\nM2 is an SSTV transmission mode which is popular with European Ham\nradio operators and differs significantly from the system that NASA\nused to transmit images back from the moon.\n\nKeep in mind that this post is just for fun and a little\ncomplicated. If you want an easy way to send pictures to mobile\nphones, you should look at [Twilio Picture Messaging](https://www.twilio.com/mms).\n\nIn any event, here is what I’ll be covering in this blog post:\n\n-   How to use Dial-A-Cat\n-   Motivation: Why I built dial-a-cat\n-   How it works\n-   How to convert an image to an SSTV transmission\n-   How to build an IVR with Twilio\n\n## How To Use Dial-A-Cat\n\nAre you wondering what this all looks like? Below is a video that\nshows my cellphone and laptop to receiving a cat picture.\n\n[![A video that\nshows my cellphone and laptop to receiving a cat picture](https://img.youtube.com/vi/B7bVzBhg_GM/0.jpg)](https://youtu.be/B7bVzBhg_GM?t=35s)\n\nWant to try that out yourself? Before you can start “dialing” cat pictures, you’ll need the following:\n\n-   A telephone with good audio quality.\n    (If your cell-phone doesn’t work, try a hardwired phone)\n\nOne of the following:\n\n-   A computer running Linux, OS X, or Windows\n-   An iPhone or Android phone\n-   [SSTV software capable of decoding Martin M2 transmissions](http://en.wikipedia.org/wiki/Slow-scan_television#External_links)\n    -   Linux – [qsstv](http://users.telenet.be/on4qz/) (Ubuntu users should use this [PPA](http://askubuntu.com/questions/4983/what-are-ppas-and-how-do-i-use-them): `ppa:kamalmostafa/qsstv`)\n    -   OS X – [MultiScan 2SL](https://s3.amazonaws.com/jf-files/MultiScan_2SL.zip)\n    -   Windows – [RX-SSTV](http://users.belgacom.net/hamradio/rxsstv.htm)\n    -   iPhone – [SSTV](https://itunes.apple.com/us/app/sstv/id387910013) ($3)\n    -   Android – [DroidSSTV](https://play.google.com/store/apps/details?id=com.wolphi.sstv\u0026hl=en) ($7)\n\n## How to dial your first cat:\n\n-   Use one of the links above to download and install the SSTV software\n    for your computer or smartphone.\n-   Use a telephone to dial this number: +1 855-MEOW-JAM (855 636-9526)\n-   You’ll hear some instructions on how to use “dial-a-cat”.\n-   When you hear “standby”, hold the speaker of your phone up to the microphone of the device running the SSTV decoding software.\n-   Wait for your cat picture to show up.\n\nWhat to do after you’ve seen all of the pre-rendered images:\n\n-   Press the “8” at any time while you’re connected to dial-a-cat.\n-   A cat picture will be selected Just For You using [The Cat API](http://www.thecatapi.com/).\n-   This image will be rendered into an SSTV transmission for you on-the-fly.\n-   It takes about 15-30 seconds for this image to be rendered, be\n    patient.\n\nWhat happens if you press the “7” button while connected to dial-a-cat? It’s a mystery.\n\n## Tips For Receiving A Clear Transmission\n\nSSTV is an analog audio transmission, so it is vulnerable to\ninterference from other audio sources in your environment. Try the\nfollowing things if you’re having trouble receiving images:\n\nCall from a phone with good audio quality. A hardwired telephone is\nideal. Move to a quiet room. Plug earbuds into your phone and hold\nthe earbuds right next to the microphone on your device.\n\nIf you are running SSTV software on Android, you can use earbuds to\n“loopback” the audio to your phone as follows:\n\n-   Plug earbuds into your phone.\n-   Dial +1 855-636-9526\n-   Turn the volume on your earbuds to a medium-low volume.\n-   When you start to hear the instructions, switch to DroidSSTV.\n-   Move your earbuds to bottom of your phone, near your phone’s microphone.\n-   Hold your earbuds in place until you receive the image. (I used\n    this method to get the cat image below)\n    \n    \u003cimg class=\"aligncenter\" id=\"docs-internal-guid-23564e96-e6a5-f3e4-9335-f212f2dae5af\" alt=\"\" src=\"https://lh6.googleusercontent.com/CNckYGCVUKfbC_WJSL6L4STeD8JjAZ_cnoVwVsRo2FcFeTjY2iQcIsrbhZ0ma4rsTdajaikgc98RtpZll0F-PM7Zz3Et8xzdqo3K9Ywjh9wI0JlKpMN59E93\" width=\"376px;\" height=\"144px;\"\u003e\n\n## Motivation\n\nWhen I first met Dave Rauchwerk, we quickly discovered that we both\nshared a love of obscure or forgotten technology.\n\nAmong the things that we discussed, Dave told me about SSTV, how\nSSTV was used to send images back from the moon and about an art\ninstallation he had done using SSTV.\n\nDave’s art installation consisted of a room with digital picture\nframes that would listen for SSTV audio nearby and display images\nfrom those SSTV transmissions in the picture frame. Also inside the\nroom was a digital camera that Dave had modified to “play” the\npictures it took into the room. When a visitor took a picture with\nthe camera, the camera would use sound to transmit the image to the\npicture frames.\n\nI was so inspired by Dave’s art project that I knew I had to do\nsomething with Twilio and SSTV.\n\nNaturally, I wanted build something that would use the\ntelephone. But what to send? I had trouble deciding at first. Then I\nrealized that everybody likes pictures of cats.\n\n(Incidentally, one of my co-workers suggested that dial-a-cat would\nbe a great way to generate album art for your band’s next album.)\n\n## How It Works\n\n\nNow that you’ve seen what dial-a-cat does and learned what inspired\nme to build it. Let’s dig into how it works.\n\nHere are the components that make up dial-a-cat:\n\n-   Twilio\n-   Python\n-   [Flask](http://flask.pocoo.org/)\n-   [pySSTV](https://github.com/dnet/pySSTV)\n-   [The Cat API](http://thecatapi.com/)\n-   [FileGenerator](https://github.com/jpf/FileGenerator)\n\nThe components above are combined into code to that generates an\nSSTV transmission audio stream from an image, and a Twilio IVR to\ncontrol dial-a-cat. This allows people to switch between\npre-rendered and “live” SSTV transmissions.\n\nMy code makes heavy use of the excellent [pySSTV](https://github.com/dnet/pySSTV) library from [András\nVeres-Szentkirályi](http://techblog.vsza.hu/).  If you’re at all interested in learning more\nabout SSTV, I recommend that you read the source to pySSTV.\n\nAll the code that I used to build [dial-a-cat is available on\nGitHub](https://github.com/jpf/dial-a-cat). I’m only going to be covering the key parts of dial-a-cat in\nthis post, so if you want to really get a good understand of how\neverything works, you’ll need to look at the code.\n\n## Transmitting SSTV images over the telephone\n\nThe simplest way to transmit SSTV images over the telephone is to\npre-render an audio file with the SSTV transmission and have Twilio\nplay that file.\n\nHere’s the function that I use to do that. This function will\nrandomly pick a URL from a list of pre-rendered SSTV transmissions\nand return that URL in TwiML:\n\n    @app.route('/voice/random-prerendered-cat', methods=['GET', 'POST'])\n    def voice_prerendered_cat():\n        f = open('image-list.txt')\n        images = [i.strip() for i in f.readlines()]\n        wav = 'https://s3.amazonaws.com/jf-sstv-cats/%s' % choice(images)\n    \n        gather_args = get_gather_args()\n        r = twiml.Response()\n        with r.gather(**gather_args) as g:\n            g.play(wav)\n            g.say(\"Stand by for transmission\")\n        r.redirect(url_for('voice_prerendered_cat', _external=True))\n        return str(r)\n\nEasy, right? Almost too easy.\n\nWhat if we wanted to generate an SSTV transmission from some random\nimage on the internet? Well, that’s a little bit more complicated\nand involves approximately 30 methods across 5 files.\n\nAs you read the code, you’ll probably be wondering why it is so much\nmore complicated than what I just showed you above. Well, the key\nobstacle that I needed to overcome here was getting data to Twilio\nbefore the 15 second timeout. It takes about 20 seconds to generate\na Martin M2 SSTV transmission and Twilio will close the connection\nif doesn’t get a response to a HTTP request after 15 seconds.\n\nTo overcome this obstacle, I wrote a little hack to stream the SSTV\ntransmission as it’s being generated. Read on to see how.\n\n    @app.route('/cat-api/v1/sstv-\u003cid\u003e.wav')\n    def cat_sstv_wav(id):\n        cat = CatAPIPicture(id=id)\n        cat.image_get()\n        cat.image_scale_to_martin_m2()\n        rv = live_martin_m2_renderer(cat.image)\n        timeout = 14400  # 4 hours\n        rv.headers['Cache-Timeout'] = timeout\n        return rv\n\nThis is the core function that handles “live” conversion of a random image to a Martin M2 SSTV transmission. At a high level, this function gets an image from the Cat API, scales it to be appropriately sized for for Martin M2 (160 pixels by 256 pixels), passes the scaled image to a function that will render that image into Martin M2, then has Flask to feed rendered transmission to Twilio.\n\nAll the “heavy lifting” is done in the live\\_martin\\_m2\\_render() function. So, let’s take a closer look at that:\n\n    def live_martin_m2_renderer(image):\n        generator = FileGenerator()\n        slowscan = MartinM2Generator(image, 48000, 16)\n    \n        MartinM2GeneratorWorker(slowscan, generator).start()\n    \n        rv = Response(generator.read_generator(), mimetype='audio/wav')\n        rv.headers['Content-Length'] = 5661190\n        return rv\n\nLet’s cover this line by line.\n\n    generator = FileGenerator()\n\nHere I am instantiating a file-like object that can be read via a\ngenerator, I call this a `FileGenerator`. This is the key part of what\nallows me to stream the WAV file as it is being written.\n\n    slowscan = MartinM2Generator(image, 48000, 16)\n\nThis is instantiating a `MartinM2Generator` object, a class that is\nextended from the pySSTV’s `MartinM2` class and modified so that it\ncan be used with a `FileGenerator`.\n\n    MartinM2GeneratorWorker(slowscan, generator).start()\n\nThis starts up a thread which starts writing the WAV file to the\nFileGenerator.\n\n    rv = Response(generator.read_generator(), mimetype='audio/wav')\n    rv.headers['Content-Length'] = 5661190\n    return rv\n\nFinally, I return a generator that [Flask will use to stream](http://flask.pocoo.org/docs/patterns/streaming/) the\ncontents of the WAV file to the user, as the WAV file is being\nwritten.\n\nTo fully understand what’s going on, you will also want to look at\nthe code for FileGenerator, MartinM2Generator, and\nMartinM2GeneratorWorker classes.\n\nNow that you know how I’m streaming cat pictures to you over the\ntelephone. Let’s take a look at how I built the controls for\ndial-a-cat.\n\n## Building an IVR with Twilio\n\nBy default, dial-a-cat will pick a pre-rendered SSTV transmission at\nrandom, play it, and keep doing that until you hang up. However, you\ncan press “0” anytime during your call and hear about the other\nbuttons you can press. For example, you can press “8” to show have\ndial-a-cat fetch a random cat image for you off of the internet and\nrender it into an SSTV transmission.\n\nIn the telecom world, a “phone tree” or “phone menu” is called an\n“IVR” ([Interactive Voice Response](http://en.wikipedia.org/wiki/Interactive_voice_response)).\n\nHere is how I built an IVR into dial-a-cat:\n\nThe key part of building an IVR with Twilio is to use the [TwiML tag](http://www.twilio.com/docs/api/twiml/gather)\n(TwiML is the XML based instruction set that you use to tell Twilio\nwhat to do with your call)\n\nThe tag tells Twilio to make an HTTP request to your application\nwhen the user presses one or more buttons on their phones keypad.\n\nLet’s take a look at my code to see how I do this.\n\n    @app.route('/voice', methods=['GET', 'POST'])\n    def voice_main():\n        r = twiml.Response()\n        r.say(\"Welcome to dial a cat.\")\n        r.redirect(url_for('voice_instructions', _external=True))\n        return str(r)\n\nThis is the main entry point for dial-a-cat. It reads the text\n“Welcome to dial a cat” to the user and then does a redirect to the\ncode below, which reads instructions for dial-a-cat to you:\n\n    def get_gather_args():\n        return {'action': url_for('voice_handle_gather', _external=True),\n                'numDigits': 1,\n                'timeout': 1}\n    \n    \n    @app.route('/voice/instructions', methods=['GET', 'POST'])\n    def voice_instructions():\n        gather_args = get_gather_args()\n        r = twiml.Response()\n        with r.gather(**gather_args) as g:\n            g.say((\"An S S T V Transmission \"\n                   \"in the Martin M Two format will be starting shortly.\"))\n            g.pause()\n            g.say(\"For help press 0\")\n            g.pause()\n            g.say(\"Stand by for transmission.\")\n        r.redirect(url_for('voice_prerendered_cat', _external=True))\n        return str(r)\n\nThe line to focus on here is this one: `with r.gather(**gather_args) as g:`\n\nThe twilio-python TwiML generator uses Python’s “with” statement to\ngenerate TwiML that is wrapped in a tag. Here is the XML that the\nstatement above generates:\n\n    \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n    \u003cResponse\u003e\n      \u003cGather action=\"http://twilio-dial-a-cat.herokuapp.com/voice/handle-gather\" numDigits=\"1\" timeout=\"1\"\u003e\n        \u003cSay\u003eAn S S T V Transmission in the Martin M Two format will be starting shortly.\u003c/Say\u003e\n        \u003cPause /\u003e\n        \u003cSay\u003eFor help press 0\u003c/Say\u003e\n        \u003cPause /\u003e\n        \u003cSay\u003eStand by for transmission.\u003c/Say\u003e\n      \u003c/Gather\u003e \u003cRedirect\u003ehttp://twilio-dial-a-cat.herokuapp.com/voice/random-prerendered-cat\u003c/Redirect\u003e\n    \u003c/Response\u003e\n\nNote the “action” property in the tag, this is the URL that Twilio\nwill send button presses to. When building a complex phone tree,\nthis URL will change as your user traverses through your phone\ntree. I just wanted users to be able to switch between “live”\nrendered and pre-rendered cats, so I use the same handler for\neverything. Here is what the code for my handler looks like:\n\n    @app.route('/voice/handle-gather', methods=['POST'])\n    def voice_handle_gather():\n        digit = request.form['Digits']\n        if digit == '0':\n            return redirect(url_for('voice_help', _external=True))\n        elif digit == '1':\n            return redirect(url_for('easter_egg', id='1', _external=True))\n        elif digit == '2':\n            return redirect(url_for('voice_prerendered_cat', _external=True))\n        elif digit == '4':\n            return redirect(url_for('easter_egg', id='2', _external=True))\n        elif digit == '7':\n            return redirect(url_for('easter_egg', id='3', _external=True))\n        elif digit == '8':\n            return redirect(url_for('voice_live_rendered_cat', _external=True))\n        else:\n            return redirect(url_for('voice_instructions', _external=True))\n\nAs you can see, this is pretty simple. Based on the digits that are\nsent, we will return TwiML asking Twilo to the user to the\nappropriate instructions.\n\nI hope you’ve enjoyed using your telephone to receive pictures of\ncats and I hope that you learned something useful while reading\nabout how I made dial-a-cat. The full source code for [this project\nis available on GitHub](https://github.com/jpf/dial-a-cat).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpf%2Fdial-a-cat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpf%2Fdial-a-cat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpf%2Fdial-a-cat/lists"}