Python」タグアーカイブ

festival command

Linux で Mac OS の say コマンドのようなことをやりたい。
筆者は Mac を持っていないのだが検索すると面白そう。

厳密には GTK+ アプリ等で選択した文字列なんかを喋るようにしたい。

この英語スペルはどう発音するか等の簡易チェックに使いたい。
頻繁に使うわけではないので組み込みではなく単独アプリのほうがいい。
chrome 等から DnD で文字列を受け取ると読み上げるみたいな感じで。

GNOME3 には orca というアプリがある。
フォーカスのあるウインドウ文字列を読み上げる。
だけなのか。
つまり全部読み上げるので超鬱陶しい、求めているのはコレじゃない!
PyGI がエラーを吐いているし、日本語部はチャイニーズうんたらって何よ。

orca

というか、検索すると皆日本語を読ませようと四苦八苦しているみたい。
業務用途なら当然そうなるけど個人利用なら必要性をまったく感じないんだが。
英語の発音チェックに使いたいと考える筆者みたいな人はあまりいないのね。

ということで。

探してみると festival というコマンドがあるようだ。
Festival

私の Fedora 18 には最初から入っていた。

# stdout
echo "Read and Write" | festival --tts
# Read file
festival --tts read.txt

こんな感じで使える、これは使えそうだ。
英語で一定速しか読み上げできないけど私的には充分だ。

文字列のドロップで読み上げる PyGI ウインドウを作ってみる。
ついでにファイルドロップでも中身を読み上げできるように。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, Gdk, GLib
import subprocess

class SayWin(Gtk.Window):
    def __init__(self):
        """
            Speech at the Drop string
        """
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("Say")
        # DnD
        dnd_list = [Gtk.TargetEntry.new("text/plane", 0, 0),
                    Gtk.TargetEntry.new("text/uri-list", 0, 1) ]
        self.drag_dest_set(
                Gtk.DestDefaults.MOTION |
                Gtk.DestDefaults.HIGHLIGHT |
                Gtk.DestDefaults.DROP,
                dnd_list,
                Gdk.DragAction.COPY )
        self.drag_dest_add_uri_targets()
        self.drag_dest_add_text_targets()
        self.connect("drag-data-received", self.on_drag_data_received)
        # GtkLabel
        self.label = Gtk.Label("Please drop your Selection Text or File")
        self.add(self.label)
        self.show_all()

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        if info == 0:
            # text/plane
            p1 = subprocess.Popen(['echo', data.get_data()], stdout=subprocess.PIPE)
            p2 = subprocess.Popen(['festival', '--tts'], stdin=p1.stdout)
            p1.stdout.close()
            p2.communicate()[0]
        elif info == 1:
            # text/uri-list
            uris = data.get_uris()
            filename = GLib.filename_from_uri(uris[0], None)
            subprocess.call(['festival', '--tts', filename])

SayWin()
Gtk.main()

say_app

GLib.spawn_command_line_async でパイプを使う方法が解らなかった。
subprocess モジュールでマニュアルどおりにしたけど何か冗長な感じ。
もう少しスマートにできそうだけど。

イマイチな感じもするけど目的どおりにはなった。
orca のほうが発音が解りやすいのでソッチが利用できればなと思うけど。

Empty the trash from PyGI

我が公開 Linux アプリでまだ PyGI 化していないものがあった。
trash だ、しょーもないアプリだと思って作ったが意外に本人は結構使う。

PyGI 化だけだと二分で終わるし何かないかな。
そうだ、ゴミ箱を空にするオプションを付けちゃえ!
実際にそんな機能を使うかどうかは別の話。

Nautilus のソースを参考にすれば多分手段は解るだろう。
ということでソレっぽい名前で grep しまくる。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""
    Empty the trash.
"""

from gi.repository import Gio

# Get Trash URI
trash_uri = Gio.file_new_for_uri("trash:")
# Get Child
enum = trash_uri.enumerate_children(
        "{0},{1}".format(
            Gio.FILE_ATTRIBUTE_STANDARD_NAME,
            Gio.FILE_ATTRIBUTE_STANDARD_TYPE ),
        Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
        None )
# Delete
for info in enum:
    f = trash_uri.get_child(info.get_name())
    f.delete(None)

こんな感じでやっていた。
当然元の C 言語はもっと複雑だったけど Python は楽だ。
普通に g_file_delete しているだけなんだな。

Nautilus から Ctrl+L でロケーションバーを表示させる。
んで trash: と打ち込んで Enter するとゴミ箱が開くんだね。
Explorer の sendto みたいなことができたのか、知らなかった。

んでファイルリスト作成と同様に enum を得る。
g_file_get_child でゴミ箱内の GFile を個別で得る。
既に GFile なのでそのまま g_file_delete できる。

パス名に変換して rm コマンドに渡すとか最初は考えていたけど不要だった。
Nautilus のゴミ箱アイコンは監視しているようでしっかり連動する。
何かメッセージを送らないといけないかと思ったけどコレも不要だった。

より手段は解った。
あとはコマンドラインオプションをどう実装するか。

15.4. optparse ? より強力なコマンドラインオプション解析器 ? Python 2.6ja2 documentation

こんな便利なモンが標準モジュールにあったのか!
使わないと損だ、ということで適用。

ただバージョン情報を -V, –version にしようと思ったけど手段が無い。
書くまでもなく Python と同じにしたかっただけですけど。
自前で –version を定義するとコンフリクトしていると怒られる。
素直にあきらめよう。

ということで trash-1.0.0 公開。
全アプリの PyGI 化完了、去年からの目標だったけど長かった。

Xubuntu 13.04 and GtkApplication

Ubuntu 13.04 は少し軽くなったとはいえ相変わらず 3D しか使えない。
テスト用途にしか使わない仮想とはいえ重すぎる。
しかも操作性最悪、何故カスタマイズしてまで Unity を使う人がいるのか?

なので今回も LXDE…
では芸が無いので Xubuntu にしてみた、実は初めてです。

sudo apt-get install xubuntu-desktop

ってなんだこれ、ちっとも軽くないというより重すぎ!

XFCE は GTK2 ベースだけど 3D 表示しているんだな。
[ウインドウマネージャ(詳細)]で合成処理の有無を切り替えできるようだ。
ということは有効状態なら下記で背景が透けるはず。
Transparent GtkWindow | PaePoi

xubuntu_visual

off にすると Lubuntu ほどではない気がするけど充分軽くなった。
コレなら継続利用できそうだ、ということで気になる部分をチェック。

デフォルトプレイヤーの parole で動画が再生できないぞ!
しかしコイツもやはり GStreamer なのか。

xubuntu_video

gnome-session-properties に何も無い。
あれ?と思い 12.04 LTS のを見ても何もない、独自処理だったのか。
キーリングとかはどうしているんだろう、便利なのに。

ub_session

つまりディレクトリ名監視なんかしていない。
普通に不要なデフォルトディレクトリを削除したり F2 キーで変名できる。
何故かデスクトップだけは変名できなかったけど。

GTK+ のバージョンは 3.6.4 って今の Fedora と同じかい。
でも pygobject が変わっているのだからもしかして。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, Gio
import sys

class Win(Gtk.Window):
    """
        Test "HANDLES_OPEN"
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        swin = Gtk.ScrolledWindow()
        self.view = Gtk.TextView()
        swin.add(self.view)
        self.add(swin)
        self.resize(320, 240)
        self.show_all()

    def add_uri(self, uris):
        buf = self.view.get_buffer()
        buf.set_text(uris)

class AppClass(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="org.gnome.test_id",
                flags=Gio.ApplicationFlags.HANDLES_OPEN )
        self.connect("startup", self.on_startup)
        self.connect("activate", self.on_activate)
        self.connect("open", self.on_open)

    def on_startup(self, data=None):
        self.window = Win()
        self.add_window(self.window)

    def on_activate(self, data=None):
        self.window.present()

    def on_open(self, application, files, n_file, hint, data=None):
        """
            Ubuntu 13.04 @ SUCSESS!
            Fedora 18    @ FAILED...
        """
        s = ""
        for f in files:
            s += "{0}\n".format(f.get_uri())
        if s == "":
            s = "FAILED..."
        self.window.add_uri(s)
        self.window.present()

if __name__ == "__main__":
    app = AppClass()
    app.run(sys.argv)

gtkapplication

ついに HANDLES_OPEN に対応してくれたようだ。
コレで多重起動防止アプリを Python で作るのが簡単になったね。
Fedora 19 が待ち遠しいぜ!

clipoli も動く、よしコイツはこのままでいいな。
memopoli も動く、いや初期状態に問題があることは解っているんだが。
とりあえず我がアプリは Y901x 以外は大丈夫であるようだ。

肝心の使い勝手は Lubuntu とたいして変わらない。
しかし標準テキストエディタはショボすぎだろ、GtkSourceView なのに。

Xubuntu は合成オフ可能なことに気がつくまではキレそうだったけど悪くない。
別に GTK2 ベースのデスクトップでも GTK3 開発は可能なわけだし。
ただ 3.6 なので Fedora 18 から乗り換える理由は何も無いけど。

FTP やスマホ接続で Nautilus が絶対にファイルマネージャである必要があるのでホスト OS は今後も GNOME にしますが。

File List in Numeric Order

よく見たら TreeView にファイルリスト配置までに物凄く無駄が多かった。
os.listdir でファイルリストを作成し更に隠しファイルを取っ払って新たに作成。
ディレクトリ名までリストに含めたソートを行い配置直前にディレクトリを弾く。

なんだこの非効率さは、実用上では問題ない速度とはいえ流石に書き換えたいわ。
当時は os.listdir 以外でファイルリストを作る手段を知らなかったわけで。

今なら Gio を利用して自力でファイルリストを作れる。
ディレクトリと隠しファイルを除き、数値優先ソートまで終わったリストを戻す関数を作ればいいかな。

#! /usr/bin/env python
# -*- coding: UTF-8 -*-

from gi.repository import GLib, Gio

def get_file_numsort_list(dirname):
    """
        param dirname: Directory Full Path Name
        return: List in Numeric Order
    """
    files = []
    d = Gio.file_new_for_path(dirname)
    enum = d.enumerate_children(
            Gio.FILE_ATTRIBUTE_STANDARD_TYPE,
            Gio.FileQueryInfoFlags.NONE,
            None )
    for info in enum:
        if info.get_file_type() == Gio.FileType.REGULAR:
            s = info.get_name()
            if not s.startswith("."):
                if not s.endswith("~"):
                    files.append(s)
    files.sort(lambda x, y : cmp(GLib.utf8_collate_key_for_filename(x, -1), GLib.utf8_collate_key_for_filename(y, -1)))
    return files

イマイチな気もするけど予定していた動作にはなった。
G_FILE_TYPE_REGULAR 判定ならディレクトリではないと解る。
隠しファイルは自力判定、not を使うと or の動作がおかしかったので階層に。

体感速度は、全然変わらないなやっぱり。

それと。
Gstreamer の Volume 調節は何故か valadoc で見つけた。

Gst.Audio.stream_volume_convert_volume ? gstreamer-audio-1.0

Namespace: Gst.Audio ということだが Gst のアトリビュートには無い。
Python では GstAudio を gir から import すれば使えるようだ。

from gi.repository import Gtk, Gst, GstAudio

class Prayer(Gtk.Window):
    ###
    # etc...
    ###
    def set_volume(self, value):
        if self.player.get_volume(GstAudio.StreamVolumeFormat.CUBIC) != value:
            self.player.set_volume(GstAudio.StreamVolumeFormat.CUBIC, value)

    def on_volume_value_changed(self, widget, event=None):
        self.set_volume(widget.get_value() / 100.0)

get_volume, set_volume は引数に GstStreamVolumeFormat が必要。
PyGtk, gst-0.10 では LINEAR からの変換が必要だったけど直でイケる。

Vala も少し勉強していて良かった!
というか、こんなにドキュメントが充実しているなら Vala で作ったほうがいいのかな。

初回再生時に上下が少し潰れる現象で現在詰まっている。
一度リサイズすれば直るのだが、0.3.9 と同じ処理をしているのに。
GtkAspectFrame ではなく自力でアスペクト比と配置調節をしたほうがいいのかな?

他細々、ということでココまでのバックアップ。
現状では Fedora 18 で Gst デコーダーを揃えた環境しか動かせないけど。
y901x-0.99.1.tar.gz

追記 @ 2013.04.24
多分コレで日本語環境なら当面は問題無いと思う、けど次 Ubuntu 公開まで様子見しますんで。
もっと弄くりたいこともあるにはあるけどメジャー更新は安定させるのが最優先なので。
GTK3, GStreamer 1.0 環境以外は弾くようにしました。
y901x-0.99.2.tar.gz
追記終わり

ついでに。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import GLib

s = "/home/oyaji/エロ画像/乳.png"

# ディレクトリ名
print GLib.path_get_dirname(s)
# ファイル名のみ
print GLib.path_get_basename(s)
# 絶対パスチェック
print GLib.path_is_absolute(s)
# URI に変換
u = GLib.filename_to_uri(s, None)
print u
# パス名に戻す
print GLib.filename_from_uri(u, None)
# フルパス作成(区切り文字の有無を自動調節)
print GLib.build_filenamev(["/home/oyaji/", "/エロ画像", "尻.png"])

なんだ GLib で日本語ファイル名もイケるんだ、os.path は使わなくていいじゃないの。
特に os.path.join と同じように使える g_build_filenamev は素晴らしい。
日本人の Python 屋って標準モジュールで止まっている人が多すぎだから少し違ったことをすると通っぽく見えて素敵かもよ。

Ubuntu 13.04 gst

第270回 Ubuntu 13.04とGNOME:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

どうやら Ubuntu 13.04 の Nautilus は 3.6 になるようだ。

ということは Ubuntu の GStreamer も 1.0 になるということだ!
Nautilus 3.6 は動画サムネイルに GStreamer 1.0 を使っている。

これは Y901x を早々に GStreamer 1.0 仕様に変更しなければ。
まだ 0.3.9 で動くからと PyGtk のまま放置のままもどうかと思うし。
作った本人、つまり筆者以外に使っている人はいないと思うけど。

Novacut/GStreamer1.0 – Ubuntu Wiki

大方はこのとおりに変更すればイケた。
playbin2 を playbin にするのを忘れないようにと。

しかし

playbin.get_property("uri") # All None

set_property(uri) は OK だが get は何をやっても None しか得られない。
既知のバグみたいなので修正を待つしかない、それまで別の手段で URI を保存しよう。

Gst.State.CHANGE_FAILURE に相当するものが見つからない。
PlayState 変更失敗はどうやって見分けるのだろう?

#self.p_position = self.player.query_position(gst.FORMAT_TIME)[0] # 0.10 PyGtk
self.p_position = self.player.query_position(Gst.Format.TIME)[1]  # 1.0 PyGI

ココでかなり苦しんだ、[0] だと bool 値だと気がつくのに時間が掛かった。
シークバーが全然動かなくて悩んだけどそういうことだった。

ボリューム変更の手段がいまだに解らない!
PyGst で gst.interfaces 以下の部分はどう変更になったのだろう?

で、
GStreamer とは関係ないところでも多々発見。

gtk.keysyms.Return が Gdk.KEY_Return と Gdk のアトリビュートになっていた。
gobject.timeout_add が GLib.timeout_add となったのも解り難い。

''' PyGtk
pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
color = gtk.gdk.Color()
blank_cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
'''
blank_cursor = Gdk.Cursor.new(Gdk.CursorType.BLANK_CURSOR)

透明マウスカーソルはこんなに簡単に作れるようになった、嬉しいぜ。
PyGtk でも実は同じような手段があったのかな?

何より一番困ったこと。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, GdkPixbuf, Gdk

class CToolBox(Gtk.Box):
    """
        Inheritance GtkBox is HORIZONTAL
    """
    def __init__(self):
        Gtk.Box.__init__(self, Gtk.Orientation.VERTICAL, 0)
        self.pack_start(Gtk.Label("Orientation"), False, False, 0)
        self.pack_start(Gtk.Label("VERTICAL"), False, False, 0)

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.add(CToolBox())
        self.show_all()

Win()
Gtk.main()

vertical

これができないみたい。
GtkVBox から継承ならイケるんだけど、この場合は下記みたくするしか無いかな…

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, GdkPixbuf, Gdk

class CToolBox(Gtk.Box):
    """
        OK
    """
    def __init__(self):
        Gtk.Box.__init__(self)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(Gtk.Label("Orientation"), False, False, 0)
        vbox.pack_start(Gtk.Label("VERTICAL"), False, False, 0)
        self.pack_start(vbox, True, True, 0)

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        self.add(CToolBox())
        self.show_all()

Win()
Gtk.main()

設定ダイアログは GtkTable を使っているので GtkGrid に書き換えだ。
面倒だから今度!

他にも何かあった気がするけど、気がついたら又今度書く。
とりあえずまだまともに動かないけど Blog にバックアップ。
もし明日筆者が死んでも誰かが続きをやってくれるのを期待して。
y901x-0.99.0.tar.gz

しかしやはり実際にアプリケーションを作ってみないと気がつかないことが多いと実感。
作った本人しか使わないと解っていても何か作り続けるといいことがある。