Digging In The Vineyard, Part 3

Previously:

  1. I figured out how to log in and post using the Vine API.
  2. The AWS keys were extracted from the Vine iOS app.

Now it's time to actually post something to Vine. Since the entire internet seems to have somehow regressed to 90s era technology and animated GIFs are more readily available than short videos, let's try and get one of those up there—albeit as a H.264 encoded video.

I'm going to help my friend Pazuzu here possess Vine. Some projectile vomiting of pea soup could really liven the place up.

Now kindly undo these straps.

This is, of course, a somewhat convoluted process.

Some things in this part may seem rather “deus ex machina”, and that's because the knowledge was really discovered through a lot of boring trial and error. For example, part way through writing these posts, Vine started to validate the handler_name metadata on videos—I assume as a result of discovering the clearly not-posted-via-Vine uploads from myself or others.

Going through the process of making slight changes to the video one at a time and retrying the submission to Vine wouldn't really make for an enthralling post, so I've left it out.

To get GIFs onto Vine will require adding three new things to the toolkit:

  1. ImageMagick to create a thumbnail, and to extract frames from the GIF.
  2. ffmpeg to encode the extracted frames as a H.264 video
  3. mp4box to tweak the container metadata and mux video with audio.
$ brew install imagemagick ffmpeg mp4box

Creating a thumbnail from the GIF is handled by using ImageMagick's convert command:

$ convert 'pazuzu.gif[0]' -resize 480x480! thumbnail.jpg

The [0] after the GIF filename specifies which frame to use, otherwise, all of them would have been resized and converted to JPEG. The ! after the new image dimensions instruct convert to not preserve the aspect ratio—unnecessary in this case, but handy to know about.

Extracting the frames from the GIF is also handled by convert:

$ mkdir frames
$ convert pazuzu.gif -coalesce frames/pazuzu%05d.png
$ ls frames
pazuzu00000.png pazuzu00002.png pazuzu00004.png pazuzu00006.png pazuzu00008.png pazuzu00010.png
pazuzu00001.png pazuzu00003.png pazuzu00005.png pazuzu00007.png pazuzu00009.png

The -coalesce option essentially instructs ImageMagick to dump the full frame, and not just the changes between a frame and the previous frame.

Moving on to the video, it's time to weed through ffmpeg's dense overgrowth of command line options to try and get a usable video encoded.

$ ffmpeg -f image2 -i frames/pazuzu%05d.png -vcodec libx264 -pix_fmt yuv420p -profile:v main -s 480x480 -r 24 -an pazuzu.m4v

We're instructing ffmpeg to create a 480x480 H.264 video from the directory of frames, using the “main” H.264 profile, with a pixel format of YUV420p, and a frame rate of 24 fps. No audio track. This is pretty close to what the Vine app itself uploads to S3.

Sometimes using FFMpeg feels like using a knock-off multi-tool that's going to explode in your hand, somehow launching a nail file directly into your cornea. It does however work really well once the right incantation has been constructed.

Finally, the video needs its metadata tweaked, and it needs an audio track. It could be empty, but I'll use an old friend here, the “Sosumi” alert that's shipped with Mac OS for a long time.

# afconvert ships with OS X
$ afconvert -f mp4f /System/Library/Sounds/Sosumi.aiff /tmp/Sosumi.m4a
$ MP4Box -add pazuzu.m4v -add /tmp/Sosumi.m4a -name '1=Core Media Video' -name '2=Core Media Audio' pazuzu.mp4

Setting the name metadata for each track is pretty important, as Vine will reject any videos that don't match this. The API will return a successful response, but the videos will never actually show up in your feed.

We now have a video that looks fairly close to a legitimate Vine—close enough to fool the validation that's in place, certainly. Gotta fly underneath the radar.

Time to see if those S3 credentials actually worked. Be a shame if I subjected myself to the agony of video encoding for no good reason at all. Here's some Ruby code to upload a thumbnail file and a video file to the Vine S3 bucket using the credentials obtained earlier.

#!/usr/bin/env ruby
# Usage: ./s3upload.rb THUMBNAIL_PATH VIDEO_PATH

require "aws-sdk"
require "uuidtools"

VINE_S3 = "vines.s3.amazonaws.com"
BUCKET_NAME = "vines"
ACCESS_KEY = "NOT-REAL-AWSG3TITY0URS3LF"
SECRET_KEY = "NOT-REAL-SCR4MKID"

raise "thumbnail and video are required" unless ARGV[0] && ARGV[1]

thumbnail_file = File.open(ARGV[0])
video_file = File.open(ARGV[1])

s3_client = AWS::S3.new(server: VINE_S3,
                        access_key_id: ACCESS_KEY,
                        secret_access_key: SECRET_KEY)

# Generate a key that looks at least somewhat like what Vine generates, UUID and all
video_name = "#{UUIDTools::UUID.random_create.to_s.upcase}-1234-00000105FD8E6096_1.0.5.mp4"
video_key = "videos/#{video_name}"
thumbnail_key = "thumbs/#{video_name}.jpg"

bucket = s3_client.buckets[BUCKET_NAME]

video_obj = bucket.objects[video_key]
video_version = video_obj.write(video_file, content_type: "video/mp4")

thumbnail_obj = bucket.objects[thumbnail_key]
thumb_version = thumbnail_obj.write(thumbnail_file, content_type: "image/jpeg")

puts "Thumbnail URL: #{thumbnail_obj.public_url(secure: true)}?versionId=#{thumb_version.version_id}"
puts "Video URL: #{video_obj.public_url(secure: true)}?versionId=#{video_version.version_id}"

Here's the moment of truth. Did pottering about with gdb and writing a little C actually pay off?

$ ./s3upload.rb thumbnail.jpg pazuzu.mp4
Thumbnail URL: https://vines.s3.amazonaws.com/thumbs/271F12BF-DF19-4FBF-836C-D1320BED15EA-1234-00000105FD8E6096_1.0.5.mp4.jpg?versionId=hB2NCVUhT8Az388kUsqpGHj7v_DnZiEN
Video URL: https://vines.s3.amazonaws.com/videos/271F12BF-DF19-4FBF-836C-D1320BED15EA-1234-00000105FD8E6096_1.0.5.mp4?versionId=RCiryWOxJCVq6_Kt_h1jaM7WCrBVBasU

Fuck yeah it did.

Digging up the code from part 1 of this series, I can plug in the URLs to the files on S3 and see if the Vine API now gives me the green light on submitting a new Vine.

$ ./vinepost.rb testvineuser@example.com passitypassword
{"code": "", "data": {"postId": 920955833397948416, "created": "2013-03-06T05:19:55.213820"}, "success": true, "error": ""}

In here. With us.

Fuck yeah it does.

The post to Vine appeared successfully in my feed, Pazuzu beeping the “Sosumi” beep at me in all his glory.

If one were so inclined, one could tie this all together in a neat little pile of code that automates the H.264 encoding, S3 upload, and Vine authentication and submission process in one fell swoop. This is an exercise left to the reader.

This was personally a pretty fulfilling romp through some fairly disparate grounds—sniffing out API behaviour with mitmproxy, learning more about the Objective-C runtime, writing C, spending some time in gdb, looking up ARM calling conventions, and playing about with video encoding. It's not every day that you get to run that particular gauntlet of technology.

Indulge your curiosity. Take an application and just wail on that thing like it owes you money until something breaks, or it gives up its secrets, or both. You'll learn a thing or two, and maybe even impress and amaze your friends with the places you'll go.


O Canada.


Thanks to the folks who took time to read earlier drafts of this work.

If you enjoyed these posts and need to pay someone to have some computers programmed, you should get in touch with me.