The Image Viewer example shows how to combine " 230 | "QLabel and QScrollArea to display an image. QLabel is " 231 | "typically used for displaying text, but it can also display " 232 | "an image. QScrollArea provides a scrolling view around " 233 | "another widget. If the child widget exceeds the size of the " 234 | "frame, QScrollArea automatically provides scroll bars.
" 235 | "The example demonstrates how QLabel's ability to scale " 236 | "its contents (QLabel.scaledContents), and QScrollArea's " 237 | "ability to automatically resize its contents " 238 | "(QScrollArea.widgetResizable), can be used to implement " 239 | "zooming and scaling features.
" 240 | "In addition the example shows how to use QPainter to " 241 | "print an image.
") 242 | 243 | def createActions(self): 244 | self.openAct = QtGui.QAction("&Open...", self, shortcut="Ctrl+O", 245 | triggered=self.open) 246 | 247 | self.printAct = QtGui.QAction("&Print...", self, shortcut="Ctrl+P", 248 | enabled=False, triggered=self.print_) 249 | 250 | self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q", 251 | triggered=self.close) 252 | 253 | self.zoomInAct = QtGui.QAction("Zoom &In (25%)", self, 254 | shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) 255 | 256 | self.zoomOutAct = QtGui.QAction("Zoom &Out (25%)", self, 257 | shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) 258 | 259 | self.normalSizeAct = QtGui.QAction("&Normal Size", self, 260 | shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) 261 | 262 | self.fitToWindowAct = QtGui.QAction("&Fit to Window", self, 263 | enabled=False, checkable=True, shortcut="Ctrl+F", 264 | triggered=self.fitToWindow) 265 | 266 | self.aboutAct = QtGui.QAction("&About", self, triggered=self.about) 267 | 268 | self.aboutQtAct = QtGui.QAction("About &Qt", self, 269 | triggered=QtGui.qApp.aboutQt) 270 | 271 | def createMenus(self): 272 | self.fileMenu = QtGui.QMenu("&File", self) 273 | #self.fileMenu.addAction(self.openAct) 274 | self.fileMenu.addAction(self.printAct) 275 | self.fileMenu.addSeparator() 276 | self.fileMenu.addAction(self.exitAct) 277 | 278 | self.viewMenu = QtGui.QMenu("&View", self) 279 | self.viewMenu.addAction(self.zoomInAct) 280 | self.viewMenu.addAction(self.zoomOutAct) 281 | self.viewMenu.addAction(self.normalSizeAct) 282 | self.viewMenu.addSeparator() 283 | self.viewMenu.addAction(self.fitToWindowAct) 284 | 285 | self.helpMenu = QtGui.QMenu("&Help", self) 286 | self.helpMenu.addAction(self.aboutAct) 287 | self.helpMenu.addAction(self.aboutQtAct) 288 | 289 | self.menuBar().addMenu(self.fileMenu) 290 | self.menuBar().addMenu(self.viewMenu) 291 | self.menuBar().addMenu(self.helpMenu) 292 | 293 | def updateActions(self): 294 | self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) 295 | self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) 296 | self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) 297 | 298 | def scaleImage(self, factor): 299 | if not self.fitToWindowAct.isChecked(): 300 | self.scaleFactor *= factor 301 | 302 | self.imageLabel.resize(self.scaleFactor * self.imageLabel.imageSize()) 303 | 304 | self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) 305 | self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) 306 | 307 | self.zoomInAct.setEnabled(self.scaleFactor < 3.0) 308 | self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) 309 | 310 | def adjustScrollBar(self, scrollBar, factor): 311 | scrollBar.setValue(int(factor * scrollBar.value() 312 | + ((factor - 1) * scrollBar.pageStep()/2))) 313 | 314 | def closeEvent(self, event): 315 | self.runLock.acquire(True) 316 | print("Close") 317 | self.runLock.release() 318 | self.run=0 319 | pass 320 | 321 | 322 | def wheelEvent(self,event): 323 | modifiers = QtGui.QApplication.keyboardModifiers() 324 | if modifiers == QtCore.Qt.ControlModifier: 325 | if(event.delta() >0): 326 | self.zoomOut() 327 | else: 328 | self.zoomIn() 329 | event.accept() 330 | else: 331 | event.ignore() 332 | -------------------------------------------------------------------------------- /src/FlashAir/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Jan 4, 2014 3 | 4 | Licence: GNU AGPL 5 | 6 | @author: Christian Holl 7 | ''' 8 | 9 | import sys 10 | if sys.hexversion < 0x03000000: 11 | sys.exit("Python 3 or newer is required for running this program.") 12 | 13 | __all__ = ['card','ImageViewer'] 14 | from FlashAir import card 15 | #from FlashAir import ImageViewer 16 | -------------------------------------------------------------------------------- /src/FlashAir/card.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Jan 4, 2014 3 | 4 | Licence: GNU AGPL 5 | 6 | @author: Christian Holl 7 | ''' 8 | 9 | import os 10 | import http.client 11 | 12 | class file_list_entry(object): 13 | file_name='' 14 | directory_name='' 15 | byte_size=-1 16 | attribute_Archive=False; 17 | attribute_Directly=False; 18 | attribute_Volume=False; 19 | attribute_System=False; 20 | attribute_Hidden=False; 21 | attribute_ReadOnly=False; 22 | date_human=() 23 | time_human=() 24 | time=0 25 | date=0 26 | def __init__(self, file_name, directory_name, size, attributes, date, time): 27 | self.file_name=file_name 28 | self.directory_name=directory_name 29 | self.byte_size=size 30 | 31 | 32 | 33 | attributes=int(attributes) 34 | self.attribute_Archive= not not(attributes & 1<<5) 35 | self.attribute_Directly= not not(attributes & 1<<4) 36 | self.attribute_Volume= not not(attributes & 1<<3) 37 | self.attribute_System= not not(attributes & 1<<2) 38 | self.attribute_Hidden= not not(attributes & 1<<1) 39 | self.attribute_ReadOnly= not not(attributes & 1<<0) 40 | 41 | time=int(time) 42 | self.time=time; 43 | self.time_human=(((time&(0x1F<<11))>>11),((time&(0x3F<<5))>>5),(time&(0x1F))*2) 44 | 45 | date=int(date) 46 | self.date=date; 47 | self.date_human=(((date&(0x3F<<9))>>9)+1980,((date&(0xF<<5))>>5),date&(0x1F)) 48 | 49 | 50 | class command(object): 51 | '''OPCODE - DIR DATE ADDR LENGTH DATA REQUIRED FWVERSION (Bigger/Smaller)''' 52 | Get_file_list= (100,1,0,0,0,0,'1.00.03',True) 53 | Get_the_number_of_files= (101,1,0,0,0,0,'1.00.00',True) 54 | Get_update_status= (102,0,0,0,0,0,'1.00.00',True) 55 | Get_SSID= (104,0,0,0,0,0,'1.00.00',True) 56 | Get_network_password= (105,0,0,0,0,0,'1.00.00',True) 57 | Get_MAC_address= (106,0,0,0,0,0,'1.00.00',True) 58 | Set_browser_language= (107,0,0,0,0,0,'1.00.00',True) 59 | Get_the_firmware_version= (108,0,0,0,0,0,'' ,True) 60 | Get_the_control_image= (109,0,0,0,0,0,'2.00.00',True) 61 | Get_Wireless_LAN_mode= (110,0,0,0,0,0,'2.00.00',True) 62 | Set_Wireless_LAN_timeout_length= (111,0,0,0,0,0,'2.00.00',True) 63 | Get_application_unique_information= (117,0,0,0,0,0,'2.00.00',True) 64 | Get_CID= (120,0,0,0,0,0,'1.00.03',True) 65 | Get_data_from_shared_memory= (130,0,0,1,1,0,'2.00.00',True) 66 | Set_data_to_shared_memory= (131,0,0,0,0,0,'2.00.00',True) 67 | Get_the_number_of_empty_sectors= (140,0,0,0,0,0,'1.00.03',True) 68 | Enable_Photo_Share_mode= (200,0,0,0,0,0,'2.00.00',True) 69 | Disable_Photo_Share_mode= (201,0,0,0,0,0,'2.00.00',True) 70 | Get_Photo_Share_mode_status= (202,0,0,0,0,0,'2.00.00',True) 71 | Get_SSID_for_Photo_Share_mode= (203,0,0,0,0,0,'2.00.00',True) 72 | 73 | class connection(object): 74 | ''' 75 | classdocs 76 | ''' 77 | 78 | 79 | list_directory=100 80 | fwversion='' 81 | host=0 82 | port=0 83 | timeout=0 84 | start_date=-1 85 | start_time=-1 86 | 87 | def __init__(self, host, port, timeout): 88 | ''' 89 | Constructor 90 | ''' 91 | self.host=host 92 | self.port=port 93 | self.timeout=timeout 94 | 95 | 96 | def send_command(self, opcode, directory='', date=-1, addr=-1, data=(), length=-1): 97 | if(len(opcode)==8): 98 | url="/command.cgi?op=" + str(opcode[0]) 99 | 100 | if(opcode[1] and len(directory)==0 ): 101 | print("ERROR Oppcode " + str(opcode[0]) + " requires directory") 102 | return(-1,'') 103 | elif(opcode[1]): 104 | url += '&DIR=' + directory 105 | 106 | if(opcode[2] and date<0 ): 107 | print("ERROR Oppcode " + str(opcode[0]) + " requires date") 108 | return(-1,'') 109 | elif(opcode[2]): 110 | url += '&DATE=' + date 111 | 112 | if(opcode[3] and addr<0 ): 113 | print("ERROR Oppcode " + str(opcode[0]) + " requires addr") 114 | return(-1,'') 115 | elif(opcode[3]): 116 | url += '&ADDR=' + str(addr) 117 | 118 | if(opcode[4] and length==0 ): 119 | print("ERROR Oppcode " + str(opcode[0]) + " requires length") 120 | return(-1,'') 121 | elif(opcode[4]): 122 | url += '&LEN=' + str(length) 123 | 124 | if(opcode[5] and len(data)==0 ): 125 | print("ERROR Oppcode " + str(opcode[0]) + " requires data") 126 | return(-1,'') 127 | elif(opcode[5]): 128 | url += '&DATA=' + data 129 | 130 | #Is it a firmware query? 131 | if(len(opcode[6])!=0): 132 | 133 | if(len(self.fwversion)==0): 134 | 135 | (ret, ver)=self.send_command(command.Get_the_firmware_version) 136 | if(not ret): 137 | self.fwversion=ver.decode("utf-8")[9:] 138 | print("Firmware is: ") 139 | print(self.fwversion) 140 | else: 141 | if(ret!=-2): 142 | print("ERROR: Could not determine firmware version!") 143 | return(-1,'') 144 | 145 | if(opcode[7]): #must be bigger than or equal to firmware version 146 | if(opcode[6]>self.fwversion): 147 | print("ERROR Opcode " + str(opcode[0]) + " not supported in firmware!") 148 | return(-1,'') 149 | else: 150 | if(opcode[6]>self.fwversion): 151 | print("ERROR Opcode " + str(opcode[0]) + " not supported in firmware!") 152 | return(-1,'') 153 | 154 | 155 | #Connect 156 | connection = http.client.HTTPConnection(self.host, self.port, timeout=self.timeout) 157 | 158 | try: 159 | connection.request("GET", url) 160 | response=connection.getresponse(); 161 | return (response.status!=200,response.read()) 162 | except: 163 | return (-2,'') 164 | pass 165 | 166 | 167 | def get_file_list(self,directory): 168 | (ret,lst)=self.send_command(command.Get_file_list,directory=directory) 169 | 170 | if(ret==0): 171 | 172 | lines=lst.decode("utf-8").split("\r\n") 173 | if(lines[0]=="WLANSD_FILELIST"): 174 | lines=lines[1:-1] #skip headline, and current dir at the end 175 | else: 176 | return (0,()) 177 | 178 | outlst=[] 179 | for file in lines: 180 | e=file.split(",") 181 | 182 | if(len(e)!=6): 183 | print("Error file list entry has " +str(len(e)) +" entrie(s) instead of expected 6, skipping entry") 184 | continue; 185 | #(file_name, directory_name, size, attributes, date, time): 186 | f=file_list_entry(e[1],e[0],int(e[2]),int(e[3]),int(e[4]),int(e[5])) 187 | outlst.append(f) 188 | 189 | return (0,outlst) 190 | else: 191 | return (1,()) 192 | 193 | def download_file(self, remote_location, local_path='', local_file_name=''): 194 | conn = http.client.HTTPConnection(self.host) 195 | if(len(local_file_name)==0): 196 | local_file_name = remote_location.split('/')[-1] 197 | file_size=0 198 | 199 | 200 | #does folder exist? 201 | if(not os.access(local_path, os.R_OK)): 202 | return (2,0,'') 203 | 204 | #add / if it is not there already 205 | if(len(local_path)!=0 and local_path[-1]!='/'): 206 | local_path+='/' 207 | 208 | #combine path and file 209 | local_path+=local_file_name 210 | 211 | #does file exist already? 212 | if(os.path.isfile(local_path)): 213 | return (3,0,'') 214 | 215 | print("Downloading:" + local_file_name, end=" ... ") 216 | #get the stuff from the FlashAir 217 | conn.request("GET", remote_location) 218 | download = conn.getresponse() 219 | 220 | if(download.status==200): 221 | file = open(local_path, 'wb') 222 | while True: 223 | buffer=download.read(1024*32) 224 | if not buffer: 225 | break; 226 | file_size += len(buffer) 227 | file.write(buffer) 228 | file.close() 229 | download.close() 230 | print("done (%d bytes) " % file_size) 231 | return (int(download.status!=200), file_size,local_path) 232 | 233 | def download_file_list_entry(self, entry,local_path='', local_filename=''): 234 | (status,size,local_filename)=self.download_file(entry.directory_name + '/' + entry.file_name, local_path, local_filename) 235 | if(status): 236 | return(1) 237 | 238 | 239 | if(size!=entry.byte_size): 240 | print("Error Size does not match") 241 | os.remove(local_filename) 242 | return(2) 243 | 244 | return(0) 245 | 246 | def sync_folder_to_remote_folder(self,remote_path='',local_path='',extensions=['JPG']): 247 | #all extensions to upper case 248 | extensions=[x.upper() for x in extensions] 249 | 250 | #get list of remote files 251 | (status, outlist)=self.get_file_list(remote_path) 252 | if(not status and len(outlist)): 253 | if(not os.access(local_path, os.R_OK)): 254 | return 2 255 | for entry in outlist: 256 | if ((entry.file_name.split('.')[-1].upper() in extensions) or len(extensions)==0): 257 | self.download_file_list_entry(entry, local_path) 258 | 259 | 260 | def sync_new_pictures_since_start(self,remote_path='',local_path='',extensions=['JPG']): 261 | last_file='' 262 | 263 | #all extensions to upper case 264 | extensions=[x.upper() for x in extensions] 265 | 266 | #get list of remote files 267 | (status, outlist)=self.get_file_list(remote_path) 268 | 269 | #determine latest file date and time 270 | 271 | if(self.start_date < 0): 272 | last_entry=None 273 | for entry in outlist: 274 | if(entry.date>self.start_date): 275 | self.start_date=entry.date 276 | self.start_time=entry.time 277 | last_entry=entry 278 | elif (entry.date==self.start_date): 279 | if(entry.time>self.start_time): 280 | self.start_time=entry.time 281 | last_entry=entry 282 | if(last_entry!=None): 283 | last_file=local_path+'/'+last_entry.file_name 284 | self.download_file_list_entry(last_entry, local_path) #download latest file 285 | 286 | if(not status and len(outlist)): 287 | if(not os.access(local_path, os.R_OK)): 288 | return () 289 | for entry in outlist: 290 | if ((entry.file_name.split('.')[-1].upper() in extensions) or len(extensions)==0): 291 | if(entry.date>=self.start_date): 292 | if(entry.date>self.start_date or entry.time>self.start_time): 293 | if(not self.download_file_list_entry(entry, local_path)): 294 | last_file=local_path+'/'+entry.file_name 295 | return last_file 296 | -------------------------------------------------------------------------------- /src/PyFlashAero.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Jan 25, 2014 3 | 4 | @author: cyborg-x1 5 | ''' 6 | 7 | from FlashAir import card 8 | from urllib.parse import urlparse 9 | import argparse 10 | import socket 11 | import sys 12 | import os 13 | from os.path import expanduser 14 | import time 15 | 16 | 17 | 18 | 19 | def ImageView(args): 20 | print("imageView") 21 | 22 | try: 23 | import PyQt4.QtGui as QtGui 24 | except ImportError: 25 | sys.exit("Using --imageViewer requires PyQt4, which is not installed.") 26 | 27 | from FlashAir import ImageViewer 28 | 29 | app = QtGui.QApplication(sys.argv) 30 | port=args.card_uri.port 31 | if(port == None): 32 | port = 80 33 | imageViewer = ImageViewer.ImageViewer(socket.gethostbyname(args.card_uri.hostname), port, args.timeout, args.folder_local, args.folder_remote, args.instant, args.recursive, args.debug_image) 34 | imageViewer.show() 35 | sys.exit(app.exec_()) 36 | 37 | def SyncFolder(args): 38 | print("SyncFolder") 39 | print(socket.gethostbyname(args.card_uri.hostname)) 40 | 41 | port=args.card_uri.port 42 | if(port == None): 43 | port = 80 44 | 45 | a=card.connection(socket.gethostbyname(args.card_uri.hostname), port, args.timeout) 46 | print("Use ctrl-c to exit!") 47 | while True: 48 | a.sync_folder_to_remote_folder(args.folder_remote, args.folder_local, extensions=args.ext) 49 | time.sleep(1) 50 | pass 51 | 52 | if __name__ == '__main__': 53 | parser = argparse.ArgumentParser(description='PyFlashAero, Download Tool for Toshiba FlashAir SD-Cards') 54 | parser.add_argument('--card_uri', dest='card_uri', type=urlparse, help='URI of the Toshiba FlashAir SDCard', default="http://192.168.0.1") 55 | parser.add_argument('--timeout', dest='timeout', type=int, help='Timeout in milliseconds', default=1000) 56 | 57 | parser.add_argument('--folder_local', dest='folder_local', help='Folder for storing downloaded images', default='.') 58 | parser.add_argument('--folder_remote', dest='folder_remote', help='Folder where to search for new images (remote)', default='/') 59 | parser.add_argument('--recursive', dest='recursive', action='store_const', 60 | const=True, default=False, 61 | help='Search for new images in the folder recursively (not implemented yet)') 62 | 63 | parser.add_argument('--ImageViewer', dest='processing', action='store_const', 64 | const=ImageView, default=SyncFolder, 65 | help='Shows the GUI') 66 | 67 | 68 | parser.add_argument('--GUIinstant', dest='instant', action='store_const', 69 | const=True, default=False, 70 | help='GUI will start looking for images directly') 71 | 72 | parser.add_argument('--GUIDebugImage', dest='debug_image', help='path for picture to debug the GUI') 73 | 74 | parser.add_argument('--ext', action='append', default=['JPG'], dest='ext') 75 | 76 | args = parser.parse_args() 77 | ip = socket.gethostbyname(args.card_uri.hostname) 78 | 79 | if(not os.path.isdir(args.folder_local)): 80 | print("Given folder(local) does not exist or isn't a folder!", end='\n', file=sys.stderr) 81 | exit(1) 82 | 83 | if((args.instant or args.debug_image!=None) and not args.processing==ImageView): 84 | print("Selected GUI option without selecting GUI!", end='\n', file=sys.stderr) 85 | exit(1) 86 | 87 | if(args.debug_image != None): 88 | if(not os.path.isfile(args.debug_image)): 89 | print("Debug image file does not exist!", end='\n', file=sys.stderr) 90 | exit(1) 91 | 92 | args.processing(args) 93 | 94 | 95 | --------------------------------------------------------------------------------