The branch, dharma has been updated
via 2f8ee3e824fd778444aea912da46af75d9c65289 (commit)
from e110cead8edd89210bc0bdde600d6d8c20189fd3 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=2f8ee3e824fd778444aea912da46af75d9c65289
commit 2f8ee3e824fd778444aea912da46af75d9c65289
Author: spiff <[email protected]>
Date: Mon Mar 28 20:54:43 2011 +0200
[plugin.image.iphoto] updated to version 1.4.2
diff --git a/plugin.image.iphoto/README.txt b/plugin.image.iphoto/README.txt
index ba24ff1..f09e245 100644
--- a/plugin.image.iphoto/README.txt
+++ b/plugin.image.iphoto/README.txt
@@ -6,6 +6,7 @@ see categories that correspond with their iPhoto counterparts:
* Events
* Albums
* Faces
+* Places
* Keywords
* Ratings
@@ -20,14 +21,41 @@ The iPhoto plugin can also be configured to ignore certain
album types.
It is currently hard-coded to ignore albums of type "Book" and
"Selected Event Album," but you can choose to ignore also:
-* Published -- these are albums pushed to your MobileMe Gallery.
+* Empty -- albums with no pictures.
+* Published -- MobileMe Gallery albums.
* Flagged -- albums flagged in iPhoto's interface.
-Both of these album types are ignored by default.
+All of these album types are ignored by default.
If you select "Auto update library", the plugin will compare the modification
time of your AlbumData.xml with its current database and update the database
-automatically on start. This is disabled by default.
+automatically on start. This is disabled by default, but is probably what
+you want after testing the plugin.
+
+You can also choose the view style for albums if you're using the Confluence
+skin. You may set this to "Image Wrap," "Pic Thumbs," or "Default". If you
+choose "Default," it will preserve whatever view mode you have chosen in XBMC
+for each album; otherwise, it will force the view style to the one selected
+here.
+
+About Places support
+====================
+If the plugin is configured to support the Places feature of iPhoto, it will
+parse the latitude/longitude pairs in iPhoto's database and look up the
+corresponding addresses using Google.
+
+If Google reports one or more businesses near the coordinates, the plugin
+will use the name of the nearest business for the address to show in the
+Places category. Otherwise, the street address will be used. In both cases,
+the post code and country identifier are appended to the result.
+
+The Places feature also downloads map images to display while you're browsing
+the Places category. Normally, you won't need to do anything to get this
+feature, besides enabling it in the plugin configuration. But, if you import
+your library many times within one day, Google may block you from retrieving
+map images. If you receive a map image with a red X over it, the plugin won't
+re-download the map until you clear the map image caches. You can do so by
+selecting "Remove cached maps" from the context menu of the Places category.
Translations
============
@@ -40,6 +68,6 @@ If possible, patch against the most recent version at:
Known Issues
============
-* Sorting by date is broken and therefore disabled for now.
+* Sorting by Date sorts on the file date, not the EXIF date.
See http://trac.xbmc.org/ticket/10519
-* Need icons for Faces and Keywords.
+* Need icons for Faces, Places, and Keywords.
diff --git a/plugin.image.iphoto/addon.py b/plugin.image.iphoto/addon.py
index 4b27437..87b5bc5 100644
--- a/plugin.image.iphoto/addon.py
+++ b/plugin.image.iphoto/addon.py
@@ -10,7 +10,7 @@ __url__ = "git://github.com/jingai/plugin.image.iphoto.git"
import sys
import time
import os
-import os.path
+import glob
import shutil
import xbmc
@@ -40,22 +40,35 @@ db = IPhotoDB(db_file)
apple_epoch = 978307200
-# default view in Confluence
-view_mode = addon.getSetting('view_mode')
-if (view_mode == ""):
- view_mode = "0"
- addon.setSetting('view_mode', view_mode)
-view_mode = int(view_mode)
-
# ignore empty albums if configured to do so
album_ign_empty = addon.getSetting('album_ignore_empty')
if (album_ign_empty == ""):
album_ign_empty = "true"
addon.setSetting('album_ignore_empty', album_ign_empty)
+# force configured sort method when set to "DEFAULT".
+# XBMC sorts by file date when user selects "DATE" as the sort method,
+# so we have no way to sort by the date stored in the XML or the EXIF
+# data without providing an override to "DEFAULT".
+# this works out well because I don't believe iPhoto stores the photos
+# in the XML in any meaningful order anyway.
+media_sort_col = addon.getSetting('default_sort_photo')
+if (media_sort_col == ""):
+ media_sort_col = "NULL"
+ addon.setSetting('default_sort_photo', '0')
+elif (media_sort_col == "1"):
+ media_sort_col = "mediadate"
+else:
+ media_sort_col = "NULL"
+
def render_media(media):
- global view_mode
+ # default view in Confluence
+ view_mode = addon.getSetting('view_mode')
+ if (view_mode == ""):
+ view_mode = "0"
+ addon.setSetting('view_mode', view_mode)
+ view_mode = int(view_mode)
sort_date = False
n = 0
@@ -68,15 +81,13 @@ def render_media(media):
caption = mediapath
if (caption):
- # < r34717 doesn't support unicode thumbnail paths
- try:
- item = gui.ListItem(caption, thumbnailImage=thumbpath)
- except:
- item = gui.ListItem(caption)
+ item = gui.ListItem(caption, thumbnailImage=thumbpath)
try:
item_date = time.strftime("%d.%m.%Y",
time.localtime(apple_epoch + float(mediadate)))
- #item.setInfo(type="pictures", infoLabels={ "size": mediasize,
"date": item_date })
+ #JSL: setting the date here to enable sorting prevents XBMC
+ #JSL: from scanning the EXIF/IPTC info
+ #item.setInfo(type="pictures", infoLabels={ "date": item_date })
#sort_date = True
except:
pass
@@ -94,10 +105,10 @@ def render_media(media):
return n
def list_photos_in_album(params):
- global db
+ global db, media_sort_col
albumid = params['albumid']
- media = db.GetMediaInAlbum(albumid)
+ media = db.GetMediaInAlbum(albumid, media_sort_col)
return render_media(media)
def list_albums(params):
@@ -134,10 +145,10 @@ def list_albums(params):
return n
def list_photos_in_event(params):
- global db
+ global db, media_sort_col
rollid = params['rollid']
- media = db.GetMediaInRoll(rollid)
+ media = db.GetMediaInRoll(rollid, media_sort_col)
return render_media(media)
def list_events(params):
@@ -161,11 +172,7 @@ def list_events(params):
if (not count and album_ign_empty == "true"):
continue
- # < r34717 doesn't support unicode thumbnail paths
- try:
- item = gui.ListItem(name, thumbnailImage=thumbpath)
- except:
- item = gui.ListItem(name)
+ item = gui.ListItem(name, thumbnailImage=thumbpath)
try:
item_date = time.strftime("%d.%m.%Y", time.localtime(apple_epoch +
float(rolldate)))
@@ -186,10 +193,10 @@ def list_events(params):
return n
def list_photos_with_face(params):
- global db
+ global db, media_sort_col
faceid = params['faceid']
- media = db.GetMediaWithFace(faceid)
+ media = db.GetMediaWithFace(faceid, media_sort_col)
return render_media(media)
def list_faces(params):
@@ -212,11 +219,7 @@ def list_faces(params):
if (not count and album_ign_empty == "true"):
continue
- # < r34717 doesn't support unicode thumbnail paths
- try:
- item = gui.ListItem(name, thumbnailImage=thumbpath)
- except:
- item = gui.ListItem(name)
+ item = gui.ListItem(name, thumbnailImage=thumbpath)
if (count):
item.setInfo(type="pictures", infoLabels={ "count": count })
@@ -227,11 +230,79 @@ def list_faces(params):
plugin.addSortMethod(int(sys.argv[1]), plugin.SORT_METHOD_LABEL)
return n
+def list_photos_with_place(params):
+ global db, media_sort_col
+
+ placeid = params['placeid']
+ media = db.GetMediaWithPlace(placeid, media_sort_col)
+ return render_media(media)
+
+def list_places(params):
+ global db, BASE_URL, album_ign_empty
+
+ # how to display Places labels:
+ # 0 = Addresses
+ # 1 = Latitude/Longitude Pairs
+ places_labels = addon.getSetting('places_labels')
+ if (places_labels == ""):
+ places_labels = "0"
+ addon.setSetting('places_labels', places_labels)
+ places_labels = int(places_labels)
+
+ # show big map of Place as fanart for each item?
+ show_fanart = True
+ e = addon.getSetting('places_show_fanart')
+ if (e == ""):
+ addon.setSetting('places_show_fanart', "true")
+ elif (e == "false"):
+ show_fanart = False
+
+ placeid = 0
+ try:
+ placeid = params['placeid']
+ return list_photos_with_place(params)
+ except Exception, e:
+ print to_str(e)
+ pass
+
+ places = db.GetPlaces()
+ if (not places):
+ return
+
+ n = 0
+ for (placeid, latlon, address, thumbpath, fanartpath, count) in places:
+ if (not count and album_ign_empty == "true"):
+ continue
+
+ latlon = latlon.replace("+", " ")
+
+ if (places_labels == 1):
+ item = gui.ListItem(latlon, address)
+ else:
+ item = gui.ListItem(address, latlon)
+
+ item.addContextMenuItems([(addon.getLocalizedString(30215),
"XBMC.PlayMedia(\""+BASE_URL+"?action=rm_caches\")",)])
+
+ if (thumbpath):
+ item.setThumbnailImage(thumbpath)
+ if (show_fanart == True and fanartpath):
+ item.setProperty("Fanart_Image", fanartpath)
+
+ if (count):
+ item.setInfo(type="pictures", infoLabels={ "count": count })
+ plugin.addDirectoryItem(handle = int(sys.argv[1]),
url=BASE_URL+"?action=places&placeid=%s" % (placeid), listitem = item, isFolder
= True)
+ n += 1
+
+ if (n > 0):
+ plugin.addSortMethod(int(sys.argv[1]), plugin.SORT_METHOD_UNSORTED)
+ plugin.addSortMethod(int(sys.argv[1]), plugin.SORT_METHOD_LABEL)
+ return n
+
def list_photos_with_keyword(params):
- global db
+ global db, media_sort_col
keywordid = params['keywordid']
- media = db.GetMediaWithKeyword(keywordid)
+ media = db.GetMediaWithKeyword(keywordid, media_sort_col)
return render_media(media)
def list_keywords(params):
@@ -260,7 +331,7 @@ def list_keywords(params):
continue
item = gui.ListItem(name)
- item.addContextMenuItems([(addon.getLocalizedString(30214),
"XBMC.RunPlugin(\""+BASE_URL+"?action=hidekeyword&keyword=%s\")" % (name),)])
+ item.addContextMenuItems([(addon.getLocalizedString(30214),
"XBMC.PlayMedia(\""+BASE_URL+"?action=hidekeyword&keyword=%s\")" % (name),)])
if (count):
item.setInfo(type="pictures", infoLabels={ "count": count })
plugin.addDirectoryItem(handle = int(sys.argv[1]),
url=BASE_URL+"?action=keywords&keywordid=%s" % (keywordid), listitem = item,
isFolder = True)
@@ -272,10 +343,10 @@ def list_keywords(params):
return n
def list_photos_with_rating(params):
- global db
+ global db, media_sort_col
rating = params['rating']
- media = db.GetMediaWithRating(rating)
+ media = db.GetMediaWithRating(rating, media_sort_col)
return render_media(media)
def list_ratings(params):
@@ -300,18 +371,17 @@ def list_ratings(params):
plugin.addSortMethod(int(sys.argv[1]), plugin.SORT_METHOD_LABEL)
return n
-def progress_callback(progress_dialog, nphotos, ntotal):
+def progress_callback(progress_dialog, altinfo, nphotos, ntotal):
if (not progress_dialog):
return 0
if (progress_dialog.iscanceled()):
return
- nphotos += 1
percent = int(float(nphotos * 100) / ntotal)
- progress_dialog.update(percent, addon.getLocalizedString(30211) %
(nphotos))
+ progress_dialog.update(percent, addon.getLocalizedString(30211) %
(nphotos), altinfo)
return nphotos
-def import_library(xmlpath, xmlfile):
+def import_library(xmlpath, xmlfile, enable_places):
global db
db.ResetDB()
@@ -337,13 +407,26 @@ def import_library(xmlpath, xmlfile):
if (album_ign_flagged == "true"):
album_ign.append("Shelf")
+ # download maps from Google?
+ enable_maps = True
+ e = addon.getSetting('places_enable_maps')
+ if (e == ""):
+ addon.setSetting('places_enable_maps', "true")
+ elif (e == "false"):
+ enable_maps = False
+
progress_dialog = gui.DialogProgress()
try:
progress_dialog.create(addon.getLocalizedString(30210))
+ map_aspect = 0.0
+ if (enable_maps == True):
+ res_x = float(xbmc.getInfoLabel("System.ScreenWidth"))
+ res_y = float(xbmc.getInfoLabel("System.ScreenHeight"))
+ map_aspect = res_x / res_y
except:
print traceback.print_exc()
else:
- iparser = IPhotoParser(xmlpath, xmlfile, db.AddAlbumNew, album_ign,
db.AddRollNew, db.AddFaceNew, db.AddKeywordNew, db.AddMediaNew,
progress_callback, progress_dialog)
+ iparser = IPhotoParser(xmlpath, xmlfile, album_ign, enable_places,
map_aspect, db.AddAlbumNew, db.AddRollNew, db.AddFaceNew, db.AddKeywordNew,
db.AddMediaNew, progress_callback, progress_dialog)
progress_dialog.update(0, addon.getLocalizedString(30212))
try:
@@ -382,7 +465,7 @@ def get_params(paramstring):
return params
def add_import_lib_context_item(item):
- item.addContextMenuItems([(addon.getLocalizedString(30213),
"XBMC.RunPlugin(\""+BASE_URL+"?action=rescan\")",)])
+ item.addContextMenuItems([(addon.getLocalizedString(30213),
"XBMC.PlayMedia(\""+BASE_URL+"?action=rescan\")",)])
if (__name__ == "__main__"):
xmlfile = addon.getSetting('albumdata_xml_path')
@@ -398,6 +481,13 @@ if (__name__ == "__main__"):
shutil.copyfile(origxml, xmlfile)
shutil.copystat(origxml, xmlfile)
+ enable_places = True
+ e = addon.getSetting('places_enable')
+ if (e == ""):
+ addon.setSetting('places_enable', "True")
+ elif (e == "false"):
+ enable_places = False
+
try:
params = get_params(sys.argv[2])
action = params['action']
@@ -405,27 +495,33 @@ if (__name__ == "__main__"):
# main menu
try:
item = gui.ListItem(addon.getLocalizedString(30100),
thumbnailImage=ICONS_PATH+"/events.png")
- item.setInfo("Picture", { "Title": "Events" })
+ item.setInfo(type="pictures", infoLabels={ "Title": "Events" })
add_import_lib_context_item(item)
plugin.addDirectoryItem(int(sys.argv[1]),
BASE_URL+"?action=events", item, True)
item = gui.ListItem(addon.getLocalizedString(30101),
thumbnailImage=ICONS_PATH+"/albums.png")
- item.setInfo("Picture", { "Title": "Albums" })
+ item.setInfo(type="pictures", infoLabels={ "Title": "Albums" })
add_import_lib_context_item(item)
plugin.addDirectoryItem(int(sys.argv[1]),
BASE_URL+"?action=albums", item, True)
item = gui.ListItem(addon.getLocalizedString(30105),
thumbnailImage=ICONS_PATH+"/faces.png")
- item.setInfo("Picture", { "Title": "Faces" })
+ item.setInfo(type="pictures", infoLabels={ "Title": "Faces" })
add_import_lib_context_item(item)
plugin.addDirectoryItem(int(sys.argv[1]), BASE_URL+"?action=faces",
item, True)
+ item = gui.ListItem(addon.getLocalizedString(30106),
thumbnailImage=ICONS_PATH+"/places.png")
+ item.setInfo(type="pictures", infoLabels={ "Title": "Places" })
+ add_import_lib_context_item(item)
+ item.addContextMenuItems([(addon.getLocalizedString(30215),
"XBMC.PlayMedia(\""+BASE_URL+"?action=rm_caches\")",)])
+ plugin.addDirectoryItem(int(sys.argv[1]),
BASE_URL+"?action=places", item, True)
+
item = gui.ListItem(addon.getLocalizedString(30104),
thumbnailImage=ICONS_PATH+"/keywords.png")
- item.setInfo("Picture", { "Title": "Keywords" })
+ item.setInfo(type="pictures", infoLabels={ "Title": "Keywords" })
add_import_lib_context_item(item)
plugin.addDirectoryItem(int(sys.argv[1]),
BASE_URL+"?action=keywords", item, True)
item = gui.ListItem(addon.getLocalizedString(30102),
thumbnailImage=ICONS_PATH+"/star.png")
- item.setInfo("Picture", { "Title": "Ratings" })
+ item.setInfo(type="pictures", infoLabels={ "Title": "Ratings" })
add_import_lib_context_item(item)
plugin.addDirectoryItem(int(sys.argv[1]),
BASE_URL+"?action=ratings", item, True)
@@ -456,22 +552,36 @@ if (__name__ == "__main__"):
pass
else:
if (xml_mtime > db_mtime):
- import_library(xmlpath, xmlfile)
+ import_library(xmlpath, xmlfile, enable_places)
else:
+ items = None
if (action == "events"):
items = list_events(params)
elif (action == "albums"):
items = list_albums(params)
elif (action == "faces"):
items = list_faces(params)
+ elif (action == "places"):
+ if (enable_places == True):
+ items = list_places(params)
+ else:
+ dialog = gui.Dialog()
+ ret = dialog.yesno(addon.getLocalizedString(30220),
addon.getLocalizedString(30221), addon.getLocalizedString(30222),
addon.getLocalizedString(30223))
+ if (ret == True):
+ enable_places = True
+ addon.setSetting('places_enable', "true")
elif (action == "keywords"):
items = list_keywords(params)
elif (action == "ratings"):
items = list_ratings(params)
elif (action == "rescan"):
- items = import_library(xmlpath, xmlfile)
+ import_library(xmlpath, xmlfile, enable_places)
elif (action == "hidekeyword"):
items = hide_keyword(params)
+ elif (action == "rm_caches"):
+ r = glob.glob(os.path.join(os.path.dirname(db_file), "map_*"))
+ for f in r:
+ os.remove(f)
if (items):
plugin.endOfDirectory(int(sys.argv[1]), True)
diff --git a/plugin.image.iphoto/addon.xml b/plugin.image.iphoto/addon.xml
index 0583f30..9b37740 100644
--- a/plugin.image.iphoto/addon.xml
+++ b/plugin.image.iphoto/addon.xml
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.image.iphoto" name="iPhoto" version="1.3.0"
provider-name="jingai">
+<addon id="plugin.image.iphoto" name="iPhoto" version="1.4.2"
provider-name="jingai">
<requires>
<import addon="xbmc.python" version="1.0"/>
<import addon="script.module.pysqlite" version="2.5.6"/>
+ <import addon="script.module.simplejson" version="2.0.10"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>image</provides>
@@ -13,7 +14,7 @@
<summary lang="en">Imports iPhoto library into XBMC.</summary>
<summary lang="hu">iPhoto könyvtár importálása az XBMC-be</summary>
<summary lang="pt">Importa bibliotecas iPhoto para o XBMC.</summary>
- <description lang="en">Imports iPhoto library into Events, Albums,
Faces, Keywords, and Ratings categories.</description>
+ <description lang="en">Imports iPhoto library into Events, Albums,
Faces, Places, Keywords, and Ratings categories.[CR][CR]For more information,
see the README at http://github.com/jingai/plugin.image.iphoto</description>
<description lang="hu">iPhoto könyvtár importálása az Események,
Albumok és Besorolás kategóriákba.</description>
<description lang="pt">Importa bibliotecas iPhoto para Eventos, Ãlbuns
e Classificações.</description>
<disclaimer lang="en"></disclaimer>
diff --git a/plugin.image.iphoto/changelog.txt
b/plugin.image.iphoto/changelog.txt
index 4833265..e9691f8 100644
--- a/plugin.image.iphoto/changelog.txt
+++ b/plugin.image.iphoto/changelog.txt
@@ -1,3 +1,13 @@
+1.4.2 - 20110327
+- Implement proper JSON parser for Google Maps geocoding.
+
+1.4.1 - 20110324
+- Option to set default sort method for photos.
+- Download map fanart and thumbnails for Places from Google Maps.
+
+1.4.0 - 20110323
+- Add Places support.
+
1.3.0 - 20110319
- Support for external Python 2.6 (namely for iOS devices).
- Support for smb:// style path for AlbumData.xml.
diff --git a/plugin.image.iphoto/resources/language/English/strings.xml
b/plugin.image.iphoto/resources/language/English/strings.xml
index fea2b31..3386ecb 100644
--- a/plugin.image.iphoto/resources/language/English/strings.xml
+++ b/plugin.image.iphoto/resources/language/English/strings.xml
@@ -5,29 +5,46 @@
<string id="30210">Importing iPhoto Library</string>
<string id="30211">Imported %d items</string>
<string id="30212">Scanning AlbumData.xml...</string>
- <string id="30213">Import iPhoto Library</string>
+ <string id="30213">Update library</string>
<string id="30214">Ignore Keyword</string>
+ <string id="30215">Remove cached maps</string>
+ <string id="30220">Enable Support for Places?</string>
+ <string id="30221">Places looks up addresses via Google.</string>
+ <string id="30222">This can slow down the library import a bit.</string>
+ <string id="30223">You will also need to reimport your library.</string>
<!-- Category strings -->
<string id="30100">Events</string>
<string id="30101">Albums</string>
<string id="30102">Ratings</string>
- <string id="30103"><< Import iPhoto Library >></string>
+ <string id="30103"><< Update Library >></string>
<string id="30104">Keywords</string>
<string id="30105">Faces</string>
+ <string id="30106">Places</string>
<!-- Plugin settings strings -->
<string id="30000">Path to AlbumData.xml</string>
<string id="30001">Ignore published (MobileMe) albums</string>
<string id="30002">Ignore 'Flagged' album</string>
<string id="30003">Auto update library</string>
- <string id="30004">Hide Import Library item in main menu</string>
+ <string id="30004">Hide Update Library item in main menu</string>
<string id="30005">Ignore empty albums</string>
<string id="30006">Ignore keywords</string>
<string id="30007">View mode in Confluence</string>
- <string id="30010">Default</string>
- <string id="30011">Image Wrap</string>
- <string id="30012">Pic Thumbs</string>
+ <string id="30008">Default</string>
+ <string id="30009">Image Wrap</string>
+ <string id="30010">Pic Thumbs</string>
+ <string id="30015">Enable Places support</string>
+ <string id="30016">Show Places as</string>
+ <string id="30017">Addresses</string>
+ <string id="30018">Lat/Lon Pairs</string>
+ <string id="30020">Default photo sort method</string>
+ <string id="30021">None</string>
+ <string id="30022">XML Date</string>
+ <string id="30025">Download maps from Google</string>
+ <string id="30026">Show map as fanart</string>
+
+ <!-- Plugin settings categories strings -->
<string id="30050">General</string>
<string id="30051">View</string>
<string id="30059">Advanced</string>
diff --git a/plugin.image.iphoto/resources/lib/iphoto_parser.py
b/plugin.image.iphoto/resources/lib/iphoto_parser.py
index 6329f8b..00d3609 100644
--- a/plugin.image.iphoto/resources/lib/iphoto_parser.py
+++ b/plugin.image.iphoto/resources/lib/iphoto_parser.py
@@ -8,7 +8,7 @@ __url__ = "git://github.com/jingai/plugin.image.iphoto.git"
import traceback
import xml.parsers.expat
-from urllib import unquote
+
try:
from sqlite3 import dbapi2 as sqlite
except:
@@ -16,9 +16,14 @@ except:
import sys
import os
-import os.path
import locale
+try:
+ from resources.lib.geo import *
+except:
+ from geo import *
+
+
def to_unicode(text):
if (isinstance(text, unicode)):
return text
@@ -54,7 +59,9 @@ def to_str(text):
class IPhotoDB:
def __init__(self, dbfile):
+ self.placeList = {}
try:
+ self.dbPath = os.path.dirname(dbfile)
self.dbconn = sqlite.connect(dbfile)
self.InitDB()
except Exception, e:
@@ -99,6 +106,8 @@ class IPhotoDB:
caption varchar,
guid varchar,
aspectratio number,
+ latitude number,
+ longitude number,
rating integer,
mediadate integer,
mediasize integer,
@@ -137,8 +146,7 @@ class IPhotoDB:
self.dbconn.execute("""
CREATE TABLE rollmedia (
rollid integer,
- mediaid integer,
- mediaorder integer
+ mediaid integer
)""")
except Exception, e:
pass
@@ -161,8 +169,7 @@ class IPhotoDB:
self.dbconn.execute("""
CREATE TABLE albummedia (
albumid integer,
- mediaid integer,
- mediaorder integer
+ mediaid integer
)""")
except Exception, e:
pass
@@ -185,8 +192,31 @@ class IPhotoDB:
self.dbconn.execute("""
CREATE TABLE facesmedia (
faceid integer,
- mediaid integer,
- mediaorder integer
+ mediaid integer
+ )""")
+ except Exception, e:
+ pass
+
+ try:
+ # places table
+ self.dbconn.execute("""
+ CREATE TABLE places (
+ id integer primary key,
+ latlon varchar,
+ address varchar,
+ thumbpath varchar,
+ fanartpath varchar,
+ photocount integer
+ )""")
+ except:
+ pass
+
+ try:
+ # placesmedia table
+ self.dbconn.execute("""
+ CREATE TABLE placesmedia (
+ placeid integer,
+ mediaid integer
)""")
except Exception, e:
pass
@@ -207,14 +237,13 @@ class IPhotoDB:
self.dbconn.execute("""
CREATE TABLE keywordmedia (
keywordid integer,
- mediaid integer,
- mediaorder integer
+ mediaid integer
)""")
except Exception, e:
pass
def ResetDB(self):
- for table in ['media', 'mediatypes', 'rolls', 'rollmedia', 'albums',
'albummedia', 'faces', 'facesmedia', 'keywords', 'keywordmedia']:
+ for table in ['media', 'mediatypes', 'rolls', 'rollmedia', 'albums',
'albummedia', 'faces', 'facesmedia', 'places', 'placesmedia', 'keywords',
'keywordmedia']:
try:
self.dbconn.execute("DROP TABLE %s" % table)
except Exception, e:
@@ -248,19 +277,17 @@ class IPhotoDB:
return None
def SetConfig(self, key, value):
+ cur = self.dbconn.cursor()
if (self.GetConfig(key) == None):
- cur = self.dbconn.cursor()
cur.execute("""INSERT INTO config (key, value)
VALUES (?, ?)""",
(key, value))
- self.Commit()
else:
- cur = self.dbconn.cursor()
cur.execute("""UPDATE config
SET value = ?
WHERE key = ?""",
(value, key))
- self.Commit()
+ self.Commit()
def UpdateLastImport(self):
self.SetConfig('lastimport', 'dummy')
@@ -274,6 +301,7 @@ class IPhotoDB:
try:
if (autoclean and not value):
value = "Unknown"
+
cur = self.dbconn.cursor()
# query db for column with specified name
@@ -310,13 +338,15 @@ class IPhotoDB:
pass
return albums
- def GetMediaInAlbum(self, albumid):
+ def GetMediaInAlbum(self, albumid, sort_col="NULL"):
media = []
try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
cur = self.dbconn.cursor()
cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
FROM albummedia A LEFT JOIN media M ON A.mediaid = M.id
- WHERE A.albumid = ?""", (albumid,))
+ WHERE A.albumid = ? ORDER BY %s ASC""" % (sort_col),
(albumid,))
for tuple in cur:
media.append(tuple)
except Exception, e:
@@ -337,12 +367,14 @@ class IPhotoDB:
pass
return rolls
- def GetMediaInRoll(self, rollid):
+ def GetMediaInRoll(self, rollid, sort_col="NULL"):
media = []
try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
cur = self.dbconn.cursor()
cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
- FROM media M WHERE M.rollid = ?""", (rollid,))
+ FROM media M WHERE M.rollid = ? ORDER BY %s ASC""" %
(sort_col), (rollid,))
for tuple in cur:
media.append(tuple)
except Exception, e:
@@ -364,13 +396,43 @@ class IPhotoDB:
pass
return faces
- def GetMediaWithFace(self, faceid):
+ def GetMediaWithFace(self, faceid, sort_col="NULL"):
media = []
try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
cur = self.dbconn.cursor()
cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
FROM facesmedia A LEFT JOIN media M ON A.mediaid = M.id
- WHERE A.faceid = ?""", (faceid,))
+ WHERE A.faceid = ? ORDER BY %s ASC""" % (sort_col),
(faceid,))
+ for tuple in cur:
+ media.append(tuple)
+ except Exception, e:
+ print to_str(e)
+ pass
+ return media
+
+ def GetPlaces(self):
+ places = []
+ try:
+ cur = self.dbconn.cursor()
+ cur.execute("SELECT id, latlon, address, thumbpath, fanartpath,
photocount FROM places")
+ for tuple in cur:
+ places.append(tuple)
+ except Exception, e:
+ print to_str(e)
+ pass
+ return places
+
+ def GetMediaWithPlace(self, placeid, sort_col="NULL"):
+ media = []
+ try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
+ cur = self.dbconn.cursor()
+ cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
+ FROM placesmedia A LEFT JOIN media M ON A.mediaid = M.id
+ WHERE A.placeid = ? ORDER BY %s ASC""" % (sort_col),
(placeid,))
for tuple in cur:
media.append(tuple)
except Exception, e:
@@ -382,7 +444,7 @@ class IPhotoDB:
keywords = []
try:
cur = self.dbconn.cursor()
- cur.execute("SELECT id, name, photocount FROM keywords")
+ cur.execute("SELECT id, name, photocount FROM keywords ORDER BY
name")
for tuple in cur:
keywords.append(tuple)
except Exception, e:
@@ -390,13 +452,15 @@ class IPhotoDB:
pass
return keywords
- def GetMediaWithKeyword(self, keywordid):
+ def GetMediaWithKeyword(self, keywordid, sort_col="NULL"):
media = []
try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
cur = self.dbconn.cursor()
cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
FROM keywordmedia A LEFT JOIN media M ON A.mediaid =
M.id
- WHERE A.keywordid = ?""", (keywordid,))
+ WHERE A.keywordid = ? ORDER BY %s ASC""" % (sort_col),
(keywordid,))
for tuple in cur:
media.append(tuple)
except Exception, e:
@@ -404,12 +468,14 @@ class IPhotoDB:
pass
return media
- def GetMediaWithRating(self, rating):
+ def GetMediaWithRating(self, rating, sort_col="NULL"):
media = []
try:
+ if (sort_col != "NULL"):
+ sort_col = "M." + sort_col
cur = self.dbconn.cursor()
cur.execute("""SELECT M.caption, M.mediapath, M.thumbpath,
M.originalpath, M.rating, M.mediadate, M.mediasize
- FROM media M WHERE M.rating = ?""", (rating,))
+ FROM media M WHERE M.rating = ? ORDER BY %s ASC""" %
(sort_col), (rating,))
for tuple in cur:
media.append(tuple)
except Exception, e:
@@ -516,7 +582,7 @@ class IPhotoDB:
except Exception, e:
raise e
- def AddMediaNew(self, media, archivePath, libraryPath):
+ def AddMediaNew(self, media, archivePath, libraryPath, enablePlaces,
mapAspect, updateProgress):
#print "AddMediaNew()", media
try:
@@ -550,14 +616,16 @@ class IPhotoDB:
try:
self.dbconn.execute("""
- INSERT INTO media (id, mediatypeid, rollid, caption, guid,
aspectratio, rating, mediadate, mediasize, mediapath, thumbpath, originalpath)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
+ INSERT INTO media (id, mediatypeid, rollid, caption, guid,
aspectratio, latitude, longitude, rating, mediadate, mediasize, mediapath,
thumbpath, originalpath)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(mediaid,
self.GetMediaTypeId(media['MediaType'], True),
media['Roll'],
media['Caption'],
media['GUID'],
media['Aspect Ratio'],
+ media['latitude'],
+ media['longitude'],
media['Rating'],
int(float(media['DateAsTimerInterval'])),
mediasize,
@@ -569,7 +637,94 @@ class IPhotoDB:
self.dbconn.execute("""
INSERT INTO facesmedia (faceid, mediaid)
VALUES (?, ?)""", (faceid, mediaid))
- cur = self.dbconn.cursor()
+
+ if enablePlaces == True:
+ # convert lat/lon pair to an address
+ try:
+ lat = float(media['latitude'])
+ lon = float(media['longitude'])
+ if (lat == 0.0 and lon == 0.0):
+ del lat, lon
+ lat = to_str(lat)
+ lon = to_str(lon)
+ latlon = lat + "+" + lon
+ try:
+ addr = None
+ placeid = None
+ for i in self.placeList:
+ if (latlon in self.placeList[i]):
+ addr = self.placeList[i][0]
+ placeid = i
+ break
+ if addr is None:
+ updateProgress("Geocoding %s %s" % (lat, lon))
+ addr = geocode("%s %s" % (lat, lon))[0]
+ updateProgress()
+
+ for i in self.placeList:
+ if (self.placeList[i][0] == addr):
+ placeid = i
+ break
+ if placeid is None:
+ placeid = len(self.placeList)
+ self.placeList[placeid] = []
+ #print "new placeid %d for addr '%s'" %
(placeid, addr)
+ except Exception, e:
+ print to_str(e)
+ raise e
+ except:
+ #print "No location information for photo id %d" % (mediaid)
+ pass
+ else:
+ if (addr not in self.placeList[placeid]):
+ # download thumbnail and fanart maps for Place
+ fanartpath = ""
+ thumbpath = ""
+ if (mapAspect != 0.0):
+ updateProgress("Fetching map...")
+ try:
+ map_size_x = MAP_IMAGE_X_MAX
+ map_size_y = int(float(map_size_x) / mapAspect)
+ map = staticmap(self.dbPath, latlon, False,
xsize=map_size_x, ysize=map_size_y)
+ fanartpath = map.fetch("map_", "_%dx%d" %
(map_size_x, map_size_y))
+ map.set_xsize(256)
+ map.set_ysize(256)
+ map.set_type("roadmap")
+ map.toggle_marker()
+ map.zoom("", 14)
+ thumbpath = map.fetch("map_", "_thumb")
+ except Exception, e:
+ print to_str(e)
+ pass
+ updateProgress()
+
+ # add new Place
+ self.placeList[placeid].append(addr)
+ self.dbconn.execute("""
+ INSERT INTO places (id, latlon, address, thumbpath,
fanartpath)
+ VALUES (?, ?, ?, ?, ?)""", (placeid, latlon, addr,
thumbpath, fanartpath))
+
+ if (latlon not in self.placeList[placeid]):
+ # existing Place, but add latlon to list for this
address.
+ # do this to prevent the script from hitting google more
+ # than necessary.
+ self.placeList[placeid].append(latlon)
+
+ self.dbconn.execute("""
+ INSERT INTO placesmedia (placeid, mediaid)
+ VALUES (?, ?)""", (placeid, mediaid))
+ cur = self.dbconn.cursor()
+ cur.execute("""SELECT id, photocount
+ FROM places
+ WHERE id = ?""", (placeid,))
+ for tuple in cur:
+ if (tuple[1]):
+ photocount = int(tuple[1]) + 1
+ else:
+ photocount = 1
+ self.dbconn.execute("""
+ UPDATE places SET photocount = ?
+ WHERE id = ?""", (photocount, placeid))
for keywordid in media['keywordlist']:
self.dbconn.execute("""
@@ -623,8 +778,8 @@ class IPhotoParserState:
self.valueType = ""
class IPhotoParser:
- def __init__(self, library_path="", xmlfile="", album_callback=None,
album_ign=[],
- roll_callback=None, face_callback=None, keyword_callback=None,
photo_callback=None,
+ def __init__(self, library_path="", xmlfile="", album_ign=[],
enable_places=False, map_aspect=0.0,
+ album_callback=None, roll_callback=None, face_callback=None,
keyword_callback=None, photo_callback=None,
progress_callback=None, progress_dialog=None):
self.libraryPath = library_path
self.xmlfile = xmlfile
@@ -645,8 +800,10 @@ class IPhotoParser:
self.rollList = []
self.faceList = []
self.keywordList = []
- self.AlbumCallback = album_callback
self.albumIgn = album_ign
+ self.enablePlaces = enable_places
+ self.mapAspect = map_aspect
+ self.AlbumCallback = album_callback
self.RollCallback = roll_callback
self.FaceCallback = face_callback
self.KeywordCallback = keyword_callback
@@ -667,6 +824,8 @@ class IPhotoParser:
self.currentPhoto[a] = ""
self.currentPhoto['Aspect Ratio'] = '0'
self.currentPhoto['DateAsTimerInterval'] = '0'
+ self.currentPhoto['latitude'] = '0'
+ self.currentPhoto['longitude'] = '0'
self.currentPhoto['facelist'] = []
self.currentPhoto['keywordlist'] = []
@@ -694,13 +853,13 @@ class IPhotoParser:
for a in self.currentKeyword.keys():
self.currentKeyword[a] = ""
- def updateProgress(self):
+ def updateProgress(self, altinfo=""):
if (not self.ProgressCallback):
return
state = self.state
- state.nphotos = self.ProgressCallback(self.ProgressDialog,
state.nphotos, state.nphotostotal)
- if (state.nphotos == None):
+ ret = self.ProgressCallback(self.ProgressDialog, altinfo,
state.nphotos, state.nphotostotal)
+ if (ret == None):
raise ParseCanceled(0)
def commitAll(self):
@@ -712,26 +871,31 @@ class IPhotoParser:
if (self.AlbumCallback and len(self.albumList) > 0):
for a in self.albumList:
self.AlbumCallback(a, self.albumIgn)
+ state.nphotos += 1
self.updateProgress()
if (self.RollCallback and len(self.rollList) > 0):
for a in self.rollList:
self.RollCallback(a)
+ state.nphotos += 1
self.updateProgress()
if (self.FaceCallback and len(self.faceList) > 0):
for a in self.faceList:
self.FaceCallback(a)
+ state.nphotos += 1
self.updateProgress()
if (self.KeywordCallback and len(self.keywordList) > 0):
for a in self.keywordList:
self.KeywordCallback(a)
+ state.nphotos += 1
self.updateProgress()
if (self.PhotoCallback and len(self.photoList) > 0):
for a in self.photoList:
- self.PhotoCallback(a, self.imagePath, self.libraryPath)
+ self.PhotoCallback(a, self.imagePath, self.libraryPath,
self.enablePlaces, self.mapAspect, self.updateProgress)
+ state.nphotos += 1
self.updateProgress()
except ParseCanceled:
raise
@@ -939,26 +1103,30 @@ class IPhotoParser:
return
+def test_progress_callback(progress_dialog, altinfo, nphotos, ntotal):
+ percent = int(float(nphotos * 100) / ntotal)
+ print "%d/%d (%d%%)" % (nphotos, ntotal, percent)
+ if (altinfo != ""):
+ print altinfo
+ return nphotos
+
def profile_main():
- import hotshot, hotshot.stats
- prof = hotshot.Profile("iphoto.prof")
- prof.runcall(main)
- prof.close()
- stats = hotshot.stats.load("iphoto.prof")
- stats.strip_dirs()
- stats.sort_stats('time', 'calls')
- stats.print_stats(20)
+ import cProfile,pstats
+ cProfile.run('main()', 'iphoto.prof')
+ p = pstats.Stats('iphoto.prof')
+ p.strip_dirs().sort_stats('time', 'cum').print_stats()
def main():
try:
xmlfile = sys.argv[1]
+ dbfile = sys.argv[2]
except:
- print "Usage iphoto_parser.py <xmlfile>"
+ print "Usage iphoto_parser.py <xmlfile> <db>"
sys.exit(1)
- db = IPhotoDB("iphoto.db")
+ db = IPhotoDB(dbfile)
db.ResetDB()
- iparser = IPhotoParser("", xmlfile, db.AddAlbumNew, "", db.AddRollNew,
db.AddFaceNew, db.AddKeywordNew, db.AddMediaNew)
+ iparser = IPhotoParser("", xmlfile, "", False, 0.0, db.AddAlbumNew,
db.AddRollNew, db.AddFaceNew, db.AddKeywordNew, db.AddMediaNew,
test_progress_callback)
try:
iparser.Parse()
except:
@@ -966,5 +1134,5 @@ def main():
db.Commit()
if __name__=="__main__":
- main()
- #profile_main()
+ #main()
+ profile_main()
diff --git a/plugin.image.iphoto/resources/settings.xml
b/plugin.image.iphoto/resources/settings.xml
index 2a7dd03..c5c267a 100644
--- a/plugin.image.iphoto/resources/settings.xml
+++ b/plugin.image.iphoto/resources/settings.xml
@@ -3,16 +3,23 @@
<!-- General -->
<category label="30050">
<setting id="albumdata_xml_path" type="file" source="video"
label="30000" default=""/>
+ <setting id="places_enable" type="bool" label="30015" default="true"/>
+ <setting id="places_enable_maps" type="bool" label="30025"
default="true" enable="eq(-1,true)"/>
<setting id="auto_update_lib" type="bool" label="30003"
default="false"/>
</category>
<!-- View -->
<category label="30051">
- <setting id="view_mode" type="enum" label="30007"
lvalues="30010|30011|30012" default="0"/>
+ <setting id="view_mode" type="enum" label="30007"
lvalues="30008|30009|30010" default="0"/>
+ <setting id="default_sort_photo" type="enum" label="30020"
lvalues="30021|30022" default="0"/>
+ <setting type="sep"/>
+ <setting id="places_labels" type="enum" label="30016"
lvalues="30017|30018" default="0"/>
+ <setting id="places_show_fanart" type="bool" label="30026"
default="true"/>
<setting type="sep"/>
<setting id="album_ignore_empty" type="bool" label="30005"
default="true"/>
<setting id="album_ignore_published" type="bool" label="30001"
default="true"/>
<setting id="album_ignore_flagged" type="bool" label="30002"
default="true"/>
+ <setting type="sep"/>
<setting id="hidden_keywords" type="text" label="30006" default=""/>
</category>
-----------------------------------------------------------------------
Summary of changes:
plugin.image.iphoto/README.txt | 38 +++-
plugin.image.iphoto/addon.py | 208 ++++++++++++----
plugin.image.iphoto/addon.xml | 5 +-
plugin.image.iphoto/changelog.txt | 10 +
.../resources/language/English/strings.xml | 29 ++-
plugin.image.iphoto/resources/lib/geo.py | 259 +++++++++++++++++++
plugin.image.iphoto/resources/lib/iphoto_parser.py | 268 ++++++++++++++++----
plugin.image.iphoto/resources/settings.xml | 9 +-
8 files changed, 713 insertions(+), 113 deletions(-)
create mode 100644 plugin.image.iphoto/resources/lib/geo.py
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Create and publish websites with WebMatrix
Use the most popular FREE web apps or write code yourself;
WebMatrix provides all the features you need to develop and publish
your website. http://p.sf.net/sfu/ms-webmatrix-sf
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons