python: โค๊ดที่ทดลองเขียน

บันทึกโค๊ดที่ทดลองเขียน

Topic: 

หา factor

หาค่า 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

Topic: 

แปลงไฟล์ จาก tis620 เป็น utf8

วันนี้มีงานต้องแปลงไฟล์ จาก 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 เป็นไฟล์เก่า

(ใครเขียนไพธอนเก่ง ๆ ฝากแนะนำด้วยครับ อยากเขียนให้กระชับ และดูง่าย)

แปลงไฟล์ จาก utf8 กลับเป็น tis620

มีงานต้องแปลงไฟล์กลับ เลยเขียนโค๊ดอีกทีนึง
( งานที่ทำคือ สันติรำลึก เป็นการแปลงไฟล์กลับจาก 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)

โค๊ดยังไม่เรียบร้อยดี แต่ขอแปะโค๊ดไว้ก่อน

แปลงรหัส Id3 จาก cp874 เป็น utf8

จาก 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()

python : แตกไฟล์ JPG จากไฟล์ภาพ Canon

มีโจทย์อยู่คือ
เวลาไปเที่ยวหรือมีงานที่ต้องถ่ายภาพเป็นจำนวนมาก เกินการ์ดหน่วยความจำที่มีอยู่
เวลาการ์ดเต็ม ก็ต้องถ่ายออกมาเก็บไว้ในโน๊ตบุ๊ก

ปัญหาคือเวลาจะดูภาพจากโน๊ตบุ๊ก ซึ่งสเปคเครื่องต่ำมาก โหลดไฟล์ภาพใหญ่ ๆ ไม่ไหว มันจะดูได้ช้ามาก ๆ ดูภาพ 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()
Topic: 

debian: บันทึก Raw Image 2

ได้ลองใช้ 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

debian: บันทึกการทำงานกับไฟล์ภาพ cr2

เพิ่งได้เริ่มหัดถ่ายภาพแบบ Raw ของกล้อง Canon มีนามสกุลเป็น .CR2

ถ้าต้องการแบบบรรจง
$ sudo aptitude install gimp gimp-ufraw
เวลาเราใช้ Gimp เปิดไฟล์สกุล CR2 เขาจะขึ้นไดอะล๊อกมาให้ปรับค่าต่าง ๆ
ส่วนใหญ่ก็จะปรับแค่ exposure-ความสว่าง กับ temperature-อุณหภูมิสี
ของกล้อง canon 5d ลองดูแล้ว ค่าอุณหภูมิสีที่ออกมาดูดี อยู่ระหว่าง CameraWB กับ Daylight โดยอยู่ค่อนไปทาง CameraWB
ค่าสีเขียวก็เหมือนกัน ปรับให้อยู่ระหว่างนี้
ถ้าต้องการให้ออกมาในโทนเย็น ก็ปรับให้ค่อนไปทาง CameraWB
แต่ถ้าต้องการให้ออกมาในโทนอุ่น ก็ปรับให้ค่อนไปทาง Daylight
(AutoWB ออกมาไม่ดีเลย)
ถ้าต้องการแบบบรรทัดคำสั่ง
$ 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

python: ค้นหา Python Document

ทำตัวอย่างการค้นเนื้อหาไพธอน (20%)

เสร็จประมาณ 10% แล้ว
อยากให้ทำงานได้แบบ php help จัง

update

50-03-02
  • ทำต้นแบบตัวค้นหาแบบแยกคำเสร็จ ทดลองค้นคำได้แล้ว แต่ผลการค้นยังแย่อยู่
  • ทดลองเขียนการแสดงผลใหม่ แบบแยกเทมเพลต โดยใช้แท็ก <?py ... ?>
50-03-12
ทำ comment เสร็จแบบหยาบ ๆ
50-03-14 ได้สัก 20%
เริ่มโพสต์ comment แทรกตามเนื้อหา
Topic: 

python: ปรับปรุง multiple pages tif annotation

ต่อเนื่องจากครั้งก่อนเรื่อง 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

  • แก้บั๊กเรื่องตรวจสอบชื่อไฟล์
  • แก้บั๊ก convert แปลงฟอนต์ไทยไม่ตรง ด้วยการส่งออกจาก inkscape เป็น png ก่อน

python: ลองเขียนสคริปต์ลบสแปม

ลองเขียนสคริปต์ลบแสปม
ใช้กับบอร์ด 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(' &#187;')[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

python: เขียนสคริปต์ไว้ update Drupal

เขียนสคริปต์ไว้เพื่อให้เปลี่ยนรุ่น 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)

python: crop ไฟล์ pdf

มีงานที่จะต้องทำไฟล์เป็น 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 ตามลำดับ

  • ทำขอบขาวรอบ pdf ขนาด A4 (595 x 842 ปอยต์) โดยเว้นระยะ 5 มม.(ประมาณ 14 ปอยต์)
    x0=-14, y0=-14, x1=595+14=609, y1=842+14=856
    $ ./croppdf.py "-14 -14 609 856" TEMPFILE.pdf OUTFILE.pdf
  • ทำขอบขาวรอบ pdf ขนาด A5 (420 x 595 ปอยต์) โดยเว้นระยะ 5 มม.(ประมาณ 14 ปอยต์)
    x0=-14, y0=-14, x1=420+14=434, y1=595+14=609
    $ ./croppdf.py "-14 -14 434 609" TEMPFILE.pdf OUTFILE.pdf
  • crop จาก A4 เป็น A5 โดยเอกสารขนาด A5 อยู่ตรงกลางและชิดขอบบน
    x0=(595-421)/2=87, y0=842-595=247, x1=87+421=508, y1=842
    $ ./croppdf.py "87 247 508 842" TEMPFILE.pdf OUTFILE.pdf
  • แปลง Letter (612x792) เป็น A4 (595x842) แบบจัดกึ่งกลาง
    x0=(612-595)/2=8, y0=(792-842)/2=-25, x1=612-8=604, y1=792+25=817
    $ ./croppdf.py "8 -25 604 817" TEMPFILE.pdf OUTFILE.pdf
  • แปลง Letter (612x792) เป็น A4 (595x842) แบบจัดชิดซ้าย
    x0=0, y0=(792-842)/2=-25, x1=595, y1=792+25=817
    $ ./croppdf.py "0 -25 595 817" TEMPFILE.pdf OUTFILE.pdf

เสร็จแล้วครับ

Topic: 

python: ค้นหาและแทนที่แบบง่าย

ที่มาจริง ๆ แล้ว ต้องการค้นหาและแทนที่เอกสารในไฟล์ .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

Topic: 

python: ปรับปรุงโมดูลอ่านไฟล์ dbf

จากครั้งก่อนเรื่อง 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())

ผลการปรับปรุงปรากฎว่าใช้งานได้ดีพอควรครับ