บันทึกโค๊ดที่ทดลองเขียน
หาค่า factor ของ 2 จำนวนเต็ม
สมการคือ (nX * x) + (nY * y) = z
รู้ x, y, และ z
อยากทราบจำนวน nX และ nY ที่เป็นจำนวนเต็ม ว่ามีค่าเท่าไหร่มั่ง
ตั้งชื่อโปรแกรมว่า d.calc2.py
$ vi d.calc2.py
#!/usr/bin/env python import sys if sys.argv[3]==None: nSum=input("Enter sum amount: ") nXparms = input("Enter x parms: ") nYparms = input("Enter y parms: ") else: nSum=eval(sys.argv[1]) nXparms=eval(sys.argv[2]) nYparms=eval(sys.argv[3]) # nX=0 nY=1 print "nX*%f + nY*%f = %f" % (nXparms, nYparms, nSum) while nY>0: nY=float(nSum-nXparms*nX)/nYparms nRemain=float(nY-int(nY)) if nRemain==0: print "nX=%i, nY=%i" % (nX,nY) # nX=nX+1 #
ทดลองเรียกใช้งาน
$ ./d.calc2.py 50 1.5 2.5
nX*1.500000 + nY*2.500000 = 50.000000
nX=0, nY=20
nX=5, nY=17
nX=10, nY=14
nX=15, nY=11
nX=20, nY=8
nX=25, nY=5
nX=30, nY=2
วันนี้มีงานต้องแปลงไฟล์ จาก tis620 ไป utf8
คำสั่งที่ต้องใช้คือ
$ iconv -f tis620 -t utf8 -o NEWFILE.TXT OLDFILE.TXT
แต่ปัญหาคือหลายไฟล์แปลงไม่ได้ เขาไม่ยอมแปลงให้
อาจเป็นเพราะมีรหัสแปลกปลอมในไฟล์
เลยเขียนสคริปต์ไว้ใช้แปลงเอง จะได้เผื่อสำหรับงานหน้าด้วย
ตั้งชื่อว่า d.tis2utf เอาไว้ใน /usr/local/bin
$ sudo touch /usr/local/bin/d.tis2utf
$ sudo chmod 755 /usr/local/bin/d.tis2utf
$ sudo vi /usr/local/bin/d.tis2utf
#!/usr/bin/env python # CONVERT FILE CONTENT FROM tis620(cp874) TO utf8 import sys,os # GLOBAL VARS decodec="cp874" encodec="utf8" # # VARIABLE decodec AND encodec CAN BE CHANGED. # POSSIBLE STANDARD ENCODINGS VALUES ARE: # ascii, big5, big5hkscs, cp037, cp424, cp437, cp500, cp737, cp775, cp850, # cp852, cp855, cp856, cp857, cp860, cp861, cp862, cp863, cp864, cp865, # cp866, cp869, cp874, cp875, cp932, cp949, cp950, cp1006, cp1026, cp1140, # cp1250, cp1251, cp1252, cp1253, cp1254, cp1255, cp1256, cp1257, cp1258, # euc_jp, euc_jis_2004, euc_jisx0213, euc_kr, gb2312, gbk, gb18030, hz, # iso2022_jp, iso2022_jp_1, iso2022_jp_2, iso2022_jp_2004, iso2022_jp_3, # iso2022_jp_ext, iso2022_kr, latin_1, iso8859_2, iso8859_3, iso8859_4, # iso8859_5, iso8859_6, iso8859_7, iso8859_8, iso8859_9, iso8859_10, # iso8859_13, iso8859_14, iso8859_15, johab, koi8_r, koi8_u, mac_cyrillic, # mac_greek, mac_iceland, mac_latin2, mac_roman, mac_turkish, ptcp154, # shift_jis, shift_jis_2004, shift_jisx0213, utf_16, utf_16_be, utf_16_le, # utf_7, utf_8, utf_8_sig # # PLEASE SEE http://docs.python.org/lib/standard-encodings.html # FOR MORE INFORMATION. def usage(progname): print "Usage: %s FILE" % (progname) print "Convert FILE from %s to %s, save old file in FILE.bak" % (decodec,encodec) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def genfilename(filename="",ext="new"): if filename=="": return "" # if ext.lower()=="new": ext="new" # if ext.lower()!="new" and ext.lower()!="bak": ext="bak" # if os.path.exists(filename+"."+ext): i=0 while os.path.exists(filename+"."+ext+str(i)) and (i < 1000): i=i+1 # if i>999: return "" # return filename+"."+ext+str(i) else: return filename+"."+ext # def convertfile(fs_old, fs_new): for eachline in fs_old: try: newline=eachline.decode(decodec).encode(encodec) except: newline=eachline # fs_new.write(newline) # return True if __name__=="__main__": progname=os.path.basename(sys.argv[0]) try: oldfile=sys.argv[1] except: usage(progname) sys.exit(1) # try: fsold=open(oldfile) except: cannotopenfile(oldfile) sys.exit(1) # newfile=genfilename(oldfile,"new") if newfile=="": print "Cannot save backup file" sys.exit(1) # try: fsnew=open(newfile,"w") except: cannotopenfile(newfile) sys.exit(1) # if convertfile(fsold,fsnew)==False: fsold.close() fsnew.close() print "Convert file %s faild" % (oldfile) sys.exit(1) # fsold.close() fsnew.close() bakfile=genfilename(oldfile,"bak") if bakfile=="": print "Cannot create bakup file, so utf8-file is %s" % (newfile) sys.exit(1) # os.rename(oldfile,bakfile) os.rename(newfile,oldfile) print "Convert %s success, save backup file in %s" % (oldfile,bakfile)
วิธีใช้ก็สั่ง d.tis2utf FILENAME.TXT
จะได้ FILENAME.TXT เป็นรหัส utf8 และ FILENAME.TXT.bak เป็นไฟล์เก่า
(ใครเขียนไพธอนเก่ง ๆ ฝากแนะนำด้วยครับ อยากเขียนให้กระชับ และดูง่าย)
มีงานต้องแปลงไฟล์กลับ เลยเขียนโค๊ดอีกทีนึง
( งานที่ทำคือ สันติรำลึก เป็นการแปลงไฟล์กลับจาก Word มาเป็น HTML แบบ tis-620 )
#!/usr/bin/env python # CONVERT FILE CONTENT FROM utf8 TO tis620 import sys,os # GLOBAL VARS decodec="utf8" encodec="cp874" # # VARIABLE decodec AND encodec CAN BE CHANGED. # ALL STANDARD ENCODINGS IS: # ascii, big5, big5hkscs, cp037, cp424, cp437, cp500, cp737, cp775, cp850, # cp852, cp855, cp856, cp857, cp860, cp861, cp862, cp863, cp864, cp865, # cp866, cp869, cp874, cp875, cp932, cp949, cp950, cp1006, cp1026, cp1140, # cp1250, cp1251, cp1252, cp1253, cp1254, cp1255, cp1256, cp1257, cp1258, # euc_jp, euc_jis_2004, euc_jisx0213, euc_kr, gb2312, gbk, gb18030, hz, # iso2022_jp, iso2022_jp_1, iso2022_jp_2, iso2022_jp_2004, iso2022_jp_3, # iso2022_jp_ext, iso2022_kr, latin_1, iso8859_2, iso8859_3, iso8859_4, # iso8859_5, iso8859_6, iso8859_7, iso8859_8, iso8859_9, iso8859_10, # iso8859_13, iso8859_14, iso8859_15, johab, koi8_r, koi8_u, mac_cyrillic, # mac_greek, mac_iceland, mac_latin2, mac_roman, mac_turkish, ptcp154, # shift_jis, shift_jis_2004, shift_jisx0213, utf_16, utf_16_be, utf_16_le, # utf_7, utf_8, utf_8_sig # # SEE http://docs.python.org/lib/standard-encodings.html # FOR MORE INFORMATION. def usage(progname): print "Usage: %s FILE" % (progname) print "Convert FILE from %s to %s, save old file in FILE.bak" % (decodec,encodec) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def genfilename(filename="",ext="new"): if filename=="": return "" # if ext.lower()=="new": ext="new" # if ext.lower()!="new" and ext.lower()!="bak": ext="bak" # if os.path.exists(filename+"."+ext): i=0 while os.path.exists(filename+"."+ext+str(i)) and (i < 1000): i=i+1 # if i>999: return "" # return filename+"."+ext+str(i) else: return filename+"."+ext # def replace_invalid_char(line,utf_char,tis_char): return line.replace(utf_char,tis_char) def convertline(line): # CHECK INVALID CHAR line=replace_invalid_char(line,"\xe2\x80\x98","'") line=replace_invalid_char(line,"\xe2\x80\x99","'") line=replace_invalid_char(line,"\xe2\x80\x9c",'"') line=replace_invalid_char(line,"\xe2\x80\x9d",'"') line=replace_invalid_char(line,"\xe2\x80\xa6","...") line=replace_invalid_char(line,"\xef\x9c\x8f","\xe0\xb8\x8d") #YOR YING line=replace_invalid_char(line,"\xef\x9c\x9a","\xe0\xb8\xba") #PINTU line=replace_invalid_char(line,"\xe2\x80\x93","-") line=replace_invalid_char(line,"\xef\x82\xae","->") line=replace_invalid_char(line,"\xef\xa3\x82","") # UNKNOWN line=replace_invalid_char(line,"\xef\xa3\x83","") # UNKNOWN return line.decode(decodec).encode(encodec) def convertfile(fs_old, fs_new): for eachline in fs_old: newline=convertline(eachline) # try: # newline=convertline(eachline) # except: # newline=eachline # # fs_new.write(newline) # return True if __name__=="__main__": progname=os.path.basename(sys.argv[0]) try: oldfile=sys.argv[1] except: usage(progname) sys.exit(1) # try: fsold=open(oldfile) except: cannotopenfile(oldfile) sys.exit(1) # newfile=genfilename(oldfile,"new") if newfile=="": print "Cannot save backup file" sys.exit(1) # try: fsnew=open(newfile,"w") except: cannotopenfile(newfile) sys.exit(1) # if convertfile(fsold,fsnew)==False: fsold.close() fsnew.close() print "Convert file %s faild" % (oldfile) sys.exit(1) # fsold.close() fsnew.close() bakfile=genfilename(oldfile,"bak") if bakfile=="": print "Cannot create bakup file, so utf8-file is %s" % (newfile) sys.exit(1) # os.rename(oldfile,bakfile) os.rename(newfile,oldfile) print "Convert %s success, save backup file in %s" % (oldfile,bakfile)
โค๊ดยังไม่เรียบร้อยดี แต่ขอแปะโค๊ดไว้ก่อน
จาก ThaiLinuxCafe: แก้ไข ID3Tags ใน mp3 ให้ใช้กับ Amarok 1.4 และ Noatun
ใช้แปลงไฟล์ mp3 จากการเข้ารหัสแบบ cp874 มาเป็นยูนิโค๊ด utf8
ตัวโปรแกรมจะแปลงชื่อไฟล์และ ID3 Tags ในไฟล์
ถ้าจะนำไปใช้ โปรดใช้ด้วยความระมัดระวัง
เพราะไม่ได้เขียนฟังก์ชั่นการเตือนไว้ด้วยครับ
ตั้งชื่อไฟล์ว่า d.tags2utf8
$ sudo touch /usr/local/bin/d.tags2utf8
$ sudo chmod 755 /usr/local/bin/d.tags2utf8
$ sudo vi /usr/local/bin/d.tags2utf8
#!/usr/bin/env python """ Convert ID3 Tags from CP874 to UTF8 and auto rename file recursive into subdirectory. Coding from Khun pong_th's article at: http://www.thailinuxhosting.com/yabbse/index.php?board=6;action=display;threadid=9429 """ # 49-11-18 ADD ID3V1 CONVERSION import os # GLOBAL VARIABLE skel=".mp3" decodec="cp874" encodec="utf8" def d_passcheck_invalid_char(string): if string=="": return False i=0 for ch in ['\x00','\xff']: i=i+1 if ch in string: print "%d CH IN STRING %s" % (i,string) return False # # return True def d_convert(string): string=string.split('\00')[0] # TRIM '\x00' CHARACTER if d_passcheck_invalid_char(string): return string.decode(decodec).encode(encodec) else: return string # def d_rename2utf8(dir,strname): """Convert coding from TIS-620 to UTF-8.""" for i in strname: if i>'\x7f': # CHECK FOR UTF STRING if i=='\xe0': return strname newstr=d_convert(strname) print "rename file: %s -> %s" % (strname,newstr) os.rename(dir+os.sep+strname, dir+os.sep+newstr) return newstr # # return strname def d_getID3V1data(fstream): """Get old tags format From: http://www.faqs.org/docs/diveintopython/fileinfo_files.html""" # ID3V1 format (from:http://www.id3.org/id3v2-00.txt) # Field Length Offsets # Tag 3 0-2 # Songname 30 3-32 # Artist 30 33-62 # Album 30 63-92 # Year 4 93-96 # Comment 30 97-126 # Genre 1 127 fstream.seek(-128,2) tags=fstream.read(128) fstream.seek(0) return [tags[3:32],tags[33:62],tags[63:92],tags[93:96],tags[97:126],tags[127]] def d_write_eachtags(fstream,tagstitle,tagsdata): fstream.write(tagstitle+'\x00\x00\x00') fstream.write(chr(len(tagsdata)+1)+'\x00\x00\x03') fstream.write(tagsdata) return def d_change_tags2utf8(filename): """Change ID3 Tags content from cp874 to utf8""" fstream=open(filename,"r+b") ispass=False if fstream.read(3)=="ID3": # READ ID3 TAGS DATA fstream.read(6) nbyte=ord(fstream.read(1)) ltags=[] ctagsname=fstream.read(4) while ctagsname in ["TIT2","TPE1","TALB"]: fstream.read(3) ntagsbyte=ord(fstream.read(1)) fstream.read(3) ctagscontent=fstream.read(ntagsbyte-1) ltags.append([ctagsname,ntagsbyte,ctagscontent]) ctagsname=fstream.read(4) # # CONVERT TO utf8 nnewbyte=0 for eachtags in ltags: if not '\xe0' in eachtags[2]: eachtags[2]=d_convert(eachtags[2]) if not ispass: ispass=True # else: print "File %s already in utf8 format." % (filename) fstream.close() return False # eachtags[1]=len(eachtags[2]) nnewbyte=nnewbyte+4+3+3+eachtags[1]+1 # # WRITE BACK CONVERTED DATA fstream.seek(9) fstream.write(chr(nnewbyte)) for eachtags in ltags: d_write_eachtags(fstream,eachtags[0],eachtags[2]) # if nnewbyte<nbyte: for i in range(nbyte-nnewbyte): fstream.write('\x00') # # if ispass: print "Id3 Tags: file %s converted" % (filename) # else: # CHECK FOR ID3V1 fstream.seek(0) wholefile=fstream.read(-1) if 'TAG' in wholefile: ltags=d_getID3V1data(fstream) nnewbyte=0 for i in range(len(ltags)): ltags[i]=d_convert(ltags[i]) if d_passcheck_invalid_char(ltags[i]): nnewbyte=nnewbyte+len(ltags[i])+4+3 # # fstream.close() fstream=open(filename,"w") fstream.write('ID3'+'\x04\x00\x00\x00\x00\x08') fstream.write(chr(nnewbyte)) # Songname 30 3-32 :TIT2 # Artist 30 33-62 :TPE1 # Album 30 63-92 :TALB # Year 4 93-96 :TDOR # Comment 30 97-126 :COMM # Genre 1 127 :---- print ltags[0] if d_passcheck_invalid_char(ltags[0]): d_write_eachtags(fstream,'TIT2',ltags[0]) if d_passcheck_invalid_char(ltags[1]): d_write_eachtags(fstream,'TPE1',ltags[1]) if d_passcheck_invalid_char(ltags[2]): d_write_eachtags(fstream,'TALB',ltags[2]) if d_passcheck_invalid_char(ltags[3]): d_write_eachtags(fstream,'TDOR',ltags[3]) if d_passcheck_invalid_char(ltags[4]): d_write_eachtags(fstream,'COMM',ltags[4]) # DISCARD Genre TAGS for i in range(1016): fstream.write('\x00') # fstream.write(wholefile) else: print "ID3 Tags not found in %s" % (filename) # # fstream.close() def process_dir(dir): """Process all files in the folder""" for f in os.listdir(dir): file = dir + os.sep + f if os.path.isdir(file): print "Enter directory %s" % (file) process_dir(file) print "---exit directory %s" % (file) # if f[-4:]==skel: # DO CONVERT FILENAME file=d_rename2utf8(dir,file) # DO CHANGE ID3 TAGS d_change_tags2utf8(file) # # return def main(): """main routine""" process_dir('.') return if __name__=='__main__': main()
มีโจทย์อยู่คือ
เวลาไปเที่ยวหรือมีงานที่ต้องถ่ายภาพเป็นจำนวนมาก เกินการ์ดหน่วยความจำที่มีอยู่
เวลาการ์ดเต็ม ก็ต้องถ่ายออกมาเก็บไว้ในโน๊ตบุ๊ก
ปัญหาคือเวลาจะดูภาพจากโน๊ตบุ๊ก ซึ่งสเปคเครื่องต่ำมาก โหลดไฟล์ภาพใหญ่ ๆ ไม่ไหว มันจะดูได้ช้ามาก ๆ ดูภาพ 10 ภาพ ใช้เวลาไป 15 นาที
ทางแก้คือคัดลอกไฟล์ภาพมาแปลงเป็นไฟล์เล็ก (อาจจะแปลงด้วย gimp หรือ imagemagick ก็ได้) แต่เนื่องจากสเปคเครื่องต่ำมาก แปลงไฟล์แต่ละครั้งกินเวลาเป็นชั่วโมง ไม่ทันต่อเหตุการณ์
ทางออกอีกทางคือไปแตกเอาไฟล์ JPG อันเล็ก ที่ซ่อนอยู่ภายใต้ไฟล์ตัวจริงซึ่งใหญ่มาก เอาออกมาแทน วิธีนี้จะทำงานได้รวดเร็วกว่ามาก
เคยเขียน C ไว้เป็นไฟล์เล็ก ๆ บนวินโดวส์ แต่เที่ยวนี้ผมลองเอามาคอมไพล์บนลินุกส์ ปรากฎว่าคอมไพล์ไม่ผ่าน และภาษา C ก็ลืมสิ้นแล้ว อย่ากระนั้นเลย พึ่งไพธอนดีกว่า
ผมใช้กล้อง Canon และเราจะแตกไฟล์ JPG อันเล็ก ซึ่งเป็นไฟล์ลำดับที่ 3 ที่ซ่อนอยู่ในไฟล์ใหญ่ เลยตั้งชื่อโปรแกรมว่า canon3.py
เวลาใช้งานก็สั่ง
$ ./canon3.py FILENAME.JPG
ก็จะแตกไฟล์ FILENAME.JPG ไปเป็น canon3/FILENAME.JPG
หรือถ้าสั่ง
$ ./canon3.py
เฉย ๆ
ก็จะควานหาทุกไฟล์ในไดเรกทอรี่ที่เป็น JPG หรือ CRW และแตกไฟล์ย่อยออกมาใส่ในไดเรกทอรี่ย่อย canon3
โดยโปรแกรมจะคัดลอกเอาข้อมูล Exif ของกล้องติดไปด้วย (แต่ข้อมูลขนาดภาพใน Exif จะผิดจากความเป็นจริง ขี้เกียจแก้แล้วอ่ะ)
ทดลองแล้วความเร็วใสการแตกไฟล์ดีมาก (ไพธอนนี่ดีกว่าที่คิดเยอะเลย)
โค๊ดมีดังนี้
#!/usr/bin/env python # EXTRACT THIRD jpg FILE IN Canon CAMERA # # jpg file format # start with: FF D8 FF E1 NN NN ... (contain exif data to byte NN NN) # first JPG : FF D8 ... FF D9 (thumbnail) # second JPG: FF D8 ... FF D9 (real JPG) # third JPG : FF D8 ... FF D9 (hidden small JPG) # SKIP TO 2/3 OF FILE THEN SEARCH FOR THIRD #FFD8 TO #FFD9 # import sys, os, string # VAR tag_beg="\xff\xd8" tag_end="\xff\xd9" subdir="canon3" file_skel=[".JPG",".jpg",".jpeg"] dir_skel=["DCIM","CANON"] # PROCEDURE def process_file(filename): for fskel in file_skel: if fskel in filename: #OPEN FILE IN BINARY MODE try: f = open(filename, 'rb') except: print 'Could not open file to read !', filename sys.exit(3) if f is None: print 'Error opening file ', filename sys.exit(3) # COPY EXIF HEADING TO NEW FILE IN SUBDIR canon3 basename=os.path.basename(filename) print "%s -> %s/%s" % (basename,subdir,basename) if not os.path.exists(subdir): os.mkdir(subdir) # f_new=open(os.path.join(subdir,basename),"wb") # SEEK FF D8 FF E1 NN NN f.seek(4) offset_low=f.read(1) offset_hi=f.read(1) no_of_byte=ord(offset_low)*256+ord(offset_hi)-4 ## print "no_of_byte=%i" % no_of_byte # WRITE TO NEW FILE f_new.write("\xff\xd8\xff\xe1"+offset_low+offset_hi+f.read(no_of_byte)) # SEEK FOR THE REST 3RD JPG FILE # INCREASE SPEED BY SKIP TO 2/3 OF FILE f.seek(os.path.getsize(filename) * 2/3) is_found=False for i in f: if tag_beg in i: is_found=True # WRITE THIRD JPG TO NEW FILE f_new.write(tag_beg+i.split(tag_beg)[1]) for j in f: if tag_end in j: f_new.write(j.split(tag_end)[0]+tag_end) break f_new.close() else: f_new.write(j) # # break # # f.close() if not is_found: print "third JPG file not found." # # # def process_dir(dirname): ## print "dirname=%s" % dirname for dskel in dir_skel: if dskel in dirname: print "enter %s" % dirname os.chdir(dirname) filelist=os.listdir(".") for i in filelist: if os.path.isdir(i): process_dir(i) else: process_file(i) # # print "exit %s" % dirname os.chdir("..") # # # MAIN PROG def main(): if len(sys.argv) < 2: # PROCESS ALL FILE&DIR for filelist in os.listdir("."): if os.path.isdir(filelist): process_dir(filelist) else: process_file(filelist) # # else: filename = os.path.abspath(sys.argv[1]) if os.path.isdir(filename): process_dir(filename) else: process_file(filename) # # print "finished" if __name__=="__main__": main()
แถมอีกนิด
ถ้าจะให้หมุนภาพอัตโนมัติด้วย ต้องใช้แพคเกจ jhead
$ sudo apt-get install jhead
$ jhead -autorot *
ลดขนาดรูปพร้อมกับหมุนภาพอัตโนมัติ ทำลึกลงไปทุกไดเรคทอรี่
$ sudo apt-get install imagemagick jhead
$ sudo touch /usr/local/bin/d.canon5.py
$ sudo chmod 0755 /usr/local/bin/d.canon5.py
$ sudo vi /usr/local/bin/d.canon5.py
#!/usr/bin/env python import sys, os, string # VAR subdir="canon3" file_skel=[".JPG",".jpg",".jpeg"] dir_skel=["DCIM","CANON","100EOS5D"] # PROCEDURE def process_file(filename): for fskel in file_skel: if fskel in filename: if not os.path.exists(subdir): os.mkdir(subdir) # print "convert %s -> %s" % (filename, os.path.join(subdir,filename)) os.system('convert -resize 40%'+" %s %s" % (filename,os.path.join(subdir,filename))) os.system("jhead -autorot %s" % (os.path.join(subdir,filename))) # # def process_dir(dirname): ## print "dirname=%s" % dirname for dskel in dir_skel: if dskel in dirname: print "enter %s" % dirname os.chdir(dirname) filelist=os.listdir(".") for i in filelist: if os.path.isdir(i): process_dir(i) else: process_file(i) # # print "exit %s" % dirname os.chdir("..") # # # MAIN PROG def main(): if len(sys.argv) < 2: # PROCESS ALL FILE&DIR for filelist in os.listdir("."): if os.path.isdir(filelist): process_dir(filelist) else: process_file(filelist) # # else: filename = os.path.abspath(sys.argv[1]) if os.path.isdir(filename): process_dir(filename) else: process_file(filename) # # print "finished" if __name__=="__main__": main()
ได้ลองใช้ ufraw แล้วรู้สึกว่าสีสวยกว่า dcraw
ติดตั้งด้วย
$ sudo aptitude install ufraw
ทำงานแบบบรรทัดคำสั่ง
$ cd /PATH/TO/IMAGE
$ mkdir jpg
$ for i in *cr2; do
ufraw-batch \
--wb=camera \
--exposure=auto \
--out-type=jpeg \
--compression=96 \
--out-path=./jpg \
$i
done
เป็นการทำงานกับไฟล์ภาพนามสกุล cr2 โดยให้ผลิตไฟล์ jpg ไปอยู่ในโฟลเดอร์ชื่อ jpg
เอามาจาก
Linux Photography : Workflow (3) - quick RAW converting batch
เพิ่งได้เริ่มหัดถ่ายภาพแบบ Raw ของกล้อง Canon มีนามสกุลเป็น .CR2
$ sudo aptitude install gimp gimp-ufraw
$ sudo aptitude install dcraw
$ for i in *.CR2; do dcraw -c -q 3 -B 2 4 -w $i | cjpeg -quality 100 > `basename $i .CR2`.jpg; done
เอามาจาก How to convert raw cr2 pictures with linux, and merge pictures by date and Exif data with jhead
ทำตัวอย่างการค้นเนื้อหาไพธอน (20%)
เสร็จประมาณ 10% แล้ว
อยากให้ทำงานได้แบบ php help จัง
update
ต่อเนื่องจากครั้งก่อนเรื่อง imagemagick: ทำ annotate ไฟล์ tif
มีข้อบกพร่องเล็กน้อยคือ เวลาที่ inkscape นำเข้าไฟล์ tif จะกำหนดขนาดเป็นพิกเซลตามไฟล์ tif
ซึ่งจะมีขนาดใหญ่กว่า A4 พอสมควร (เทียบที่ค่าปริยายของ inkscape คือ 90dpi)
ถ้าจะแก้ไขด้วยการปรับหน้ากระดาษและลดขนาดภาพใน inkscape ทุกครั้ง ก็ดูไม่ค่อยสะดวก
และจะแก้ไขด้วยการลดขนาดพิกเซลของภาพ ก็เสียดายความละเอียด
จึงทดลองแปลงสคริปต์ไฟล์เดิม จากการใช้เชลล์สคริปต์ของ bash มาเป็นไพธอนแทน ทั้งนี้เพียงเพื่อหาขนาดพิกเซลของภาพเท่านั้น (แทบไม่ได้ใช้ความสามารถจริง ๆ ของไพธอนเลย)
วิธีการใช้แบบโกง ๆ หน่อย คือในครั้งแรกที่แตกไฟล์ เราสร้างไฟล์ svg ซึ่งเป็นไฟล์ xml ขึ้นมาเองเลย
โดยให้เขียนค่าพิกเซลที่เหมาะสม คือลดหรือเพิ่มขนาดให้ไฟล์ภาพลงบนความกว้างของหน้ากระดาษ A4 ได้
อย่าลืมต้องติดตั้งแพกเกจที่ต้องการก่อน
$ sudo aptitude install imagemagick inkscape evince python python-imaging
เนื้อไฟล์ตั้งชื่อว่า d.tifannotate.py มีดังนี้
$ sudo vi /usr/local/bin/d.tifannotate.py
#!/usr/bin/env python # ANNOTATE MULTIPLE PAGES TIF FILE # PREREQUISITE: inkscape imagemagick evince python-imaging import sys import os import commands try: import Image except: print "Missing PIL library, require packages python-imaging." sys.exit(1) #PARAMETER overwrite_new_file = True #OVERWRITE image-new.tif __width = 744.09448819 #DEFAULT A4 SIZE IN PIXEL __height = 1052.3622047 # #REQUIRE PACKAGES packages = ( ("inkscape","inkscape"), ("convert","imagemagick"), ("evince","evince") ) for i in range(len(packages)): exec("status, %s = commands.getstatusoutput('which %s')" % (packages[i][0], packages[i][0]) ) if status != 0: print "Missing command %s, require packages %s." % (packages[i][0], packages[i][1],) sys.exit(1) #GLOBAL VARS err_open = "Cannot open file %s." #PROCEDURE def usage(progname): print "Annotate multiple pages TIF image." print "Usage: %s IMGFILE PAGES..." % (progname) print "Ex1 : %s image.tif 0 2 = Edit image.tif page 0 and page 2." % (progname) def errmsg(msg, *arg): if not '%s' in msg: return msg if type(arg)!=type([]): return msg % (arg) lmsg = msg.split('%s') for i in range(len(arg)): lmsg[1] = lmsg[0] + str(arg[i]) + lmsg[1] lmsg.pop(0) return '%s'.join(lmsg) def genfilename(filename="",tail="new",overwrite=True): if filename=="": return "" if tail.lower()=="new": tail="new" if tail.lower()!="new" and tail.lower()!="bak": tail="bak" name,ext = os.path.splitext(filename) if not overwrite and os.path.exists(name+'-'+tail+ext): i=0 while os.path.exists(name+'-'+tail+str(i)+ext) and (i < 1000): i=i+1 if i>999: return "" return name+"-"+tail+str(i)+ext else: return name+'-'+tail+ext def createsvg(tifname): img = Image.open(tifname) width, height = img.size newwidth = __width newheight = float(height)/width*newwidth file, ext = os.path.splitext(tifname) f = open(file+'.svg', 'w') f.write("""\ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" width="%s" height="%s" id="svg2"> <defs id="defs5" /> <image xlink:href="%s" x="0" y="0" width="%s" height="%s" id="image9" /> </svg>""" % (__width, __height, tifname, newwidth, newheight,)) f.close() return #MAIN PROGRAM def main(tiffile, pages): newfile=genfilename(tiffile,"new",overwrite_new_file) if newfile=="": print "Cannot save backup file." sys.exit(1) #SPLIT FILE name,ext = os.path.splitext(tiffile) dirname = name+'~' if not os.path.exists(dirname): os.mkdir(dirname) if not os.path.exists(os.path.join(dirname, name+'0'+ext)): os.system('%s %s %s' % (convert, tiffile, os.path.join(dirname,name)) +'%d'+ ext) #CREATE svg FILE IN SUBDIR os.chdir(dirname) filelist = [i for i in os.listdir(os.path.curdir) if i[-(len(ext)):]==ext ] for i in filelist: n,e = os.path.splitext(i) c = n+'.svg' if not os.path.exists(c): createsvg(i) #EDIT/ADD ANNOTATE for i in pages: if int(i) < len(filelist): c = str(i) os.system('%s %s.svg' % (inkscape, name+c,)) os.system('%s -e %s.png %s.svg' % (inkscape, name+c, name+c,)) else: print "Invalid page %s." % (i,) #MERGE BACK TO NEW NAME:convert img0.svg ... -adjoin -compress lzw ../img-new.tif allfile = ' '.join( [i for i in os.listdir(os.path.curdir) if i[-(4):]=='.png' ] ) os.system('%s %s -adjoin -compress lzw %s' % (convert, allfile, os.path.join(os.path.pardir,newfile), )) #CHDIR BACK os.chdir(os.path.pardir) print "Annotate %s success, save new file in %s. Viewing with %s" % (tiffile,newfile,evince) os.system('%s %s' % (evince, newfile,)) if __name__=="__main__": #sys.argv=[progname tiffile 0 1 2] progname=os.path.basename(sys.argv[0]) try: tiffile=sys.argv[1] except: usage(progname) sys.exit(1) if not os.path.isfile(tiffile): print errmsg(err_open, tiffile) sys.exit(1) if len(sys.argv)<3: usage(progname) sys.exit(1) pages = sys.argv[2:] main(tiffile, pages)
เรียกใช้เหมือนเดิมคือ
$ d.tifannotate.py IMAGE.tif 0 1
ได้ผลออกมาเป็น IMAGE-new.tif
หมายเหตุ
ถ้าต้องการพิมพ์จาก inkscape ต้องติดตั้งแพกเกจ cupsys-bsd ด้วย
$ sudo aptitude install cupsys-bsd
update 50-09-11
ลองเขียนสคริปต์ลบแสปม
ใช้กับบอร์ด yabbse กับ smf
ของ smf ยังไม่เสร็จ บันทึกเอาไว้เพื่อลองดูผลเท่านั้น
ต้องเปลี่ยนแปลงสคริปต์ตามธีมที่ใช้ด้วย
*** สคริปต์นี้ใช้กับ thailinuxhosting.com/yabbse เท่านั้น เพราะใส่โค๊ดที่แก้ปัญหาบอร์ดไว้ด้วยครับ
#!/usr/bin/env python # -*- coding: utf-8 -*- user = "wd" password = "mypassword" enc_password = "XXXXXXXXXX" # *** GET ENCRYPTED PASSWORD FROM BROWSER COOKIE site = "http://www.thailinuxhosting.com/yabbse" #"http://www.thaitux.info/smf" board = "yabbse" # "smf", "yabbse" charset = "tis620" # "utf8", "tis620" max_loop = 5 # = RECENT LIST OF BOARD root = "/home/wd/spam" backup_file = root+"/thailinuxhosting-bak.txt" spamtext_file = root+"/spamlist.txt" cookie_file = root+"/thailinuxhosting-cookie" import sys import os import time ##### PRE RUN FOR RETRIEVE COOKIE ##### import urllib2 import cookielib login = "/index.php?action=login2;user=%s;passwrd=%s;cookielength=302400" % (user, password,) cj = cookielib.MozillaCookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) sock = opener.open(site+login) cj.save(cookie_file, ignore_discard=True, ignore_expires=True) sock.close() ####################################### spamlist = [] def decoding(txt): if charset == "tis620": return txt.decode("utf8").encode("tis620") elif charset == "utf8": return txt else: print "Error, CHARSET is not defined" sys.exit[0] def search_line(txt, l, occur=1): for i in range(len(l)): if txt in l[i]: if occur > 1: occur=occur-1 else: return i return -1 def get_msgid(url): if board == "smf": # ...#msgXX return url.split("#msg")[-1] elif board == "yabbse": # ...;start=XX return url.split(";start=")[-1] def check_spam(txt): global spamlist for i in spamlist: if i in txt: return True, i return False, '' def save_backup(txt): f = open(backup_file,'a') f.write(txt+'\n\n\n') f.close() return def die_board(): print "board not exist" sys.exit[0] if board == "smf": # recent_str = "กระททู้เมมื่อเร็วๆ นนี้" recent_str = "\xe0\xb8\x81\xe0\xb8\xa3\xe0\xb8\xb0\xe0\xb8\x97\xe0\xb8\xb9\ \xe0\xb9\x89\xe0\xb9\x80\xe0\xb8\xa1\xe0\xb8\xb7\xe0\xb9\x88\xe0\xb8\xad\ \xe0\xb9\x80\xe0\xb8\xa3\xe0\xb9\x87\xe0\xb8\xa7\xe0\xb9\x86 \xe0\xb8\x99\ \xe0\xb8\xb5\xe0\xb9\x89" elif board == "yabbse": # recent_str = "โพสต์เมมื่อเร็วๆนนี้" recent_str = "\xe0\xb9\x82\xe0\xb8\x9e\xe0\xb8\xaa\xe0\xb8\x95\xe0\xb9\x8c\ \xe0\xb9\x80\xe0\xb8\xa1\xe0\xb8\xb7\xe0\xb9\x88\xe0\xb8\xad\xe0\xb9\x80\ \xe0\xb8\xa3\xe0\xb9\x87\xe0\xb8\xa7\xe0\xb9\x86\xe0\xb8\x99\xe0\xb8\xb5\xe0\xb9\x89" else: die_board() #LOAD SPAM DATA if not os.path.exists(spamtext_file): f = open(spamtext_file,'w') f.close() f = open(spamtext_file) for i in f: if i!='' and len(i)>3: spamlist.append(decoding(i.strip())) f.close() recent_str = decoding(recent_str) #INIT COOKIE & OPENER cj = cookielib.MozillaCookieJar() cj.load(cookie_file) opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) loop_count = 0 url_list_pair = [] #SOLVE yabbse ONLY INDEX TO LAST MESSAGE, SO WE CREATE OUR OWN while loop_count < max_loop: #FIRST PAGE sock = opener.open(site) #HACK: SOLVE yabbse'S BOARD COOKIE ERROR cj._cookies['thailinuxhosting.com']['/yabbse']['YaBBSE140usernamev14'].value = user cj._cookies['thailinuxhosting.com']['/yabbse']['YaBBSE140passwordv14'].value = enc_password html = sock.read() sock.close() l = html.split('\n') #SESSIONID if board == "smf": sstr = "sesc=" line = search_line(sstr, l) if line < 0: sys.exit[0] session_id = l[line].split(sstr)[1].split('">')[0] else: session_id = "" #SEARCH FOR RECENT POST sstr = recent_str line = search_line(sstr, l) if line < 0: sys.exit[0] if board == "smf": url = l[line+9+loop_count].split('<a href="')[1].split('">')[0] author = "" date_submitted = "" elif board == "yabbse": url = l[line+4+loop_count].split('<td valign="top"><a href="')[1].split('">')[0] url_list = [ i[0] for i in url_list_pair ] # SOLVE yabbse MESSAGE INDEX if url in url_list: i = url_list.index(url) url_list_pair[i][1] += 1 index_dec = url_list_pair[i][1] else: url_list_pair.append([url,0]) index_dec = 0 # tmp = 'โดย ' tmp = decoding('\xe0\xb9\x82\xe0\xb8\x94\xe0\xb8\xa2 ') author = l[line+4+loop_count].split(tmp)[1].split('</td>')[0] else: die_board() msgid = get_msgid(url) sock = opener.open(url) html = sock.read() sock.close() l = html.split('\n') #PARSE HTML is_spam = False spam_keyword = '' if board == "smf": sstr = "msg_%s" % (msgid,) line = search_line(sstr, l) elif board == "yabbse": sstr = '<hr width="100%" size="1" class="windowbg3">' count = (int(msgid)-index_dec) % 20 + 1 # 20 MESSAGES PER PAGE - yabbse INDEX DECREMENT print 'loop=',loop_count,' /// count=',count line = search_line(sstr, l, count) tmp = decoding("javascript:DoConfirm('") try: delete_url = l[line-3].split(tmp)[1].split("','")[1].split("""');"><img src""")[0] date_submitted = l[line-4].split('</B> ')[1].split(' »')[0] title = l[line-5].split('<B>')[1].split('</b>')[0] process_line = line+1 is_spam, spam_keyword = check_spam(l[process_line]) if is_spam: print 'line=',line,' /// l[line-3]=', l[line-3] print 'delete_url=',delete_url print "is_spam=",is_spam," /// keyword=",spam_keyword," /// line=",l[process_line] except: is_spam = False else: die_board() if is_spam: if board == "smf": pass elif board == "yabbse": save_backup('delete url: '+delete_url+\ '\nspam keyword: '+spam_keyword+\ '\nscan date: '+time.ctime(time.time())+\ '\ntitle: '+title+\ '\nauthor: '+author+\ '\nsubmitted date: '+date_submitted+\ '\n'+l[process_line]) sock = opener.open(delete_url) sock.close() url_list = [ i[0] for i in url_list_pair ] # RESET yabbse MESSAGE INDEX if url in url_list: i = url_list.index(url) url_list_pair.remove(url_list_pair[i]) else: loop_count = loop_count+1
เขียนสคริปต์ไว้เพื่อให้เปลี่ยนรุ่น Drupal ง่าย ๆ เผื่อมีหลายไซต์
สมมุติว่าไดเรกทอรี่ติดตั้งอยู่ที่ /var/www/drupal
URL คือ http://www.example.com
ฐานข้อมูลชื่อ DATABASE_NAME
ผู้ใช้ชื่อ ADMIN และรหัสผ่านคือ ADMIN_PASSWORD
อย่าลืมต้องให้ ADMIN อ่านได้เท่านั้น เพราะจะมีรหัสผ่านอยู่ในสคริปต์
$ cd /var/www/drupal
$ touch sed.py
$ chmod 700 sed.py
ขั้นตอน upgrade
$ cd /var/www/drupal
$ wget http://ftp.drupal.org/files/projects/drupal-X.X.tar.gz
$ tar xfx drupal-X.X
$ cd drupal-X.X
$ cp -xa * ..
$ cd ..
$ rm -rf drupal-X.X
$ ./sed.py
### DO UPDATE AT www.example.com/update.php
### SET BACK
$ vi update.php
.. $access_check = TRUE; #$access_check = FALSE; ...
เนื้อไฟล์ sed.py มีดังนี้
$ vi sed.py
#!/usr/bin/env python # cd /var/www/drupal # cp -xa ../drupal-5.3/* . # ./sed.py # ---> http://www.example.com/update.php # vi update.php #SET $access_check = TRUE; import os import sys db_url = "$db_url = 'mysql://ADMIN:ADMIN_PASSWORD@localhost/DATABASE_NAME';" base_url = "$base_url = 'http://www.example.com'; // NO trailing slash!" basedir = os.path.abspath(os.curdir); #------------------------------------------------------------------------- def sed_file(file,dict_txt): filename = os.path.basename(file) dirname = os.path.abspath(file) bakfile = file+'.bakbak' os.rename(file,bakfile) f_old = open(bakfile) f_new = open(file,'w') txt_old = [i for i in dict_txt] txt_new = [i for i in dict_txt.values()] print txt_old for i in f_old: line = i.strip() if line in txt_old: n = txt_old.index(line) print 'old=',line print 'new=',txt_new[n] f_new.write(txt_new[n]+'\n') else: f_new.write(i) f_new.close() f_old.close() #FIRST FILE sites/default/setting.php file = os.path.join(basedir,'sites/default/settings.php') tmp_db = "$db_url = 'mysql://username:password@localhost/databasename';" tmp_base = "# $base_url = 'http://www.example.com'; // NO trailing slash!" dict_txt = {\ tmp_db: '#'+tmp_db+'\n'+db_url, \ tmp_base: tmp_base+'\n'+base_url \ } sed_file(file,dict_txt) #SECOND update.php file = os.path.join(basedir,'update.php') dict_txt = {\ "$access_check = TRUE;":\ "#$access_check = TRUE;\n$access_check = FALSE;"\ } sed_file(file,dict_txt)
มีงานที่จะต้องทำไฟล์เป็น pdf เพื่อส่งโรงพิมพ์
งานนี้ทำจาก Word ในวินโดวส์ พิมพ์ลงไฟล์โดยใช้ไดรฟเวอร์เครื่องพิมพ์ Image Setter แล้วจึงแปลงเป็น pdf ด้วยลินุกซ์ ด้วยคำสั่ง ps2pdf12
โดยเลือกใช้รุ่น 1.2 เพราะต้องการความเข้ากันได้
แต่เนื่องจากขนาดกระดาษของงานเป็นขนาด A5 จึงต้องเลือกพิมพ์เป็น A4 แทน
ปัญหาคือตัวโปรแกรม ps2pdf ซึ่งไปเรียกใช้ ghostscript (gs
) อีกทีนึง ไม่สามารถ crop ขนาดจาก A4 เป็น A5 ได้ (จริง ๆ แล้วอาจทำได้ แต่ค้นคำสั่งไม่พบ และโรงพิมพ์ต้องการงานขนาด A5 แบบมีขอบขาวเว้นไว้ด้านละ 3 มม. ซึ่งคงจะใช้คำสั่ง gs ยาก)
ค้นไปค้นมา พบมอดูลไพธอนที่จะทำงานนี้ได้ คือมอดูล pyPdf
เริ่มเลยแล้วกัน
ติดตั้งมอดูล pyPdf
$ sudo aptitude install python-pypdf
เขียนสคริปต์ ตั้งชื่อว่า croppdf.py
$ vi croppdf.py
#!/usr/bin/env python #prerequisites: aptitude install python-pypdf import sys import pyPdf def usage(progname): print """ usage: %s "lowerLeft-x lowerLeft-y upperRight-x upperRight-y" infile.pdf outfile.pdf """ % progname sys.exit(1) try: argl = [ int(i) for i in sys.argv[1].split(" ") if i ] infile = sys.argv[2] outfile = sys.argv[3] inpdf = pyPdf.PdfFileReader(file(infile,"rb")) outpdf = pyPdf.PdfFileWriter() for i in range(inpdf.numPages): page = inpdf.getPage(i) page.mediaBox.upperRight = tuple(argl[2:]) page.mediaBox.lowerLeft = tuple(argl[:2]) outpdf.addPage(page) outstream = file(outfile, "wb") outpdf.write(outstream) outstream.close() except: usage(sys.argv[0])
$ chmod 755 croppdf.py
(พอดีเป็นงานด่วน เลยเขียนแบบด่วนจริง ๆ)
ขั้นตอนการแปลงคือ
1. แปลงจาก ps เป็น pdf ด้วย ps2pdf12
$ ps2pdf12 INFILE.ps TEMPFILE.pdf
2. crop เป็นขนาด A5 แบบมีขอบขาวข้างละ 3 มม. (ประมาณ 9 px)
$ ./croppdf.py "75 238 523 850" TEMPFILE.pdf OUTFILE.pdf
ตัวเลข 4 ตัวคือค่าเป็นปอยต์ (pt) ของ x-มุมล่างซ้าย y-มุมล่างซ้าย และ x-มุมบนขวา y-มุมบนขวา ตามลำดับ
แปลงจาก มม. โดยคูณด้วย 2.8378
หรือแปลงจากนิ้ว โดยคูณด้วย 72
ดูขนาดเอกสาร เป็นปอยต์ ด้วยคำสั่ง pdfinfo FILENAME.pdf
สมมุติว่า lowerLeft-x เป็น x0, lowerLeft-y เป็น y0, upperRight-x เป็น x1, upperRight-y เป็น y1 ตามลำดับ
$ ./croppdf.py "-14 -14 609 856" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "-14 -14 434 609" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "87 247 508 842" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "8 -25 604 817" TEMPFILE.pdf OUTFILE.pdf
$ ./croppdf.py "0 -25 595 817" TEMPFILE.pdf OUTFILE.pdf
เสร็จแล้วครับ
ที่มาจริง ๆ แล้ว ต้องการค้นหาและแทนที่เอกสารในไฟล์ .doc
จึงสั่งด้วยคำสั่งว่า
$ sed -i 's/OLD/NEW/g' *.doc
ไม่ได้ผล นึกว่ารหัสเอกสารผิด เลยเป็นที่มาของสคริปต์อันนี้ คือค้นหาและแทนที่เอกสารทั้ง utf-8 และ tis-620 โดยไม่สนใจว่าเป็นเอกสารชนิดใด
(สุดท้ายปรากฎว่าไม่ได้ผล เพราะ OpenOffice ไม่ได้เก็บไว้ในรูป Text file ปกติ
ถึงจะโง่ไปแล้ว แต่บันทึกไว้หน่อยดีกว่า เผื่อได้ใช้ทีหลัง)
$ vi sed_i.py
#!/usr/bin/env python # -*- coding: utf8 -*- """Replace thai string in file""" import sys, os def usage(prog): print 'Usage: %s "old" "new" filename' % (prog) def cannotopenfile(filename): print "Cannot open file %s" % (filename) def getbakfilename(filename="", ext="bak"): if filename == "": return "" if os.path.exists(filename + "." + ext): i = 0 while os.path.exists(filename + "." + ext + str(i)) and (i < 1000): i += 1 if i > 999: return "" return filename + "." + ext + str(i) else: return filename + "." + ext # def main(old,new,filename): if not os.path.exists(filename): cannotopenfile(filename) sys.exit(0) ismod = False newdoc = [] f = open(filename) for line in f: newline1 = line.replace(old, new) newline2 = line.replace(old.decode('utf8').encode('tis620'), new.decode('utf8').encode('tis620')) if not ismod and (newline1 != line or newline2 != line): ismod = True if newline1 != line: newdoc.append(newline1) else: newdoc.append(newline2) f.close() if ismod: bakfile = getbakfilename(filename) os.rename(filename, bakfile) f = open(filename, "w") f.write('\n'.join(newdoc)) f.close() print "%s changed, save backup in %s." % (filename, bakfile) else: print "Pattern not found, no changed." if __name__ == "__main__": if len(sys.argv) < 4: usage(sys.argv[0]) sys.exit[0] else: main(*sys.argv[1:4])
ใช้งานด้วยคำสั่ง
$ ./sed_i.py "OLD" "NEW" filename
ถ้าเจอ จะแทนที่ และบันทึกไว้ในชื่อเดิม แต่สำรองไฟล์ไว้ด้วย ในชื่อ filename.bak
จากครั้งก่อนเรื่อง adodb: กับดักข้อมูล ที่ได้นำเอาโมดูล DBF Reader จากเว็บของคุณ Yusdi Santoso มาทดลองใช้งาน
เมื่อได้นำมาใช้จริง สำหรับไฟล์ dbf ของ Visual Foxpro สามารถใช้ได้ผลดีพอควร แต่สำหรับ dbf เก่า ๆ ที่เป็นของ Foxpro for Dos นั้น ปรากฎว่าไม่สามารถอ่านได้
อีกเรื่องนึงคือเรื่องชื่อไฟล์ในลินุกซ์ เมื่อใช้งานผ่าน samba (เมานต์แบบ cifs) แล้ว เข้าใจว่าไม่สามารถควบคุมได้ บางครั้งปรากฎเป็นตัวใหญ่หมด บางครั้งออกมาเป็นชื่อไฟล์ตัวใหญ่แต่นามสกุลตัวเล็ก หรือกลับกัน เราจึงมาปรับปรุงในส่วนนี้ด้วย
ได้ออกมาเป็นซอร์สไฟล์ดังนี้ครับ
$ vi dbf.py
#!/usr/bin/env python """ This is a DBF reader which reads Visual Fox Pro DBF format with Memo field. Usage: rec = readDbf('test.dbf') for line in rec: print line['name'] @author Yusdi Santoso @date 13/07/2007 """ import struct import os, os.path import sys import csv import tempfile import ConfigParser class Dbase: def __init__(self): self.fdb = None self.fmemo = None self.db_data = None self.memo_data = None self.fields = None self.num_records = 0 self.header = None self.memo_file = '' self.memo_header = None self.memo_block_size = 0 self.memo_header_len = 0 def _drop_after_NULL(self, txt): for i in range(0, len(txt)): if ord(struct.unpack('c', txt[i])[0])==0: return txt[:i] return txt def _reverse_endian(self, num): if not len(num): return 0 #OLD CODE #val = struct.unpack('<L', num) #val = struct.pack('>L', val[0]) #val = struct.unpack('>L', val) #return val[0] #wd's IMP: IMPROVE READING OLD FOXPRO MEMO try: #VFP DBF: BINARY 4 BYTES MEMO FIELD REF val = struct.unpack('<L', num) val = struct.pack('>L', val[0]) val = struct.unpack('>L', val) return val[0] except: #OLD FOXPRO DBF: STRING 10 BYTES MEMO FIELD REF val = long('0'+num.strip()) return val def _assign_ids(self, lst, ids): result = {} idx = 0 for item in lst: id = ids[idx] result[id] = item idx += 1 return result def open(self, db_name): filesize = os.path.getsize(db_name) if filesize <= 68: raise IOError, 'The file is not large enough to be a dbf file' self.fdb = open(db_name, 'rb') self.memo_file = '' #OLD CODE #if os.path.isfile(db_name[0:-1] + 't'): # self.memo_file = db_name[0:-1] + 't' #elif os.path.isfile(db_name[0:-3] + 'fpt'): # self.memo_file = db_name[0:-3] + 'fpt' #wd's IMP: SOLVE MISMATCHED UPPER/LOWER FILENAME AND EXTENSION basename = os.path.basename(db_name) dirname = os.path.dirname(os.path.join('.',db_name)) allfile=[i for i in os.listdir(dirname) if i[:-4].lower()==basename[:-4].lower()] for i in allfile: if i[:-1].lower()==basename[:-1].lower() and i[-1].lower()=='t': self.memo_file = os.path.join(dirname, i) elif i[:-3].lower()==basename[:-3].lower() and i[-3:].lower()=='fpt': self.memo_file = os.path.join(dirname, i) if self.memo_file: #Read memo file self.fmemo = open(self.memo_file, 'rb') self.memo_data = self.fmemo.read() self.memo_header = self._assign_ids(struct.unpack('>6x1H', self.memo_data[:8]), ['Block size']) block_size = self.memo_header['Block size'] if not block_size: block_size = 512 self.memo_block_size = block_size self.memo_header_len = block_size memo_size = os.path.getsize(self.memo_file) #Start reading data file data = self.fdb.read(32) self.header = self._assign_ids(struct.unpack('<B 3B L 2H 20x', data), ['id', 'Year', 'Month', 'Day', '# of Records', 'Header Size', 'Record Size']) self.header['id'] = hex(self.header['id']) self.num_records = self.header['# of Records'] data = self.fdb.read(self.header['Header Size']-34) self.fields = {} x = 0 header_pattern = '<11s c 4x B B 14x' ids = ['Field Name', 'Field Type', 'Field Length', 'Field Precision'] pattern_len = 32 for offset in range(0, len(data), 32): if ord(data[offset])==0x0d: break x += 1 data_subset = data[offset: offset+pattern_len] if len(data_subset) < pattern_len: data_subset += ' '*(pattern_len-len(data_subset)) self.fields[x] = self._assign_ids(struct.unpack(header_pattern, data_subset), ids) self.fields[x]['Field Name'] = self._drop_after_NULL(self.fields[x]['Field Name']) self.fdb.read(3) if self.header['# of Records']: data_size = (self.header['# of Records'] * self.header['Record Size']) - 1 self.db_data = self.fdb.read(data_size) else: self.db_data = '' self.row_format = '<' self.row_ids = [] self.row_len = 0 for key in self.fields: field = self.fields[key] self.row_format += '%ds ' % (field['Field Length']) self.row_ids.append(field['Field Name']) self.row_len += field['Field Length'] def close(self): if self.fdb: self.fdb.close() if self.fmemo: self.fmemo.close() def get_numrecords(self): return self.num_records def get_record_with_names(self, rec_no): """ This function accept record number from 0 to N-1 """ if rec_no < 0 or rec_no > self.num_records: raise Exception, 'Unable to extract data outside the range' offset = self.header['Record Size'] * rec_no data = self.db_data[offset:offset+self.row_len] record = self._assign_ids(struct.unpack(self.row_format, data), self.row_ids) if self.memo_file: for key in self.fields: field = self.fields[key] f_type = field['Field Type'] f_name = field['Field Name'] c_data = record[f_name] if f_type=='M' or f_type=='G' or f_type=='B' or f_type=='P': c_data = self._reverse_endian(c_data) if c_data: record[f_name] = self.read_memo(c_data-1).strip() else: record[f_name] = c_data.strip() return record def read_memo_record(self, num, in_length): """ Read the record of given number. The second parameter is the length of the record to read. It can be undefined, meaning read the whole record, and it can be negative, meaning at most the length """ if in_length < 0: in_length = -self.memo_block_size offset = self.memo_header_len + num * self.memo_block_size self.fmemo.seek(offset) if in_length<0: in_length = -in_length if in_length==0: return '' return self.fmemo.read(in_length) def read_memo(self, num): result = '' buffer = self.read_memo_record(num, -1) if len(buffer)<=0: return '' length = struct.unpack('>L', buffer[4:4+4])[0] + 8 block_size = self.memo_block_size if length < block_size: return buffer[8:length] rest_length = length - block_size rest_data = self.read_memo_record(num+1, rest_length) if len(rest_data)<=0: return '' return buffer[8:] + rest_data def readDbf(filename): """ Read the DBF file specified by the filename and return the records as a list of dictionary. @param filename File name of the DBF @return List of rows """ db = Dbase() db.open(filename) num = db.get_numrecords() rec = [] for i in range(0, num): record = db.get_record_with_names(i) rec.append(record) db.close() return rec if __name__=='__main__': rec = readDbf('dbf/sptable.dbf') for line in rec: print '%s %s' % (line['GENUS'].strip(), line['SPECIES'].strip())
ผลการปรับปรุงปรากฎว่าใช้งานได้ดีพอควรครับ