Get it here! Perl DBD::Sybase 1.14.01 for Active State Perl 5.16 Windows XP/Vista/7/8 32bit AND 64bit

Assuming that you installed Sybase SDK OpenClient 15.7.

Sybase Openclient is included in the Sybase Developer’s Kit, and ASE PC Client. If you don’t have a license, you can download the ASE 15.7 Developer Edition for Windows which will include it.

Install ActiveState Perl from http://www.activestate.com (free) and install DBI if it isn’t already installed.  It should be but you never know…

  1. Start -> ActiveState Perl -> Perl Package Manager
  2. install DBI
  3. exit

Now, the easy part.  Install the DBD-Sybase-1.14 1.14 PPM:

  1. download DBD-Sybase-1.14
  2. extract zip file to temporary directory (e.g. c:\test)
  3. Start -> Run -> cmd.exe (as Administrator if Vista or Windows 7)
  4. cd \test
  5. ppm install DBD-Sybase.ppd
  6. exit

That’s it :)

It should automatically install the DBD::Sybase for Perl 5.16 32bit or 64bit depending on which version of Active State Perl you have installed.

UPDATE (March 6th, 2013):

The PPM is fixed now. Please let me know of any issues.

Share Button

Audiobook chapter support for FFprobe (Perl) module

I have multiple audiobook files (m4b) that ffprobe is able to retrieve the chapters from just fine… except the chapter information is printed to stderr and never in the formatted (STDOUT) output. The Perl module FFprobe doesn’t handle the chapters so I submitted feature request #73803

Feature request is to format the chapter output.

jason@jason-Inspiron-1545 ~/bin $ ffprobe "/home/jason/Audiobooks/Ben Bova/Mars/Mars 1.m4b" 1>/dev/null
  libavutil    51.  7. 0 / 51.  7. 0
  libavcodec   53.  5. 0 / 53.  5. 0
  libavformat  53.  2. 0 / 53.  2. 0
  libavdevice  53.  0. 0 / 53.  0. 0
  libavfilter   2.  4. 0 /  2.  4. 0
  libswscale    2.  0. 0 /  2.  0. 0
  libpostproc  52.  0. 0 / 52.  0. 0
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xddfac0] max_analyze_duration reached
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/home/jason/Audiobooks/Ben Bova/Mars/Mars 1.m4b':
  Metadata:
    major_brand     : M4B 
    minor_version   : 0
    compatible_brands: M4B mp42isom
    creation_time   : 2009-09-08 16:19:29
    album           : Mars
    artist          : Ben Bova
    genre           : Audiobook
  Duration: 03:51:23.41, start: 0.000000, bitrate: 81 kb/s
    Chapter #0.0: start 0.000000, end 2779.567914
    Metadata:
      title           : Mars - 01 of 24
    Chapter #0.1: start 2779.567914, end 5555.049161
    Metadata:
      title           : Mars - 02 of 24
    Chapter #0.2: start 5555.049161, end 8334.617075
    Metadata:
      title           : Mars - 03 of 24
    Chapter #0.3: start 8334.617075, end 11110.098322
    Metadata:
      title           : Mars - 04 of 24
    Chapter #0.4: start 11110.098322, end 13883.419864
    Metadata:
      title           : Mars - 05 of 24
    Stream #0.0(und): Audio: aac, 44100 Hz, stereo, s16, 80 kb/s
    Metadata:
      creation_time   : 2009-09-08 16:19:29
    Stream #0.1(eng): Subtitle: text / 0x74786574
    Metadata:
      creation_time   : 2009-09-08 17:31:00
Unsupported codec with id 94213 for input stream 1

patch to add m4b chapter support:

82c82
< my ($tree, $branch, $tag, $stream);
---
>     my ($tree, $branch, $tag, $stream, $chapter);
100c100,108
< }
---
> 	} elsif ($line =~ m/Chapter \#(\d+\.*\d+): start (\d+\.*\d+)\, end (\d+\.*\d+)/i) {
>       my ($start, $end) = ($2, $3);
>       $chapter = $1;
>       $chapter =~ s/\.//g;
>       $chapter =~ s/^0+(\d)/$1/;
> 
>       $$tree{chapters}{$chapter} = { start => $start, end => $end };
>     } elsif ($line =~ /title\s+: (.+)$/) {
>       $$tree{chapters}{$chapter}{title} = $1;
101a110
>   }
Share Button

Perl module Flickr::Simple2 v0.03 for the Flickr API released!

I just uploaded the Perl module Flickr::Simple2 version 0.03 to CPAN (PAUSE) so it might be a few minutes/hours before it shows up in the CPAN mirrors.

Revision history for Perl extension Flickr::Simple2.

0.03  Wed May 13 11:18:00 2009
– significant changes to the API to make more methods of the Flickr API available.
– using Iterator::Simple so we can retrieve one photo at a time and internalize the handling of Flickr Photo *pages*
0.02  Initial CPAN release
0.01  Thu Feb 12 11:33:35 2009
– original version; created by h2xs 1.23 with options
-XA -n Flickr::Simple2

Share Button

First official release of Flickr::Simple2 to Perl’s CPAN repository :)

Flickr
Flickr

Flickr::Simple2 is an XML::Simple based Perl API to communicate with Flickr.  It is in the early phases of development… comments are welcome :)

You will need to supply your own Flickr API key.

I released version 0.02 to Perl’s CPAN repository this morning after several delays related to the birth of my son a week ago.  As always, comments and constructive critism are welcome.

Share Button

Why oh why is working with XML so bloomin’ difficult in Perl?

I love perl, I really do. The problem is that I need to also work with XML as well. CPAN has numerous modules that work with XML to one degree or another.

Producing XML is simple and easy. Reading or manipulating it involves loading the XML through a parser. The parsers will create a myriad of hashes, arrays and the like but navigating them even with tools like XPath make you want to perform oral surgery on yourself using rusty tiddly winks.

So my question is this: Are there any XML parsers for Perl that are easy to use without having to deal with arbitrary hashes of hashes of hashes and …. (don’t forget the arrays)?

Share Button

The Perl Review: Issue 5.1, Winter 2008 has been released!

brian d foy and crew release another amazing Perl Review magazine issue.  If you haven’t subscribed yet, please please do so, it is a treasure trove of information!  You can even claim the subscription on your 2009 taxes as a business expense.  How cool is that?!?

Issue 5.1, Winter 2008

Surviving Your First Week of Perl (sample)

Issue 5.1, Winter 2008
Issue 5.1, Winter 2008

Programming with CPAN.pm (sample)

CPAN Patching with Git (sample)

Refactoring Factorial (sample)

Downloading From TiVo (sample)

plus Commentary and Module Reviews by Nadim Khemir, Perl News, and more…

Share Button

Received ‘Can’t call method “value” on an undefined value at /usr/share/perl5/IO/Uncompress/RawInflate.pm’ on Ubuntu 8.04 Server (Hardy Heron)

On my media server, I wanted to set up Perl’s CPAN but when I ran install Bundle::CPAN, I ctrl-c’d out of it when I noticed that bzip2 wasn’t installed.  Silly me.  Soon after I started receiving:

Can’t call method “value” on an undefined value at /usr/share/perl5/IO/Uncompress/RawInflate.pm

Reinstalling Perl and all the related ubuntu packages doesn’t fix it, nor does removing the ~root/.cpan dir.  There is an easy workaround though.

aptitude remove libcompress-raw-zlib-perl
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Reading extended state information      
Initializing package states... Done
Building tag database... Done      
The following packages are BROKEN:
  libio-compress-zlib-perl 
The following packages have been kept back:
  bind9-host dnsutils libbind9-30 libisccfg30 
The following packages will be REMOVED:
  libcompress-raw-zlib-perl 
0 packages upgraded, 0 newly installed, 1 to remove and 4 not upgraded.
Need to get 0B of archives. After unpacking 319kB will be freed.
The following packages have unmet dependencies:
  libio-compress-zlib-perl: Depends: libcompress-raw-zlib-perl (>= 2.008) but it is not installable
Resolving dependencies...
The following actions will resolve these dependencies:

Remove the following packages:
apparmor-utils
libcompress-zlib-perl
libio-compress-zlib-perl
libmp3-tag-perl
librpc-xml-perl
libwww-perl
libxml-parser-perl
ubuntu-standard

Score is 592

Accept this solution? [Y/n/q/?] 

Make a note of the packages that it will remove, then tell it to go ahead. It will remove these packages!

Start cpan, and type in

install Bundle::CPAN

When it is complete, reinstall the ubuntu packages that you removed earlier (for example):

aptitude install apparmor-utils libcompress-zlib-perl libio-compress-zlib-perl libmp3-tag-perl librpc-xml-perl libwww-perl libxml-parser-perl ubuntu-standard

That’s it. Your cpan is working again 🙂

Share Button

FlickrDownload.pl

If you’ve ever tried to back up your Flickr account or download the public Flickr photos of other users, then your options were pretty limited on Linux. After trying and failing to get FlickrDown to work under wine, I decided to write my own… in perl 🙂

The various Flickr API’s on CPAN are incomplete with respect to obtaining the list of public/friends photos. I had to use XML::Parser::Lite::Tree::XPath to extract the photos from each Flickr page.

By default, we obtain the largest photo available (orginal -> large -> medium -> small), populate the EXIF comments field with the Flickr photo title, description and url. We verify the file type with File::MimeInfo::Magic and change/add the file extension as necessary.

During testing I received numerous duplicate files which were mostly resolved by adding a rudimentary duplicate file checker.

It should work on just about any platform that Perl does.

Thoughts?
FlickrDownload.pl

FlickrDownload.pl

#!/usr/bin/perl

use strict;
use warnings;

use Digest::MD5 qw(md5_hex);
use Config::Simple;
use Image::ExifTool;
use File::MimeInfo::Magic qw(mimetype extensions);
use Flickr::API::Photos;
use Flickr::Person;
use Getopt::Std;
use HTML::Entities ();
use File::MimeInfo::Magic;
use IO::Scalar;
use LWP;
use Tie::File::AsHash;
use XML::Parser::Lite::Tree::XPath;

##############################

my $cfg = new Config::Simple('FlickrDownload.ini');

my $user_name;
my $id;
my $email;
my %arg_options;
my $photo_directory;
my $found;

getopts('e:i:u:', \%arg_options);

$user_name = $arg_options{u};
$id = $arg_options{i};
$email = $arg_options{e};

my $flickr_api_key = $cfg->param('Flickr.API_KEY');
my $flickr_secret = $cfg->param('Flickr.API_SHARED_SECRET');
my $flickr_email = $cfg->param('Flickr.email');
my $flickr_password = $cfg->param('Flickr.password');

##############################
sub decode_html {
  my $string = shift;

  my $new_string = HTML::Entities::decode($string);

  if ($string ne $new_string) {
    $new_string = decode_html($new_string);
  }

  return $new_string;
}
##############################

my $flickr_person = Flickr::Person->new( {
    api_key    => $flickr_api_key,
    email    => $flickr_email,
    password  => $flickr_password
  } );

if ($user_name) {
  $found = $flickr_person->find( { username => $user_name } );
} elsif ($email) {
  $found = $flickr_person->find( { email => $email } );
  $user_name = $flickr_person->username();
  $found = $flickr_person->find( { username => $user_name } );
} elsif ($id) {
  $found = $flickr_person->id( {id => $id} );
  $user_name = $flickr_person->username();
  $found = $flickr_person->find( { username => $user_name } );
}

if ( $found ) {
  my $page_num = 1;
  my $more_pages = 1;
  my $api = $flickr_person->{people_api}->{api};

  $photo_directory = $cfg->param('Photos.directory') . "/" . $user_name;
  $api->{api_secret} = $flickr_secret;

  my $flickr_photos = Flickr::API::Photos->new(
    $flickr_api_key,
    $flickr_email,
    $flickr_password);

  unless (-d $photo_directory) {
    mkdir $photo_directory
      or die ('Unable to create directory "' . $photo_directory . '"' );
  }

  # for determine whether we might be downloading a duplicate, we need a hash with
  #   the MD5 sum &amp; filename.  We tie both hashes to a file in the flickr user's
  #   directory
  tie my %MD5_HASH, 'Tie::File::AsHash', $photo_directory . "/.md5s", split => ':'
    or die "Problem tying %hash: $!\n";

  tie my %FILES_HASH, 'Tie::File::AsHash', $photo_directory . "/.files_md5s", split => ':'
    or die "Problem tying %hash: $!\n";

  while ($more_pages) {
    my $response = $api->execute_method('flickr.people.getPublicPhotos', {
      api_key    => $flickr_api_key,
      user_id    => $flickr_person->id,
      per_page  => 500,
      page    => $page_num
      } );

    my $xpath = new XML::Parser::Lite::Tree::XPath($response->{tree});
    my @nodes = $xpath->select_nodes('/photos/photo');

    if ($#nodes > 0) {
      foreach my $node (@nodes) {
        my $original_photo;
        my $photo_id = $node->{attributes}->{id};
        my $photo_hash = $flickr_photos->getInfo($photo_id);
        my $photo_title =
          $photo_hash->{'title'}
            ? $photo_hash->{'title'}
            : "";
        my $description =
          $photo_hash->{'description'}
            ? decode_html( $photo_hash->{'description'} )
            : "";

        my %photo_sizes =
          map { $_->{'label'} => $_ }
            @{ $flickr_photos->getSizes($photo_id)->{sizes} };

        if (exists $photo_sizes{'Original'}) {
          $original_photo = $photo_sizes{'Original'};
        } elsif (exists $photo_sizes{'Large'}) {
          $original_photo = $photo_sizes{'Large'};
        } elsif (exists $photo_sizes{'Medium'}) {
          $original_photo = $photo_sizes{'Medium'};
        } elsif (exists $photo_sizes{'Small'}) {
          $original_photo = $photo_sizes{'Small'};
        } else {
          warn "Unable to find url.  Skipping photo."
        }

        printf "name: %s id: %s description: %s\n",
          $photo_title,
          $photo_hash->{'id'},
          $description;

        my $photo_filename = $photo_directory . '/' . $photo_title;
        $photo_filename =~ s/\.\w+$//;

        # Prepopulating the file extension will allow us to eliminate
        #   the vast majority of duplicate images by not downloading
        #   them in the first place.
        if (exists $photo_hash->{'originalformat'}) {
          my $extension = $photo_hash->{'originalformat'};
          $photo_filename .= "_" . $photo_hash->{'id'} . "." . $extension;
        } else {
          # if we don't know at this point what format the image file is
          #   without downloading the image, we can assume it is a jpg
          #   because the vast majority of the photos are jpg.
          $photo_filename .= "_" . $photo_hash->{'id'} . ".jpeg";
        }

        if (-f $photo_filename &amp;&amp; (stat($photo_filename))[7] > 2048) {
          printf "We already have photo %s .. Skipping\n", $photo_title;
        } else {
          my $FH;

          my $request = HTTP::Request->new(GET => $original_photo->{'source'} );
          my $response = $api->request($request);
          my $md5_sum = md5_hex($response->content);

          # since we have downloaded the photo, let's put the proper file
          #   extension on it.
          if (my $file_ext = extensions( mimetype(new IO::Scalar \($response->content) ) ) ) {
            $photo_filename =~ s/\.\w+$//;
            $photo_filename .= "." . $file_ext;
          }

          if (exists $MD5_HASH{$md5_sum}) {
            printf "We already have photo %s .. Skipping\n", $photo_title;
          } else {
            $MD5_HASH{$md5_sum} = $photo_filename;
            $FILES_HASH{$photo_filename} = $md5_sum;

            open($FH, ">", $photo_filename)
              or warn ("Unable to write to $photo_filename.\n" );

            binmode $FH;
            print $FH $response->content;
            close $FH;

            # We're going to use Image::ExifTool instead of the built in
            #  exif extracted information from Flickr::API::Photos because
            #  we want to write to the file.
            my $exifTool = new Image::ExifTool;
            my $info = $exifTool->ImageInfo($photo_filename);

            unless ($info->{'DateTimeOriginal'}) {
              if ($photo_hash->{'dates'}->{'taken'}) {
                my $taken_date = $photo_hash->{'dates'}->{'taken'};
                $taken_date =~ s/\-/\:/g;
                $exifTool->SetNewValue("DateTimeOriginal", $taken_date);
              }
            }

            $exifTool->SetNewValue("Comment", $photo_title . ": " . $description . " " . $original_photo->{'source'});
            my $result = $exifTool->WriteInfo($photo_filename);
          }
        }
      }

      $page_num++;
    } else {
      $more_pages = undef;
    }

    untie %MD5_HASH;
    untie %FILES_HASH;
  }
}

FlickrDownload.ini

[Flickr]
email=jason_froebe@email.org
password=**SuperSecretPassword**
API_KEY=**YOUR Flickr API KEY**
API_SHARED_SECRET=**YOUR SHARED SECRET FROM Flickr**

[Photos]
#  where you want to put the photos
directory=/home/jason/flickr
Share Button