addimages.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. #
  4. # This program loads images into an SQLite database, tagging
  5. # them with the proper metadata to allow for easy searching
  6. # by its counterpart - viewimages.py.
  7. #
  8. # VERSIONING HISTORY:
  9. # 20140207 v0.1 - Minimal operating version; fetches an image
  10. # and adds it to the database (creates one if
  11. # it doesn't exist). Reads metadata info from
  12. # the command-line.
  13. #
  14. # 20140208 v0.2 - Added an ugly UI that works - fetches file
  15. # info, puts it into database.
  16. #
  17. # 20140208 v0.3 - Added a previewing feature that shows the
  18. # user what photo will be inserted before
  19. # inserting. Needs to be tidied up, though.
  20. #
  21. # Constrainted the previews to a fixed size
  22. # of 250x250, so it won't mess up the window
  23. #
  24. # 20140302 v0.4-rc1 - Removed annoying bottom bar so that the
  25. # window behaves more naturally. This is
  26. # fully worthy of a release now.
  27. #
  28. # 20140304 v1.0 - Released first sprint. Now able to insert
  29. # values for the events taking place in the
  30. # pictures
  31. #
  32. # TODO:
  33. #
  34. # - Due to a requirements change, we will now have to implement
  35. # a new metadata field: "Event." This will have to comply
  36. # with the existing data, and everything else will have to be
  37. # tagged anew.
  38. #
  39. # - Enhancement proposal: query the last added photo and show
  40. # information about it upon startup.
  41. #
  42. import os
  43. import sys
  44. import sqlite3 as sql
  45. import tkFileDialog
  46. from PIL import Image, ImageTk
  47. from cStringIO import StringIO
  48. import webbrowser
  49. # Dubious imports need to be handled (not part of the standard library):
  50. try:
  51. import Tkinter as tk
  52. except ImportError:
  53. print "This program depends on Tkinter to work."
  54. print "Please install the package containing Tkinter for your distribution."
  55. print "Also, please note that this program is not Python3 compatible..."
  56. sys.exit(1)
  57. try:
  58. from PIL import Image, ImageTk
  59. except ImportError:
  60. print "This program depends on PIL to work"
  61. print "Please install the python-imaging package for your distribution."
  62. print "If you'd like to install it yourself, remember to enable JPEG support."
  63. sys.exit(1)
  64. # Global variables for the database link.
  65. conn = sql.connect("images.db")
  66. cursor = conn.cursor()
  67. if os.name == 'nt':
  68. pathsep = '\\'
  69. else:
  70. pathsep = '/'
  71. def sanitize():
  72. genesis_query = """
  73. CREATE TABLE IF NOT EXISTS photos (
  74. photo_id INTEGER PRIMARY KEY AUTOINCREMENT,
  75. filename VARCHAR(80),
  76. location VARCHAR(60),
  77. people TEXT,
  78. date_taken VARCHAR(10),
  79. event VARCHAR(60),
  80. photo_data MEDIUMBLOB NOT NULL
  81. );
  82. """
  83. cursor.execute(genesis_query)
  84. conn.commit()
  85. def add_image(filename, location, people, date_taken, photo_data, event):
  86. add_query = """
  87. INSERT INTO photos (filename, location, people, date_taken, photo_data, event)
  88. VALUES
  89. (?,?,?,DATE(?),?, ?)
  90. ;
  91. """
  92. cursor.execute(add_query,[filename, location, people, date_taken,
  93. sql.Binary(photo_data), event])
  94. conn.commit()
  95. print("'%s' was successfully added to the database." % filename)
  96. class AddInterface():
  97. '''
  98. This is the graphical interface that will allow users to
  99. easily insert images with the correct metadata in the
  100. database. This interface should have a complementary pair
  101. to it, an interface that will search for and show the
  102. pictures that the users uploaded.
  103. There are five fields that must be filled in for optimal
  104. categorization:
  105. - Name of file (e.g. Example001.jpg)
  106. - Location where image was taken (e.g San Francisco)
  107. - People tagged in the image (e.g Me, Mom, Dad)
  108. - Date when image was taken (e.g. 1999-06-14)
  109. - Path to the image file (e.g /home/user1/Example001.jpg)
  110. - Event that took place in the picture (e.g Graduation)
  111. '''
  112. def gather(self):
  113. '''
  114. This method reads the fields' values, processes them
  115. and clears them for another brand-new use.
  116. Need to develop a way to validate the input on fields
  117. such as date and image location!
  118. Update: Now saving the full path to the image, to
  119. restore it as a preview.
  120. '''
  121. self.f1 = self.imagepath_field.get()
  122. self.f2 = self.location_field.get()
  123. self.f3 = self.people_field.get()
  124. self.f4 = self.date_field.get()
  125. self.f6 = self.event_field.get()
  126. try:
  127. with file(self.imagepath_field.get(), 'rb') as blob:
  128. self.f5 = blob.read()
  129. # process fields...
  130. add_image(self.f1, self.f2, self.f3, self.f4, self.f5, self.f6)
  131. self.statusbar.config(text = "Imagem %s foi adicionada com sucesso!" % self.f1, fg="#080")
  132. except IOError:
  133. self.statusbar.config(text = "Nao foi possivel encontrar o arquivo '%s'" % self.imagepath_field.get(), fg="#B00")
  134. # blank fields for new use!
  135. self.location_field.delete(0, tk.END)
  136. self.people_field.delete(0, tk.END)
  137. self.date_field.delete(0, tk.END)
  138. self.imagepath_field.delete(0, tk.END)
  139. self.event_field.delete(0, tk.END)
  140. def picker(self):
  141. '''
  142. Picks a file nice and easy through a GUI interface.
  143. Hey, at least this is better thatn manually typing
  144. the ABSOLUTE path to every single image, isn't it?
  145. '''
  146. try:
  147. with tkFileDialog.askopenfile(
  148. parent=self.window, mode="rb",
  149. filetypes=[
  150. ("Imagem JPEG", '*.jpg'),
  151. ("Imagem PNG", '*.png'),
  152. ("Imagem GIF", '*.gif')
  153. ],
  154. title="Escolha uma imagem"
  155. ) as img:
  156. # Set the path so we can later use it with the SQL script
  157. self.imagepath_field.delete(0, tk.END)
  158. self.imagepath_field.insert(0, os.path.realpath(img.name))
  159. # Create a nice overlay preview image!!!
  160. self.placeholder = Image.open(os.path.realpath(img.name))
  161. # Scale it down to an appropriate size if necessary:
  162. if self.placeholder.size[0] < 250 and self.placeholder.size[1] < 250:
  163. pass
  164. else:
  165. self.photoconstraint1 = 250.0 / self.placeholder.size[0]
  166. self.photoconstraint2 = 250.0 / self.placeholder.size[1]
  167. if self.photoconstraint1 > self.photoconstraint2:
  168. self.photolimit = self.photoconstraint2
  169. else:
  170. self.photolimit = self.photoconstraint1
  171. self.placeholder = self.placeholder.resize((int(self.photolimit * self.placeholder.size[0]), int(self.photolimit * self.placeholder.size[1])), Image.ANTIALIAS)
  172. self.preview_img = ImageTk.PhotoImage(self.placeholder)
  173. self.preview.config(image=self.preview_img)
  174. except AttributeError:
  175. pass # You didn't return __exit__ and "with" complained. Ignore...
  176. def get_last_image(self):
  177. '''
  178. This reads the last added image from the database and fetches
  179. information to help the user. This should be displayed in the GUI.
  180. '''
  181. print "Fetching information about last picture added..."
  182. cursor.execute('''
  183. SELECT location, date_taken, filename
  184. FROM photos
  185. WHERE photo_id = (SELECT COUNT(*) FROM photos);
  186. ''')
  187. try:
  188. self.results = cursor.fetchall()[0]
  189. self.lastpic = str(self.results[2])
  190. self.statusbar.config(text="Ultima foto adicionada: %s" % self.lastpic.split(pathsep)[len(self.lastpic.split(pathsep)) - 1])
  191. # Create a nice overlay preview image!!!
  192. self.placeholder = Image.open(os.path.realpath(self.lastpic))
  193. self.photoconstraint1 = 250.0 / self.placeholder.size[0]
  194. self.photoconstraint2 = 250.0 / self.placeholder.size[1]
  195. if self.photoconstraint1 > self.photoconstraint2:
  196. self.photolimit = self.photoconstraint2
  197. else:
  198. self.photolimit = self.photoconstraint1
  199. self.placeholder = self.placeholder.resize((int(self.photolimit * self.placeholder.size[0]), int(self.photolimit * self.placeholder.size[1])), Image.ANTIALIAS)
  200. self.preview_img = ImageTk.PhotoImage(self.placeholder)
  201. self.preview.config(image=self.preview_img)
  202. except IndexError:
  203. print "No previous picture found."
  204. except IOError, e:
  205. print "Previous picture belonged to an older version of the app: %s" % e
  206. def __init__(self):
  207. '''
  208. Create the overlay widgets and pack them into the window.
  209. The interface is the following:
  210. - One label-field pair for text field.
  211. - A date-picking field for date
  212. - A button to choose a file from.
  213. - A PREVIEW for the chosen image!
  214. - A status bar to where all error/status messages
  215. will be chanelled.
  216. '''
  217. self.window = tk.Tk()
  218. self.window.title("Image database indexer - VMAN")
  219. # Frames to hold the field-label pairs.
  220. self.leftpane = tk.Frame(self.window)
  221. self.rightpane = tk.Frame(self.window, width=250, height=250)
  222. self.rightpane.pack_propagate(0)
  223. self.location = tk.Frame(self.leftpane)
  224. self.people = tk.Frame(self.leftpane)
  225. self.date = tk.Frame(self.leftpane)
  226. self.imagepath = tk.Frame(self.leftpane)
  227. self.event = tk.Frame(self.leftpane)
  228. # Menubar! ZOMG
  229. self.menubar = tk.Menu(self.window)
  230. self.filemenu = tk.Menu(self.menubar, tearoff=0)
  231. self.filemenu.add_command(
  232. label="Sobre",
  233. command=lambda url="http://pilimage.googlecode.com/": webbrowser.open(url)
  234. )
  235. self.filemenu.add_command(label="Sair", command=self.window.quit)
  236. self.menubar.add_cascade(label="Arquivo", menu=self.filemenu)
  237. self.window.config(menu=self.menubar)
  238. # Fields
  239. self.location_field = tk.Entry(self.location, bg="#DDDDDD", relief="flat",
  240. highlightcolor="#069D00")
  241. self.people_field = tk.Entry(self.people, bg="#DDDDDD", relief="flat",
  242. highlightcolor="#069D00")
  243. self.date_field = tk.Entry(self.date, bg="#DDDDDD", relief="flat",
  244. highlightcolor="#069D00")
  245. self.imagepath_field = tk.Entry(self.imagepath, bg="#DDDDDD", relief="flat",
  246. highlightcolor="#069D00")
  247. self.text_content = tk.Label(self.window, text="""\
  248. Adicione as informações necessárias para\ncadastrar as imagens e clique Cadastrar""",
  249. font="TkDefaultFont 12")
  250. self.gather_info = tk.Button(self.leftpane, text="Cadastrar", command=self.gather)
  251. self.choose_file = tk.Button(self.imagepath, text="Escolher arquivo",
  252. command = self.picker)
  253. self.event_field = tk.Entry(self.event, bg="#DDDDDD", relief="flat",
  254. highlightcolor="#069D00")
  255. # Labels
  256. self.location_label = tk.Label(self.location, text="Lugar da foto:")
  257. self.people_label = tk.Label(self.people, text="Pessoas na foto:")
  258. self.date_label = tk.Label(self.date, text="Data tirada:")
  259. self.event_label = tk.Label(self.event, text="Evento da foto")
  260. self.imagepath_label = tk.Label(self.imagepath, text="Arquivo:")
  261. self.statusbar = tk.Label(self.leftpane,
  262. font = "tkDefaultFont 12",
  263. text = "Banco de dados pronto")
  264. # This bit refers to the image preview feature! Holy shit!
  265. # First, create a placeholder:
  266. self.preview = tk.Label(self.rightpane, width=250, height=250)
  267. # packing begins
  268. self.text_content.pack(padx=5, pady=10)
  269. self.leftpane.pack(side='left')
  270. self.rightpane.pack(side='right')
  271. self.imagepath.pack(expand=1)
  272. self.location.pack(expand=1)
  273. self.people.pack(expand=1)
  274. self.date.pack(expand=1)
  275. self.event.pack(expand=1)
  276. self.imagepath_label.pack(side='left')
  277. self.imagepath_field.pack(side='left', expand=1, fill='x', pady=5)
  278. self.choose_file.pack(side='left', padx=5, pady=5)
  279. self.location_label.pack(side='left')
  280. self.location_field.pack(side='left', expand=1, fill='x', pady=5)
  281. self.people_label.pack(side='left')
  282. self.people_field.pack(side='left', expand=1, fill='x', pady=5)
  283. self.date_label.pack(side='left')
  284. self.date_field.pack(side='left', expand=1, fill='x', pady=5)
  285. self.event_label.pack(side='left')
  286. self.event_field.pack(side='left', expand=1, fill='x', pady=5)
  287. self.gather_info.pack(side="bottom")
  288. self.preview.pack(padx=10, pady=10)
  289. self.statusbar.pack(side='bottom', pady=10)
  290. def main(self):
  291. self.window.mainloop()
  292. if __name__ == "__main__":
  293. sanitize()
  294. print("Fresh new database created!")
  295. print("Getting ready to add file.")
  296. app = AddInterface()
  297. app.get_last_image()
  298. app.main()