123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- #!/usr/bin/env python
- # -*- coding: UTF-8 -*-
- #
- # This program loads images into an SQLite database, tagging
- # them with the proper metadata to allow for easy searching
- # by its counterpart - viewimages.py.
- #
- # VERSIONING HISTORY:
- # 20140207 v0.1 - Minimal operating version; fetches an image
- # and adds it to the database (creates one if
- # it doesn't exist). Reads metadata info from
- # the command-line.
- #
- # 20140208 v0.2 - Added an ugly UI that works - fetches file
- # info, puts it into database.
- #
- # 20140208 v0.3 - Added a previewing feature that shows the
- # user what photo will be inserted before
- # inserting. Needs to be tidied up, though.
- #
- # Constrainted the previews to a fixed size
- # of 250x250, so it won't mess up the window
- #
- # 20140302 v0.4-rc1 - Removed annoying bottom bar so that the
- # window behaves more naturally. This is
- # fully worthy of a release now.
- #
- # 20140304 v1.0 - Released first sprint. Now able to insert
- # values for the events taking place in the
- # pictures
- #
- # TODO:
- #
- # - Due to a requirements change, we will now have to implement
- # a new metadata field: "Event." This will have to comply
- # with the existing data, and everything else will have to be
- # tagged anew.
- #
- # - Enhancement proposal: query the last added photo and show
- # information about it upon startup.
- #
- import os
- import sys
- import sqlite3 as sql
- import tkFileDialog
- from PIL import Image, ImageTk
- from cStringIO import StringIO
- import webbrowser
- # Dubious imports need to be handled (not part of the standard library):
- try:
- import Tkinter as tk
- except ImportError:
- print "This program depends on Tkinter to work."
- print "Please install the package containing Tkinter for your distribution."
- print "Also, please note that this program is not Python3 compatible..."
- sys.exit(1)
- try:
- from PIL import Image, ImageTk
- except ImportError:
- print "This program depends on PIL to work"
- print "Please install the python-imaging package for your distribution."
- print "If you'd like to install it yourself, remember to enable JPEG support."
- sys.exit(1)
- # Global variables for the database link.
- conn = sql.connect("images.db")
- cursor = conn.cursor()
- if os.name == 'nt':
- pathsep = '\\'
- else:
- pathsep = '/'
- def sanitize():
- genesis_query = """
- CREATE TABLE IF NOT EXISTS photos (
- photo_id INTEGER PRIMARY KEY AUTOINCREMENT,
- filename VARCHAR(80),
- location VARCHAR(60),
- people TEXT,
- date_taken VARCHAR(10),
- event VARCHAR(60),
- photo_data MEDIUMBLOB NOT NULL
- );
- """
- cursor.execute(genesis_query)
- conn.commit()
- def add_image(filename, location, people, date_taken, photo_data, event):
- add_query = """
- INSERT INTO photos (filename, location, people, date_taken, photo_data, event)
- VALUES
- (?,?,?,DATE(?),?, ?)
- ;
- """
- cursor.execute(add_query,[filename, location, people, date_taken,
- sql.Binary(photo_data), event])
- conn.commit()
- print("'%s' was successfully added to the database." % filename)
- class AddInterface():
- '''
- This is the graphical interface that will allow users to
- easily insert images with the correct metadata in the
- database. This interface should have a complementary pair
- to it, an interface that will search for and show the
- pictures that the users uploaded.
- There are five fields that must be filled in for optimal
- categorization:
- - Name of file (e.g. Example001.jpg)
- - Location where image was taken (e.g San Francisco)
- - People tagged in the image (e.g Me, Mom, Dad)
- - Date when image was taken (e.g. 1999-06-14)
- - Path to the image file (e.g /home/user1/Example001.jpg)
- - Event that took place in the picture (e.g Graduation)
- '''
- def gather(self):
- '''
- This method reads the fields' values, processes them
- and clears them for another brand-new use.
- Need to develop a way to validate the input on fields
- such as date and image location!
- Update: Now saving the full path to the image, to
- restore it as a preview.
- '''
- self.f1 = self.imagepath_field.get()
- self.f2 = self.location_field.get()
- self.f3 = self.people_field.get()
- self.f4 = self.date_field.get()
- self.f6 = self.event_field.get()
- try:
- with file(self.imagepath_field.get(), 'rb') as blob:
- self.f5 = blob.read()
-
- # process fields...
- add_image(self.f1, self.f2, self.f3, self.f4, self.f5, self.f6)
- self.statusbar.config(text = "Imagem %s foi adicionada com sucesso!" % self.f1, fg="#080")
- except IOError:
- self.statusbar.config(text = "Nao foi possivel encontrar o arquivo '%s'" % self.imagepath_field.get(), fg="#B00")
- # blank fields for new use!
- self.location_field.delete(0, tk.END)
- self.people_field.delete(0, tk.END)
- self.date_field.delete(0, tk.END)
- self.imagepath_field.delete(0, tk.END)
- self.event_field.delete(0, tk.END)
- def picker(self):
- '''
- Picks a file nice and easy through a GUI interface.
- Hey, at least this is better thatn manually typing
- the ABSOLUTE path to every single image, isn't it?
- '''
- try:
- with tkFileDialog.askopenfile(
- parent=self.window, mode="rb",
- filetypes=[
- ("Imagem JPEG", '*.jpg'),
- ("Imagem PNG", '*.png'),
- ("Imagem GIF", '*.gif')
- ],
- title="Escolha uma imagem"
- ) as img:
- # Set the path so we can later use it with the SQL script
- self.imagepath_field.delete(0, tk.END)
- self.imagepath_field.insert(0, os.path.realpath(img.name))
- # Create a nice overlay preview image!!!
- self.placeholder = Image.open(os.path.realpath(img.name))
- # Scale it down to an appropriate size if necessary:
- if self.placeholder.size[0] < 250 and self.placeholder.size[1] < 250:
- pass
- else:
- self.photoconstraint1 = 250.0 / self.placeholder.size[0]
- self.photoconstraint2 = 250.0 / self.placeholder.size[1]
- if self.photoconstraint1 > self.photoconstraint2:
- self.photolimit = self.photoconstraint2
- else:
- self.photolimit = self.photoconstraint1
- self.placeholder = self.placeholder.resize((int(self.photolimit * self.placeholder.size[0]), int(self.photolimit * self.placeholder.size[1])), Image.ANTIALIAS)
- self.preview_img = ImageTk.PhotoImage(self.placeholder)
- self.preview.config(image=self.preview_img)
- except AttributeError:
- pass # You didn't return __exit__ and "with" complained. Ignore...
- def get_last_image(self):
- '''
- This reads the last added image from the database and fetches
- information to help the user. This should be displayed in the GUI.
- '''
- print "Fetching information about last picture added..."
- cursor.execute('''
- SELECT location, date_taken, filename
- FROM photos
- WHERE photo_id = (SELECT COUNT(*) FROM photos);
- ''')
- try:
- self.results = cursor.fetchall()[0]
- self.lastpic = str(self.results[2])
- self.statusbar.config(text="Ultima foto adicionada: %s" % self.lastpic.split(pathsep)[len(self.lastpic.split(pathsep)) - 1])
- # Create a nice overlay preview image!!!
- self.placeholder = Image.open(os.path.realpath(self.lastpic))
- self.photoconstraint1 = 250.0 / self.placeholder.size[0]
- self.photoconstraint2 = 250.0 / self.placeholder.size[1]
- if self.photoconstraint1 > self.photoconstraint2:
- self.photolimit = self.photoconstraint2
- else:
- self.photolimit = self.photoconstraint1
- self.placeholder = self.placeholder.resize((int(self.photolimit * self.placeholder.size[0]), int(self.photolimit * self.placeholder.size[1])), Image.ANTIALIAS)
- self.preview_img = ImageTk.PhotoImage(self.placeholder)
- self.preview.config(image=self.preview_img)
- except IndexError:
- print "No previous picture found."
- except IOError, e:
- print "Previous picture belonged to an older version of the app: %s" % e
- def __init__(self):
- '''
- Create the overlay widgets and pack them into the window.
- The interface is the following:
-
- - One label-field pair for text field.
- - A date-picking field for date
- - A button to choose a file from.
- - A PREVIEW for the chosen image!
- - A status bar to where all error/status messages
- will be chanelled.
- '''
- self.window = tk.Tk()
- self.window.title("Image database indexer - VMAN")
- # Frames to hold the field-label pairs.
- self.leftpane = tk.Frame(self.window)
- self.rightpane = tk.Frame(self.window, width=250, height=250)
- self.rightpane.pack_propagate(0)
- self.location = tk.Frame(self.leftpane)
- self.people = tk.Frame(self.leftpane)
- self.date = tk.Frame(self.leftpane)
- self.imagepath = tk.Frame(self.leftpane)
- self.event = tk.Frame(self.leftpane)
- # Menubar! ZOMG
- self.menubar = tk.Menu(self.window)
- self.filemenu = tk.Menu(self.menubar, tearoff=0)
- self.filemenu.add_command(
- label="Sobre",
- command=lambda url="http://pilimage.googlecode.com/": webbrowser.open(url)
- )
- self.filemenu.add_command(label="Sair", command=self.window.quit)
- self.menubar.add_cascade(label="Arquivo", menu=self.filemenu)
- self.window.config(menu=self.menubar)
- # Fields
- self.location_field = tk.Entry(self.location, bg="#DDDDDD", relief="flat",
- highlightcolor="#069D00")
- self.people_field = tk.Entry(self.people, bg="#DDDDDD", relief="flat",
- highlightcolor="#069D00")
- self.date_field = tk.Entry(self.date, bg="#DDDDDD", relief="flat",
- highlightcolor="#069D00")
- self.imagepath_field = tk.Entry(self.imagepath, bg="#DDDDDD", relief="flat",
- highlightcolor="#069D00")
- self.text_content = tk.Label(self.window, text="""\
- Adicione as informações necessárias para\ncadastrar as imagens e clique Cadastrar""",
- font="TkDefaultFont 12")
- self.gather_info = tk.Button(self.leftpane, text="Cadastrar", command=self.gather)
- self.choose_file = tk.Button(self.imagepath, text="Escolher arquivo",
- command = self.picker)
-
- self.event_field = tk.Entry(self.event, bg="#DDDDDD", relief="flat",
- highlightcolor="#069D00")
- # Labels
- self.location_label = tk.Label(self.location, text="Lugar da foto:")
- self.people_label = tk.Label(self.people, text="Pessoas na foto:")
- self.date_label = tk.Label(self.date, text="Data tirada:")
- self.event_label = tk.Label(self.event, text="Evento da foto")
- self.imagepath_label = tk.Label(self.imagepath, text="Arquivo:")
- self.statusbar = tk.Label(self.leftpane,
- font = "tkDefaultFont 12",
- text = "Banco de dados pronto")
-
- # This bit refers to the image preview feature! Holy shit!
- # First, create a placeholder:
- self.preview = tk.Label(self.rightpane, width=250, height=250)
- # packing begins
- self.text_content.pack(padx=5, pady=10)
- self.leftpane.pack(side='left')
- self.rightpane.pack(side='right')
- self.imagepath.pack(expand=1)
- self.location.pack(expand=1)
- self.people.pack(expand=1)
- self.date.pack(expand=1)
- self.event.pack(expand=1)
-
- self.imagepath_label.pack(side='left')
- self.imagepath_field.pack(side='left', expand=1, fill='x', pady=5)
- self.choose_file.pack(side='left', padx=5, pady=5)
- self.location_label.pack(side='left')
- self.location_field.pack(side='left', expand=1, fill='x', pady=5)
- self.people_label.pack(side='left')
- self.people_field.pack(side='left', expand=1, fill='x', pady=5)
- self.date_label.pack(side='left')
- self.date_field.pack(side='left', expand=1, fill='x', pady=5)
- self.event_label.pack(side='left')
- self.event_field.pack(side='left', expand=1, fill='x', pady=5)
- self.gather_info.pack(side="bottom")
- self.preview.pack(padx=10, pady=10)
- self.statusbar.pack(side='bottom', pady=10)
- def main(self):
- self.window.mainloop()
- if __name__ == "__main__":
- sanitize()
- print("Fresh new database created!")
- print("Getting ready to add file.")
- app = AddInterface()
- app.get_last_image()
- app.main()
|