├── showDB.py ├── image64.py ├── README.md └── gui.py /showDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import pprint 3 | from gui import * 4 | 5 | pprint.pprint(fs_load()) 6 | -------------------------------------------------------------------------------- /image64.py: -------------------------------------------------------------------------------- 1 | def convert(pic): 2 | with open(pic, "rb") as f: 3 | data12 = f.read() 4 | UU = data12.encode("base64") 5 | return(UU) 6 | 7 | #print(convert('divider.png')) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fast-photo-sorter 2 | ================= 3 | 4 | Organize a ton of photos by hashtags, made to be as fast as possible. 5 | 6 | run with: 7 | python gui.py 8 | 9 | then send your browser to URL: http://localhost:8090/ 10 | 11 | donations: 1676NCiezxodBpM5j1Zafnuxuaeuz2vfPR -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 2 | import string,cgi,time, json, random, copy, pickle, image64, os 3 | import PIL 4 | from PIL import Image 5 | PORT=8090 6 | picture_height4display=300 #counted in pixels. Change this to change the size of the displayed picture in your browser 7 | picture_height4thumbnail=300 #change this to make the picture in higher or lower detail. smaller numbers are less detailed and load quicker. 8 | output_file='save.txt' 9 | def fs2dic(fs): 10 | dic={} 11 | for i in fs.keys(): 12 | a=fs.getlist(i) 13 | if len(a)>0: 14 | dic[i]=fs.getlist(i)[0] 15 | else: 16 | dic[i]="" 17 | return dic 18 | def to_thumbnail(img): 19 | location=img 20 | baseheight = picture_height4thumbnail 21 | img = Image.open(img) 22 | hpercent = (baseheight/float(img.size[1])) 23 | wsize = int((float(img.size[0])*float(hpercent))) 24 | img = img.resize((wsize,baseheight), PIL.Image.ANTIALIAS) 25 | string=location[-3:]+'_thumbnail.jpg' 26 | img.save(string) 27 | img=file2hexPicture(string) 28 | return img 29 | 30 | form=''' 31 |
32 | {} 33 |
{} 34 | ''' 35 | def easyForm(link, button_says, moreHtml='', typee='post'): 36 | a=form.format(link, '{}', button_says, moreHtml, "{}") 37 | if typee=='get': 38 | return a.format('get', '{}') 39 | else: 40 | return a.format('post', '{}') 41 | linkHome = easyForm('/', 'HOME', '', 'get') 42 | def page1(): 43 | fs=fs_load() 44 | tags=[] 45 | for key in fs: 46 | if fs[key]['tag'] not in tags: 47 | tags.append(fs[key]['tag']) 48 | if '' in tags: 49 | tags.remove('') 50 | out="

photo organizer


{}" 51 | out=out.format(easyForm('/tagPhotos', 'tag untagged photos from this location on your hard-drive, \nThis might take a LONG TIME to load after you click this button.', '')) 52 | out=out.format("

example: /home/Mike/Pictures

{}") 53 | out=out.format(easyForm('/writeQuit', 'Write & Quit')) 54 | out=out.format(easyForm('/delete', 'Delete saved tags')) 55 | ''' for tag in tags: 56 | out=out.format(easyForm('/viewPhotos', 'look at photos tagged as: '+tag, ''.format(tag))) 57 | ''' 58 | for tag in tags: 59 | out=out.format(easyForm('/renameGroup', 'retag every {} photo to: '.format(tag), ''.format(tag))) 60 | return out.format('') 61 | def hex2htmlPicture(string): 62 | return '{}'.format(str(picture_height4display), string, '{}') 63 | def file2hexPicture(fil): 64 | return image64.convert(fil) 65 | def file2htmlPicture(fil): 66 | return hex2htmlPicture(file2hexPicture(fil)) 67 | def newline(): 68 | return '''
69 | {}''' 70 | empty_page='{}' 71 | initial_db={}#{photo_location.jpg:{'thumbnail':thumbnail_location.jpg, 'tag':'summer 2008'},...} 72 | database='tags.db' 73 | def fs_load(): 74 | try: 75 | out=pickle.load(open(database, 'rb')) 76 | if 'tag' not in out[out.keys()[0]]: 77 | fs_save(initial_db) 78 | return initial_db 79 | return out 80 | except: 81 | fs_save(initial_db) 82 | return pickle.load(open(database, 'rb')) 83 | def fs_save(dic): 84 | pickle.dump(dic, open(database, 'wb')) 85 | def grab_photos(location): 86 | files = [f for f in os.listdir(location)]# if os.path.isfile(f)] 87 | photos=[] 88 | for f in files: 89 | if f[-4:] in ['.jpg', '.JPG']: 90 | photos.append(location+'/'+f) 91 | return photos 92 | def tagPhotos(dic_in): 93 | print('dic_in: ' +str(dic_in)) 94 | if dic_in['location'][-1:]=='/' and len(dic_in['location'])>1: 95 | dic_in['location']=dic_in['location'][:-1] 96 | photos=grab_photos(dic_in['location']) 97 | fs=fs_load() 98 | untagged=[] 99 | undoable='' 100 | for photo in photos: 101 | if photo not in fs: 102 | fs[photo]={} 103 | fs[photo]['tag']='' 104 | fs[photo]['thumbnail']=to_thumbnail(photo) 105 | fs_save(fs) 106 | if fs[photo]['tag']=='': 107 | untagged.append(photo) 108 | if 'tag' in dic_in: 109 | undoable=untagged[0] 110 | fs[untagged[0]]['tag']=dic_in['tag'] 111 | untagged.remove(untagged[0]) 112 | fs_save(fs) 113 | if 'undoable' in dic_in: 114 | undoable=dic_in['undoable'] 115 | fs[undoable]['tag']='' 116 | fs_save(fs) 117 | untagged=[undoable]+untagged 118 | out=empty_page 119 | for photo in untagged: 120 | print('photo: ' +str(photo)) 121 | out=out.format('

{}/{}

{}'.format(len(photos)-len(untagged)+1,len(photos),'{}')) 122 | if 'thumbnail' in fs[photo]: 123 | out=out.format(hex2htmlPicture(fs[photo]['thumbnail'])) 124 | else: 125 | out=out.format(file2htmlPicture(photo)) 126 | out=out.format(easyForm('/tagPhotos', 'next_photo', ''.format(dic_in['location']))) 127 | out=out.format(easyForm('/tagPhotos', 'UNDO', ''.format(undoable, dic_in['location']))) 128 | out=out.format(linkHome) 129 | return out.format('') 130 | out=out.format('

all photos have been tagged

{}') 131 | out=out.format(easyForm('/tagPhotos', 'UNDO', ''.format(undoable, dic_in['location']))) 132 | out=out.format(linkHome) 133 | return out.format('') 134 | def renameGroup(dic): 135 | newName=dic['newName'] 136 | oldName=dic['oldName'] 137 | fs=fs_load() 138 | for i in fs: 139 | if fs[i]['tag']==oldName: 140 | fs[i]['tag']=newName 141 | fs_save(fs) 142 | out=empty_page 143 | out=out.format('

successfully renamed group

{}') 144 | print('linkhome: ' +str(type(linkHome))) 145 | out=out.format(linkHome) 146 | print('out: ' +str(out)) 147 | return out.format('') 148 | def writeQuit(dic): 149 | fs=fs_load() 150 | out={} 151 | for i in fs: 152 | if fs[i]['tag'] not in out: 153 | out[fs[i]['tag']]=[] 154 | out[fs[i]['tag']].append(i) 155 | f=open(output_file, 'w') 156 | for i in out: 157 | for j in out[i]: 158 | f.write('{}\t{}\n'.format(i, j)) 159 | f.close() 160 | out=empty_page.format('

successfully saved to file {}

{}'.format(output_file, '{}')) 161 | out=out.format(linkHome) 162 | return out.format('') 163 | def delete(dic): 164 | fs_save(initial_db) 165 | out=empty_page.format('

successfully deleted saved tags

{}') 166 | out=out.format(linkHome) 167 | return out.format('') 168 | '''def viewPhotos(dic_in): 169 | out='{}' 170 | photos=[] 171 | fs=fs_load() 172 | print('dic: ' +str(dic_in)) 173 | tag=dic_in['tag'] 174 | for i in fs: 175 | if fs[i]['tag']==tag: 176 | photos.append(i) 177 | if 'picture_numbered' in dic_in: 178 | n=int(dic_in['picture_numbered']) 179 | else: 180 | n=0 181 | out=out.format('

photos tagged as {}

{}'.format(tag,'{}')) 182 | if n >= len(photos): 183 | n=0 184 | if 'retag' in dic_in: 185 | fs[photos[n]]['tag']=dic_in['retag'] 186 | fs_save(fs) 187 | photos.remove(photos[n]) 188 | if 'untag' in dic_in: 189 | fs.pop(photos[n]) 190 | fs[photos[n]]='' 191 | fs_save(fs) 192 | photos.remove(photos[n]) 193 | if n >= len(photos): 194 | out=out.format("

no photos with this tag

{}") 195 | out=out.format(linkHome) 196 | return out.format('') 197 | out=out.format(file2htmlPicture(photos[n])) 198 | out=out.format(easyForm('/viewPhotos', 'next_photo', ''.format(dic_in['tag'], str(int(dic_in['picture_numbered'])+1)))) 199 | out=out.format(easyForm('/viewPhotos', 'untag_photo', ''.format(dic_in['tag'], dic_in['picture_numbered'], dic_in['picture_numbered']))) 200 | print('dic: ' +str(dic_in)) 201 | out=out.format(easyForm('/viewPhotos', 'retag_photo', ''.format('', dic_in['tag'], dic_in['picture_numbered']))) 202 | out=out.format(linkHome) 203 | return out.format('') 204 | ''' 205 | class MyHandler(BaseHTTPRequestHandler): 206 | def do_GET(self): 207 | try: 208 | if self.path == '/' : 209 | # page = make_index( '.' ) 210 | self.send_response(200) 211 | self.send_header('Content-type', 'text/html') 212 | self.end_headers() 213 | self.wfile.write(page1()) 214 | return 215 | else : # default: just send the file 216 | filepath = self.path[1:] # remove leading '/' 217 | if [].count(filepath)>0: 218 | # f = open( os.path.join(CWD, filepath), 'rb' ) 219 | #note that this potentially makes every file on your computer readable bny the internet 220 | self.send_response(200) 221 | self.send_header('Content-type', 'application/octet-stream') 222 | self.end_headers() 223 | self.wfile.write(f.read()) 224 | f.close() 225 | else: 226 | self.send_response(200) 227 | self.send_header('Content-type', 'text/html') 228 | self.end_headers() 229 | self.wfile.write("
Don't do that
") 230 | return 231 | return # be sure not to fall into "except:" clause ? 232 | except IOError as e : 233 | # debug 234 | print e 235 | self.send_error(404,'File Not Found: %s' % self.path) 236 | def do_POST(self): 237 | print("path: " + str(self.path)) 238 | # try: 239 | ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) 240 | print(ctype) 241 | if ctype == 'multipart/form-data' or ctype=='application/x-www-form-urlencoded': 242 | fs = cgi.FieldStorage( fp = self.rfile, 243 | headers = self.headers, # headers_, 244 | environ={ 'REQUEST_METHOD':'POST' }) 245 | else: raise Exception("Unexpected POST request") 246 | self.send_response(200) 247 | self.end_headers() 248 | dic=fs2dic(fs) 249 | 250 | if self.path=='/tagPhotos': 251 | self.wfile.write(tagPhotos(dic)) 252 | # elif self.path=='/viewPhotos': 253 | # self.wfile.write(viewPhotos(dic)) 254 | elif self.path=='/renameGroup': 255 | self.wfile.write(renameGroup(dic)) 256 | elif self.path=='/writeQuit': 257 | self.wfile.write(writeQuit(dic)) 258 | elif self.path=='/delete': 259 | self.wfile.write(delete(dic)) 260 | else: 261 | print('ERROR: path {} is not programmed'.format(str(self.path))) 262 | def main(): 263 | try: 264 | server = HTTPServer(('', PORT), MyHandler) 265 | print 'started httpserver...' 266 | server.serve_forever() 267 | except KeyboardInterrupt: 268 | print '^C received, shutting down server' 269 | server.socket.close() 270 | if __name__ == '__main__': 271 | main() 272 | 273 | 274 | 275 | 276 | --------------------------------------------------------------------------------