試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

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版のppmckmml2nsf.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)