#!/usr/bin/env ruby # Summary: # This utility will take each jpg, png, gif in the specified directory, including sub-directories, and # copy it to the specified output directory (optional) for the year and month the photo was taken/created. # (example: ./unsorted/image.jpg -> ./sorted/2013/10/image.jpg) # # Code Author: Jason Savage #------------------------------------------ require 'exifr' require 'fileutils' def run() return if ARGV.length < 1 # get dir argument in_dir = ARGV[0] out_dir = ARGV[1] || in_dir # fix path if on windows in_dir = in_dir.gsub(%r{\\}) { "/" } out_dir = out_dir.gsub(%r{\\}) { "/" } puts 'in: ' + in_dir, 'out: ' + out_dir if Dir.exist? in_dir # loop through each *.jpg in the folder and move to dir/#{year}/ Dir.glob( in_dir + '/**/*.{jpg,png,gif}') do |file| move_file(out_dir, file) end end end; def move_file(dir, file) date_time = EXIFR::JPEG.new(file).date_time || File.mtime( file ) if date_time != nil && date_time.year != '' # get path as #{dir}/year/month out_dir = File.join(dir, date_time.year.to_s, zero_pad(date_time.month.to_s)) # create #{dir}/year/month if it doesn't exist? FileUtils.mkdir_p out_dir unless Dir.exist?(out_dir) # check if image file name is already used i = 1 ext = File.extname(file) fname = File.basename(file, ext) while File.exist? File.join(out_dir, fname + ext) fname = File.basename(file, ext) + '_' + i.to_s i += 1 end # move file to new directory FileUtils.cp( file, File.join(out_dir, fname + ext) ) end end def zero_pad(str) if str.to_f < 10 return '0' + str end return str end # run script run if __FILE__ == 'photo_date_sort.rb'
Thursday, October 24, 2013
Ruby Photo Sorter
Today, I worked on a personal project in Ruby that would sort my ever expanding library of family photos into a common folder structure. I like the way iTunes handles this by sorting them into sub-folders as /year/month the photo was taken so that's what I went with.
Wednesday, October 9, 2013
SQL to find distance from latitude/longitude
This is a quick post, mostly so i never forget this. A guy I work with either wrote this or found it on the internet. If you have a database of addresses with lat/long pairs for each entry, this SQL statement will find the distance (I think in miles) from a given lat/long.
SELECT ( 3959 * acos( cos( radians(origin_lat) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(origin_long) ) + sin( radians(origin_lat) ) * sin( radians( latitude ) ) ) ) AS distance FROM zip_codes HAVING distance <= _miles ORDER BY zip;
Monday, September 30, 2013
YouTube Browser-based Uploading with OAuth
YouTube has changed over to OAuth for using their data API (API 3.0)
https://developers.google.com/youtube/v3/
Which is fine but it seems they haven't move all of their code over to the new API or haven't finished the docs for it yet, so this is sort of a missing manual for implementing the old API 2.0 Browser-based Uploading seen here:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading
https://developers.google.com/youtube/registering_an_application
- when you create a client ID, set application type to web application
- redirect url can be this: http://localhost/oauth2callback, you only need it once
You will also need to get a developer key from here:
https://code.google.com/apis/youtube/dashboard/
The developer_key will not change so make sure you add this to your config file.
Now that you have your application registered, you should have an oAuth Client ID and Client Secret. Go ahead and save them into your project as a config variable since they don't change, but you need them to get an access_token. You now need to get an authorization code from the API. This is a one time use token that can be exchanged for an access_token and refresh_token. They call it a refresh_token in the docs, but it's really a long term token that doesn't expire, which is what we want.
The code url will be this:
After you call this url you will be taken to a page and asked if your application is allowed to access this Google account. Click accept and you will be redirected to the url you specified earlier with the get variable code=... added to the end of the url. This is your one time use token so save it into your text editor while you create the next request.
To make a request to get the refresh_token, which is another one time thing, you can use PHP cURL code below or the command line.
If everything worked, you should get something like this back:
Bingo!, copy and pasted the refresh_token into your config file. Like I said before, it doesn't expire like the access_token will. You will use it to get a new access_token each time your form loads. Using a token to get a token seems strange but it's Google so I guess they know what they're doing.
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading
You need to create a video object on the YouTube server to upload a video to. To do this, you'll need to get an upload_token and a post_url from the YouTube API.
http://gdata.youtube.com/action/GetUploadToken
To create a video object, a video title and description along with some keywords and a category are required before can you get the post_url so, in my case, I just created temporary ones and updated them later.
The category definitions can be found here:
http://gdata.youtube.com/schemas/2007/categories.cat
In the code above, the method '''get_access_token();''' uses the refreash_token to make a cURL request to the YouTube API to get a valid access_token.
The form needs to have the 2 fields 'token' and 'file', which the API is expecting:
When video is finished uploading, the form will redirect to the url you set for next=... along with some extra GET variables from YouTube. The variable status=... is always returned, so you can use that to check if it was successful, which the value would be 200.
If the upload was a success, you will also get the variable id=... which will have the video's new id ( http://www.youtube.com/watch?v={{id}} ).
If the status wasn't 200, then an error occured and you will get the variable code=... which will describe the error.
That should be it. Easy huh? after 3 days I finally got this working so I figured it needed documenting and like i said, it's probably going to change soon so who knows how long this will work.
Google also has some code libraries you can use:
https://developers.google.com/youtube/v3/code_samples/php
I looked into them, but was having trouble fitting the code into my project, so maybe if I started with all this in mind, I would've been able to use them.
https://developers.google.com/youtube/v3/
Which is fine but it seems they haven't move all of their code over to the new API or haven't finished the docs for it yet, so this is sort of a missing manual for implementing the old API 2.0 Browser-based Uploading seen here:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading
Step 1- register your app with Google
If you are creating this for one of your brands you need to login/create a Google account for them. After you logged in, you'll need to register your application by following the instructions found here:https://developers.google.com/youtube/registering_an_application
- when you create a client ID, set application type to web application
- redirect url can be this: http://localhost/oauth2callback, you only need it once
You will also need to get a developer key from here:
https://code.google.com/apis/youtube/dashboard/
The developer_key will not change so make sure you add this to your config file.
Step 2 - get a refresh_token
https://developers.google.com/youtube/v3/guides/authenticationNow that you have your application registered, you should have an oAuth Client ID and Client Secret. Go ahead and save them into your project as a config variable since they don't change, but you need them to get an access_token. You now need to get an authorization code from the API. This is a one time use token that can be exchanged for an access_token and refresh_token. They call it a refresh_token in the docs, but it's really a long term token that doesn't expire, which is what we want.
The code url will be this:
$url = 'https://accounts.google.com/o/oauth2/auth?'; $url .= 'client_id=' . {{insert_your_client_id_here}} $url .= '&redirect_uri=' . urlencode('http://localhost/oauth2callback'); //this is whatever url you set in the "create client id dialog" $url .= '&scope=' . urlencode('https://gdata.youtube.com'); $url .= '&response_type=code'; $url .= '&access_type=offline'; $url .= '&approval_prompt=force';
After you call this url you will be taken to a page and asked if your application is allowed to access this Google account. Click accept and you will be redirected to the url you specified earlier with the get variable code=... added to the end of the url. This is your one time use token so save it into your text editor while you create the next request.
To make a request to get the refresh_token, which is another one time thing, you can use PHP cURL code below or the command line.
//using PHP cUrl $curl = curl_init( 'https://accounts.google.com/o/oauth2/token' ); $post_fields = array( 'code' => '{{insert_the_code_you_just_got_here}}', 'client_id' => '{{insert_your_client_id_here}}', 'client_secret' => '{{insert_your_client_secret_here}}', 'redirect_uri' => 'http://localhost/oauth2callback', //this doesn't do anything, but it's validated so i needs to match what you've been using 'grant_type' => 'authorization_code' ); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post_fields)); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded' )); //send request $response = curl_exec($curl); print_r($response);
If everything worked, you should get something like this back:
{ "access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc", "token_type" : "Bearer", "expires_in" : 3600, "refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74" }
Bingo!, copy and pasted the refresh_token into your config file. Like I said before, it doesn't expire like the access_token will. You will use it to get a new access_token each time your form loads. Using a token to get a token seems strange but it's Google so I guess they know what they're doing.
Step 3 - post url and upload token
We are back to this guide:https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading
You need to create a video object on the YouTube server to upload a video to. To do this, you'll need to get an upload_token and a post_url from the YouTube API.
http://gdata.youtube.com/action/GetUploadToken
To create a video object, a video title and description along with some keywords and a category are required before can you get the post_url so, in my case, I just created temporary ones and updated them later.
The category definitions can be found here:
http://gdata.youtube.com/schemas/2007/categories.cat
public function get_video_upload_info() { //get youtube access token $access_token = $this->get_access_token(); //create a video obj with temp info $video_title = 'Video Temp Title ' . rand(1, 9999); $video_desc = 'Temp Desc'; $video_keywords = 'facebook, contest'; //setup request body as xml $xml_str = implode('', array( '<?xml version="1.0"?>', '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">', '<media:group>', '<media:title type="plain">' . $video_title . '</media:title>', '<media:description type="plain">' . $video_desc . '</media:description>', '<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">Animals</media:category>', '<media:keywords>' . $video_keywords . '</media:keywords>', //'<yt:private/>', '<yt:accessControl action="list" permission="denied"/>', //causes the video to be unlisted '</media:group>', '</entry>')); //use curl to call youtube api $ch = curl_init( 'http://gdata.youtube.com/action/GetUploadToken' ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Authorization: Bearer ' . $access_token, 'GData-Version: 2', 'X-GData-Key: key=' . {{your_developer_key}}], 'Content-Type: application/atom+xml; charset=UTF-8' )); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_str); //send request $xml_response = curl_exec($ch); //close connection curl_close($ch); $result = simplexml_load_string( $xml_response ); if( $result->getName() === 'errors' ) { return array('post_url' => 'broke', 'upload_token' => 'not_a_real_token'); } return array('post_url' => (string) $result->url, 'upload_token' => (string) $result->token); }
In the code above, the method '''get_access_token();''' uses the refreash_token to make a cURL request to the YouTube API to get a valid access_token.
private function get_access_token() { $ch = curl_init( 'https://accounts.google.com/o/oauth2/token' ); $post_fields = array( 'client_id' => {{insert_your_client_id_here}}, 'client_secret' => {{insert_your_client_secret_here}}, 'refresh_token' => {{insert_your_refresh_token_here}}, 'grant_type' => 'refresh_token' ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields)); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded' )); //send request $response = json_decode( curl_exec($ch) ); curl_close($ch); return $response->access_token; }
Step 4 - upload the video to YouTube
Using the post_url we can now display the form to complete this whole process. The post_url you will use in your form will need to have the GET variable next=... added to it. This will be the url that the form will redirect to after the video has finished uploading.The form needs to have the 2 fields 'token' and 'file', which the API is expecting:
<form method="post" action="{{post_url}}?next={{next_url}}" enctype="multipart/form-data"> <input name="token" type="hidden" value="{{upload_token}}"/> <input type='file' id='file' name='file' accept="video/*" /> </form>
When video is finished uploading, the form will redirect to the url you set for next=... along with some extra GET variables from YouTube. The variable status=... is always returned, so you can use that to check if it was successful, which the value would be 200.
If the upload was a success, you will also get the variable id=... which will have the video's new id ( http://www.youtube.com/watch?v={{id}} ).
If the status wasn't 200, then an error occured and you will get the variable code=... which will describe the error.
That should be it. Easy huh? after 3 days I finally got this working so I figured it needed documenting and like i said, it's probably going to change soon so who knows how long this will work.
Google also has some code libraries you can use:
https://developers.google.com/youtube/v3/code_samples/php
I looked into them, but was having trouble fitting the code into my project, so maybe if I started with all this in mind, I would've been able to use them.
Monday, June 10, 2013
The Selected() Plugin
Part of the code I write requires a sort of radio button or toggle button functionality. The core ability is to be able to set an item as selected (or checked, active, clicked, etc.). I ran into this a lot and found myself writing the same simple code over and over again until I created the selected plugin.
Now i can simply write this with jQuery:
https://github.com/jasonsavage2/jquery.selected
Let me know what you think.
Now i can simply write this with jQuery:
$("a").selected(true); var isSelected = $("a").selected();Of course this led to being able to have a group of buttons with only one selected at a time:
$("a.tab").selectedGroup();I have the plugin up on GitHub with a little more explanation and a demo:
https://github.com/jasonsavage2/jquery.selected
Let me know what you think.
Thursday, June 6, 2013
Minimal jQuery.slideshow()
Yesterday, I worked on a barebones jQuery slideshow that would be able to be easily customized for our websites. Why create another slideshow plugin when we have this wonderful plugin: http://jquery.malsup.com/cycle/? I don't know, waste some time at work, ha!
I only wanted the basic functionality of a slideshow. It needs to select a group of items and cycle through them at a set speed. There should be public methods like: next(), prev(), gotoSlide(), pause(), play() for manually switching slides. While building this slideshow, I figured that it doesn't actually need to switch the slides, just dispatches "change" event when it's time.
In the end i did break down and add a bit of code to auto generate previous button, next button, and/or a menu if needed, but other than that it is very small and ment to be extended to your needs.
https://github.com/jasonsavage2/jquery.slideshow
Let me know what you think, or if you have any ideas on how it could be better.
I only wanted the basic functionality of a slideshow. It needs to select a group of items and cycle through them at a set speed. There should be public methods like: next(), prev(), gotoSlide(), pause(), play() for manually switching slides. While building this slideshow, I figured that it doesn't actually need to switch the slides, just dispatches "change" event when it's time.
In the end i did break down and add a bit of code to auto generate previous button, next button, and/or a menu if needed, but other than that it is very small and ment to be extended to your needs.
//default settings (w menu) $(".slideshow").slideshow({ nav : "menu", delay : 700, slidesSelector : "ul.slides > li", onChange : [function (event, index, $slides, $slideshow)] });Now it's up on Git Hub (which I'm a bit of a noob at). you can download it here:
https://github.com/jasonsavage2/jquery.slideshow
Let me know what you think, or if you have any ideas on how it could be better.
Wednesday, June 5, 2013
Day 1
So I started this blog because it seems like everyone who is in the web development biz has some kind of blog to show what they figure out from day to day while building websites. We'll see how it goes, I'm not that great of a writer...
Subscribe to:
Posts (Atom)