Programming Languages

FacePop: A New Extension for Face Processing in Stable Diffusion Web UI

Hey there! I’m excited to finally share something I’ve been working on — FacePop, my brand-new extension for AUTOMATIC1111’s Stable Diffusion Web UI. If you’ve been looking for a way to take control of face detection and enhancement in your images, this tool might just be what you’re looking for. I created it as a better solution to some of the limitations in the current tools, like Zoom Enhancer, and it’s packed with cool features that give you way more control over facial enhancement in your images.

So what is FacePop exactly, and why did I create it? Well, if you’ve ever worked with Stable Diffusion, you know that while the image generation is great, sometimes the faces just don’t come out quite right. This is where FacePop comes in. It’s designed to detect faces in an image, crop them, upscale them, enhance them with some nifty processing tricks, and then seamlessly blend them back into the main image. It’s like giving the faces in your images a personal makeover!

Why I Built FacePop

Initially, I used Zoom Enhancer to zoom in on faces and fix the quality, but I found myself wanting more control. I also wanted to integrate with some other great plugins like ControlNet, ReActor, and After Detailer. FacePop lets you do all that and more, without needing to rely on other extensions like Unprompted.

Basically, I wanted a way to get faces in my images looking as good as possible, but I didn’t want to jump through hoops or have a ton of separate tools cluttering up my workflow. With FacePop, everything’s built-in and ready to go.

How Does FacePop Work?

Here’s a quick breakdown of what FacePop does:

  1. Detect Faces: It accurately detects faces in the image using Mediapipe. Once the faces are found, it identifies key facial landmarks to make sure everything is aligned.
  2. Crop & Upscale: The tool crops out the faces, scales them up (with padding if you want), and gets them ready for processing.
  3. Enhance the Faces: Each face gets processed separately — you can do things like color correction, sharpening, background removal using MODNet, and more.
  4. Mask & Blend: After processing, it creates a mask around the faces and blends them back into the original image. This means no awkward edges or mismatches.
  5. Final Touches: Once the faces are placed back in, the whole image gets processed again for any final tweaks or adjustments.

The best part? You can fine-tune everything! From face width and height to padding and detection confidence, it’s all in your hands.

Why Should You Care?

Whether you’re a digital artist, photographer, or just someone who loves generating images, you’ve probably run into situations where the faces in your artwork just didn’t look quite right. FacePop fixes that. You don’t have to manually touch up every image anymore — just let the tool handle it.

It’s also integrated with ControlNet for advanced manipulation, and it’s designed to play nicely with popular tools like ReActor and After Detailer, giving you the flexibility to enhance faces while still maintaining creative control.

Easy to Use

FacePop is super easy to install and use. If you’ve got the Stable Diffusion Web UI set up already, installing this extension is a breeze. Just grab it from the Extensions tab using the URL:

https://github.com/TheCodeSlinger/FacePop.git

Once it’s installed, you can access all its features directly in the Img2Img interface, where you’ll find a ton of options for tweaking and customizing how faces are processed.

What’s Next?

I’m planning to keep improving FacePop and add more features based on user feedback. So if you try it out and have ideas for improvements, hit me up! There’s a lot of potential to keep pushing this tool further, especially as more people start using it in different workflows.

So, if you’re tired of low-quality faces in your Stable Diffusion images or just want more control over how faces are enhanced, FacePop might be the tool you’ve been waiting for. Download it, give it a spin, and let me know what you think!


That’s it for now! I’m super excited to see what you all do with FacePop and how it fits into your creative process. Stay tuned for updates and feel free to share your results or any feedback. Happy generating!

Cheers!

Under The Sea Daze

Of course I never live up to my New Year Resolutions and haven’t contributed to this blog in a while. I just get too damned wrapped up in my projects and stuff going on. I also easily get sidetracked into other projects. Like this one, a visual screen saver concept that is to go with the new Junior Desktop for children friendly Kiosk systems. This is a personal journal entry in my blog to go over the project, what it is about, my goals and solutions.

Building on the framework for the Windows Screen Savers which supports multi-monitor configurations. This image above shows a panorama view of the program spanning across 3 displays.

I had made lots of screen savers in 2017, I basically spent that entire year tweaking the engine and coming up with new concepts for screen savers to add to my site WindowsScreenSavers.com. It had always been a goal, since I started that project, to make an aquarium version. I will admit that it isn’t as visually beautiful as some of the others, nor does it really compete with the video versions of similar aquatic screen savers, but what it does have is character.

Every day there is something new to be witnessed in this program. Special holidays included, like Christmas, Easter, Halloween, and Columbus Day. Not just those major holidays but obscure ones I didn’t even know existed until I began researching this project. Such as Popcorn Day, the Ides of March, Submarine Day, Goth Day, Kite Day, Ask a Stupid Question Day. The list was daunting, but I have finally closed in to near to handful of the last ones and the end is in sight.

A lot of the assets I had or were easily obtained through Unit’s Asset store or places like TurboSquid. However as I added more assets to the project the file size grew, and grew and when it peaked 1.5 Gigs I knew I needed to reign this madness in. Using tools like Rhino 3D I decimated the meshes taking some like the bust of George Washington from over a 300,000 vertices down to 52,000 (and I could probably crunch that more with little loss of detail.) Then I had to crunch the texture maps, because there was a ton of them and a high quality map of 2048×2048 usually resulted in eating up over 2.7 MB. When you consider most material texture requires a Color Map, Normal Map and often Occlusion and Metallic map all at 2048×2048 that one material is then eating up 10 MP of space. So crunch crunch crunch down to 256 or 512, or if it is close to the camera and the detail is needed it got to be 1024×1024. I also would ditch the occlusion and often the metallic maps. Eventually I got the entire thing down to just under 450 GB. Not bad, more than 60% reduction in space required and more importantly it will fit on CD-R media disc for easier distribution. Of course there will be a download option too.

I created a lot of the models and fish skins myself using Rhino 3D, Corel Draw and Substance Painter. Yeah I have the Adobe Suite with Photoshop, but my god it just too bulky and clumsy to use unless absolutely needed, and it is sometimes, especially when assets come with textures in PSD format, stop that people…just STOP! Use PNG or TGA!

As this project winds up, my son Nick is bugging me to finish work on another project we had dabbled on while in Breckinridge skiing for a week, The Boom Game. That one I’ll write up another journal on I would imagine. I just hope it doesn’t take as much time as this one did, 3 months so far. I also have my major project the dungeon crawl game which was coming along well and looking amazing when it got put on hold to do this silly thing. Ok it’s not silly, I hope it will help drive the new Kid Computers site. Let’s face it hardware is dead, I have to re-energize that company with software solutions.

That’s about it for now. Hopefully I will post again before 2020. HAHAHAHA.

PHP CVS to MySQL

This PHP program is intended as a heavy duty automated tool for converting CSV (comma separated value) to a MySQL import file or querying it directly into a MySQL database. It can be used simply with a single static method call or with more flexibility and power by creating it as an object.

Download this CVS to MySQL from github.

It is not so trivial as one may first think to convert data from one format to another. To truly take advantage of MySQL querying of data in useful ways the data must be properly assigned a data type, such as INT, FLOAT, VARCHAR, TEXT, TIME and so on. The method implemented in this script to do this is REGEX. The regex pattern matching is leveraged to pigeon hole the data into the most appropriate type for MySQL to use. Every data entry must be scanned to detect if it is a certain type of data, a data type that maybe only integer numbers would of course become an INT type, but if even one of ten thousand entries has a decimal in it, then the entire set must become a float, double or numeric.

An INT simply converted to a VARCHAR is not very useful when trying to query data that should be an integer. The default regex rules file is “regex_mysql_data.txt” and has comment lines in it that start with the # character. You may want to go about modifying this file to fit your needs or to improve upon the matching capabilities.

Besides regex pattern matching, the data string length is also considered. This is done first to determine if that data should even be considered being compared to the regex pattern type. This is most useful for text based data to determine if it should be of types VARCHAR, TEXT, MEDIUMTEXT, LONGTEXT
Here is a simple static example to create a CSV to MySQL import.

CSVtoMySQL::ToHTML('test.csv');

All the static methods assume there is a header as the first row of the CSV file. This static method will try to detect a primary key, if it cannot determine a suitable primary key it will assign an INT at the beginning named ‘id’.

If you do not want to rely upon the auto detection of a primary key use this example:

CSVtoMySQL::ToHTMLMyKey ('test.csv', ‘MyID’);

Where “MyID” (optional) will become the name of the new primary key and no auto detection will be attempted.

-= THE STATIC METHODS =-

ToString – These static methods will display no output but only return the results as a string.

$string = CSVtoMySQL::ToString( $in_file [,$delim = ‘,’] )
$string = CSVtoMySQL::ToStringMyKey( $in_file [,$my_key = ‘id’ [,$delim = ‘,’]] )

ToFile – These methods will send the information to a file supplied as $out_file.
Null = CSVtoMySQL::ToFile( $in_file, $out_file, [$delim = ‘,’] )
Null = CSVtoMySQL::ToFileMyKey( $in_file, $out_file [,$my_key = ‘id’, [$delim = ‘,’]] )

ToScreen – These methods will print the mysql import information directly to the screen.

Null = CSVtoMySQL::ToScreen( $in_file [,$delim = ‘,’] )
Null = CSVtoMySQL::ToScreenMyKey( $in_file [,$my_key = ‘id’ [,$delim = ‘,’]] )

ToHTML – Like ToScreen methods but they will also add the HTML line break tag where the new line is.

Null = CSVtoMySQL::ToHTML( $in_file [,$delim = ‘,’] )
Null = CSVtoMySQL::ToHTMLMyKey( $in_file [,$my_key = ‘id’ [,$delim = ‘,’]] )

ToMySQL – These methods will use your mysql connection to send the mysql query directly to the database. You must have already connected to the mysql server and database before calling either of these methods.

Null = CSVtoMySQL::ToMySQL( $in_file [,$delim = ‘,’] )
Null = CSVtoMySQL::ToMySQLMyKey( $in_file [,$my_key = ‘id’ [,$delim = ‘,’]] )

-= CLASS USAGE =-

Creating a class object is more powerful then the static methods as there a lot of helper methods for fine tuning and debugging.

To create as an object:

$c2m = new CSVtoMySQL('test.csv');

//Then you can do something like:
$c2m->add_blank_tag('NA');
$c2m->add_blank_tag('M','PHONE');
$c2m->set_mysql_file(‘mymysql.sql’);
$c2m->detect_primary_key();
$c2m->to_file();

Here is another example where you export the CSV file and import directly to the mysql database.


<?php

require_once('CSVtoMySQL.php');

$sql = mysql_connect('xxx.xxx.xxx.xxx', 'user', 'password');
mysql_select_db('database',$sql);

$c2m = new CSVtoMySQL('test.csv');
$c2n->set_table_name(‘mytable’);
If($c2m->detect_primary_key() == false)
	{
	$c2m->add_primary_key(‘id’);
}
$c2m->to_mysql();

-= CLASS METHODS =-

The constructor:
__construct($csv, [$mysql = “mysql.sql” [,$hashead = true]])

This method loads the regex file and can be load a custom regex file.
Null = load_regex($regex_file = ”)

Reserved words are words that conflict with mysql syntax statements, such as VARCAR, INSERT, UPDATE, DATASE to prevent conflicts a rule file named “reserved_mysql_words.txt” is loaded and used to compare against the CSV header names. Any matches are renamed to prevent conflicts. You can override this file with your own using this method.
Null =load_reserved_words($f = ”)

Method to set the CSV file
Null = set_csv_file($file)

Method to set the path and name of the mysql output file, but only needed if actually creating an out file.
Null = set_mysql_file($file)

By default the CSV delimiter (data separator character) is comma “,” but there is an auto detect pass that will try and match with other common delimiters (such as |,tabs, spaces). If you need to set this manually use this method.
Null = set_delimiter($v)

Use this method to set the mysql table name, by default the table name is the name of the CSV file itself minus the extension.
Null = set_table_name($s)

When reading in CSV file line by line, the max length of each line is set to 0, which in PHP 5.1+ is unlimited to end of line. However, if you need to set this to a specific length use this method.
Null = set_max_line_length($v)

This method allows you to insert a new field that does not exist in the CSV file. $v is the name of the field, and the optional secondary value is the type which is defaulted to VARCHAR(255)
Null = add_field($v [,$type = ‘VARCHAR(255)’])

This method allows you to change the field name based on $n which can be an index number or name and $name is the new name to be given.
Bool = change_field_name($n,$name])

Use this method to set the primary key index. If $v is a number then the key is the field index, if a name it is matched against the header field name.
Bool = primary_key($v)

Like above method but only applies to the field name, not the index
Bool = primary_key_col_by_name($s)

Like above but only applies to setting the primary key by index, where the first field index = 0, not 1!
Bool = primary_key_col_by_number($n)

Add your own custom primary key with this method. This should be an INT as it will also be set to auto increment. Set the starting point of the auto increment public variable $user_primary_key_inc [ = 0]
Null = add_primary_key ([$name = ‘id’ [,$type = ‘INT’ [,$start_at = -1]]])

This method is used to try and detect which field in the CSV file should be used as the primary key. It begins with the first column and tries to match any INT or VARCHAR type that is all unique and contains no empty records. As soon as it finds one it sets that as the primary key. Also see notes in the “regex_mysql_data.txt” file. If $n is supplied it can either be a number which matches the index of the CSV column (where first column is 0, not 1) or the name of the actual column. This method retruns true if it was able to match a primary key, and false if it failed.
Bool = detect_primary_key($n = ”)

A helper method to test the types of fields detected
Null = print_types()

Same as above but outputs as HTML
Null = print_html_types()

The method to call for returning the results as a string.
String = to_string()

Send the output to the screen. I use it for when I am working in telnet or ssh
Null = to_screen()

Send the output like the to_screen() method but includes html breaks at the new line locations.
Null = to_html()

This method writes the output to a file, if you hadn’t already set the output file name you can supply it.
Bool = to_file([$file = ”])

This method sends the parsed CSV file directly to the MySQL database, you must have a connection already established (see usage above for an example.)
Bool = to_mysql()

Adds a blank tag identifier to the blank_tags array. Sometimes data will be in a CSV file that should be treated as if it were blank, such as with ‘NA’, ‘-‘, or the like. You can add global tag blanks with this method that cause this type of data to be ignored or treated as if it were empty. You can set the column field name here which apply the blank tag to just a specific column otherwise if blank it is treated globally against all columns.
Null = add_blank_tag($v [,$col = ”])

This method is ran automatically by several functions, but if you need to call it yourself you can. This method will attempt to determine the data type a column is using the “regex_mysql_data.txt” file and its rules.
Null = detect_types()

Used to try and detect if the CVS file contains a header. This is very problematic and not 100% accurate. By default the public variable $detect_header = false and must be set to true for this method to work. Otherwise it assumed there is a header. The method returns true if it detected a header and false if it did not.
Bool = detect_header($s)

-= ADDITIONAL CLASS HELPERS =-

CSVtoMySQL_DetectType is a class that is created and stored in the $regex_match_file array that contains the information from the “regex_mysql_data.txt” file.

CSVtoMySQL_FieldType is a class that is created and stored in the $fields array and contains information regard each CSV column and it’s fields.

Simple Ajax Tutorial

ajax

Ajax is at the heart of Web2.0 design it is used across the most popular websites from Facebook to Twitter. What is Ajax? It is a method of a web page making a request to another file or program to obtain data and then to dynamically show that data without reloading the page.

There are lots of Ajax libraries available, most of which claim they are easy to setup and understand but I find that incorrect in most instances as their API is fairly bloated and one can find it difficult to grasp what is the real Ajax part and the rest of the bloated API part. So here I will attempt to explain just the most basic fundamental aspect of Ajax.

Ajax uses just a few lines of JavaScript to request another page similar to how a form would access another page using either GET or POST method. However it is not necessary to send any GET or POST data unless the page that is being accessed is in itself dynamic responding to the GET or POST method request data fields. In this example we will ignore sending any data and forget about the page we are calling being any type of PHP, Perl, ASP, CGI dynamic page, we will simply request a regular text file.

The comments in the below code should tell you everything you need to know. I’ve stripped the entire Ajax process down to its most basic elements so you can look at the code and see what it is with no extra baggage that can me it confusing.

File: blurb.txt (make this file and safe it to your webserver)

My voice is my password, verify.

File: ajax.html (make this file and save it to your webserver)

<html>
<head>
<title>Simple Ajax Example</title>

<script language="Javascript">
function simpleAjax()
	{
	var xmlhttp = new XMLHttpRequest(); // The XMLhttpRequest is the built object that actually dos the Ajax call
	xmlhttp.onreadystatechange = function () // this function is what will be called AFTER the requested page has been fetched
		{
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) // verifies the status of the fetched page to be OK
			{
				var x = xmlhttp.responseText; // Get the results of the fetched page and put in the variable "x"
				document.getElementById("data").innerHTML = x; // change the contents of DIV with id "data" to the value of "x"
			}
		}
	xmlhttp.open("GET", "blurb.txt", true); // the page request to make, using method GET (could be POST) and is located in the same dir path as calling file
	xmlhttp.send(); // send the request which then calls the above function to process the results
	}
</script>

</head>
<body>

<a href="" onclick="simpleAjax();return false;">Click Here for Ajax</a>

<div id="data">
	What is your Password?
</data>

</body>
</html>

Goto http://(yourdomain)/ajax.html and click on “Click Here for Ajax”. You should see the text in the DIV with id=”data” change. That is all Ajax is.

Text to Speech with PHP (TTS)

elephanttalk

A really quick and easy Text to Speech class for PHP that will generate an MP3 file. You can also easily setup an Ajax call on a website to play the text to speech audio file as you generate it. The quality is pretty good compared to other solutions but I haven’t figured out how to adjust pitch and tone or mix background music with it (yet.)

This is not really a solid solution for robust TTS as it relies on Google’s TTS API service; for more advanced solutions with lots of controls and embedding into videos we use Microsoft’s TTS system. But for easy to deploy and on demand web services this solution is a synch.

This Text to Speech with PHP version can be found on my CodeSlinger GitHub page.

<?php
/******************************************************************
Projectname:   PHP Text 2 Speech Class 
Version:       1.0 
Author:        Radovan Janjic <rade@it-radionica.com> 
Last modified: 11 06 2013 
Copyright (C): 2012 IT-radionica.com, All Rights Reserved 

* GNU General Public License (Version 2, June 1991) 
* 
* This program is free software; you can redistribute 
* it and/or modify it under the terms of the GNU
* General Public License as published by the Free
* Software Foundation; either version 2 of the License, 
* or (at your option) any later version.
* 
* This program is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the 
* implied warranty of MERCHANTABILITY or FITNESS FOR A 
* PARTICULAR PURPOSE. See the GNU General Public License 
* for more details. 

Description: 

PHP Text 2 Speech Class 

This class converts text to speech using Google text to  
speech API to transform text to mp3 file which will be  
downloaded and later used as eg. embed file.  

Example: 

****************************************************************** 
<?php
$t2s = new PHP_Text2Speech; 
?> 

// Simple example 
<audio controls="controls" autoplay="autoplay"> 
  <source src="<?php echo $t2s->speak('If you hear this sount it means that you are using PHP text to speech class.'); ?>" type="audio/mp3" /> 
</audio>

// Example use of other language 
<audio controls="controls" autoplay="autoplay"> 
  <source src="<?php echo $t2s->speak('Wie geht es Ihnen', 'de'); ?>" type="audio/mp3" /> 
</audio> 

******************************************************************/ 

class PHP_Text2Speech { 
     
    /** Max text characters
     * @var    Integer  
     */ 
    var $maxStrLen = 100; 
     
    /** Text len
     * @var    Integer  
     */ 
    var $textLen = 0; 
     
    /** No of words 
     * @var    Integer  
     */ 
    var $wordCount = 0; 
     
    /** Language of text (ISO 639-1) 
     * @var    String  
     * @link https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 
     */ 
    var $lang = 'en'; 
     
    /** Text to speak 
     * @var    String  
     */ 
    var $text = NULL; 
     
    /** File name format 
     * @var    String  
     */ 
    var $mp3File = "%s.mp3"; 
     
    /** Directory to store audio file
     * @var    String  
     */ 
    var $audioDir = "audio/"; 

    /** Contents 
    * @var    String 
    */ 
    var $contents = NULL; 
     
    /** Function make request to Google translate, download file and returns audio file path 
     * @param     String     $text        - Text to speak 
     * @param     String     $lang         - Language of text (ISO 639-1) 
     * @return     String     - mp3 file path 
     * @link https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
     */ 
    function speak($text, $lang = NULL) { 
         
        if ($lang !== NULL) { 
            $this->lang = $lang; 
        } 

        // Create dir if not exists 
        if (!is_dir($this->audioDir)) { 
            mkdir($this->audioDir, 0755) or die('Could not create audio dir: ' . $this->audioDir); 
        } 
         
        // Try to set writing permissions for audio dir. 
        if (!is_writable($this->audioDir)) {  
            chmod($this->audioDir, 0755) or die('Could not set appropriate permissions for audio dir: ' . $this->audioDir); 
        } 
         
        // Can not handle more than 100 characters so split text 
        if (strlen($text) > $this->maxStrLen) {
            $this->text = $text;

            // Generate unique mp3 file name 
            $file = sprintf($this->mp3File, $this->audioDir . md5($this->text)); 
            if (!file_exists($file)) {
                $texts = array();
                $words = explode(' ', $this->text);
                $i = 0;
                $texts[$i] = NULL;
                foreach ($words as $w) {
                    $w = trim($w);
                    if (strlen($texts[$i] . ' ' . $w) < $this->maxStrLen) {
                        $texts[$i] = $texts[$i] . ' ' . $w;
                        if (preg_match('/[:;,.!?-]$/', $w)) { $i++; } // seperate at common breaks
                    } else {
                        $texts[++$i] = $w;
                    } 
                }

                // Get get separated files contents and marge them into one
                foreach ($texts as $txt) {
                    $pFile = $this->speak($txt, $this->lang); 
                    $this->contents .= $this->stripTags(file_get_contents($pFile)); 
                    unlink($pFile);
                }
                unset($words, $texts); 
                 
                // Save file
                file_put_contents($file, $this->contents); 
                $this->contents = NULL;
            }
        } else {
             
            // Generate unique mp3 file name 
            $file = sprintf($this->mp3File, $this->audioDir . md5($text)); 

            if (!file_exists($file)) { 
                // Text lenght 
                $this->textLen = strlen($text); 
                 
                // Words count 
                $this->wordCount = str_word_count($text); 

                // Encode string 
                $text = urlencode($text);

                // Download new file
                $this->download("http://translate.google.com/translate_tts?ie=UTF-8&q={$text}&tl={$this->lang}&total={$this->wordCount}&idx=0&textlen={$this->textLen}", $file);
            }
        } 
         
        // Returns mp3 file path 
        return $file; 
    } 
     
    /** Function to find the beginning of the mp3 file 
     * @param     String     $contents        - File contents 
     * @return     Integer 
     */  
    function getStart($contents) { 
        for($i=0; $i < strlen($contents); $i++){ 
            if(ord(substr($contents, $i, 1)) == 255){ 
                return $i; 
            } 
        } 
    } 
     
    /** Function to find the end of the mp3 file 
     * @param     String     $contents        - File contents 
     * @return     Integer 
     */  
    function getEnd($contents) { 
        $c = substr($contents, (strlen($contents) - 128)); 
        if(strtoupper(substr($c, 0, 3)) == 'TAG'){ 
            return $c; 
        }else{ 
            return FALSE; 
        } 
    } 

    /** Function to remove the ID3 tags from mp3 files 
     * @param     String     $contents        - File contents
     * @return     String
     */
    function stripTags($contents) {
        // Remove start
        $start = $this->getStart($contents);
        if ($start === FALSE) { 
            return FALSE;
        } else { 
            return substr($contents, $start);
        } 
        // Remove end tag 
        if ($this->getEnd($contents) !== FALSE){ 
            return substr($contents, 0, (strlen($contents) - 129));
        } 
    } 

    /** Function to download and save file 
     * @param     String     $url        - URL 
     * @param     String     $path         - Local path 
     */
    function download($url, $path) {  
        // Is curl installed? 
        if (!function_exists('curl_init')){ // use file get contents  
            $output = file_get_contents($url);
        }else{ // use curl  
            $ch = curl_init();  
            curl_setopt($ch, CURLOPT_URL, $url);  
            curl_setopt($ch, CURLOPT_AUTOREFERER, true);
            curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3) Gecko/20041001 Firefox/0.10.1");  
            curl_setopt($ch, CURLOPT_HEADER, 0);  
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            $output = curl_exec($ch);  
            curl_close($ch);
        } 
        // Save file
        file_put_contents($path, $output);
    }
}

Here is an example of using the TTS class in PHP.

<?php
include 'PHP_Text2Speech.class.php';

$t2s = new PHP_Text2Speech;
?>

<audio controls="controls" autoplay="autoplay">
  <source src="<?php echo $t2s->speak('What are you looking at? Wipe that face off your head.'); ?>" type="audio/mp3" />
</audio>

Automating Google Webmaster Tools Validation (PHP)

webtools
In order to claim a website with Google’s Webmaster Tools you have to verify you own it or at least have access to the file system. In order to do so Google gives you a file to upload to the domains www directory that is something like this: google9bf026a5h34deb40d.html

It use to be this file was completely empty returning nothing. However this probably lead to some hacking abuse where certain websites were not properly configured to give an empty page for 404 (page not found.) It doesn’t take a genius to figure out such a website could easily be registered as your own as the call to the validation file would return exact what Google was looking for.

The validation file is no longer empty and Google won’t accept empty files they now must contain the following:

google-site-verification: google9bf026a5h34deb40d.html

You can see the name of the file is now part of the content which helps eliminate validation hijacking

Suppose you got a lot of Google friends or accounts that you want to allow to have access to your site with their webmaster tools. Updating their individual validation files can be a chore so here is an easy way to automate the process.

This example is not exact you will need to adjust it to your own needs.

First add a line in your .htaccess file.

RewriteRule ^google ([a-zA-Z0-9]+).html$ googlevalidateme.php?v=$1 [NC,L]

Then have a PHP program called googlevalidateme.php

<?php
echo 'google-site-verification: google'.$_REQUEST[v].'.html';

So with this example any calles to googlexxxx.html will pass xxxx as a value to googlevalidateme.php which will return the correct validation code for Google.

Of course you might want to improve this with a call to a master list someplace so that unwanted others can’t validate and spy on you.

Auto Post to Posthaven via Email with Images (PHP)

posthaven_smash

If you were a fan of Posterous before it was bought out by Twitter, you will want to check out Posthaven. It is as of right now still in development but looks very promising, offering a more flexible and simpler blogging solution then with other sites.  This new blogging service isn’t free, but at $5 a month for 10 sub domain blogs it is extremely affordable.  If you want to lock in a wealth of prime real-estate on this upcoming service in the way of subdomain keywords, now is the time.

Like previous articles I have covered on automating blog posting, Posthaven works the same and is really easy. However there is a huge advantage using Posthaven over the other blog services and that is your automated posts can also get put right into Facebook page and Twitter account at the same time. That is cool! And it leverages your blogs big time.

Start by creating a Posthaven account, then login and create one or more site accounts.

To post to your account via email you need to then click on “Edit Your Account” and goto the section “Post by Email Settings”. Click on the checkbox next to ”Use a secret word to verify my emails” and enter a secret password in the field.

You also need to make sure that the email address you will be sending from is listed at the top where it says “Your Email Addresses”

For example lets say your secret is “mysecret” and your site account sub domain is “abc”. Then the email address you will send to post a new blog article would be post.mysecret@abc.posthaven.com

The subject of the email will be the articles title. The body will be the article itself and if you want to include an image then you send it with the email as an attachment. Lets see some code in PHP that will email post to your Posthaven blog. (We will use PHPMAILER found here.)

 

include('class.phpmailer.php');

$posthaven_account = "YOUR SUBDOMAIN NAME"; //Example "abc" NOT "abc.posthaven.com"
$posthaven_secret = "YOUR POSTHAVEN SECRET";

$gmail_your_name = "YOUR NAME";
$gmail_username = "YOUR GMAIL USERNAME";
$gmail_password = "YOUR GMAIL PASSWORD";
$gmail_email = "YOUR GMAIL EMAIL ADDRESS";
$image_location = 'C:/YOUR LOCATION OF IMAGE/IMAGE.JPG';
$email_title = "EMAIL TITLE";
$email_body = "EMAIL BODY"; // (LIMITED) HTML OK

$mail = new PHPMailer();
$mail->IsHTML(true);
$mail->IsSMTP();
$mail->SMTPAuth = true;
$mail->SMTPSecure = "ssl";
$mail->Host = "smtp.gmail.com";
$mail->Port = 465;
$mail->Username = $gmail_username;
$mail->Password = $gmail_password;
$fromname = $gmail_your_name;

$posthaven_blog_email = 'post.'.$posthaven_secret.'@'.$posthaven_account.'.posthaven.com';

$To = trim($posthaven_blog_email,"\r\n");

$mail->AddAttachment($image_location);
$mail->From = $gmail_email;
$mail->FromName = $fromname;
$mail->Subject = $email_title;
$mail->Body = $email_body;
$mail->AddAddress($To);
$mail->set('X-Priority', '3'); //Priority 1 = High, 3 = Normal, 5 = low
$mail->Send();

If you then want to capture the URL of the newly posted posthaven blog you can use the following code:

$posthaven_url = 'http://'.$posthaven_account.'.posthaven.com';
sleep(30); // give it enough time to receive and update the post (30 seconds)
$bf = file_get_contents(rtrim($posthaven_url,'/').'/posts.atom');
list($t,$b1) = explode("<updated>",$bf,2);
list($t,$b2) = explode('href="',$b1,2);
list($b3,$t) = explode('"',$b2,2);
$bb = trim(str_replace('"','',$b3));
$bb = trim(str_replace("'",'',$bb));
$bb = trim(str_replace(' ','',$bb));
echo '<li>LINK IS='.$bb;

WordPress.com Post By Email with Images (PHP)

wordpress_smash

In an earlier blog I showed how one can use a simple PHP script to send blog posts to blogger via email. The same thing can also be done to other blogs such as wordpress.com. When I say WordPress.com I mean the hosted site not the self hosted WordPress offered by wordpress.org (I will address self hosted WP sites in a future blog.)

Here are the steps to turn on Post By Email for WordPress.com blogs.

1. From the main account area: At the top of your wordpress.com login click “My Blogs” then under the blog listing “Blog Admin”, this will put you in the blogs dashboard
2. From the dashboard: On the left side menu, mouse over “Settings” on the sub menu select “Writing”. At the bottom of the Writing Settings page there is a section called “Post by Email” Click on the “My Blogs” link.
3. There should now be a list of all your blogs, in a table, one of the columns is called “Post by Email” and there is a button to push called “Enable” push it and it will generate a secret email that you can use to post blogs via emails to. Write this down we will use it in the code below.

When you send an email to this secret email address it will post the contents of the email to the blog and the subject of the email will become the title of the article. If you include an image as an attachment then the image will also be included in the blog and hosted on wordpress.com’s server.
However there is an issue at this time with such image attachments, the image is posted following the blog article contents, not at the top. In the forums the developers state they are looking into a fixing this issue but I don’t think it is a priority for them.

Another issue regards HTML content. Not all HTML tags and formatting will make as wordpress.com strips some out. Currently I have no list of what is and isn’t passed through but form and javascript tags are stripped.

I should also note that Post By Email for wordpress.com includes some extra custom tagging options. These options include:

• [category x,y,z]
• [excerpt]some excerpt[/excerpt]
• [tags x,y,z]
• [delay +1 hour]
• [comments on | off]
• [status publish | pending | draft | private]
• [password secret-password]
• [slug some-url-name]
• [title Your post title]
• [end] – everything after this shortcode is ignored (i.e. signatures). Make sure it’s on its own line with a blank line above it.
• [slideshow] – replaces the auto-gallery with a slideshow
• [nogallery] – disables the auto-gallery and displays all images inline
• [more] – more tag
• [nextpage] – pagination
• [geotag on | off] – override your geotagging privacy defaults to enable or disable the showing of geo information
• [publicize off|twitter|facebook] – change Publicize options
• [poll]question and answers[/poll] – insert a Polldaddy poll into your post

For details on how these tags operate you can read the documentation page on the wordpress.com site here: http://en.support.wordpress.com/post-by-email/

Now for the PHP code. I recommend using the PHPMAILER class and using a gmail account for the sender, this seems to be the most stable way of performing the email posts.

include('class.phpmailer.php');

$gmail_your_name = "YOUR NAME";
$gmail_username = "YOUR GMAIL USERNAME";
$gmail_password = "YOUR GMAIL PASSWORD";
$gmail_email = "YOUR GMAIL EMAIL ADDRESS";
$wordpress_email = "YOUR WORDPRESS SECRET EMAIL";
$wordpress_url = "YOUR WORDPRESS.COM BLOG URL";
$image_location = 'C:/YOUR LOCATION OF IMAGE/IMAGE.JPG';
$email_title = "EMAIL TITLE";
$email_body = "EMAIL BODY"; // (LIMITED) HTML OK

$mail = new PHPMailer();
$mail->IsHTML(true);
$mail->IsSMTP();
$mail->;SMTPAuth = true;
$mail->SMTPSecure = "ssl";
$mail->Host = "smtp.gmail.com";
$mail->Port = 465;
$mail->Username = $gmail_username;
$mail->Password = $gmail_password;
$fromname = $gmail_your_name;

$To = trim($wordpress_email,"\r\n");

$mail->AddAttachment($image_location);
$mail->From = $gmail_email;
$mail->FromName = $fromname;
$mail->Subject = $email_title;
$mail->Body = $email_body;
$mail->AddAddress($To);
$mail->set('X-Priority', '3'); //Priority 1 = High, 3 = Normal, 5 = low
$mail->Send();

If you are wanting to capture the URL for the newly posted blog after it has been made you can use the following.

sleep(15); // give it enough time to receive and update the post (15 seconds)
$bf = file_get_contents(rtrim($d2s[html_code],'/').'/feed/');
list($t,$b1) = explode("<item>",$bf,2);
list($t,$b2) = explode('<link>',$b1,2);
list($b3,$t) = explode('<',$b2,2);
$bb = trim(str_replace('"','',$b3));
$bb = trim(str_replace("'",'',$bb));
$bb = trim(str_replace(' ','',$bb));
echo '<li>LINK IS='.$bb;

Stop Giving Your Affiliate Link Juice

broken-link

It is no wonder that Amazon and other giant shopping sites that offer affiliate programs rank highest on search engines like Google. The associate that use the affiliate programs are helping drive those stores link popularity up by using the standard links which in turn hurts their own affiliate marketing strategies. There are easy ways you can implement linking to prevent sabotaging your own SEO.

The Problem

When you create a link that points to a product or the associated program for commission referrals, the search engines follow that link even if it is redirected by a 301 or other means. Even if you try and use Google’s “nofollow” rules the link is still observed as a pointer from your site to theirs. Even using a jump gate and robot.txt rules the search engines will still observe the outgoing link to the destination. When you have thousands of other associates using their own links to point to that same product then OF COURSE that drives the authority of the affiliate programs own page to the top of the search engine listings.

Then of course customers are not going to drill down several pages deep in search engine results to click on your link. That is if you get listed on the search engines at all with such tactics. It is counterintuitive to use such direct links for any type of reseller campaign.

The Solution

First you need to stop using the original manufacturer information supplied by the affiliate program for your affiliate link pages. When search engines see the exact same content on a thousand different pages then you will get very little traction if any. I will cover “content mutation” in a future article.

Addressing the problem with links and link juice pouring out of you page to the affiliate program you must eliminate the ability of the search engine to recognize and follow the actual link while not blocking your own web traffic from channeling through the link.

There are several different approaches that can be used, AJAX, Javascript, CSS Dom to name a few but the easiest is to simply use a POST form. Currently search engines such as Google do not try and press on form buttons and follow them, they are completely ignored, except in the case of simple action GET forms.

<form action="http://MY AFFILIATE LINK" method="POST">
<input type="submit" value="BUY NOW" />
</form>

This is not totally ideal as the link can still be picked up from the action tag, however not all affiliates will have the ability to add a jump gate, but it is better than nothing, it won’t be considered a hole in your page for link juice.

This is not totally ideal as the link can still be picked up from the action tag, however not all affiliates will have the ability to add a jump gate, but it is better than nothing, it won’t be considered a hole in your page for link juice.

A better solution is to use a simple jump gate that masks the URL, such as something like this.

<form action="jump.php" method="POST">
<input type="hidden" name="id" value="<?php echo base64_encode(http://MY AFFILIATE LINK); ?>" />
<input type="submit" value="BUY NOW" />
</form>

jump.php

<?php
header('Location: '.base64_decode($_REQUEST[id]));
exit;
?>

Mail2Blogger with Images (PHP)

blogger_smash

Blogger allows you to send posts via email, intended for on the go posting via mobile devices and such. To activate this feature you have to update your blog settings with a secret key.

1. Log into Blogger
2. Select “Settings for your Blog”
3. Select “Mobile and email”
4. Where it says “Posting using email” there is a box, in front of the box is your username, following the box is @blogger.com. In the box put your secret key.
5. On the radio selection below that make sure “Publish email immediately” is selected.
6. Click Save Settings

Now all you have to do to send a post to your blog is send it to that email address that contains your secret key. The title of your email will be the title. Keep in mind that if your email automatically adds a signature to your email that it will probably also appear on your post.

You can include an image that appears at the top of the article by attaching an image in your email. Blogger saves this to its server and hosts it (10MB limit for images)
Using PHP we can now easily post new blog articles at any time or even automatically using the PHPMAILER class from Synchro at Github. It is best that you use a gmail account email that is associated with your blog to reduce any imperial entanglements. Also by using gmail account and this method you are not required to have a running mail server or do any other funky relaying (great for those doing projects in XAMPP.)

include('class.phpmailer.php');

$gmail_your_name = "YOUR NAME";
$gmail_username = "YOUR GMAIL USERNAME";
$gmail_password = "YOUR GMAIL PASSWORD";
$gmail_email = "YOUR GMAIL EMAIL ADDRESS";
$blogger_secret_key = "YOUR BLOGGER SECRET KEY";
$blogger_url = "YOUR BLOGGER URL";
$image_location = 'C:/YOUR LOCATION OF IMAGE/IMAGE.JPG';
$email_title = "EMAIL TITLE";
$email_body = "EMAIL BODY"; // HTML OK

$mail = new PHPMailer();
$mail->IsHTML(true);
$mail->IsSMTP();
$mail->;SMTPAuth = true;
$mail->SMTPSecure = "ssl";
$mail->Host = "smtp.gmail.com";
$mail->Port = 465;
$mail->Username = $gmail_username;
$mail->Password = $gmail_password;
$fromname = $gmail_your_name;

$To = trim($gmail_username.'.'.$blogger_secret_key.'@blogger.com',"\r\n");

$mail->AddAttachment($image_location);
$mail->From = $gmail_email;
$mail->FromName = $fromname;
$mail->Subject = $email_title;
$mail->Body = $email_body;
$mail->AddAddress($To);
$mail->set('X-Priority', '3'); //Priority 1 = High, 3 = Normal, 5 = low
$mail->Send();

If you are keeping a database of your posts for other projects such as twittering and other linking you can extract the new posts link with the following code. Just add it after the end of the above code.

sleep(15); // blogger enough time to receive and update the post (15 seconds)
$bf = file_get_contents($blogger_url);
list($t,$b1) = explode("'post-title entry-title'",$bf,2);
list($t,$b2) = explode('<a href=",$b1,2); list($b3,$t) = explode(">',$b2,2);
$bb = trim(str_replace('"','',$b3));
$bb = trim(str_replace("'",'',$bb));
$blog_post_url = trim(str_replace(' ','',$bb));<a href="http://charleshays.com/wp-content/uploads/2013/07/blogger_smash.png"><img src="http://charleshays.com/wp-content/uploads/2013/07/blogger_smash-300x300.png" alt="blogger_smash" width="300" height="300" class="alignnone size-medium wp-image-102" /></a>
echo 'NEW POST AT='.$blog_post_url;
</a>