Showing posts with label Flickr. Show all posts
Showing posts with label Flickr. Show all posts

Sunday, September 20, 2015

Transferring photos from Flickr to PicasaWeb

I haven't had to transfer photos from my Flickr account to a PicasaWeb account in a while.  This morning, I found out the migrate-flickr-to-picasa-nokey.py script no longer works.  I was getting this error when the script attempts to authenticate with PicasaWeb:  "Modification only allowed with api authentication".  Apparently, Google had dropped support for the older authentication method and opted to use OAuth2 instead.  I had to dig around the web for some readily available code to cobble together a solution.

My solution was derived from the following two sources:
  • http://www.edparsons.com/2011/06/migrating-from-flickr-to-picasaweb/
  • http://stackoverflow.com/questions/30474269/using-google-picasa-api-with-python
Make sure you read the first.

Well, here it is in its entirety:


#! /usr/bin/python
#
# requires flickrapi, gdata, and oauth2client
#
# It's a little ugly, but it is heavily tested and works!
#
#
#
# Sources:
# http://www.edparsons.com/2011/06/migrating-from-flickr-to-picasaweb/
# http://stackoverflow.com/questions/30474269/using-google-picasa-api-with-python
# https://github.com/MicOestergaard/picasawebuploader/blob/master/main.py
#
# http://photonfarmers.blogspot.ca/2013/02/flickr-to-picasa-web.html
# 

import flickrapi, StringIO
import gdata
import gdata.data
import gdata.photos.service
from getpass import getpass
from urllib import urlretrieve
from tempfile import mkstemp
from threadpool import ThreadPool, WorkRequest
import os
import sys, os.path, StringIO
import time
import gdata.service
import gdata
import atom.service
import atom
import gdata.photos
import getopt
import webbrowser
import httplib2
args_opts, album_title_to_move = getopt.getopt(sys.argv[1], '')
print "Will copy " + album_title_to_move + "..."
from shutil import copyfile

from datetime import datetime, timedelta

from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage

from gdata.photos.service import GPHOTOS_INVALID_ARGUMENT, GPHOTOS_INVALID_CONTENT_TYPE, GooglePhotosException

video_too_large_save_location = os.path.join(os.path.sep.join(__file__.split(os.path.sep)[:-1]), 'picasa_videos')

if not os.path.exists(video_too_large_save_location):
    os.mkdir(video_too_large_save_location)

class VideoEntry(gdata.photos.PhotoEntry):
    pass
    
gdata.photos.VideoEntry = VideoEntry

def InsertVideo(self, album_or_uri, video, filename_or_handle, content_type='image/jpeg'):
    """Copy of InsertPhoto which removes protections since it *should* work"""
    try:
        assert(isinstance(video, VideoEntry))
    except AssertionError:
        raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT,
            'body':'`video` must be a gdata.photos.VideoEntry instance',
            'reason':'Found %s, not PhotoEntry' % type(video)
        })
    try:
        majtype, mintype = content_type.split('/')
        #assert(mintype in SUPPORTED_UPLOAD_TYPES)
    except (ValueError, AssertionError):
        raise GooglePhotosException({'status':GPHOTOS_INVALID_CONTENT_TYPE,
            'body':'This is not a valid content type: %s' % content_type,
            'reason':'Accepted content types:'
        })
    if isinstance(filename_or_handle, (str, unicode)) and \
        os.path.exists(filename_or_handle): # it's a file name
        mediasource = gdata.MediaSource()
        mediasource.setFile(filename_or_handle, content_type)
    elif hasattr(filename_or_handle, 'read'):# it's a file-like resource
        if hasattr(filename_or_handle, 'seek'):
            filename_or_handle.seek(0) # rewind pointer to the start of the file
        # gdata.MediaSource needs the content length, so read the whole image 
        file_handle = StringIO.StringIO(filename_or_handle.read()) 
        name = 'image'
        if hasattr(filename_or_handle, 'name'):
            name = filename_or_handle.name
        mediasource = gdata.MediaSource(file_handle, content_type,
            content_length=file_handle.len, file_name=name)
    else: #filename_or_handle is not valid
        raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT,
            'body':'`filename_or_handle` must be a path name or a file-like object',
            'reason':'Found %s, not path name or object with a .read() method' % \
            type(filename_or_handle)
        })

    if isinstance(album_or_uri, (str, unicode)): # it's a uri
        feed_uri = album_or_uri
    elif hasattr(album_or_uri, 'GetFeedLink'): # it's a AlbumFeed object
        feed_uri = album_or_uri.GetFeedLink().href

    try:
        return self.Post(video, uri=feed_uri, media_source=mediasource,
            converter=None)
    except gdata.service.RequestError, e:
        raise GooglePhotosException(e.args[0])
        
gdata.photos.service.PhotosService.InsertVideo = InsertVideo

def clear_input_retriever(setting):
    return raw_input(setting.name + ":")

def passwd_input_retriever(setting):
    return getpass(setting.name + ':')

class Setting(object):
    
    def __init__(self, name, default=None, input_retriever=clear_input_retriever, empty_value=None):
        self.name = name
        self._value = default
        self.input_retriever = input_retriever
        self.empty_value = empty_value
        
    @property
    def value(self):
        while self._value == self.empty_value:
            self._value = self.input_retriever(self)
            
        return self._value
    

FLICKR = None
    
picasa_username = Setting('Picasa Username(complete email)')
picasa_username._value = ""
picasa_password = Setting('Picasa Password', input_retriever=passwd_input_retriever)
picasa_password._value = ""
picasa_oauth_client_secrets_filename = Setting('Picasa OAuth Client Secrets')
picasa_oauth_client_secrets_filename._value = 'migrate-flickr-to-picasa.secrets'

flickr_api_key = Setting('Flickr API Key')
flickr_api_key._value = ""
flickr_api_secret = Setting('Flickr API Secret')
flickr_api_secret._value = ""

flickr_usernsid = None

def flickr_token_retriever(setting):
    global FLICKR
    global flickr_usernsid
    if FLICKR is None:
        FLICKR = flickrapi.FlickrAPI(flickr_api_key.value, flickr_api_secret.value)
    
    (token, frob) = FLICKR.get_token_part_one(perms='write')
    
    if not token: raw_input("Press ENTER after you authorized this program")
    
    FLICKR.get_token_part_two((token, frob))
    
    flickr_usernsid = FLICKR.auth_checkToken(auth_token=token).find('auth').find('user').get('nsid')
    
    return True
    

def get_gd_client():

    gd_client = gdata.photos.service.PhotosService()
    gd_client.email = picasa_username.value
    gd_client.password = picasa_password.value
    gd_client.source = 'migrate-flickr-to-picasa.py'
    gd_client.ProgrammaticLogin()

    return gd_client

#
# Source:  http://stackoverflow.com/questions/30474269/using-google-picasa-api-with-python
#
def OAuth2Login(client_secrets, credential_store, email):
    scope='https://picasaweb.google.com/data/'
    user_agent='myapp'

    storage = Storage(credential_store)
    credentials = storage.get()
    if credentials is None or credentials.invalid:
        flow = flow_from_clientsecrets(client_secrets, scope=scope, redirect_uri='urn:ietf:wg:oauth:2.0:oob')
        uri = flow.step1_get_authorize_url()
        webbrowser.open(uri)
        code = raw_input('Enter the authentication code: ').strip()
        credentials = flow.step2_exchange(code)
        storage.put(credentials)

    if (credentials.token_expiry - datetime.utcnow()) < timedelta(minutes=5):
        http = httplib2.Http()
        http = credentials.authorize(http)
        credentials.refresh(http)

    gd_client = gdata.photos.service.PhotosService(source=user_agent,
                                               email=email,
                                               additional_headers={'Authorization' : 'Bearer %s' % credentials.access_token})

    return gd_client

def do_migration(threadpoolsize=7):

    print 'Authenticating with Picasa...'
    #gd_client = get_gd_client()
    gd_client = OAuth2Login(picasa_oauth_client_secrets_filename.value, 'migrate-flickr-to-picasa.store', picasa_username.value)

    print 'Authenticating with Flickr..'
    flickr_token = Setting('Flickr Token', input_retriever=flickr_token_retriever)
    token = flickr_token.value # force retrieval of authentication information...

    tmp_sets = FLICKR.photosets_getList().find('photosets').getchildren()
    sets = []
    for aset_id in range(len(tmp_sets)): # go through each flickr set
        aset = tmp_sets[aset_id]
        set_title = aset.find('title').text
        # Transfer only this one photo set ...
 if set_title == album_title_to_move:
            sets = [ aset ]
            break 

    print 'Found %i sets to move over to Picasa.' % len(sets)


    def get_picasa_albums(id, aset, num_photos):
        all_picasa_albums = gd_client.GetUserFeed(user=picasa_username.value).entry
        picasa_albums = []
        id = id.strip()
    
        orig_id = id
    
        for i in range((num_photos/1000) + 1):
            if i > 0:
                id = orig_id + '-' + str(i)
        
            picasa_album = None
        
            for album in all_picasa_albums:
                if album.title.text == id:
                    picasa_album = album
                    break
            
            if picasa_album is not None:
                print '"%s" set already exists as an album in Picasa.' % id
            else:
                picasa_album = gd_client.InsertAlbum(title=id, summary=aset.find('description').text, access='protected')
                print 'Created picasa album "%s".' % picasa_album.title.text
    
            picasa_albums.append(picasa_album)
    
        return picasa_albums
    

    def get_picasa_photos(picasa_albums):
        photos = []
    
        for album in picasa_albums:
            photos.extend(gd_client.GetFeed(album.GetFeedLink().href).entry)
    
        return photos

    def get_photo_url(photo):
        if photo.get('media') == 'video':
            return "http://www.flickr.com/photos/%s/%s/play/orig/%s" % (flickr_usernsid, photo.get('id'), photo.get('originalsecret'))
        else:
            return photo.get('url_o')


    def move_photo(flickr_photo, picasa_album):
    
        def download_callback(count, blocksize, totalsize):
            
            download_stat_print = set((0.0, .25, .5, 1.0))
            downloaded = float(count*blocksize)
            res = int((downloaded/totalsize)*100.0)
 
            for st in download_stat_print:
                dl = totalsize*st
                diff = downloaded - dl
                if diff >= -(blocksize/2) and diff <= (blocksize/2):
                    downloaded_so_far = float(count*blocksize)/1024.0/1024.0
                    total_size_in_mb = float(totalsize)/1024.0/1024.0
                    print "photo: %s, album: %s --- %i%% - %.1f/%.1fmb" % (flickr_photo.get('title'), picasa_album.title.text, res, downloaded_so_far, total_size_in_mb)

        dest = os.path.join(video_too_large_save_location, flickr_photo.get('title'))
        if os.path.exists(dest):
            print 'Video "%s" of "%s" already exists in download cache of files over 100MB. Aborting download.' % (flickr_photo.get('title'), picasa_album.title.text)
            return
    
        photo_url = get_photo_url(flickr_photo)
        print 'Downloading photo "%s" at url "%s".' % (flickr_photo.get('title'), photo_url)
        (fd, filename) = tmp_file = mkstemp()
        (filename, headers) = urlretrieve(photo_url, filename, download_callback)
        print 'Download Finished of %s for album %s at %s.' % (flickr_photo.get('title'), picasa_album.title.text, photo_url)
    
        size = os.stat(filename)[6]
        if size >= 100*1024*1024:
            print 'File "%s" of set "%s" larger than 100mb. Moving to download directory for manual handling. ' % (flickr_photo.get('title'), picasa_album.title.text)
            copyfile(filename, dest)
            os.close(fd)
            os.remove(filename)
            return
    
        print 'Uploading photo %s of album %s to Picasa.' % (flickr_photo.get('title'), picasa_album.title.text)

        if flickr_photo.get('media') == 'photo':
            picasa_photo = gdata.photos.PhotoEntry()
        else:
            picasa_photo = VideoEntry()

        picasa_photo.title = atom.Title(text=flickr_photo.get('title'))
        picasa_photo.summary = atom.Summary(text=flickr_photo.get('description'), summary_type='text')
        photo_info = FLICKR.photos_getInfo(photo_id=flickr_photo.get('id')).find('photo')
        picasa_photo.media.keywords = gdata.media.Keywords()
        picasa_photo.media.keywords.text = ', '.join([t.get('raw') for t in photo_info.find('tags').getchildren()])
        picasa_photo.summary.text = photo_info.find('description').text
    
        if flickr_photo.get('media') == 'photo':
            gd_client.InsertPhoto(picasa_album, picasa_photo, filename, content_type=headers.get('content-type', 'image/jpeg'))
        else:
            gd_client.InsertVideo(picasa_album, picasa_photo, filename, content_type=headers.get('content-type', 'video/avi'))

        print 'Upload Finished of %s for album %s.' % (flickr_photo.get('title'), picasa_album.title.text)

        os.close(fd)
        os.remove(filename)
    

    threadpool = ThreadPool(threadpoolsize)

    for aset_id in range(len(sets)): # go through each flickr set
        aset = sets[aset_id]
        set_title = aset.find('title').text
        print 'Moving "%s" set over to a picasa album. %i/%i' % (set_title, aset_id + 1, len(sets))

        print 'Gathering set "%s" information.' % set_title
    
        num_photos = int(aset.get('photos')) + int(aset.get('videos'))
        all_photos = []
    
        page = 1
        while len(all_photos) < num_photos:
            all_photos.extend(
                FLICKR.photosets_getPhotos(
                    photoset_id=aset.get('id'),
                    per_page=500,
                    extras="url_o,media,original_format",
                    page=page,
                    media='all'
                ).find('photoset').getchildren()
            )
            page += 1

        print 'Found %i photos and videos in the %s flickr set.' % (num_photos, set_title)
    
        picasa_albums = get_picasa_albums(set_title, aset, len(all_photos))
        picasa_photos = get_picasa_photos(picasa_albums)
    
        for photo_id in range(len(all_photos)):
        
            photo = all_photos[photo_id]
            photo_found = False
        
            for p_photo in picasa_photos:
                if p_photo.title.text == photo.get('title'):
                    print 'Already have photo "%s", skipping' % photo.get('title')
                    photo_found = True
                    break

            if photo_found:
                continue
            else:
                print 'Queuing photo %i/%i, %s of album %s for moving.' % (photo_id + 1, len(all_photos), photo.get('title'), set_title)

            p_album = None
            for album in picasa_albums:
                if int(album.numphotosremaining.text) > 0:
                    album.numphotosremaining.text = str(int(album.numphotosremaining.text) - 1)
                    p_album = album
                    break
        
            req = WorkRequest(move_photo, [photo, p_album], {})
            threadpool.putRequest(req)
       
    
    threadpool.wait()
    
    
if __name__ == "__main__":
    
    print """
    This script will move all the photos and sets from flickr over to picasa. 
    That will require getting authentication information from both services...
    """
    
    do_migration()
    


I hope this will save someone else a bit of grief.

Sunday, February 10, 2013

Flickr to Picasa Web

We have a Toshiba Blu-Ray BDX-2300 player.  It is a Linux based machine tailored for multimedia purposes.  In addition to local media file support, it has support for Internet-based services like Netflix and YouTube for videos, and Picasa Web for photos.  It is the latter service that I am interested in.

I use Flickr Pro to store my many thousand copies of my photos.  I do not want to use Picasa Web but it looks like I would have to store photos there if I want to display on my TV using this Toshiba player.

I have not familiarized myself with Picasa (the desktop software) to manage photos in Picasa Web and the Picasa Web interface seems to lack a feature to auto-resize photos before uploading them.  So, I thought I'd find a tool to transfer photos from Flickr to Picasa Web--my photos in Flickr are already resized.

I found this Python script called "migrate-flickr-to-picasa-nokey.py" that will do just that.  For installation instructions, visit http://www.edparsons.com/2011/06/migrating-from-flickr-to-picasaweb/.  If installing in a Windows environment, you also need to install Python 2.7.3 from http://www.python.org/getit/ first and then "easy_install.exe" which comes from the following package:  http://pypi.python.org/pypi/flickrapi.  Download it, expand it, and run "distribute_setup.py".  The "easy_install.exe" will be found in C:\Python27\Scripts\easy_install.exe.

By default, the migrate-flickr-to-picasa-nokey.py script will transfer everything you have from Flickr to Picasa Web.  I want to transfer only selected ones, so I modified the Python script to allow me to specify the Flickr album name I want to transfer on the command line.  I also hardcoded my Flickr username and password in the script.  I could then execute the script as follows:

migrate-flickr-to-picasa-nokey.py New-Year-Eve-20121231

Well, it works well with only one small issue.  It looks like the script would download each photo from Flickr first then upload it to Picasa Web one at a time.  I thought there was a way to transfer from Flickr to Picasa Web directly through some API calls.  Anyways, it is an unattended process so I guess it's not a big deal.

For anyone interested in the modifications, find below the UNIX diff output between the original and my modifications.  My code is not the most efficient as I have forgotten most of the Python language but it does what I need it to.

25a26,28
> import getopt
> args_opts, album_title_to_move = getopt.getopt(sys.argv[1], '')
> print "Will copy " + album_title_to_move + "..."
115a119
> picasa_username._value = "YOUR_PICASA_USERNAME"
116a121
> picasa_password._value = "YOUR_PICASA_PASSWORD"
118a124
> flickr_api_key._value = "YOUR_FLICKR_API"
119a126
> flickr_api_secret._value = "FLICKR_SECRET_VALUE"
159c166,174
<     sets = FLICKR.photosets_getList().find('photosets').getchildren()
---
>     tmp_sets = FLICKR.photosets_getList().find('photosets').getchildren()
>     sets = []
>     for aset_id in range(len(tmp_sets)): # go through each flickr set
>         aset = tmp_sets[aset_id]
>         set_title = aset.find('title').text
>         # Transfer only this one photo set ...
>       if set_title == album_title_to_move:
>             sets = [ aset ]
>             break
336c351
<
\ No newline at end of file
---
>

Tuesday, October 11, 2011

Picasa vs. Flickr

I wrote about this before, I think.  I am going to do a quick revisit on this subject now.

Flickr has been a great tool for sharing pictures with families, friends, and the world.  It has an online tool for image manipulation and it has a batch file uploader that can resize the photos to a maximum 2048x2048 pixels on the fly before uploading--this is a great feature because my Internet connection is not fast.  I have been paying $25 per year for a Pro account so that I can upload any number of photos to Flickr with no limit.  The free Flickr account offers 300MB per month.  The photos resized to 2048x2048 are still pretty big--at least 500KB big.

In talking to Chris yesterday, I think that Picasa will actually work well for me.  It has an unlimited space capacity for images under the 2048x2048 size.  Since that is the same image size that I upload to Flickr, this is perfect.  I will look more into Picasa tonight and might start using it then...

Sunday, September 19, 2010

What is 5,000,000,000 ?

Flickr just announced they have reached 5,000,000,000 picture uploads yesterday. What does that mean? Nowadays, you can purchase 2TB hard disk drives for less than $200. How many of these drives would you need to store 5,000,000,000 pictures, assuming each picture is minimally about 2MB on average.

Well, I threw the numbers in the calculator, and this is what it means. You would need 10,000,000,000,000,000 bytes of storage space so you would need to buy 5000 of those 2TB drives to hold 5 billion pictures. They are probably using various storage configurations which would require more than 5000 drives but roughly speakingCool!

Sunday, September 13, 2009

Flickr

Well, I have uploaded a couple thousand photos to Flickr. It was a painless process assisted by a Flickr Uploader software installed on my notebook. The software can do image resizing on the fly but I decided to resize my photos before uploading. I could have uploaded the original image however if I wished but it would have taken days to upload all those photos.

A couple of things I like about my Flickr Pro account -- and they may exist in the free version too -- is that you have access to online photo editing and photo management tools. With the photo editing tool, you could do cropping, colour saturation changes, brightness, contrast, and other useful photo editing functions. The management tools allows you to create collections and sets, and apply three different levels of access permissions on each photo. I could for example create a collection for my trip to California and within the collection, I could create sets for each day of the week or for specific locations and events. Because these photos are of my family, I could make it so only family members can view them. If I had wanted friends to view some pictures, I could also open them up to friends. These can be done one a file-by-file basis or set-by-set basis.

What I don't like about the management tool is, there is no way I could find to select multiple photos in set and apply global changes to them. For example, I may have a family photo set. If there are twenty photos in the family photo set I want to share with friends, I have to open each photo individually and change its permission setting. I thought you might be able to use the Ctrl key to select multiple photos but that did not seem to work.

With Flickr, I have stopped using JAlbum.

Saturday, August 15, 2009

Flickr online photo album

Well, I have finally bought into the Pro Flickr account. It started to become difficult to share photos I took of families and JAlbum, while it's great, requires a desktop application to create the photo album. So, I decided to use Flickr's Pro account. At about $25 USD per year, it is a good deal. I will post more about Flickr later after I have had more experience with it.

Potensic Atom Follow-Me Mode

The Potensic Atom's Follow-Me mode is one of its "intelligent flight" modes.  It's a really nifty feature that uses visual...