#!/bin/bash set -u # report use of uninitialized variables ####################################################################### # # Bash shell script to upload files to SmugMug. Optionally uses # GraphicConverter to extract IPTC caption and keywords from image and # video files. # # Originally based on script # http://braindump.dk/tech/2007/10/03/smugmug-uploader/ # MYVERSION="2.0" # May 22, 2014 # ####################################################################### # # Smugmug API documentation for functions used here: # # smugmug.albums.get (1.1.1) # smugmug.images.get (1.2.0) # smugmug.images.delete (1.1.1) # smugmug.images.changeSettings (1.2.2) # smugmug.login.withPassword (1.1.1) # smugmug.login.withHash (1.1.1) # smugmug.logout (1.1.1) # upload.smugmug.com # # http://api.smugmug.com/services/api/?version=1.2.2&method=smugmug.images.changeSettings # ####################################################################### # # May 22, 2014 # - Uploading to an empty album (gallery) is no longer an error. # - Now URL-encoding captions and keywords rather than RFC 5918 # encoding during original upload # # April 30, 2014 # - Checking if file exists before uploading. # - Added Perl scriptlet to parse album list to obtain keys. # - Added Perl scriptlet to parse album info to check if file exists. # - Added '-s' and '-r' options to force skipping or replacing # duplicate files. # - Added '-u' to update files or their caption/keywords if they # differ. If a file's size or MD5 hash differ, the file is # replaced. If the caption or keywords differ, it is updated. # '-u' is the default. # - Added RFC 5987 encoding and URL-encoding to protect captions and # keywords that contain characters unsafe for raw HTTP transport # (such as multi-line captions). # - Added '-c' option to only update caption/keywords. # # April 15, 2014 # - Used own API key and user agent string # - Save and supply cookies so API call to fetch album IDs works # - Use Perl scriptlet to parse and display album list # - Can run script without list of files or album ID to list albums # - Added '-h' option to save/use hash file to save entering password # # # May 20, 2012 (Randall Gellens): # - Changed 'bin/sh' to '/bin/bash' to be able to use Bash features. # - Now prompt for password if not supplied as parameter or in file. # - Check for 'md5sum' and 'md5' and use whichever is found. # - Added numerous comments (so I could follow the code). # - Various minor code tweaks for readability (I'm not a wizard and # was having trouble following the code). # - Added '-v' option to print verbose messages. # - Added '-m' option to print progress messages ('-p' is already # used for password). # - Added '-i' option to extract IPTC caption and keywords using # GraphicConverter and embedded AppleScript. # - More detailed usage information. # - Print usage information when run with no parameters or unknown # parameter. # - Added '-l' (lower-case "L") option to include only files with # specified Finder label index or color name. # - Added '-L' option to exclude files with specified Finder label # index or color name. # # ####################################################################### MYNAME="${0##*/}" # expression replaces 'basename' MYDIR="${0%/*}" # expression replaces 'dirname' #----------------------------------------------- # Show script usage #----------------------------------------------- function usage () { echo "Uploads files to a SmugMug album" echo "Usage: $MYNAME [ options ] files..."; echo "Options:" echo " -U email (optional; SmugMug email or login ID)" echo " -P password (optional; SmugMug password, prompt if" \ "omitted)" echo " -h (save password hash, re-use if still valid)" echo " -a albumID (optional; lists albums if omitted)" echo " -c (only update caption/keyword info, and only if needed)" echo " -i (obtain IPTC caption and keywords from GraphicConverter)" echo " -l Finder-label-index (lower-case \"L\") (optional;" \ "includes only files with specified" echo " Finder label index)" echo " -L Finder-label-index (optional; excludes files with" \ "specified Finder label index)" echo " -m (print a progress line for each file uploaded)" echo " -r (replace file if already exists)" echo " -s (skip file if already exists)" echo " -u (update files or their caption/keywords if they " \ "differ. If a file's size or MD5" echo " hash differ, the file is replaced. If the caption" \ "or keywords differ, it is" echo " updated. '-u' is the default.)" echo " -v (verbose messages)" echo "" echo "If none of '-c', '-r', '-s', nor '-u' are set, '-u' is used." echo "" echo "Use of '-h' means rarely needing to enter password. Can " \ "also set username (and " echo "optionally password) in file .smugup" \ "rather than via '-U' and '-P'." echo echo "Syntax of .smugup file:" echo " EMAIL=\"recht@somewhere\"" echo " PASSWORD=\"passhere\"" echo "" echo "Default label colors:" echo " 0 No color" echo " 1 Orange" echo " 2 Red" echo " 3 Yellow" echo " 4 Blue" echo " 5 Purple" echo " 6 Green" echo " 7 Gray" } #----------------------------------------------- # Verbose messages #----------------------------------------------- function say () { if [[ "${opt_verbose}" = "y" ]]; then echo "$*" >&2 # echo to stderr fi } #----------------------------------------------- # Progress messages #----------------------------------------------- function progress () { if [[ ( "${opt_progress}" = "y" ) || ( "${opt_verbose}" = "y" ) ]]; then echo "$1" fi } function progress_interim () { if [[ ( "${opt_progress}" = "y" ) || ( "${opt_verbose}" = "y" ) ]]; then echo -n "$1" fi } #----------------------------------------------- # URL-encode a string # # Input string is in parameter $1. # Result is stored in global variable URL_ENCODED_STR #----------------------------------------------- function url_encode () { local string="${1}" local strlen=${#string} local encoded= local pos local c local o # # Loop through string, examining each character. # Safe characters are copied to new string as-is. # Unsafe characters are copied as '%' and the hex code # of the character (using the bash built-in 'printf'). # for (( pos=0 ; pos < strlen ; pos++ )); do c=${string:$pos:1} # 'c' is current character case "$c" in [-_.~a-zA-Z0-9] ) # safe characters are copied as-os o="${c}" ;; * ) # everything else is encoded printf -v o '%%%02x' "'$c" esac encoded+="${o}" # 'o' is output character done say "url_encode ( $string ) = ${encoded}" URL_ENCODED_STR="${encoded}" } #----------------------------------------------- # URL-decode a string # # Input string is in parameter $1. # Result is stored in global variable URL_DECODED_STR #----------------------------------------------- function url_decode () { # # Sets global URL_DECODED_STR to the input string after # replacing each sequence of a percent (%) sign followed # by two hex digits with the literal character. # # This is perhaps a risky gambit, but since all escape characters # must be encoded, we can replace %NN with \xNN and pass the lot to # 'printf -b', which will decode hex for us # printf -v URL_DECODED_STR '%b' "${1//%/\\x}" say "url_decode ( $1 ) = \"$URL_DECODED_STR\"" } #----------------------------------------------- # RFC 5987 encode a string # # Input string is in parameter $1. # Result is stored in global variable URL_ENCODED_STR #----------------------------------------------- function rfc5987_encode () { local string="${1}" local strlen=${#string} local encoded= local pos local c local o local TICK=\' # # Set up encoded string preamble, which is: # charset "'" [ language ] "'" value-chars # encoded="UTF-8${TICK}${TICK}" # # Loop through string, examining each character. # Safe characters are copied to new string as-is. # Unsafe characters are copied as '%' and the hex code # of the character (using the bash built-in 'printf'). # # Safe characters are: # ALPHA / DIGIT # "!" / "#" / "$" / "&" / "+" / "-" / "." # "^" / "_" / "\`" / "|" / "~" # for (( pos=0 ; pos < strlen ; pos++ )); do c=${string:$pos:1} # 'c' is current character case "$c" in [\!\#$\&+-.\^_\`\|~a-zA-Z0-9] ) # safe characters copied as-is o="${c}" ;; * ) # everything else is encoded printf -v o '%%%02x' "'$c" esac encoded+="${o}" # 'o' is output character done say say "rfc5987_encode ( $string ) = ${encoded}" URL_ENCODED_STR="${encoded}" } #----------------------------------------------- # escape-quote encode a string # # Input string is in parameter $1. # Result is stored in global variable URL_ENCODED_STR #----------------------------------------------- function escape_quote () { local string="${1}" local strlen=${#string} local encoded= local pos local c local o # # Loop through string, examining each character. # Safe characters are copied to new string as-is. # Unsafe characters are copied as '\' and the character. # for (( pos=0 ; pos < strlen ; pos++ )); do c=${string:$pos:1} # 'c' is current character if [[ "$c" < "!" || "$c" == "\\" || "$c" > "~" ]]; then o="\\${c}" # non-printable and backslash are encoded else printf -v o '%%%02x' "'$c" fi encoded+="${o}" # 'o' is output character done say "escape_quote ( $string ) = ${encoded}" URL_ENCODED_STR="${encoded}" } #----------------------------------------------- # Parse a typical SmugMug result response # # Caller executes 'RSLT=(curl ....)' # # 'curl' response is in parameter $1. # Result is stored in global variables STAT, CODE, and MSG #----------------------------------------------- function parse_result () { STAT= CODE= MSG= local RSLT=$1 # # BASH string manipulation: # #${string#substring} -- Deletes shortest match from front #${string%substring} -- Deletes shortest match from back #${string##substring} -- Deletes longest match from front #${string%%substring} -- Deletes longest match from back # STAT=${RSLT##*\*} if [[ "$STAT" == "fail" ]]; then # parse: CODE=${RSLT##*\ "/tmp/iptc-${IPTC_ITEM}.txt" IPTC_DATA="$IPTC" # # Restore internal field separator # IFS=$SAVEIFS } #----------------------------------------------- # Extract label color (as index) using Finder # # File name passed as parameter $1 # # Label index returned in 'LABEL' (as integer) #----------------------------------------------- function get_label () { local DIR local FN local THE_FILE say "get_label ( $1 )" # # Make sure we have a full (normalized) path # SAVEIFS=$IFS IFS=$(echo -en "\n\b") # DIR="${1%/*}" # expression replaces 'dirname' DIR=$(unset CDPATH && cd "$DIR" && pwd) FN="${1##*/}" # expression replaces 'basename' THE_FILE="${DIR}/${FN}" # IFS=$SAVEIFS # # By default, shell script breaks parameters at space # change to break at new line # SAVEIFS=$IFS IFS=$(echo -en "\n\b") LABEL= LABEL=$(/usr/bin/osascript << EOT set file_path to "$THE_FILE" set a_file to POSIX file file_path tell application "Finder" to set the_label to label index of file a_file return the_label EOT ) say "Label index of file \"$THE_FILE\" is $LABEL" # # Restore internal field separator # IFS=$SAVEIFS } #----------------------------------------------- # Fetch list of albums # # File name into which to store list of albums is in parameter $1 #----------------------------------------------- function get_albums () { local ALBUM_FILE="$1" local RSLT say "get_albums ( $ALBUM_FILE )" RSLT=$( curl -A "$UA" --cookie $COOKIES --cookie-jar $COOKIES -s "https://api.smugmug.com/hack/rest/1.1.1/?method=smugmug.albums.get&SessionID=${SID}&APIKey=${APIKEY}" ) parse_result "$RSLT" # stores result in STAT, CODE, and MSG if [[ "$STAT" != "ok" ]]; then echo "attempt to list albums failed: ($CODE): $MSG" echo "URL:" echo "https://api.smugmug.com/hack/rest/1.1.1/?method=smugmug.albums.get&SessionID=$SID&APIKey=$APIKEY " echo echo "RSLT: " echo "$RSLT" exit 1 fi # # Now write the result into $ALBUM_FILE" # echo "$RSLT" >"$ALBUM_FILE" say "fetched album info to $ALBUM_FILE" } #----------------------------------------------- # Print list of albums (ID and title) to stdout. # # Also optionally saves list of album keys (ID and key) in file. # # File name of XML list of albums passed as parameter $1 # File name to store list of albums and keys passed as parameter $2 #----------------------------------------------- function list_albums () { local ALBUMS_FILE=$1 local RSLT say "parsing $ALBUMS_FILE" # # Perl script to parse album list file # PERLSCRIPT=$( cat <<-'__END__' #!/usr/bin/perl use strict; use warnings; use XML::Simple; #use Data::Dumper; my $albums_file = $ARGV[0]; my $albums_keys_file = $ARGV[1]; my $albums_data = XMLin ( $albums_file, forcearray => [ 'Album' ] ); my $KEYS_FILE; if ( defined $albums_keys_file && "$albums_keys_file" != "" ) { if ( open ( $KEYS_FILE, ">", "$albums_keys_file" ) ) { # print "Opened file $albums_keys_file as $KEYS_FILE\n"; } else { $KEYS_FILE = ""; print "Albums keys file can't be opened.\n" } } else { $KEYS_FILE = ""; print "Albums keys file not specified.\n" } #print "data:\n\n"; #print Dumper ( $albums_list ); my %albums_list = (); # extra hash for simple list of albums #my %albums_keys = (); # extra hash for simple list of album keys # # Undoubtedly there is a much simpler way to do this, but this # works and isn't too hard to follow: first we loop through the # album data from the XML list and fill in the simple album # list. Then we loop through the simple album list to produce a # sorted list of albums. # foreach my $album ( keys %{$albums_data->{Albums}->{Album}} ) { $albums_list{$album} = $albums_data->{Albums}->{Album} ->{$album}->{Title}; #$albums_keys{$album} = $albums_data->{Albums}->{Album} # ->{$album}->{Key}; } my @keys = keys %albums_list; my $size = @keys; print "$size albums:\n"; foreach my $name ( sort { lc $albums_list{$a} cmp lc $albums_list{$b} } keys %albums_list ) { print $name . ': ' . $albums_list{$name} . "\n"; } __END__ ) # # Call Perl script to parse album XML file # perl -e "$PERLSCRIPT" $ALBUMS_FILE RSLT=$? if [[ "$RSLT" != "0" ]]; then echo "Unable to parse albums list ($ALBUMS_FILE): $RSLT" fi } #----------------------------------------------- # Return the album key for a specified album. # # File name of XML list of albums passed as parameter $1 # ID of desired album is in parameter $2 # # Returns key in $ALBUM_KEY #----------------------------------------------- function get_album_key () { local ALBUMS_FILE=$1 local ALBUM_ID=$2 local RSLT ALBUM_KEY=0 say "get_album_key ( $ALBUMS_FILE, $ALBUM_ID )" # # Perl script to parse album list file # PERLSCRIPT=$( cat <<-'__END__' #!/usr/bin/perl use strict; use warnings; use XML::Simple; my $albums_file = $ARGV[0]; my $album_id = $ARGV[1]; my $albums_data = XMLin ( $albums_file, forcearray => [ 'Album' ] ); my $album_key = ""; foreach my $album ( keys %{$albums_data->{Albums}->{Album}} ) { if ( $album == $album_id ) { $album_key = $albums_data->{Albums}->{Album} ->{$album}->{Key}; print "$album_key\n"; exit 0; } } exit 1; __END__ ) # # Call Perl script to parse album XML file # ALBUM_KEY="" say "$LINENO: Calling perl" ALBUM_KEY=$(perl -e "$PERLSCRIPT" "$ALBUMS_FILE" "$ALBUM_ID") RSLT=$? if [[ "$RSLT" != "0" ]]; then echo "[$LINENO]: Unable to parse albums list ($ALBUMS_FILE): $RSLT" fi if [[ "$ALBUM_KEY" == "" ]]; then echo "Can't find key for album $ALBUM_ID" else say "perl -e returned $RSLT; \$ALBUM_KEY = $ALBUM_KEY" fi } #----------------------------------------------- # Fetches list of files in an album and writes to a file. # # Album ID passed as parameter $1. # Album key passed as parameter $2. # File name to store list of files passed as parameter $3 # File name to store full XML details of album in $4 # # Optionally dumps parsed XML structure if parameter $5 is "true" #----------------------------------------------- function get_files_in_album () { local ALB_ID=$1 local ALB_KEY=$2 local FILES_LIST=$3 local ALB_XML=$4 local DUMP=$5 local RSLT say "get_files_in_album ( $ALB_ID, $ALB_KEY, $FILES_LIST," \ "$ALB_XML, $DUMP )" RSLT=$( curl -A "$UA" --cookie $COOKIES --cookie-jar $COOKIES -s "https://api.smugmug.com/hack/rest/1.2.0/?method=smugmug.images.get&&APIKey=${APIKEY}&SessionID=${SID}&AlbumID=${ALB_ID}&AlbumKey=${ALB_KEY}&Heavy=true" ) parse_result "$RSLT" # parses into STAT, CODE, and MSG if [[ "$STAT" != "ok" ]]; then echo "attempt to fetch album file info failed: ($CODE): $MSG" echo "URL:" echo "https://api.smugmug.com/hack/rest/1.2.0/?method=smugmug.images.get&APIKey=${APIKEY}&SessionID=${SID}&AlbumID=${ALB_ID}&AlbumKey=${ALB_KEY}&Heavy=true" echo echo "RSLT: $RSLT" echo "" >"$ALB_XML" # write an empty file echo "" >"$FILES_LIST" # write an empty file return 0 fi # # Now write the result into file $ALB_XML # echo "$RSLT" >"$ALB_XML" say "fetched album file info to $ALB_XML" # # Perl script to parse album file info # PERLSCRIPT=$( cat <<-'__END__' #!/usr/bin/perl use strict; use warnings; use XML::Simple; my $album_data_file = $ARGV[0]; my $album_file_list = $ARGV[1]; my $do_dump_data = ( defined $ARGV[2] && $ARGV[2] eq "true" ); my $album_data = XMLin ( $album_data_file, forcearray => [ 'Image' ] ); my $LIST_FILE; if ( defined $album_file_list && "$album_file_list" ne "" ) { if ( open ( $LIST_FILE, ">", "$album_file_list" ) ) { # print "Opened file $album_file_list as $LIST_FILE\n"; } else { $LIST_FILE = ""; print "Album list file $album_file_list can't be " . "opened.\n" } } else { $LIST_FILE = ""; print "Album list file not specified.\n" } if ( $do_dump_data ) { use Data::Dumper; print "data:\n\n"; print Dumper ( $album_data ); print "\n\n\n\n"; print "keys:\n\n"; print Dumper ( keys %{$album_data->{Images}->{Image}} ); print "\n\n\n\n"; } foreach my $image ( keys %{$album_data->{Images}->{Image}} ) { print "In loop; image ID=$image\n" if ( $do_dump_data ); my $ref = $album_data->{Images}->{Image}->{$image}; if ( $do_dump_data ) { print "file \#$ref->{Position}: $ref->{FileName}\n"; print "ID: $image" . "; key: $ref->{Key}" . "; size: $ref->{Size}" . "; MD5: $ref->{MD5Sum}" . "; date upl: $ref->{Date}" . "; updated: $ref->{LastUpdated}" . "\n"; } # # Write to file: file name, ID, key, size, MD5, mod date # print $LIST_FILE ( "$ref->{FileName}" . "\t" . "$image" . "\t" . "$ref->{Key}" . "\t" . "$ref->{Size}" . "\t" . "$ref->{MD5Sum}" . "\t" . "$ref->{LastUpdated}" . "\n" ); } close ( $LIST_FILE ); #print ( "Wrote files list to $album_file_list\n" ); __END__ ) # # Call Perl script to parse album XML file # perl -e "$PERLSCRIPT" "$ALB_XML" "$FILES_LIST" "$DUMP" RSLT=$? if [[ "$RSLT" != "0" ]]; then echo "$LINENO: Unable to fetch and parse album file list: $RSLT" fi } #----------------------------------------------- # Return an attribute of a specific file in an album. # # File name of XML list of files in the album passed as parameter $1 # ID of desired file is in parameter $2 # Desired attribute is in parameter $3 # Sample attributes: "Caption", "Keywords", "Position", # "Longitude", "Latitude", "Altitude" # # Returns attribute in $FILE_ATTRIBUTE #----------------------------------------------- function get_file_attribute () { local ALB_XML=$1 local FILE_ID=$2 local ATTRIB=$3 local RSLT FILE_ATTRIBUTE= say "get_file_attribute ( $ALB_XML, $FILE_ID, $ATTRIB )" # # Perl script to parse files list file # PERLSCRIPT=$( cat <<-'__END__' #!/usr/bin/perl use strict; use warnings; use XML::Simple; my $album_data_file = $ARGV[0]; my $file_id = $ARGV[1]; my $attrib = $ARGV[2]; my $album_data = XMLin ( $album_data_file, forcearray => [ 'Image' ] ); my $attrib_value = ""; foreach my $image ( keys %{$album_data->{Images}->{Image}} ) { if ( $image == $file_id ) { $attrib_value = $album_data->{Images}->{Image}->{$image} ->{$attrib}; print "$attrib_value\n"; exit 0; } } exit 1; __END__ ) # # Call Perl script to parse album XML file # FILE_ATTRIBUTE= say "$LINENO: Calling perl" FILE_ATTRIBUTE=$( perl -e "$PERLSCRIPT" "$ALB_XML" "$FILE_ID" \ "$ATTRIB" ) RSLT=$? if [[ "$RSLT" != "0" ]]; then echo "[$LINENO]: Unable to extract file attribute from album" \ "XML ($ALB_XML): $RSLT" fi if [[ "$FILE_ATTRIBUTE" == "" ]]; then echo "Can't find $ATTRIB attribute for file $FILE_ID" else local TICK=\' say "perl -e returned $RSLT; $ATTRIB (${#FILE_ATTRIBUTE}):" \ "${TICK}${FILE_ATTRIBUTE}${TICK}" fi } #----------------------------------------------- # Deletes a file from an album. # # File ID is in parameter $1. # # API version 1.2.0 does not take album ID. I assume # the file ID is unique to an album. #----------------------------------------------- function delete_file_from_album () { local IMAGE_ID="$1" local RSLT local DEL_ID say "delete_file_from_album ( $IMAGE_ID )" RSLT=$( curl -A "$UA" --cookie $COOKIES --cookie-jar $COOKIES -s "https://api.smugmug.com/hack/rest/1.1.1/?method=smugmug.images.delete&SessionID=${SID}&APIKey=${APIKEY}&ImageID=${IMAGE_ID}" ) parse_result "RSLT" # stores result in STAT, CODE, and MSG if [[ "$STAT" == "fail" ]]; then echo "$LINENO: Delete failed: status=$STAT; code=$CODE; message=\"$MSG\"" fi if [[ "$STAT" == "ok" ]]; then DEL_ID=${RSLT##*\*/} say "Upload status: $STAT" if [[ "$STAT" == "ok" ]]; then IMG_URL=$( grep "URL=" "$OUT" ) IMG_URL=${IMG_URL/*URL=\"/} IMG_URL=${IMG_URL/\"*/} progress "...done" say "Image URL: $IMG_URL" IMG_ID=$( grep "id=" "$OUT" ) IMG_ID=${IMG_ID/*Image id=\"/} IMG_ID=${IMG_ID/\"*/} say "Image ID: $IMG_ID" elif [[ "$STAT" == "fail" ]]; then # parse: RSLT=$( grep "/} SID=${SID/<\/SessionID>*/} PWHASH=$( echo "$RSLT" | grep PasswordHash ) PWHASH=${PWHASH/*/} PWHASH=${PWHASH/<\/PasswordHash>*/} USERID=$( echo "$RSLT" | grep UserID ) USERID=${USERID/*/} USERID=${USERID/<\/UserID>*/} if [[ -z "${SID:-}" ]]; then echo "Unable to login" echo "$RSLT" exit 1 fi if [[ "$opt_pwhash" == "y" ]]; then echo "USERID=$USERID" > $HASH_FILE echo "PWHASH=$PWHASH" >> $HASH_FILE say "Saved hash to $HASH_FILE" fi say "Successfully logged in" say " session ID: $SID" say " password hash: $PWHASH" say " user ID: $USERID" } #----------------------------------------------- # Login with saved hash. # # If successful, session ID is saved in $SID. #----------------------------------------------- function login_with_hash () { local RSLT say "login_with_hash ()" source "$HASH_FILE" say "Read hash file" say " password hash: $PWHASH" say " user ID: $USERID" RSLT=$( curl -A "$UA" --cookie $COOKIES --cookie-jar $COOKIES \ -s "https://api.smugmug.com/hack/rest/1.1.1/?method=smugmug.login.withHash&UserID=${USERID}&PasswordHash=${PWHASH}&APIKey=${APIKEY}" ) SID=$( echo "$RSLT" | grep SessionID ) SID=${SID/*/} SID=${SID/<\/SessionID>*/} if [[ -z "${SID:-}" ]]; then echo "Unable to login" echo "$RSLT" exit 1 fi say "Successfully logged in using PW hash" say " session ID: $SID" } #----------------------------------------------- # Start of main script #----------------------------------------------- if [[ "$#" == "0" ]]; then usage exit $LINENO fi opt_progress="n" opt_verbose="n" opt_iptc="n" opt_pwhash="n" opt_exclude_label= opt_include_label= opt_skip="n" opt_replace="n" opt_update="n" opt_capt_key="n" SID= EMAIL= while getopts ":chmvia:P:U:L:l:rsu-" opt do case $opt in c) opt_capt_key="y" if [[ ( "$opt_replace" == "y" ) || ( "$opt_skip" == "y" ) || ( "$opt_update" == "y" ) ]]; then echo "Only set one of '-c' (caption/keyword only), '-r'" \ "(replace), '-s' (skip), or '-u' (update)." echo usage exit $LINENO fi say "Only updating caption/keyword info (if needed)" ;; h) opt_pwhash="y" say "Saving/re-using password hash" ;; U) EMAIL="$OPTARG" say "email: $EMAIL" ;; P) PASSWORD="$OPTARG" say "password: ******" ;; a) ALBUM="$OPTARG" say "Album: $ALBUM" ;; i) opt_iptc="y" say "fetching IPTC caption and keywords from GraphicConverter" ;; L) if [[ "$OPTARG" =~ [0-7]$ ]] # '$' means end of input pattern then LABEL_INDEX="$OPTARG" else get_label_index "$OPTARG" fi if [[ "$LABEL_INDEX" =~ [0-7]$ ]]; then opt_exclude_label="$LABEL_INDEX" say "excluding files with label index $opt_exclude_label" else echo "Label must be an index (0-7) or color name." exit $LINENO fi ;; l) if [[ "$OPTARG" =~ [0-7]$ ]] # '$' means end of input pattern then LABEL_INDEX="$OPTARG" else get_label_index "$OPTARG" fi if [[ "$LABEL_INDEX" =~ [0-7]$ ]]; then opt_include_label="$LABEL_INDEX" say "including only files with label index $opt_include_label" else echo "Label must be an index (0-7) or color name." exit $LINENO fi ;; m) say "print progress line for each file uploaded" opt_progress="y" ;; v) opt_verbose="y" say "verbose mode" ;; r) opt_replace="y" if [[ ( "$opt_capt_key" == "y" ) || ( "$opt_skip" == "y" ) || ( "$opt_update" == "y" ) ]]; then echo "Only set one of '-c' (caption/keyword only), '-r'" \ "(replace), '-s' (skip), or '-u' (update)." echo usage exit $LINENO fi say "Replacing files that already exist" ;; s) opt_skip="y" if [[ ( "$opt_replace" == "y" ) || ( "$opt_capt_key" == "y" ) || ( "$opt_update" == "y" ) ]]; then echo "Only set one of '-c' (caption/keyword only), '-r'" \ "(replace), '-s' (skip), or '-u' (update)." echo usage exit $LINENO fi say "Skipping files that already exist" ;; u) opt_update="y" if [[ ( "$opt_replace" == "y" ) || ( "$opt_capt_key" == "y" ) || ( "$opt_skip" == "y" ) ]]; then echo "Only set one of '-c' (caption/keyword only), '-r'" \ "(replace), '-s' (skip), or '-u' (update)." echo usage exit $LINENO fi say "Updating files that already exist if they differ from" \ "local files" ;; -) #echo "--" ;; *) echo "Unrecognized option: $opt" usage exit $LINENO ;; esac done shift $(($OPTIND - 1)) if [[ ( "$opt_replace" == "n" ) && ( "$opt_update" == "n" ) && ( "$opt_skip" == "n" ) ]]; then opt_update="y" fi say "Parameter: ${1:-}" HAVE_CURL=$( command -v curl ) if [[ "$HAVE_CURL" != "" ]]; then say "HAVE_CURL: $HAVE_CURL" else echo "Curl is not on the path" exit $LINENO fi if command -v md5sum >/dev/null 2>&1 ; then MD5CMD="md5sum -b" MD5SFX=" | awk '{print $1}'" elif command -v md5 >/dev/null 2>&1 ; then MD5CMD="md5 -q " MD5SFX= else echo "Can't find 'md5sum' or 'md5'" exit $LINENO fi say "Using \"$MD5CMD\" for MD5 hash" # # Configuration can be supplied via file .smugup # if [[ -f ~/.smugup ]]; then source ~/.smugup fi UA="${MYNAME}/2.0" APIKEY="TFslWefjvWbfQ0wGm806JAH9Q6LHEPnK" # # Make sure we can access a cookies file # COOKIES="${HOME}/.${MYNAME}.cookies" if [[ ! -f "$COOKIES" ]]; then touch $COOKIES RSLT=$? if [[ "$RSLT" == "0" ]]; then rm $COOKIES # let it be created as needed else echo "Unable to access cookies file $COOKIES: $RSLT" fi fi # # If told to, make sure we can access a password hash file # if [[ "$opt_pwhash" == "y" ]]; then HASH_FILE="${HOME}/.${MYNAME}.hash" if [[ ! -f "$HASH_FILE" ]]; then touch $HASH_FILE RSLT=$? if [[ "$RSLT" == "0" ]]; then rm $HASH_FILE # let it be created as needed else echo "Unable to access pw hash file $HASH_FILE: $RSLT" fi fi fi if [[ ( "$opt_pwhash" == "y" ) && ( -f "$HASH_FILE" ) ]]; then login_with_hash fi # # If we successfully logged in with hash (above), $SID holds session ID. # If we didn't log in (above), $SID is empty so login with password. # if [[ -z "${SID:-}" ]]; then login_with_password fi # # Store a list of albums into a temp file so we can use it to list all # albums and find the key of a specific album # ALBUMS_BASE="/tmp/${RANDOM}-${MYNAME}-albums" ALBUMS_KEYS_FILE="${ALBUMS_BASE}-keys.txt" ALBUM_FILE="${ALBUMS_BASE}.xml" get_albums "$ALBUM_FILE" "$ALBUMS_KEYS_FILE" if [[ -z "${ALBUM:-}" ]]; then list_albums "$ALBUM_FILE" if [[ "$#" -eq "0" ]]; then # no files specified echo echo "Re-run with one or more files to upload." exit 1 fi echo echo read -p "Album ID: " ALBUM fi if [[ "$ALBUM" != "" ]]; then get_album_key "$ALBUM_FILE" "$ALBUM" # sets $ALBUM_KEY if [[ "$ALBUM_KEY" == "" ]]; then echo "Can't find album. Re-run without album ID to list all" \ "albums or specify valid album." exit $LINENO fi fi # # Fetch list of files in specified album # # Parameters: album ID, album key, file name to store list of files, # file name to store full XML details of album # ALBUM_XML="/tmp/${RANDOM}-${MYNAME}-alb.xml" ALBUM_FILE_LIST="/tmp/${RANDOM}-${MYNAME}-alb.txt" get_files_in_album "$ALBUM" "$ALBUM_KEY" "$ALBUM_FILE_LIST" "$ALBUM_XML" "false" # # If no files specified to upload, nothing more to be done # if [[ "$#" == "0" ]]; then echo "Please supply one or more files to upload (or omit -a to list all albums)." exit $LINENO fi OUT="/tmp/${RANDOM}-${MYNAME}" # # Process each file passed in. # for A_FILE in "$@"; do upload_file "$A_FILE" done rm -f $OUT curl -s -o /dev/null -A "$UA" "https://api.smugmug.com/hack/rest/1.1.1/?method=smugmug.logout&SessionID=$SID&APIKey=$APIKEY"