VocalShifterプロジェクトファイル復旧メモ
https://gyazo.com/5bf49f5a0316bba9da062e545f23ed73
VocalShifterでは、プロジェクト内で使った音声ファイルが見つからない場合、ファイルを再選択するよう促される。
この作業は、見つからないファイル毎に行う必要がある。
例えば、音声ファイルの入ったフォルダを丸ごと移動した場合、中のファイルを1つずつ手動で再選択する必要がある。
これはとても辛い。
このファイルの再選択作業を効率化しようと悪戦苦闘したので、稚拙ながらメモを残す。
結論
vshpファイル内の音声ファイルパスを書き換えるコードを書いてみた。
※ 個人的に作成したプログラムのため、問題が生じる可能性があります。
※ 使用は自己責任でお願いします。
目次
1. プロジェクトファイルをバイナリエディタで開いてみる
2. ドライブ名のみ変更する場合は単純に置換するだけで良い
3. もう少し詳しく見てみる
4. パスの長さが変わる場合の措置
5. コードを書いてみる
6. GoogleColabに置いておく
プロジェクトファイルをバイナリエディタで開いてみる
https://gyazo.com/62cec610a59c9fb7590dd17786b058c2
音声ファイルのパスは、I T M P 00 02 00 00の後にSHIFT_JIS形式で保存されている。
単純に置換すれば良さそう?
ドライブ名のみ変更する場合は単純に置換するだけで良い
https://gyazo.com/97828292d5dac52d6200b4f4055030cc
E:\~~ → F:\~~のように、ドライブを変更するだけであれば、単純に置換するだけで良い。
(厳密には、置換後のパス文字列のバイト数が置換前と同じにサイズになる場合なら置換で良い(理由は後述))
https://gyazo.com/98c34cad98e5c58c0e79a0fe89bfd2c2
E:\フォルダ1\音声.wav → E:\フォルダ1\フォルダ2\音声.wavのように、パスの長さが変わってしまう場合、単純に置換するだけではプロジェクトファイルが読み込めなくなる。
もう少し詳しく見てみる
https://gyazo.com/2be41791242bf9bbb219793202d6a788https://gyazo.com/478136f5e7296e7e2569db4f608fa2fd
ファイルパス周辺を詳しく見てみると、255バイトの領域を占有していることがわかる。
255バイト未満の場合、末尾に00が埋められる。
ファイルパスの領域は255バイト丁度でなければならない。
単純な置換によって255バイトを上回る or 下回るとエラーが起こる。
(余談だが、255バイトを上回るパスを持つ音声ファイルを、VocalShifter上から読み込もうとするとクラッシュする)
パスの長さが変わる場合の措置
上手いことやってくれるソフトがあれば良いが、知らない。
自前で作るとすれば……
1. 置換前、置換後の文字列のバイト数の差を計算
2. 置換前 < 置換後の場合、先頭から255バイト先のアドレスから、前方向に余剰分の00を削除
2. 置換前 > 置換後の場合、先頭から255バイト先のアドレスに不足分の00を追加
3. 文字列の置換
……とか?
正規表現を使うのであれば……
code:py
diff = len(置換前) - len(置換後) # 置換前と置換後の文字数の差
path_hex = data_hexidx:idx+510 # 255バイトのパス領域を抽出した16進文字列 re.sub(f'^{置換前}(.*?){"0" * -diff}$', f'{置換後}\g<1>{"0" * diff}', path_hex)
……とか? なんだこれ。
<追記> もしくは、
1. パスが格納されている、255バイト分の領域を切り出す
2. 以下、切り出したパス領域に対して
1. 文字列を置換
2. 末尾に適当な数の0を追加
3. 先頭から255バイト分にトリミング
3. 切り出したパス領域を元の位置に戻す
……とか?
.rstrip('0')で末尾の連続する0を消してから、255バイトになるように0を足しても良い。
コードを書いてみる
説明のため、コメント多め。
拙いコードで恥ずかしい。
code:replace.py
import re
# 例として、'test.vshp' というプロジェクトファイルを使う
FILE_NAME = '.\\test.vshp'
# 例として、'F:\\フォルダ1' から 'F:\\フォルダ1\\フォルダ2' に置換してみる
BEFORE = 'F:\\フォルダ1'
AFTER = 'F:\\フォルダ1\\フォルダ2'
# 16進strに変換。文字列で扱った方がマッチしたindex一覧を得られるため
before_hex = BEFORE.encode('shift_jis').hex()
after_hex = AFTER.encode('shift_jis').hex()
# 置換前と置換後の文字数の差
diff = len(before_hex) - len(after_hex)
# 255バイトに満たない場合に追加する'0'。追加しない場合は空文字
add_zero = '0' * diff
# 255バイトを超える場合に削除する'0'。削除しない場合は空文字
sub_zero = '0' * -diff
# プロジェクトファイルをバイナリデータとして読み込む
with open(FILE_NAME, 'rb') as f:
data = f.read()
# 16進strに変換
data_hex = data.hex()
# before_hexにマッチするインデックスが入ったリスト
for idx in indices:
# 255バイト分のパス領域を抽出する
# 抽出したパス領域内で、before_hexをafter_hexに置換しながら、余剰分の0を削除しながら、不足分の0を追加する
# 読みづらい
path_hex = re.sub(f'^{before_hex}(.*?){sub_zero}$', f'{after_hex}\g<1>{add_zero}', path_hex)
# 抽出したパス領域をプロジェクトファイルデータに戻す
# Hex -> bytesに変換
data = bytes.fromhex(data_hex)
# 置換したデータをファイルに書き込む
with open(FILE_NAME, 'wb') as f:
f.write(data)
この処理だと、BEFOREとAFTERはドライブ名から始まらなくてはならない。
GoogleColabに置いておく
備考
今回はプロジェクトファイルを書き換えるという方法を取ったが、ファイルが破損する等の安全面に懸念が残る。
またバージョンアップにより、この方法が使えなくなる場合がある。
あと倫理的な問題もある。