Watermarks on Your Custom Twitter Image Hosting Service

Motivation

I'm always reluctant giving up things to The Internet when I can host them myself, and image hosting can be personalised in so many ways, it seemed remiss of me to not at least investigate. So, armed with my Rimuhosting VPS, I dove in to getting my web server to accept images from my Twitter client.

Requirements:

  • Currently, only Tweetbot (as far as I know) allows you to specify a custom location for your image uploading service. Which means an iPhone.
  • PHP 5 (will probably work on PHP 4 though, I tried to keep it pretty generic)

PHP Code

Really this is just code to copy & past (and fill in the blanks), which I've spread across 3 php scripts, and much of which I did not originally craft; it's quicker to google and customise than to write plain code off your own bat (in recognition of this, I have attempted to provide credit in the comments). I will attempt to explain what each page does:

  1. oauth.class.php
<?php
class TwitterOAuthEcho
{
  public $verificationUrl = 'https://api.twitter.com/1.1/account/verify_credentials.json';
  public $userAgent = __CLASS__;

  public $verificationCredentials;

  /**
   *
   * @var int
   */
  public $resultHttpCode;
  /**
   *
   * @var array
   */
  public $resultHttpInfo;
  public $responseText;

  /**
   * Save the OAuth credentials sent by the Consumer (e.g. Twitter for iPhone, Twitterrific)
   */
  public function setCredentialsFromRequestHeaders()
  {    
    $this->verificationCredentials = isset($_SERVER['HTTP_X_VERIFY_CREDENTIALS_AUTHORIZATION']) 
      ? $_SERVER['HTTP_X_VERIFY_CREDENTIALS_AUTHORIZATION'] : '';
  }

  /**
   * Verify the given OAuth credentials with Twitter
   * @return boolean
   */
  public function verify()
  {
    $curl = curl_init($this->verificationUrl);
    curl_setopt($curl, CURLOPT_USERAGENT, $this->userAgent);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array(
        'Authorization: ' . $this->verificationCredentials,
      ));

    $this->responseText = curl_exec($curl);
    $this->resultHttpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $this->resultHttpInfo = curl_getinfo($curl);
    curl_close($curl);

    return $this->resultHttpCode == 200;
  }
}
?>

I've had this lying around on my computer for a while, so I'm not exactly sure of its provenance. But, very simply, it will make API calls to Twitter, which is what we want in order to be able to verify the user's existence, and also pull out information about them.

  1. index.php
<?php

require_once('oauth.class.php');
require_once('process.php');

// Some code from https://gist.github.com/sebietter/4749689/

$saveDir = "/images-folder-here/"; // place where the images should be saved.
$domain = "https://example.com"; // your domain.

//server-side directory
$directory_self = str_replace(basename($_SERVER['PHP_SELF']), '', $_SERVER['PHP_SELF']);
$uploadsDirectory = $_SERVER['DOCUMENT_ROOT'] . $directory_self . $saveDir;

$valid_users = array('cool_twitter_user_handle', 'my_mates_twitter_account');

$oauthecho = new TwitterOAuthEcho();
$oauthecho->userAgent = 'My Custom Image Service App 1.0';
$oauthecho->setCredentialsFromRequestHeaders();

if ($oauthecho->verify()) {
	
	$userInfo = json_decode($oauthecho->responseText, true);
	if(in_array($userInfo['screen_name'], $valid_users)) {

		// Image filetype check source:
		// http://designshack.net/articles/php-articles/smart-file-type-detection-using-php/
		$tempFile = $_FILES['media']['tmp_name'];
		//$imginfo_array = getimagesize($tempFile);
		$file_info = filesize($tempFile);

		//if ($imginfo_array !== false) {
		if($file_info > 0)
		    $mime_type = $imginfo_array['mime'];
		    $mime_array = array("video/quicktime", "image/png", "image/jpeg", "image/gif", "image/bmp", "video/mp4");
		    if(in_array($_FILES['media']['type'], $mime_array)) {
		    //if (in_array($mime_type , $mime_array)) {

		  	//generate random filename
				while(file_exists($uploadFilename = $uploadsDirectory.time().'-'.$_FILES['media']['name'])){$now++;}
				
				//upload the file to the webserver
				@move_uploaded_file($_FILES['media']['tmp_name'], $uploadFilename);

				//generate the filename that will be given to Tweetbot
				$outputFilename = $domain . $saveDir  . basename($uploadFilename);

				watermark_image($uploadsDirectory, basename($uploadFilename), '@' . $userInfo['screen_name']);

				//respond with JSON encoded media URL
				$response = array(url=>$outputFilename);
				echo json_encode($response);
		    //}
		} else {
		    echo "This is not a valid image file";
		}
	} else {
		echo "Not a valid user for this service.";
	}
} else {
	echo "Not a valid user according to Twitter.";
}



?>

This code does a few things:

  1. Sets up variables such as where we're going to store the images on the server (relative to the script's placement);
  2. Sets which users may use this service (otherwise, any valid twitter user could end up using your valuable disk space!);
  3. Does some (very rudimentary) MIME-type checks, to see if the file being uploaded is the appropriate kind of media;
  4. Generates a "random" file name, for the image to be placed. (I plan to fix this code up — let me know if you've got any hot tips in the comments);
  5. Finally, does any processing you want, and returns the resource location of the uploaded file.
  1. process.php
<?php

function image_fix_orientation(&$image, $filename) {
    $exif = exif_read_data($filename);
    $change = 0;

    if (!empty($exif['Orientation'])) {
        switch ($exif['Orientation']) {
            case 3:
                $image = imagerotate($image, 180, 0);
                break;

            case 6:
                $image = imagerotate($image, -90, 0);
                $change = 1;
                break;

            case 8:
                $image = imagerotate($image, 90, 0);
                $change = 1;
                break;
        }
    }
    return $change;
}

function watermark_image($path, $image, $watermark, $font_path = '/usr/share/fonts/truetype/path-to-truetype-font.ttf') {
	file_put_contents('log.txt', $text);
	$accepted_files = array('jpg', 'jpeg', 'png');
	$img_src = $path . $image;

	$file_info = pathinfo($path.$image);

	switch ($file_info['extension']) {
		case 'jpeg':
			$img = imagecreatefromjpeg($img_src);
			$change = image_fix_orientation($img, $img_src);
			break;
		case 'jpg':
			$img = imagecreatefromjpeg($img_src);
			$change = image_fix_orientation($img, $img_src);
			break;
		case 'png':
			$img = imagecreatefrompng($img_src);
			break;
	}

	if(in_array($file_info['extension'], $accepted_files)) {

		$text_colour = imagecolorallocate($img, 160, 160, 160);


		if($change == 0) {
			list($img_width, $img_height,,) = getimagesize($img_src);
		} else {
			list($img_height, $img_width,,) = getimagesize($img_src);
		}

		// find font-size for $txt_width = 80% of $img_width...
		$font_size = 1; 
		$txt_max_width = intval(0.7 * $img_width);    

		do {

		    $font_size++;
		    $p = imagettfbbox($font_size,0,$font_path, $watermark);
		    $txt_width=$p[2]-$p[0];
		    // $txt_height=$p[1]-$p[7]; // just in case you need it

		} while ($txt_width <= $txt_max_width);

		// now center text...
		$y = $img_height * 0.5; // baseline of text at 90% of $img_height
		$x = ($img_width - $txt_width) / 2;

		imagettftext($img, $font_size, 0, $x, $y, $text_colour, $font_path, $watermark);
		rename($path.$image, $path.'orig/'.$file_info['filename'] . '.' . $file_info['extension']);

		switch ($file_info['extension']) {
			case 'jpeg':
				imagejpeg($img, $path.$image);
				break;
			case 'jpg':
				imagejpeg($img, $path.$image);
				break;
			case 'png':
				imagepng($img, $path.$image);
				break;
		}
	imagedestroy($img);
	}
}


?>

This file is completely optional; if you don't want to do anything fancy, then you can just comment the
'require_once()' line and the 'watermark_image()' lines, and be done with this.

However, if you'd like to put, say, your username obnoxiously front and center of the image you're uploading, then this function is for you.

How it Works:

  1. 'image_fix_orientation()' is an attempt to deal with a fun little quirk of the iPhone — basically, iPhones don't rotate their images normally, but store the rotation data in the image's metadata. Of course.
  2. We then start with the smallest font size possible, and iterate using the ever-helpful 'imagettfbbox()'
  3. We've ascertained how big the font can be; we then mix whatever text we'd like (here, I've chosen the Twitter handle of the uploading user, but print out the array $userInfo for a wealth of information), and superimpose the two, discreetly moving the original image out of the way.
  4. We just refuse to deal with GIFs, mainly because I figured I only posted animated GIFs, which I never create anyway. Imagemagick is the way to go, if your heart's set on this.

And there you have it! Now you can redirect Tweetbot to upload to your index.php script, and maintain ownership of another aspect of your online presence.