Wine上のppmckで.mmlファイルを.nsfファイルに変換する処理を自動化するPythonスクリプト(2009/12/7版)
(2010/2/17)「GNU/Linux上における ppmckについてのその後(2010/2/2現在)」にてGNU/Linux向けにビルドしたppmckが使えるようになっていることが分かったため、Wineとこの記事のスクリプトを用いる必要はなくなっている。公式のppmckに追加されているmknsfを用いると変換処理を簡単に行える。
以下、以前の内容となる。
「GNU/Linux上におけるppmckの覚え書き(2009/9/7現在)」ではWineでWin32版のppmckをmml2nsf.pl(Wineを用いるように修正したもの)から用いて正常に.mmlファイルから.nsfファイルへの変換処理を行えることが分かったが、このPerlスクリプトが行っている変換補助的な処理に加え、Wine環境の設定やppmck関係のインストールなども自動化し、GNU/Linux上での.mml->.nsf変換に適した形の変換スクリプトをPythonで作成した。ZIPファイルのダウンロードや展開などの処理もPythonで行っている(これらの処理に関しては最後の関連記事を参照)。
このスクリプトに.mmlファイルの場所を引数に指定して実行すると、このスクリプトのディレクトリ以下にWine環境のwineprefixディレクトリを作成し、この中にWin32版ppmckを自動的にダウンロード/インストールし、これを利用して一時ディレクトリ内で変換処理を行い、スクリプトのあるディレクトリのoutディレクトリ(無ければ作成される)に.nsfファイルを書き出す。wineprefixディレクトリは一度作成してppmckがインストールされると次回以降再利用される。
Wineのバージョンが1.1.33以上の場合はGeckoのインストールダイアログが出てしまうが、これはキャンセルでOK。このGeckoインストールの機能はWineを絡めた自動化処理と相性が悪い気がする。
このコード中にppmckの配布パッケージのURLを含むため、将来バージョンが上がったときにはこれに合わせる必要がある。また、もしかすると不具合が残っているかもしれない。
[任意]ファイル名: mml2nsf.py ライセンス: GPL-3 (or lator)
#! /usr/bin/python # -*- encoding: utf-8 -*- # mml2nsf.py for ppmck 20091207 (C) 2009 kakurasan # installer/wrapper for ppmck on GNU/Linux # Licensed under GPLv3+ from zipfile import ZipFile import subprocess import tempfile import shutil import urllib import time import sys import os class ProcessMMLFailed(Exception): pass class URL: pass class Path: pass class File: pass URL.ppmck = 'http://takamatsu.cool.ne.jp/dutycycle/ppmck/ppmck09.zip' Path.wineprefix = os.path.abspath(os.path.join(os.path.dirname(__file__), 'wineprefix')) Path.dosdevicesdir = os.path.join(Path.wineprefix, 'dosdevices') Path.outdir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'out')) Path.mckdir = os.path.join(Path.dosdevicesdir, 'c:', 'mck') Path.ppmckc = os.path.join(Path.mckdir, 'bin', 'ppmckc_e.exe') ##Path.ppmckc = os.path.join(Path.mckdir, 'bin', 'ppmckc.exe') Path.nesasm = os.path.join(Path.mckdir, 'bin', 'nesasm.exe') Path.nesinc = os.path.join(Path.mckdir, 'nes_include') Path.incasm = os.path.join(Path.nesinc, 'ppmck.asm') os.environ['WINEPREFIX'] = Path.wineprefix os.environ['NES_INCLUDE'] = Path.nesinc # argv if len(sys.argv) != 2: print 'usage: %s [mmlfile]' % sys.argv[0] sys.exit(1) Path.source = sys.argv[1] Path.source_base = os.path.basename(Path.source) # input file if not os.access(Path.source, os.R_OK): print 'cannot access "%s"' % Path.source sys.exit(1) (name, ext) = os.path.splitext(Path.source_base) if ext != '.mml' and ext != '.mck' and ext != '.mus' and \ ext != '.MML' and ext != '.MCK' and ext != '.MUS': print >> sys.stderr, 'file extension must be ".mml" or ".mck" or ".mus".' sys.exit(1) # check output dir if not os.path.isdir(Path.outdir): try: os.mkdir(Path.outdir) except OSError, (errno, msg): print >> sys.stderr, 'os.mkdir(%s) failed: %s' % (Path.outdir, msg) sys.exit(1) # check wineprefix if not os.path.isdir(Path.dosdevicesdir): os.system('wineboot') # check/install ppmck if not os.path.isdir(Path.mckdir): # not installed ppmck_name = os.path.basename(URL.ppmck) try: urllib.urlretrieve(URL.ppmck, ppmck_name) except: print >> sys.stderr, 'downloading ppmck failed' sys.exit(1) # extract ppmck files = [] dirs = [] try: File.ppmckzip = ZipFile(ppmck_name, 'r') except IOError, (errno, msg): print >> sys.stderr, 'open failed: %s: IOError[%d]: %s' % (ppmck_name, errno, msg) sys.exit(1) try: # finally try: # make list of files/dirs for (item, info) in zip(File.ppmckzip.namelist(), File.ppmckzip.infolist()): if item.endswith('/'): dirs.append((item, info)) else: files.append((item, info)) # create dirs for (item, info) in dirs: outpath = os.path.join(os.path.dirname(Path.mckdir), item) try: os.makedirs(outpath) except OSError, (errno, msg): print >> sys.stderr, 'makedirs failed: %s: OSError[%d] %s' % (outpath, errno, msg) # extract files for (item, info) in files: outpath = os.path.join(os.path.dirname(Path.mckdir), item) try: File.uncompressed = open(outpath, 'wb') except IOError, (errno, msg): print >> sys.stderr, 'open failed: %s: IOError[%d] %s' % (outpath, errno, msg) continue try: # finally try: File.uncompressed.write(File.ppmckzip.read(item)) except IOError, (errno, msg): print >> sys.stderr, 'write failed: %s: IOError[%d] %s' % (outpath, errno, msg) finally: File.uncompressed.close() # timestamp(files) try: timestamp = time.mktime(time.strptime('%d/%02d/%02d %02d:%02d:%02d' % info.date_time, '%Y/%m/%d %H:%M:%S')) os.utime(outpath, (timestamp, timestamp)) except OSError, (errno, msg): print >> sys.stderr, 'utime failed: %s: OSError[%d] %s' % (outpath, errno, msg) # timestamp(dirs) for (item, info) in dirs: outpath = os.path.join(os.path.dirname(Path.mckdir), item) try: timestamp = time.mktime(time.strptime('%d/%02d/%02d %02d:%02d:%02d' % info.date_time, '%Y/%m/%d %H:%M:%S')) os.utime(outpath, (timestamp, timestamp)) except OSError, (errno, msg): print >> sys.stderr, 'utime failed: %s: OSError[%d] %s' % (outpath, errno, msg) except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) finally: File.ppmckzip.close() try: os.remove(ppmck_name) except OSError: pass # process mml file Path.tempdir = tempfile.mkdtemp() os.chdir(Path.tempdir) try: # finally try: try: shutil.copyfile(Path.source, Path.source_base) except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) raise ProcessMMLFailed except shutil.Error, msg: print >> sys.stderr, 'shutil.Error: %s' % msg raise ProcessMMLFailed # execute ppmckc try: subprocess.call(['wine', Path.ppmckc, '-i', Path.source_base, 'songdata.h']) except OSError, (errno, msg): print >> sys.stderr, 'call failed: %s: OSError[%d] %s' % (outpath, errno, msg) raise ProcessMMLFailed try: shutil.move('define.inc', 'temp.asm') except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) raise ProcessMMLFailed except shutil.Error, msg: print >> sys.stderr, 'shutil.Error: %s' % msg raise ProcessMMLFailed # write assembly file try: File.asm = open('temp.asm', 'a') except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) raise ProcessMMLFailed try: # finally try: # append contents from ppmck.asm # and drop lines containing 'effect.h' or 'define.inc' for line in open(Path.incasm, 'r'): if not 'effect.h' in line and not 'define.inc' in line: File.asm.write(line) # append contents from effect.h # and replace '.include songdata.h' with its contents for line in open('effect.h', 'r'): if '.include\t"songdata.h"' in line: for line2 in open('songdata.h', 'r'): File.asm.write(line2) else: File.asm.write(line) except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) raise ProcessMMLFailed finally: File.asm.close() # execute nesasm try: subprocess.call(['wine', Path.nesasm, '-s', '-raw', Path.source_base, 'temp.asm']) except OSError, (errno, msg): print >> sys.stderr, 'call failed: %s: OSError[%d] %s' % (outpath, errno, msg) raise ProcessMMLFailed # move output file try: shutil.move('temp.nes', os.path.join(Path.outdir, name + '.nsf')) except IOError, (errno, msg): print >> sys.stderr, 'IOError[%d]: %s' % (errno, msg) raise ProcessMMLFailed except shutil.Error, msg: print >> sys.stderr, 'shutil.Error: %s' % msg raise ProcessMMLFailed except ProcessMMLFailed: print >> sys.stderr, 'failed to process MML file' finally: shutil.rmtree(Path.tempdir)