Saturday, July 23, 2016

osx how to drag and drop to a php command line script (or, any script) // hack hack hack

Sorry for the clumsy blog entry title, I'm trying to up the google juice.

Digging my way out of problems with iTunes, I would like to drag a music file from my old archive to a new location; but the gotcha is I want it to copy a subset of the the path (containing the artist and album info) from the source folder to the destination folder.

Most "drag and drop" / PHP googling shows how to make an upload web application. (And obviously, it's probably weird that I've started using PHP so much for this stuff - but since I grew up with Perl as both a command line scripting tool and a cgi resource, it makes a kind of sense.)  If memory serves, on Windows I used to be able to drop a file onto a script file in Explorer, and it would run the script with the file as an argument, but that trick doesn't seem to work on OSX. Or MacOS. Whatever. (Incidentally, for the PHP upload case, I've had great luck with dropzone.js)

So, to the Terminal! (Obviously one of the big appeals of MacOS for a certain flavor of geek was a proper shell terminal vs a DOS Window.) We're going to leverage how dragging a file onto a Terminal window puts in the file path - but it would be annoying to have to type "php myscript.php" before each drag operation, so I'm going to make a convenience script, called "c", just so the name is easy to type:

#!/bin/sh
php copyIncludeSubpath.php "$@"

And then, for EXTRA typing laziness, I'm going to add the current directory to the path (not a good practice in general, but ok for a temporary Terminal window, to save typing ./ all the time)


export PATH=$PATH:.

Anyway, here's the script:
<?php
$oldroot = "/Volumes/My Book/monk/data/music/";
$newroot = "/Users/kisrael/Dropbox/dev/2016/scripting/LibraryXMLCompare/musicToAdd/";
for($i = 1; $i < count($argv);$i++){ #go over everything passed in, skipping name of php script
    $path = $argv[$i];
    copyFileFromRootToRoot($path,$oldroot,$newroot);
}
function copyFileFromRootToRoot($path, $oldroot,$newroot){
    if(substr($path,0,strlen($oldroot)) != $oldroot) { #check for oldroot at start of path
        print "$path doesn't start with $oldroot!\n";
        return;
    }
    $mainpart = substr($path, strlen($oldroot)); #get relevant part of path
    $oldloc = $oldroot.$mainpart;
    $newloc = $newroot.$mainpart;
    $path = substr($newloc,0,strrpos($newloc,DIRECTORY_SEPARATOR)); #get path minus file name
    if(! is_dir($path)) @mkdir($path, 0777, true);  #make it, eating errors
    copy($oldloc, $newloc) || print "FAIL ON $newloc\n";
}
?>
It's a little clumsy, but does the job.

Thursday, July 21, 2016

the history of the url

The History of the URL. Interesting to think about some of the "might have beens", and just to remember that the standards a coder so assumes now weren't handed down by divine mandate, or intuitive common sense...

Monday, July 18, 2016

osx command line power and sorting folders in perl

Talking with some fellow greybeard geeks, I realize I could be consider python instead of PHP as a scripting "light file management and text manipulation tasks" tool, or maybe node.js (native manipulation of JSON definitely appeals to me)

But PHP it is for me for now. I used that php recursive file iterator from the other week to do photo wrangling. I've been going through two decades of digital photos and picking out the dozen best photos from each year - it's a great way to regain a sense of time and place of those years that I might otherwise lose. I've never embraced a photo management program (I tried iPhoto for a while but it's really not geared for collections as big as mine) so my photos are in folders of the form "data/photos/YYYY/some_date_or_descriptor". When I copied each year's best into a new folder, the "metadata" of the folder descriptor was lost, so I made a kind of hacky PHP script to find files that had the matching name in the original folder (considering a matching file size a match) and renaming the chosen out file to embed a mangled form of the extra path info. (Most of the folders had some kind of sortable date at the head of the name, though the actual format varied from year to year.)

There were two small OSX tricks I used, nothing too obscure: one was leveraging how dragging files to terminal copies their full paths, which I would then use as arguments for my script. The other was using PHP's shell_exec() to call "open", which for an image file pops open preview. With those two things I was able to construct a crude UI, so for ambiguous file names (where file size didn't match, because I rotated things) it could open up the two images and say "are these the same file?" (Also, "open ." is the easiest way to pop up the current working directory in a Finder window)

Of course, folder and file name hackery is kind of an ugly way to deal with photo metadata, but over the years it's the only thing I've been able to trust. In a similar note, I've noticed a whole bunch of music missing from iTunes. Not sure what happened, but I think I may have to resort to similar easy-to-backup, possible-to-verify handling for my music and playlists, rather than relying on an endless "smart playlist" for getting back to my new music.

For my homebrew blog, I use a drag and drop photo uploader script combined with some ImageMagick resizing. I so wish I had written it many years ago; my blog is one of my most reliable archives, and my new scripts' method of generating viewable-sized versions - but with links to the fullsize originals - would have kept some big image files safe that I've since lost track of. C'est la vie.

Anyway, the wrapper script for the image processing and construction of img tags and links to fullsize versions is old Perl. Before now I didn't have much of a reason to sort file order (usually I'd upload just one or two images at once) but now I do, so I had to lookup code like the following:

$imagetempDir = ".";
opendir(DIR,$imagetempDir) or print "can't open $dirname";
my @files = sort { $a cmp $b } readdir(DIR);
while ( my $f = shift @files ) {
    if($f ne "." && $f ne ".."){
        print "look at $f<br>\n";
   }
}


I'm putting the code here on the offchance someone else finds it useful. I'm also putting it on my old Perl cheatsheet... kept in a homebrew database system I've been using for over a decade. These days, Simplenote probably does a better job for this kind of stuff, but I didn't have Simplenote and Dropbox back then - they are pretty awesome though.

Saturday, July 16, 2016

parsing itunes Library.xml into json

Is it fair to say iTunes' Library.xml situation is a hot mess, representing all the reasons why the early 2000's embrace of xml got replaced by an affection for json? I'll let you be the judge...

I've grown to distrust iTunes, sadly. A month or two ago I noticed one album that I remembered having (Mario Bros the Movie soundtrack... don't judge.) was missing. The other week I realized a King Missile album wasn't there, and today a difficult-to-replace album from a college A Capella group I worked with once (again, don't judge).

Some of these files I can recover from old file backups. Plus I had a copy of iTunes' "Library.xml" (What you get when you goto "File | Library | Export Library") from when I switch my collection to my mac so I should be able to figure out what has flown the coop.

Except look at this xml file excerpt (again, not with the judging of the music choice, ok?)
<dict>
        <key>Track ID</key><integer>3853</integer>
        <key>Name</key><string>Diggin' Your Scene</string>
        <key>Artist</key><string>Smash Mouth</string>
        <key>Album Artist</key><string>Smash Mouth</string>
        <key>Composer</key><string>Gregory Camp</string>
        <key>Album</key><string>Astro Lounge</string>
        <key>Genre</key><string>Alternative Pop</string>
        <key>Kind</key><string>MPEG audio file</string>
        <key>Size</key><integer>3803563</integer>
        <key>Total Time</key><integer>190066</integer>
      [...]
</dict>

I think the # one thing I don't like about XML is when it relies on node proximity for semantic values, especially since it has a rather rich ability to label metadata as attributes of the like. Having what is roughly a flat list of key tag, value tag (of some kind), key, value tag (of some kind).  Wouldn't something like
<dict>
  <item key="Track ID" type="integer">3853</item>
</dict>
make more sense? Or if that was too difficult to validate (and heaven knows XML engineers loved their validations) making key an attribute
<dict>
  <integer key="Track ID">3853</integer>
</dict>
at least nesting these things into a wrapper, so it's an iterable set?
<dict>
  <item><key>Track ID</key><integer>3853</integer></item>
</dict>
Do I just not "get" the magic of XML in this case?

Anyway, I decided to get this into JSON, where I'm most comfortable manipulating things. My first attempt was using sparkbuzz's generic jQuery-xml2json but it choked pretty hard on Apple's mess, giving me separate piles of keys, integers, and string values. I know you can use JQuery's selectors on xml, so I decided to go that route. Here is my in-progress embedded script (using the old python miniwebserver trick to serve up the files) - it doesn't yet do the comparison of the old Library contents with the new, but that should be pretty easy with this JSON format:
  {
   "Track ID": "3853",
   "Name": "Diggin' Your Scene",
   "Artist": "Smash Mouth",
   "Album Artist": "Smash Mouth",
   "Composer": "Gregory Camp",
   "Album": "Astro Lounge",
   "Genre": "Alternative Pop",
   "Kind": "MPEG audio file",
   "Size": "3803563",
   "Total Time": "190066",
  [...]
  }
Like a breath of fresh air, ain't it?

Anyway, here's the relevant bit of script: (small.xml was my test file, and when I'm ready to gear up to the real files, I'll add multiple calls in the "$.when()" so that files are loaded in parallel and work doesn't begin until all are loaded.

var library = {};
$.when(
        $.ajax({
        url: 'small.xml',
        dataType: 'xml',
        success: function(response) {
            xmlToLibraryJson("small",response);
        }
    })    
).then(function() {      
  //just a place holder to show my results  
    $("#guts").append(JSON.stringify(library,null," "));  
});

function xmlToLibraryJson(file,response){
          $xml = $(response);//$.parseXML( response );
          var lib = [];
          $xml.find("key:contains('Tracks')+dict > dict").each(function(i,elem){
            var $dict = $(elem);
            var dict = {};
            $dict.find("key").each(function(j,thiskey){
              dict[$(thiskey).text()] = $(thiskey).next().text();
            });
            lib.push(dict);            
          });
          library[file] = lib;
}

So, that's it - it was a little harder to assemble than it looks, I haven't had to use $.next() and fancy proximity CSS-ish selectors that much before. I still have to accept the fact that if I add in old songs to iTunes, there's virtually no way to get iTunes to not treat them as new songs, and that will absolutely screw up my rolling "new music" Smart Playlists. But it's better than not having my old music at all.

Thursday, July 14, 2016

easier shared location groups with google maps

Over the years I've put in a fair chunk of work on integrating Google Maps into Porchfest websites and using that core to make up posters as well.

The music festival HONK!TX takes what looks to be an easier approach - if you look at their schedule page the "Map It!" links go to instances made in something called Google My Maps that can even be embedded on other websites.

This might be an easier bet for small organizations that don't have a lot of developer firepower.

Monday, July 11, 2016

odd little #anchor and css trick

The :target Trick ... some cleverness you can do with the #url fragment and CSS. Sometimes it's fun to avoid jQuery etc, but other times I wonder if it's worth the fiddling with the browser history.

Saturday, July 2, 2016

where am i?

I'm proud of the mobile mode of the JP Porchfest website - I think it looks ok and it's easy to both scoot around the Google map, get information on what's playing at a certain porch, as well as look at the big block schedule.

This year I added Gelocation Marker for Google Maps, so that people wandering JP can see right where they are. For the developer using it could be hardly be simpler, including the small .js file and then adding a line like
var GeoMarker = new GeolocationMarker(map);
does the trick.