2013年8月14日 星期三

[python] 使用 python 呼叫 console 執行檔,一個通用的 recipe

用 python 呼叫 console 執行檔有什麼稀奇呢?不就 import os, os.system 就好了嗎?
對,就這麼簡單。

import os
os.system('net')

但是要在程式中知道執行的成功或失敗,甚至得到執行檔的輸出,又該如何呢?

最近挖了一個 guiminer 的 python 專案的程式碼來看,學到一些東西。
該專案使用 wxpython 當做 gui。正好是 gui 程式與執行檔的整合範例。
這範例有以下特點:

1. 使用 subprocess 是目前 python 建議方式

subprocess.Popen(cmd, cwd=cwd,
  stdout=subprocess.PIPE,
  stderr=subprocess.STDOUT,
  universal_newlines=True,
  creationflags=flags,
  shell=(sys.platform != 'win32'))

2. 通常希望不要新開視窗,上述的 flags 可以控制視窗形式。其值是如下方式取得

try: import win32process

except ImportError: flags = 0

else: flags = win32process.CREATE_NO_WINDOW


3. 為了要監控程式的輸出,把 stdout 及 stderr 導至 PIPE 及 STDOUT (如 1.) 如此程式便需要不斷地讀取 stdout。


4. 使用 regular expression 針對輸出獲得自己想要的結果。


5. 也因為執行外部程式時需不斷地讀取 stdout,必定要另開 thread 來處理。使用 threading.Event() 來控制 run() 裡面的 while 迴圈何時離開。


觀察到以上 5 點,接下來便是改寫他的程式成為通用的 recipe。因為不想跟 gui 套件綁在一起,所以就把 gui 相關的拿掉,整理之後成為如下的 class。



import threading
import re
import subprocess
import sys

class CmdThreadListener(threading.Thread):
    '''
    Assign cmd and cwd
    '''

    LINES = []
    def __init__(self, cmd, cwd, callback=None, unhandle_callback=None):
        threading.Thread.__init__(self)
        self.shutdown_event = threading.Event()
        self.cmd = cmd
        self.cwd = cwd
        self.callback = callback
        self.unhandle_callback = unhandle_callback

    def run(self):
        try:
            import win32process
        except ImportError:
            flags = 0
        else:
            flags = win32process.CREATE_NO_WINDOW

        sub_proc = subprocess.Popen(self.cmd, cwd=self.cwd,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.STDOUT,
                                      universal_newlines=True,
                                      creationflags=flags,
                                      shell=(sys.platform != 'win32'))

        self.proc = sub_proc
        while not self.shutdown_event.is_set():
            line = self.proc.stdout.readline().strip()
            if not line:
                continue
            if len(self.LINES) == 0:
                unhandleline = line
                if self.unhandle_callback is not None:
                    self.unhandle_callback(unhandleline)

            for s, event_func in self.LINES:
                match = re.search(s, line, flags=re.I)
                if match is not None:
                    event = event_func(match)
                    if event is not None:
                        if self.callback is not None:
                            self.callback(event)
                        break
                else:
                    unhandleline = line
                    if self.unhandle_callback is not None:
                        self.unhandle_callback(unhandleline)

    def stop(self):
        if self.proc is not None and self.proc.returncode is None:
            try:
                self.proc.terminate()
            except OSError:
                pass
        self.shutdown_event.set()

 


以下是它的使用方法:



    import time
    import os

    module_name = sys.executable if hasattr(sys, 'frozen') else __file__
    abs_path = os.path.abspath(module_name)
    cmd = 'net'
    cwd = os.path.dirname(abs_path)

    print cmd

    cmd = cmd.encode('cp950') # quick modify for windows console

    def p(x):
        print(x)

    listener_instance = CmdThreadListener(cmd, cwd, unhandle_callback=p)
    listener_instance.daemon = True
    listener_instance.start()

    time.sleep(5)

    listener_instance.stop()
    listener_instance.join()


它的輸出結果如下:



net
這個命令的語法是:
NET
[ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |
HELPMSG | LOCALGROUP | PAUSE | SESSION | SHARE | START |
STATISTICS | STOP | TIME | USE | USER | VIEW ]


在 windows 下,Popen 的 shell=False 時,cmd 不能夠是 shell 裡面的指令。所以 dir 之類的就不行用。若是想執行 shell 裡面的指令,Popen 的 shell 參數要設成 True。


註:若是照我的測試的程式碼放到 IDLE 直接執行,可能會出現 NameError: global name '__file__' is not defined。那是 IDLE 忘了 assign __file__ 的 bug。請到 cmd 底下執行 python 程式。

2013年8月12日 星期一

[sozi] Inkscape 的簡報外掛:使用心得

Sozi 的展示概念,可以想成有一張大海報掛在牆上,而我們是手拿攝影機的導演。導演選擇順序與角度,帶領觀眾跟著導演的鏡頭,觀賞海報的內容。

 

0opkSDo 
來源:http://prometheusis.blogspot.tw/2013/08/blog-post.html

 

因此,製作 sozi 簡報的時候,內容被放置的位置很重要,一但放置之後就不能動了。因為移動的是鏡頭,按順序移動時相近的主題放在附近,不會有轉場時看見雜物,也不會有轉場過久或過快的不適感。

如果,內容是用向量物件製作,那麼製作時不需在乎解析度。在鏡頭的 zoom in 或 zoom out 下,向量物件會保持很好的解析度。除非物件又多又移動快。尤其字體這物件是最吃運算,大家都建議把字體轉成 path 來提升效能。同時,當字體轉成路徑也比較平滑一點。

 

使用字型

 

使用路徑

 

在製作手順上,只要用長方形物件框住你要的東西,那個長方形,就是鏡頭的視角,在 sozi 裡叫做 frame。在製作時,最好讓每個 frame 長寬比都一致,觀眾會比較舒服,除非有特殊的目的。例如製造窄視角的狹隘感。直接使用你製作的物件當做 frame 也是可以,但是會有兩點不方便,一個是長寬比的掌握,另一個缺點是 sozi 預設 frame 是被隱藏的,這意味著每次新增 frame 都要記得把 hide 的勾勾拿掉,多了一個動作我不愛。另外,可以把所有的 frame 畫在一個新增的 layer 上,將會變得很好管理。

image

自動轉場可以做出動態影片的感覺。因為物件不能動,而是鏡頭動,在場景設計上要針對這特性做考量。一般 power point 物件飛來飛去的特效就難以做到,我想到的方法是 frame by frame 一張張製作,像是製作停格動畫,這太花工夫了。所以擺好場景最重要!

以下是個非常漂亮的展示

http://www.youtube.com/watch?v=pGfexUCiCEk

2013年8月11日 星期日

[python]處理給 shell 的參數

對我個人來說也困擾很久的問題。
我自己處理在傳參數給 os.system 的時候,若是有空隔或單引號的時候,
會特別用別的字取代,但如此也就把參數值也改掉了。
只有我自己用的程式還好,但是別人就是要用空隔跟單引號怎辦呢?

今天很巧的在 pycontw 2013 的影片裡,看到有人用以下解法。
馬上抄下來,以後慢慢用。
真是感恩。

def escapeShellArg(string):
  return "'"+string.replace("'", "'\\''")+"'"

args = ' '.join(map(escapeShellArg, argv))
os.system('ffmpeg -i '+args)

來源:

http://www.youtube.com/watch?v=RszFch-fjJc 15:50

pycontw 2013 郭學聰