gdiplus save

Keyleigh をそろそろなんとかしたいと考えた。
拾ったデルヒャァコンポーネントを使っているだけのクソソフトだが。
せっかくなので画像コンバータ機能とかでも追加すれば面白くなるかも。

GDI+ を使うのが一番だよなぁ。
C# で使えば死ぬほど簡単だ。

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace iconpoli2
{
	class Program
	{
		static void Main(string[] args)
		{
			foreach (var s in args)
			{
				string t = Path.ChangeExtension(s, "jpg");
				Bitmap b = new Bitmap(s);
				b.Save(t, ImageFormat.Jpeg);
			}
		}
	}
}

コンバートならマジでこれだけだ。
いや、せっかくなので C++ でやりたいぞ。
と思ったけど

Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);
img->Save(outname, &Gdiplus::ImageFormatJPEG);

とやっても全然上手くいかない。

Transforming a JPEG Image Without Loss of Information (Windows)
Retrieving the Class Identifier for an Encoder (Windows)

どうやら自力で CLSID を取ってくるしかないようで。
つか ImageCodecInfo って class なのにサイズを得て malloc なの?
試しに普通な new で作ったら見事にヒープ領域不足、どういう設計だよ。

とにかく MSDN の方法で上記と同じになるよう C++ で作ってみる。
C++ じゃ ChangeExtension なんて便利なものは無いし GDI+ 初期化も必須。

stdafx.h

#pragma once

// 面倒なのでココで参照追加
#pragma comment(lib, "gdiplus.lib")

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 // Vista
#endif

#include <stdio.h>
//#include <tchar.h>
#include <wchar.h>
#include <windows.h>
#include <gdiplus.h>

凄く無駄だと解っているけど tchar を利用しないで UNICODE 関数をまんま使う。
てか今となっては Ansi 関数を併用する必要は無いと思うんだが。
WCHAR や UINT のマクロも利用したくない。
だって Visual Studio は大文字マクロの型名では色が付かないんだよね。
というかなるべく小文字にしたいと思うのは多分 Python のやりすぎ。

iconpoli.cpp

#include "stdafx.h"

using namespace Gdiplus;

int GetEncoderClsid(const wchar_t* format, CLSID* pClsid)
{
	unsigned int  num = 0;
	unsigned int  size = 0;

	ImageCodecInfo * pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);
	if(size == 0)
		return -1;  // Failure

	// コレだとヒープ領域不足になる
	//ImageCodecInfo * pImageCodecInfo = new ImageCodecInfo();
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL)
		return -1;  // Failure

	GetImageEncoders(num, size, pImageCodecInfo);

	for(unsigned int j = 0; j < num; ++j)
	{
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success
		}    
	}

	free(pImageCodecInfo);
	return -1;  // Failure
}

bool ChangeExtension(const wchar_t* src, wchar_t* dst, const wchar_t* ext)
{
	wchar_t path[4][256];
	_wsplitpath_s(src, path[0], 256, path[1], 256, path[2], 256, path[3], 256);
	_wmakepath_s(dst, 1024, path[0], path[1], path[2], ext);
	return TRUE;
}

int wmain(int argc, wchar_t* argv[])
{
	// GDI+ 初期化
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	for (int i=1; i < __argc; i++)
	{
		// makepath
		wchar_t outname[1024];
		ChangeExtension(__wargv[i], outname, L".jpg");

		Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);

		CLSID encoderClsid;
		int r = GetEncoderClsid(L"image/jpeg", &encoderClsid);
		if (r == -1)
		{
			wprintf(L"no CLSID");
			return 0;
		}
		// コレは上手くいかない
		//int result = img->Save(outname, &Gdiplus::ImageFormatJPEG);
		int result = img->Save(outname, &encoderClsid);
		if (result == Gdiplus::Ok)
			wprintf(L"Encode Success");
		else
			wprintf(L"Encode Failure");
		delete img;
	}
	// 後片付け
	Gdiplus::GdiplusShutdown(gdiplusToken);
	return 0;
}

長い。。。。。

たったこれだけのモンにえらく時間が掛かった、やっぱり C# のほうがいいかなと。
それと Python や C# ばかりやっていると開放処理をつい忘れそうになるよね。

CsharpRepl

mono にはこんなのがあるらしい。
ようするに C# 言語のインタラクティブシェルだ。

CsharpRepl – Mono

Ubuntu 10.10 には最初から入っていたので試してみる。
公式の解説が思いっきり LINQ だけど普通に foreach とかはどうだ。

using System; は初期状態から行われている。
for 文は一行で終わらせるか中括弧を語尾に付けないとエラーになる。
日本語は入力時に化けるけど UNICODE として処理しているようだ。
class なんかは作れない。
終了は quit; となっているが普通に Ctrl+D でいい。

Ctrl+D に慣れすぎて Windows の cmd.exe で EXIT を打つのが面倒…
というか Windows でも cmd.exe のほうがある程度の処理は簡単な体に…
それはどうでもよくて。

インタラクティブシェルだと嫌でも Python と比較してしまう。
静的言語を無理やり使うのだから Python レベルを求めるのは無謀か。

template and generic

Fedora 15 待ちで Linux ネタが作れないのでしばらく .NET で。

.NET Tips ページもかなり整理が終わった。
UTF-8 INI 読み書き関連と STL/CLR はほとんど書き直し。
.NET Tips – L’Isola di Niente

C++/CLI を久々に使ってみて見つけた。

#include "stdafx.h"

using namespace System;

/* こっちだと演算子が使えないとエラーになる
generic <typename T>
T calc(T a, T b)
{
	return a + b;
}*/

template <class T>
T calc(T a, T b)
{
	return a + b;
}

int wmain(int argc, wchar_t* argv[])
{
	Console::WriteLine(calc(3, 7));
	Console::WriteLine(calc<double>(3.1, 7.3));
	Console::WriteLine(calc<String^>("私って", "ほんとバカ"));
	//=>10
	//=>10.4
	//=>私ってほんとバカ
	return 0;
}

template は generic へのマクロだと思っていたけど全然違う。
template キーワードなら C++ の STL と完全に同様なんだね。
使うかどうかは置いておいて。

せっかく C# の INI 読み書きクラスを作り直したんだから何かに使いたい。
SeeMe が現行 Opera で使えないのは知っているんだけどヤル気が出ない。
ヤルとしたらまったく違うインターフェイスに変更したいし、何か思いついたら。

PyGtk 関係は GTK3 が普通に使える GNOME3 Fedora に乗り換えが終わってから。
今整理したってあっというまに時代遅れになる、進化が早すぎるよ…

追記

今頃気がついたけどコレってもしかして C++ のテンプレート機能を呼んでいるだけ?
標準 C++ と何も違いが無いのだとしたら STL/CLR の存在価値って何なんだ。
知れば知るほど解らないコトが増えるのは標準 C++ と同じかよ…

Dynamic Menu in GtkUIManager

PyGtk でメニュー項目の動的な追加と削除。
コレををやらないとマルチトラック音声の切り替え機能が実装できない。

GtkUIManager を使うのが一番簡単、てか私はこの方法しか知らない。
Gedit プラグインでメニューを追加するのと同じ方法でいいはず。

Gedit プラグインの作り方 – L’Isola di Niente

切り替え自体はラジオメニューに、Totem もそうなっているし。
ということは GtkActionGroup には以前書いたコレを指定すればいいかな。

GtkRadioAction でラヂオメニューとツールバーを同期させる

しらない間に自身で方法だけは書いていた。
この2つをまとめれば動的なメニュー項目の追加削除ができそうなのでやってみる。

class AudioMenu(object):
    __slots__ = ["uimanager", "ui_id", "action_group"]
    def __init__(self, uimanager):
        self.uimanager = uimanager
        self.ui_id = -1

    def add_menu(self, actions, xml, callback, value):
        self.action_group = gtk.ActionGroup("AudioMenuActions")
        self.action_group.add_radio_actions(actions, value, callback)
        self.uimanager.insert_action_group(self.action_group, -1)
        self.ui_id = self.uimanager.add_ui_from_string(xml)

    def remove_menu(self):
        if self.ui_id != -1:
            self.uimanager.remove_ui(self.ui_id)
            self.uimanager.remove_action_group(self.action_group)
            self.uimanager.ensure_update()
            self.ui_id = -1
            del self.action_group

一ヶ所でしか使わないけど class にしたほうがメンテナンスが楽なので。

GtkRadioActionEntry List とメニュー XML とコールバックと選択するラジオメニュー値
を引数に渡してメニューを作成する。
メニューの削除は Gedit プラグインとまったく同じで大丈夫だろう。
GtkActionGroup は同じアクションを追記してしまうので都度新規作成と削除をする。

この引数に指定するコールバックを本体クラスに。

def on_soundmenu(self, action, current):
    """
        Multi Track Audio Select
    """
    n = action.get_current_value()
    self.player.set_property("current-audio", n)
    # 下記は本体側の設定保存用
    self.settingwin.n_audio = n

これだけでラジオメニューのどこが選択されているかで勝手にトラック選択になる。

後は GtkActionGroup の List と XML 文字列を黙々と完成させればいい。
とりあえず XML の大雑把なテンプレートを用意。

audio_str = """<ui>
    <menubar name="MenuBar">
        <menu action="File">
            <menu action="sound">
            {0}
            </menu>
        </menu>
    </menubar>
    <popup name="pop_main">
        <menu action="sound">
        {0}
        </menu>
    </popup>
</ui>
"""

やはり Gedit プラグイン同様にメニューの XML を用意する。
自分で作った XML ツリーだからまったく迷わないwww

str.format() なら一つの引数で {0} に入るから間違えない。
新しいフォーマッタはこういう場合には便利だね。

asink = self.player.get_property("audio-sink")
if asink:
    # versioon 0.3.6
    # 一度メニューを空にする
    self.audiomenu.remove_menu()
    # オーディオトラック数で振り分ける
    n_audio = self.player.get_property("n-audio")
    if n_audio == 1:
        self.player.set_property("current-audio", 0)
    else:
        actions = []
        x = ""
        for i in range(n_audio):
            a = "sound{0}".format(i)
            s = "トラック #{0}".format(i)
            # GtkRadioActionEntry の List を作成
            # name, stock_id, label, accelerator, tooltip, value
            t = (a, None, s, None, s, i)
            actions.append(t)
            x += '<menuitem action="{0}"/>'.format(a)
        xml = audio_str.format(x)
        # メニューに追加
        self.audiomenu.add_menu(actions, xml, self.on_soundmenu, self.settingwin.n_audio)
        self.player.set_property("current-audio", self.settingwin.n_audio)

こんな感じで for ループでひたすら文字列を加工し作成。
GtkRadioActionEntry 作成は PyGtk の場合は単なるタプルでいいので簡単。
GtkMenuItem の XML も当然文字列なので同時に作って最後にテンプレートに流し込む。

新しいことは得に何もやっていないのでスンナリと動的メニューが完成した。
まぁ GTK+ と GStreamer に命令を送っているだけのチッポケなアプリですからね。

current-audio property

動画コンテナ(DVD や mp4 等)は音声や映像のトラックを複数格納できる。
主に字幕用途で考えられた構造だろう、と思う…

しかしこの構造を利用して昨今では様々な応用というか実験が進んでいる。
…っぽい、業界全般なんて現場のオッサンに解るはずが無いのだからしょーがない。

確実なのは特定用途限定かつ軽いことだけがウリのプレイヤーにはトドメでしかない。
たとえば私が公開しているプレイヤーアプリとかである。
こんなのってないよ…あんまりだよ…

つまり我が Y901x 0.3.5 はまったく複数トラックメディアに対応していない。
当然開発終了している Windows 用もそうだが開発は終了しているので放置。
DirectShow のコードなんて海外にゴロゴロ転がっているけどシラネ!

実は Totem で指定すれば Y901x にも反映されるのであまり気にしていなかった。
ぶっちゃけ Totem が終了時に X の設定を初期化しないというのを利用しているだけ。
だったけどいいかげんに自分自身が気になるようになってきたのでそろそろ対策しよう。

日本語版 Ubuntu てか GNOME 環境専用アプリで続けるなら別に…
って次で GNOME を排除すると明言している Ubuntu は終わった感が…
GNOME だから選んでいた人は多いと思うんですけど…
私も Fedora 15 待ちで移行は確定だけど Ubuntu 11.04 は全然話題になっていないね…

いつものように長い戯れ言はこのくらいにして。

playbin2

n-audio プロパティでオーディオトラック数。
current-audio プロパティでオーディオトラックの指定。
が可能であるようだ。

Totem のソース bacon-video-widget-gst-0.10.c
にて parse_stream_info 関数がやっていることを参考にしただけなんだが。
C のコードを見て形を変えて Python で使うのは GPL には反していないよね。

映像やテキストのトラック関連もあるけど現状は「シラネ!」でいいだろう。
Y901x 0.3.5 の 1314 行目に以下を追記してみる。

# Audio
asink = self.player.get_property("audio-sink")
if asink:
    self.toolbox.volumebar.set_property("sensitive", True)
    v = self.toolbox.volumeadj.get_value() / 100.0
    self.set_volume(v)
    # 追加
    print self.player.get_property("n-audio")
    # デフォルトは -1 で先頭トラックはゼロ
    self.player.set_property("current-audio", 1)
else:
    self.toolbox.volumebar.set_property("sensitive", False)

なんだ、たったコレだけで 2 番目のオーディオトラックが再生できた。
playbin2 は本当に何でも簡単にやってくれる、頼るのもどうかとは解っているけど。
とにかく私程度のサンデープログラマーなオッサンにはありがたい存在である。

後は切り替えを実際に行うユーザーインターフェイスをどうするかだ。
やっぱりメニュー項目に加えるのが一番迷わない方法だと思うけど。
トラック数を調べて一度選択メニューを全部削除してそれからえっと、実装は面倒くさい…