スクリプト

ソーステキストをスプレッドシートで一括管理するスクリプト+aep「aepSyncer@TSV.jsx」

JSON形式データをスクリプトでaeに反映するアイディア【置換テキストを表計算で管理】1JSON形式データをスクリプトでaeに反映するアイディア【置換テキストを表計算で管理】2の集大成。

aep内の置換用テキストをスプレッドシートで管理して、一括書き換え出来たら便利なの?ということからスクリプト作るに至りました。

[概要]

tsvを元に、After Effects内のテキストを書き換えるスクリプトです。

コンポジション「tsv」の中に置換用(まるごと書き換え系)の「[$]tsvLayer」と、タグ変換用(一部書き換え系)の「[^]tsvLayer」という名前のテキストレイヤーを準備。

スプレッドシートの任意のセルを範囲でコピーしテキストとしてペーストすると自動でタブ区切りになることを利用し「tsvLayer」へペーストして使用するという塩梅です。このようにルールに従って作成されたtsvテキストを読み込んでから動作するので、tsvテキストの作成が先に必要です。

[チュートリアル動画]

あったらヤなチュートリアルを作りました。スクリプトの動きと概要を掴めます。

[使い方]

一通り作り終わったaepをテンプレート化して行くのが楽かと思います。もちろんテンプレート化を見越してaepを作り始めても構いません。

書き換えたいテキストレイヤー名の先頭に下記のルールに従って「$」か「^」を付けていきます。

テキストを置き換えるモードが2種類あります。

1テキストレイヤーのソーステキストをまるごと変える「$置換モード_」は、スプレッドシートにテキストレイヤー名と変更後のテキストを横並びで配置するだけです。

  1. aep内”$”から始まるレイヤー名がトリガーとなる。
  2. リンク用レイヤー名とその右セルに置換後のテキストを配置したtsvを用意。
  3. [tsvから同期]ボタンをクリックする。

レイヤー名をベースの文章として扱う「^[タグ変換モード_]」は、含まれるタグだけをスプレッドシートの方で指定します。

  1. レイヤー名はタグを含む文章で入力しておく。
  2. aep内”^”から始まるレイヤー名がトリガーとなる。
  3. リンク用タグ名とその右セルに置換後のテキストを配置したtsvを用意。
  4. [tsvから同期]ボタンをクリックする。

一通りレイヤー名の変更が終わったら、オプション機能「リンクレイヤー確認」でテキスト置き換え対象となったレイヤー名を一覧取得します。コピペできるウィンドウが開きますので、そのままスプレッドシートにコピペし、リンクするレイヤー名の管理をするスプレッドシートは準備完了です。

レイヤー名それぞれの一つ右セルにソーステキストを打ち込むことで、後でまとめて置き換えしますので、aep全体のテキストレイヤーを変更して回る手間が削減できます。

[オプション]

リンクレイヤー確認

aep内のリンクレイヤー(置換対象のテキストレイヤー)はレイヤー名が「^」か「$」を付けることで差別化するので、これらを重複削除し一覧化する。コピペ可能なテキストウィンドウで開くので、まるごとスプレッドシートに持っていくと漏れなく、1レイヤー名ずつ手打ちする必要がない。

リンクNGチェック

リンクレイヤー名がミスっているものをリストアップ+レイヤーを選択状態にするので修正箇所を見つけやすくする。aep準備段階において「^」で始まるタグ変換レイヤーなのに[]で囲まれているタグがなかったり、スプレッドシートを作り終わった段階においてリンク用のテキストレイヤー名がaepにはあってtsvレイヤーに吸い出されていないものがあったりした場合のチェック用。

ハッシュタグでの整形機能

タグ変換モードでは、タグ内末尾にハッシュタグを埋め込んで整形が可能。使えるハッシュタグは下記。

【英単語の整形3種】※半角英字のみ。

  • #cap…キャピタライズ。一文字目を大文字にする。Nasu
  • #upper…アッパーケース。大文字に変換。NASU
  • #lower…ローワーケース。小文字に変換。nasu

【数字の整形4種】

  • #00…ゼロパディング。2桁に合わせる。01
  • #000…ゼロパディング。3桁に合わせる。001
  • #kansu…漢数字に変換。三十一
  • #mth…月を英語に。※半角英字 1~12のみ。1→january

【汎用2種】

  • #initial…イニシャル。一文字目のみにする。MIYAGI→M
  • #space…連続スペースを1つに。a b c→a b c

[解説]

欲をかいてほしいものを好き勝手取り入れていったら機能が多くなったことに加え長くなりましたが、目的は達せました。

まず、前回のアイディアでかたちにした、aep内のテキストレイヤー名とスプレッドシートを全比較し、一致するテキストがあれば、その右セルの文字列をソーステキストに上書きする処理はそのまま「$置換モード」として生かしてあります。

加えて、レイヤー名をベースとした文章に、スプレッドシートから引っ張ってくるテキストを一部に限定した「^[タグ変換モード_]」を実装しました。

「^」から始まるテキストレイヤー名にしておき、任意の文字列内に[]で囲んだタグを仕込み、タグ内だけが書き換えられるイメージです。そしてタグ末尾にハッシュタグを埋め込めば、文字列を整形する処理も呼び出せる寸法です。

例として、「^私の誕生日は[誕生月#mth].[誕生日]です。」というレイヤー名のテキストレイヤーと、

[誕生日][TAB]22
[誕生月][TAB]11
※タブを便宜上[TAB]と表記しています。

という「[^]tsvLayer」を用意してスクリプトを実行すれば、上記テキストレイヤーのソーステキストが

私の誕生日はnovember.22です。

に書き換えられます。[誕生月#mth]には”11″が引っ張られますが、1~12までの半角数字を英語の月名に変換する「#mth」ハッシュタグが付いているので、”november”に整形されたということです。後ろの[誕生日]はハッシュタグ無しなので”22″がそのまま引っ張られています。

ハッシュタグによる整形はオプションで紹介した9種類だけ実装しました。実用性を考えるとこんなものかなというだけで、必要に迫られれば今後増やしてもいいでしょう。実務では開発してまで頼る場面がそもそも多くないと思いますし、変換機能を実現するプログラミングの勉強のためという側面が大きいです。

この2つのモードを使い分けることで、テンプレート化したaep内のソーステキストすべてをスプレッドシートで管理することが可能となります。セルに書式を設定しておけば文字数制限や、あるセルの数値から計算した値を自動入力することも可能でしょう。また、社名や人名など、何度も出てくる同一テキストもae内で何度も手打ちやペーストに駆られることなく1セルへの入力で済むため、時間的な効率化と言うよりは文字間違い防止や修正簡略化に繋がります。

今回の夢を実現するためには、aepと置換テキストを管理するスプレッドシートとを同時に準備しなくてはなりませんので、スプレッドシートの準備を効率化するaep内のリンクするレイヤーを列挙する「リンクレイヤー確認」機能と、aep内のリンクレイヤー名が正常についているかなど、aepの準備を効率化する「リンクNGチェック」機能も継ぎ接ぎしました。

ここで、これらチェック内容はコピペできるedittextで新規ウィンドウを開きたかったのと、さらにこれを表示中にどこがミスってるかコンポジションを切り替えて修正作業を行うため、paletteタイプのウィンドウで開く必要がありました。

dialogタイプのウィンドウだと、それ以外の後ろのウィンドウが操作できなくなるためです。

しかしpaletteウィンドウを呼び出しても一向に表示されません…。試行錯誤したりtwitterで軽く質問したりしても普通に開けるだろうという状況でした。

その内dialogとして開くと表示されることに気が付きました。paletteに戻すと表示されません。いや、一瞬表示され消えているような挙動です。ここにヒントがあるかと思い、「ae script palette 一瞬 表示」で検索すると…出ました。

paletteのウィンドウが一瞬しか出てこない

さかもとのサイト 様より

なんと同じ様なエラーに既に辿り着いている先人がおりました。感謝です。

原因はこの記事でも不明ですが、解決方法はしっかりと記載がありました。どうも.show();前に適当なfunctionを割り当てるだけで、この現象を回避出来るようです。

これに倣い、.aa=function(){} というような一文を加えてみると…

function GUI_win2(ttl,size,st){
    var w = new Window("palette",scName+ttl);
    w.center();
    var st1 = w.add("edittext", size, "", {multiline: true } );
    st1.text=st;
    w.aa=function(){}
    w.show();
}

これで表示された。なんそれ。

aepSyncer@TSV.jsx

動作に直接関係ない部分は「”ブログでは省略”」としました。

var scNameJ="aepSyncer@TSV(一括置換)";
var scName="aepSyncer@TSV";
var scVersion="1.0";

about_ver=
"===============\r"+
"- バージョン履歴 -\r"+
"===============\r"+
"ブログでは省略"



/*
===============
--- GUI準備 ---
===============
*/


function f_showDialog(){
	this.w = new Window("palette",scName);
	this.w.center();
        //group
        this.stpPnl = this.w.add('panel', undefined,'実行');
        this.stpPnl.stp1 = this.stpPnl.add('group');
        this.stpPnl.stp2 = this.stpPnl.add('group');
        this.stpPnl.stp3 = this.stpPnl.add('group');
        this.stpPnl.stp4 = this.stpPnl.add('group');
        //this.stpPnl.run = this.stpPnl.add('group');
        with (this.stpPnl) {

            stp1.bt = stp1.add('button', undefined, 'tsvから同期');
            stp1.bt.preferredSize = [180,20];
            stp1.infos = stp1.add('button',[10,100,30,120],'?');

            stp2.st = stp2.add('statictext', undefined, 'Option1:');
            stp2.bt = stp2.add('button', undefined, 'tsv確認');
            stp2.bt.preferredSize = [120,20];
            stp2.infos = stp2.add('button',[10,100,30,120],'?');

            stp3.st = stp3.add('statictext', undefined, 'Option2:');
            stp3.bt = stp3.add('button', undefined, 'リンクレイヤー確認');
            stp3.bt.preferredSize = [120,20];
            stp3.infos = stp3.add('button',[10,100,30,120],'?');

            stp4.st = stp4.add('statictext', undefined, 'Option3:');
            stp4.bt = stp4.add('button', undefined, 'リンクNGチェック');
            stp4.bt.preferredSize = [120,20];
            stp4.infos = stp4.add('button',[10,100,30,120],'?');
        }

        this.stpPnl.stp1.alignment = [ScriptUI.Alignment.RIGHT, ScriptUI.Alignment.TOP];
        this.stpPnl.stp2.alignment = [ScriptUI.Alignment.RIGHT, ScriptUI.Alignment.TOP];
        this.stpPnl.stp3.alignment = [ScriptUI.Alignment.RIGHT, ScriptUI.Alignment.TOP];
        this.stpPnl.stp4.alignment = [ScriptUI.Alignment.RIGHT, ScriptUI.Alignment.TOP];
            
        
        //group
        this.glbPnl = this.w.add('panel', undefined,'');
        this.glbPnl.bt = this.glbPnl.add('group');
        with (this.glbPnl) {
            bt.scname = bt.add("statictext",[10,10,144,25],scName+" "+scVersion);
            bt.infos = bt.add("button",[10,10,80,30],"ヘルプ");
        }
        //group

        gretxDef="全体の進行状況";
		this.grePnl = this.w.add('panel', undefined,gretxDef);
         this.grePnl.info = this.grePnl.add('group');
         this.grePnl.bar = this.grePnl.add('group');
         with (this.grePnl) {
            info.st = info.add("statictext",[10,10,224,25],"");
            bar.Dialog = bar.add("progressbar",[20,30,224,40],0,100);
         }
         
	this.show = function()
	{
        return this.w.show();
	}
}
var wobj = new f_showDialog;

/*
===============
--- ボタン操作 ---
===============
*/



// [step1]ボタン
wobj.stpPnl.stp1.bt.onClick = function(){
    app.beginUndoGroup("comp内リンクレイヤー同期");
    f_tsvToCell();
    if(okflag!=0){
        f_comTxtReplace();
        GUI_win2("リザルト - [step1]",[10,10,470,500],Com);
    }
    app.endUndoGroup();
}
// [?]ボタン
wobj.stpPnl.stp1.infos.onClick = function(){
    tx1=
    "[?]: tsvから同期\r\r"+
    "aep内リンクテキストレイヤーをtsvの該当テキストで置換";
    GUI_win1("ヘルプ",[10,10,500-10,70],tx1);
}


// [step2]ボタン
wobj.stpPnl.stp2.bt.onClick = function(){
    f_tsvToCell();
    if(okflag!=0){
        GUI_win2(" - [tsv確認]",[10,10,470,500],cellDataTable);
    }
}

// [?]ボタン
wobj.stpPnl.stp2.infos.onClick = function(){
    tx1=
    "[?]: tsv確認\r\r"+
    "aep内のテキストレイヤー\"tsvLayer\"の中身を\r"+
    "開いて表示。無ければお知らせ。";
    GUI_win1("ヘルプ",[10,10,500-10,70],tx1);
}


// [step3]ボタン
wobj.stpPnl.stp3.bt.onClick = function(){
    f_tsvToCell();
    f_linkLayerName();
    if(okflag!=0){
        GUI_win2(" - リンクレイヤー名確認",[10,10,470,500],nCom);
    }
}

// [?]ボタン
wobj.stpPnl.stp3.infos.onClick = function(){
    tx1=
    "[?]: リンクレイヤー確認\r\r"+
    "aep内のリンクレイヤーを一覧で表示。\r"+
    "xls制作の効率化用。";
    GUI_win1("ヘルプ",[10,10,500-10,70],tx1);
}


// [step4]ボタン
wobj.stpPnl.stp4.bt.onClick = function(){
    f_tsvToCell();
    f_linkLayerCheck();
    if(okflag!=0){
        GUI_win2(" - リンクNGチェック",[10,10,470,500],cCom);
    }
}

// [?]ボタン
wobj.stpPnl.stp4.infos.onClick = function(){
    tx1=
    "[?]: リンクNGチェック\r\r"+
    "aep内のリンクレイヤー名が正常についていないものをリストアップ+レイヤーを選択状態に。\r"+
    "aepにあるのにtsvに一致する名前がない、\"^\"で始まるのに[]で囲まれているタグがない等。\r"+
    "xlsが制作できたかの簡易チェック用";
    GUI_win1("ヘルプ",[10,10,500-10,90],tx1);
}






// [ヘルプ]ボタン
wobj.glbPnl.bt.infos.onClick = function(){
    tx9=
    scNameJ+" "+scVersion+"\r\r"+
    "tsvを元に、After Effects内のテキストを書き換えるスクリプト。\r\r"+
    "コンポジション「tsv」の中に「tsvLayer」という名前のテキストレイヤーを準備。\r\r"+
    "スプレッドシートの任意のセルを範囲でコピーしテキストとしてペーストすると自動でタブ区切りになることを利用し「tsvLayer」へペーストして使用する。\r"+
    "このようにルールに従って作成されたtsvテキストを読み込んでから動作するので、tsvテキストを先に作成必要。\r\r"+

    "-----------------------------------------------------\r\r"+
    
    "モードは2種類。\r"+
    "******$置換モード_******\r"+
    "aep内該当テキストレイヤーがtsvを元に書き換えられる。\r"+
    "1.リンク用レイヤー名とその右セルに置換後のテキストを配置したtsvを用意。\r"+
    "2.aep内\"$\"から始まるレイヤー名がトリガーとなる。\r"+
    "tsv例)\r"+
    "A1    │ A2\r"+
    "$コメント_01  │ 1/1新発売!\r\r"+
    
    "実行すると…\r"+
    "レイヤー\"$コメント_01\"のソーステキスト\r"+
    "↓\r"+
    "\"1/1新発売!\"\r\r"+
 
    "******^[タグ変換モード_]******\r"+
    "aep内該当テキストレイヤーが、「レイヤー名とtsv」を元に書き換えられる。\r\r"+
    
    "1.リンク用タグ名とその右セルに置換後のテキストを配置したtsvを用意。\r"+
    "2.aep内\"^\"から始まるレイヤー名がトリガーとなる。\r"+
    "3.レイヤー名にタグを含む文章で入力しておく。\r"+
    "-つまりレイヤー名を文章にしておく\r"+
    "-[]で囲んだものがタグ扱い。[発売月_]など\r"+
    "-タグ内末尾に整形用ハッシュタグも埋め込める。\r"+
    "-「新商品は[発売月_#kansu]月発売」など\r\r"+
    
    "○ハッシュタグ一覧\r"+
    "【英単語の整形3種】\r"+
    "※半角英字のみ。\r"+
    "#cap...キャピタライズ。一文字目を大文字にする。Nasu\r"+
    "#upper...アッパーケース。大文字に変換。NASU\r"+
    "#lower...ローワーケース。小文字に変換。nasu\r\r"+
    
    "【数字の整形4種】\r"+
    "#00…ゼロパディング。2桁に合わせる。01\r"+
    "#000…ゼロパディング。3桁に合わせる。001\r"+
    "#kansu…漢数字に変換。三十一\r"+
    "#mth…月を英語に。※半角英字 1~12のみ。1→january\r\r"+
    
    "【汎用2種】\r"+
    "#initial…イニシャル。一文字目のみにする。MIYAGI→M\r"+
    "#space…連続スペースを1つに。a     b   c→a b c\r\r"+
    
    "○英単語の整形 解説\r"+
    "※半角英字のみ。\r"+
    "tsv例)\r"+
    "A1    │ A2\r"+
    "[男名前ローマ_]  │ Kazuaki\r"+
    "[女名前ローマ_]  │ Kazuko\r"+
    "[ストーリータイトル_]  │ history\r\r"+
    
    "・#cap , #upper , #lower\r"+
    
    "レイヤー名\"^[男名前ローマ_#upper] & [女名前ローマ_#lower]'s [ストーリータイトル_#cap]\"\r"+
    "↓\r"+
    "ソーステキスト\"KAZUAKI & kazuko's History\"\r\r"+
    
    "○数字の整形 解説\r"+
    "tsv例)\r"+
    "A1   │ A2  │ A3  │ A4  │ A5  │ A6\r"+
    "[年_]  │ 1984 │[月_]  │ 5 │[日_]  │18 \r\r"+
    
    "・#00\r"+
    "レイヤー名\"^[月_#00]月[日_#00]日\"\r"+
    "↓\r"+
    "ソーステキスト\"05月18日\"\r\r"+
    
    "・#000\r"+
    "レイヤー名\"^[月_#000]月[日_#000]日\"\r"+
    "↓\r"+
    "ソーステキスト\"005月018日\"\r\r"+
    
    "・#kansu\r"+
    "レイヤー名\r\"^[月_#kansu]月[日_#kansu]日\"\r"+
    "↓\r"+
    "ソーステキスト\"五月十八日\"\r\r"+    
    
    "・#mth\r"+
    "※半角英字 1~12のみ。\r"+
    "レイヤー名\"^ON [月_#mth] [日_].[年_]\"\r"+
    "↓\r"+
    "ソーステキスト\"ON May 18.1984\"\r\r"+
    
    "○汎用 解説\r"+
    "tsv例)\r"+
    "A1    │ A2\r"+
    "[男名前漢字_]  │ 和明\r"+
    "[出身地ローマ_]  │ miyagi\r"+
    "[好きなソフト_]  │ after        effects\r"+
    "・#initial\r"+    
    "レイヤー名\"^[出身地ローマ_#initial]\"\r"+
    "↓\r"+
    "ソーステキスト\"m\"\r\r"+
    
    "・#space\r"+
    "※1つの半角スペースにまとめる。行頭末尾のスペースは削除。\r"+
    "レイヤー名\"^ [好きなソフト_#space]\"\r"+
    "↓\r"+
    "ソーステキスト\"after effects\"\r\r\r"+

    about_ver;

    GUI_win2(" ヘルプ",[10,10,430-10,500],tx9);
}

wobj.show();



/*
===============
--- メイン処理 ---
===============
*/


//tsvコンポジション tsvLayerテキストレイヤーがあれば読み込み
function f_tsvToCell(tsv){
    ttl="tsv展開";
    f_gresBar(ttl);//プログレスバー準備
    TAB = String.fromCharCode(9);
    tsvText="";tsvText1="";tsvText2="";tsvTextTable="";
    tsvFlag=0;
    for (i=1; i<=app.project.items.length; i++){
        itm = app.project.item(i);
        if(itm.name=="tsv"){
            for(var j=1;j<=itm.numLayers;j++){//コンポジション内全検索
                lyr = itm.layer(j);
                f_greSpin_Lyr(j,itm);//プログレスバー更新
                if(lyr.name=="tsvLayer"){
                    tsvFlag=1;
                    tsvText1=lyr.property("Source Text").value.text;//tsv形式のテキスト
                }
            tsvText=tsvText2+"\r"+tsvText1;
            }//for コンポジション内
        }
    }//app.project.items

    cellData=[];cellDataTable="";rowCount=0;colCount=0;
    rowStart=0;//forループ開始行 n行はデフォルトで飛ばす
    txt = tsvText+TAB+"\r"+TAB+"\r\n"+TAB+"\n";//まるごと取り込み
    brTxt=txt.replace(/[\r\n](?!(([^"]*"){2})*[^"]*$)/g,"<br>");//""内改行を<br>に置換
    gyou=brTxt.split(/\r\n|\r|\n/);//改行毎にsplit
    for(i=0;i<gyou.length;i++){
        cellData[i] = gyou[i].split(TAB);//改行splitの次はタブでsplit
        rowCount=i;
        colCount=Math.max(colCount,cellData[i].length);
        cellDataTable=cellDataTable+gyou[i].replace(/\t"|"\t/g,"\t")+"\r";
    }
    for (var row=rowStart; row<rowCount; row++){//行番号
        if(cellData[row][0]=="↓置換開始行"){rowStart=row+1;break;}//無視する行設定可能
    }
    if(tsvFlag==0){
        cellDataTable="tsvLayerがありません。"
    }
    okflag=1;
    f_gresDef(ttl);
}


function f_comTxtReplace(){
    ttl="コメント変換";
    f_gresBar(ttl);//プログレスバー準備
    Com="";Com1="";Com2="";hashTag="";
    //プロジェクトウィンドウ全検索_コンポ内処理
    for (i=1; i<=app.project.items.length; i++){
        itm = app.project.item(i);

        // - [処理B]compなら中身も全検索
        if(itm instanceof CompItem){
            for(var j=1;j<=itm.numLayers;j++){//コンポジション内全検索
                //ファイル置換はitm.nameに、テキスト変換はlyr.nameに適用
                lyr = itm.layer(j);
                f_greSpin_Lyr(j,itm);//プログレスバー更新
                
                if(lyr.property("Source Text")!=null){
                    
                    if(lyr.name.search(/^\^/)==0){// ^コメントテキストの場合
                        repFlag=0;//変更有り無しフラグ
                        lyrNameTagHash=lyr.name.match(/\[([^\[\s ]+)\]/g,"");
                        var lyrname=lyr.name.slice(1).replace(/<br>/g, '\r\n');//先頭の^削除 + <br>は改行に
                        var newTxt="";mTxt="";
                        if(lyr.property("Source Text")!=null){
                            var mTxt=lyr.property("Source Text").value.text;//元テキスト保存
                        }
                        
                        if(lyrNameTagHash!=null){
                            for(k=0;k<lyrNameTagHash.length;k++){

                                //[タグ#ハッシュタグ]とハッシュタグを分離
                                hashTag=[""];lyrNameTag=[];
                                hash=lyrNameTagHash[k].match(/#/g);
                                lyrNameTag[k]=lyrNameTagHash[k];
                                if(hash!=null){
                                    hashTag=lyrNameTagHash[k].match(/#((?:(?!#|\])[^\s ])+)/g,"");// #tag1,#tag2...
                                    for(m=0;m<hashTag.length;m++){
                                        // [タグ]内ハッシュタグを削除
                                        lyrNameTag[k]=lyrNameTag[k].replace(new RegExp(hashTag[m],"g"),"");
                                    }
                                }
                                for (var row=rowStart; row<rowCount; row++){//行番号
                                    for(var col=0; col<=cellData[0].length;col++){//列番号
                                        if(lyrNameTag[k]==cellData[row][col]){// [文字列]が[セル内容]と一致した場合
                                            repFlag=1;

                                            //cellData[row][col+1]をハッシュタグ処理
                                            cellTxt=cellData[row][col+1];//一致テキストの右セルが新しいテキスト

                                            for(m=0;m<hashTag.length;m++){
                                                cellTxt=f_hashExec(cellTxt,hashTag[m]);
                                            }
                                                
                                        }// [文字列]が[セル内容]と一致した場合
                                    }//列番号
                                }//行番号

                                lyrname=lyrname.replace(lyrNameTagHash[k],cellTxt);//一致テキストの右セルが新しいテキスト

                            }//for lyrNameTagHash.length

                            newTxt=lyrname;
                        }//if lyrNameTagHash null

                        if(repFlag==1){
                            (mTxt==newTxt)?chaNge="(最新です) "+mTxt:chaNge="→ "+newTxt;
                            /////////// ^なのに一致がない場合はComも変更なし
                            Com1+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+chaNge+"\r\r";
                            //置換実行
                            lyr.sourceText.setValue(newTxt);
                        }else{
                            lyr.sourceText.setValue(mTxt)
                        }

                    }//if ^コメントテキストの場合

                    if(lyr.name.search(/^\$/)==0){// $コメントテキストの場合
                        for (var row=rowStart; row<rowCount; row++){//行番号
                            for(var col=0; col<=cellData[0].length;col++){//列番号

                                if(lyr.name==cellData[row][col]){// レイヤー名と一致した場合
                                    mTxt=lyr.property("Source Text").value.text;//元テキスト保存
                                    // "あいう<br>2行目<br>3行目"→[ " ]と<br>を適切に
                                    //一致テキストの右セルが新しいテキスト
                                    newTxt=cellData[row][col+1].replace(/^\"/g,"").replace(/^"|"$/g,"").replace(/<br>/g,"\r");
                                    if(newTxt=="")newTxt=mTxt;
                                    lyr.sourceText.setValue(newTxt);
                                    (mTxt==newTxt)?chaNge="(最新です) "+mTxt:chaNge="→ "+newTxt;
                                    Com2+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+chaNge+"\r\r";
                                }

                            }//列番号
                        }//行番号
                    }//if $コメントテキストの場合

                }// if(lyr.property("Source Text")!=null)
            
            }//for comp内全検索
        }// - [処理B]

    }//forプロジェクトウィンドウ全検索

    Com="-----<タグ変換テキスト>-----\r\r"+Com1+"\r\r-----<置換テキスト>-----\r\r"+Com2;

    if(Com==""){Com="変換箇所なし"};
        f_gresDef(ttl);
    return Com;
}


// ハッシュタグをトリガーに変換
function f_hashExec(cellTxt,hashTag){
    execText=cellTxt;
    //キャピタライズ
        if(hashTag=="#cap")execText=cellTxt.charAt(0).toUpperCase()+cellTxt.slice(1).toLowerCase();
    //アッパーケース
        if(hashTag=="#upper")execText=cellTxt.toUpperCase();
    //ローワーケース
        if(hashTag=="#lower")execText=cellTxt.toLowerCase();
    //ゼロパディング2桁
        if(hashTag=="#00")execText=String("0"+cellTxt).slice(-2);
    //ゼロパディング3桁
        if(hashTag=="#000")execText=String("00"+cellTxt).slice(-3);
    //漢数字。日付など2桁まで想定。
        if(hashTag=="#kansu"){
            KanSuuji = ["","一","二","三","四","五","六","七","八","九"];
            cellTxt1=Number(String(cellTxt).slice(0,1));

            if(cellTxt.length>1){
                cellTxt2=KanSuuji[Number(String(cellTxt).slice(1,2))]
            }else{
                cellTxt2="";
            }
            if((cellTxt1=="")||(cellTxt1==1)){
                cellTxt1="";
            }else{
                cellTxt1=KanSuuji[cellTxt1]+"十";
            }
            execText=cellTxt1+cellTxt2;
            if(isNaN(cellTxt))execText=cellTxt;
        }//kansu
    //イニシャル抜き出し
        if(hashTag=="#initial")execText=cellTxt.charAt(0).toUpperCase();
    //月の英語化
        if(hashTag=="#mth"){
            EngMonth=["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"];
            monthnum=Number(String(cellTxt))-1;
            mth=EngMonth[monthnum];
            execText=mth;
        }//mth
    //全角→半角、複数→1つ、行頭末尾削除
        if(hashTag=="#space"){
            execText=cellTxt.replace(/ /g, " ").replace(/ +/g, " ").replace(/^\s+|\s+$/g, "");
        }//space
    return execText;
}


// tsvのリンクレイヤー名とaep内のリンクレイヤー名のエラーチェック
function f_linkLayerCheck(){
    ttl="リンクNGチェック";
    cCom="";cCom1="";cCom2="";

    //プロジェクトウィンドウ全検索_コンポ内処理
    for (i=1; i<=app.project.items.length; i++){
        itm = app.project.item(i);

        // - [処理B]compなら中身も全検索
        if(itm instanceof CompItem){
            for(var j=1;j<=itm.numLayers;j++){//コンポジション内全検索
                //ファイル置換はitm.nameに、テキスト変換はlyr.nameに適用
                lyr = itm.layer(j);
                if(lyr.name.search(/^\^/)==0){// ^コメントテキストの場合
                    lyrNameTagHash=lyr.name.match(/\[([^\[\s ]+)\]/g,"");
                    //先頭の^削除 + <br>は改行に
                    var newTxt=lyr.name.slice(1).replace(/<br>/g, '\r\n');
                    var mTxt="";
                    if(lyr.property("Source Text")!=null){
                        var mTxt=lyr.property("Source Text").value.text;//元テキスト保存
                    }
                    repFlag=0;//変更有り無しフラグ
                    
                    if(lyrNameTagHash!=null){
                        for(k=0;k<lyrNameTagHash.length;k++){

                            //[タグ#ハッシュタグ]とハッシュタグを分離
                            hashTag=[""];lyrNameTag=[];
                            hash=lyrNameTagHash[k].match(/#/g);
                            lyrNameTag[k]=lyrNameTagHash[k];
                            if(hash!=null){
                                hashTag=lyrNameTagHash[k].match(/#((?:(?!#|\])[^\s ])+)/g,"");// #tag1,#tag2...
                                for(m=0;m<hashTag.length;m++){
                                    // [タグ]内ハッシュタグを削除
                                    lyrNameTag[k]=lyrNameTagHash[k].replace(new RegExp(hashTag[m],"g"),"");
                                }
                            }//if hash
                        
                            for (var row=rowStart; row<rowCount; row++){//行番号
                                for(var col=0; col<=cellData[0].length;col++){//列番号

                                    if(lyrNameTag[k]==cellData[row][col]){// [文字列]が[セル内容]と一致した場合
                                        repFlag=1;
                                        //cellData[row][col+1]をハッシュタグ処理
                                        for(m=0;m<hashTag.length;m++){
                                            //一致テキストの右セルが新しいテキスト
                                            newTxt=newTxt.replace(lyrNameTagHash[k],f_hashExec(cellData[row][col+1],hashTag[m]));
                                        }
                                    }// [文字列]が[セル内容]と一致した場合

                                }//列番号
                            }//行番号
                        
                        }//for lyrNameTagHash.length
                    }//if lyrNameTagHash null

                    if(repFlag==0){
                        cCom1+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+"→ tsvに一致する名前がありません。"+"\r\r";
                        lyr.selected=true;
                    }
                    if(lyr.name.match(/\[([^\[\s ]+)\]/g,"")==null){
                        cCom1+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+"→ []で囲まれていません。"+"\r\r";
                        lyr.selected=true;
                    }
                    if(lyr.property("Source Text")==null){
                        cCom1+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+"→ テキストレイヤーではありません。"+"\r\r";
                        lyr.selected=true;
                    }

                }// ^コメントテキストの場合

                if(lyr.name.search(/^\$/)==0){// $コメントテキストの場合
                    repFlag=0;

                    for (var row=rowStart; row<rowCount; row++){//行番号
                        for(var col=0; col<=cellData[0].length;col++){//列番号
                            if(lyr.name==cellData[row][col]){// レイヤー名と一致した場合
                                repFlag=1;
                            }
                        }//列番号
                    }//行番号

                    if(repFlag==0){
                        cCom2+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+"→ tsvに一致する名前がありません。"+"\r\r";
                        lyr.selected=true;
                    }
                    if(lyr.property("Source Text")==null){
                        cCom2+="comp\""+itm.name+"\" / \""+lyr.name+"\"\r"+"→ テキストレイヤーではありません。"+"\r\r";
                        lyr.selected=true;
                    }

                }// $コメントテキストの場合

            }//for コンポジション内全検索
        }// - [処理B]
    }//forプロジェクトウィンドウ全検索

    if(cCom1+cCom2==""){
        cCom="エラーはありません。"
    }else{
        cCom="-----<タグ変換テキスト>-----\r\r"+cCom1+"\r\r-----<置換テキスト>-----\r\r"+cCom2;
    }
    if(tsvFlag==0){
        cCom="tsvLayerがありません。"
    }

    return cCom;
}


// aep内のリンクレイヤー名収集
function f_linkLayerName(){
    ttl="リンクレイヤー名一覧";
    nCom="";nCom1=[];nCom2=[];
    //プロジェクトウィンドウ全検索_コンポ内処理
    for (i=1; i<=app.project.items.length; i++){
        itm = app.project.item(i);
        // - [処理B]compなら中身も全検索
        if(itm instanceof CompItem){
            for(var j=1;j<=itm.numLayers;j++){//コンポジション内全検索
                //ファイル置換はitm.nameに、テキスト変換はlyr.nameに適用
                lyr = itm.layer(j);
                if(lyr.property("Source Text")!=null){

                    if(lyr.name.search(/^\^/)==0){// ^コメントテキストの場合
                        lyrNameTagHash=lyr.name.match(/\[([^\[\s ]+)\]/g,"");
                        //先頭の^削除 + <br>は改行に
                        var newTxt=lyr.name.slice(1).replace(/<br>/g, '\r\n');
                        var mTxt="";

                        if(lyr.property("Source Text")!=null){
                            var mTxt=lyr.property("Source Text").value.text;//元テキスト保存
                        }
                        repFlag=0;//変更有り無しフラグ
                        
                        if(lyrNameTagHash!=null){
                            for(k=0;k<lyrNameTagHash.length;k++){
                                //[タグ#ハッシュタグ]とハッシュタグを分離
                                hashTag=[""];lyrNameTag=[];
                                hash=lyrNameTagHash[k].match(/#/g);
                                lyrNameTag[k]=lyrNameTagHash[k];
                                if(hash!=null){
                                    hashTag=lyrNameTagHash[k].match(/#((?:(?!#|\])[^\s ])+)/g,"");// #tag1,#tag2...
                                    for(m=0;m<hashTag.length;m++){
                                        // [タグ]内ハッシュタグを削除
                                        lyrNameTag[k]=lyrNameTagHash[k].replace(new RegExp(hashTag[m],"g"),"");
                                    }
                                }
                                nCom1.push(lyrNameTag[k]);
                            }//for lyrNameTagHash.length
                        }//if lyrNameTagHash null

                    }// ^コメントテキストの場合

                    if(lyr.name.search(/^\$/)==0){// $コメントテキストの場合
                        repFlag=0;
                        nCom2.push(lyr.name);
                    }// $コメントテキストの場合

                }// if(lyr.property("Source Text")!=null)

            }//for comp内全検索
        }// - [処理B]
    }//forプロジェクトウィンドウ全検索

    f_arrayUnique(nCom1);//重複削除
    f_arrayUnique(nCom2);//重複削除

    if(String(nCom1)+String(nCom2)==""){
        nCom="リンクレイヤーが見つかりません。"
    }else{
        nCom="-----<^[タグ]変換テキスト>-----\r\r"+nCom1.sort().join("\r")+"\r\r-----<$置換テキスト>-----\r\r"+nCom2.sort().join("\r");
    }
return nCom;
}


//配列ソート-重複削除
function f_arrayUnique(ary){
    var i, j, len;
    i=0;
    len=ary.length;
    while(i < len){
        j=i+1;
        while(j < len){
            if(ary[j] === ary[i]){
                ary.splice(j, 1);
                len--;
            }else{
                j++;
            }
        }//while j
        i++;
    }//while i
}


/*
===============
--- おまけ機能 ---
===============
*/

//プログレスバー準備
function f_gresBar(ttl){
    wobj.grePnl.text=ttl+"中";
    greCnt=0;resumeNum=0;

    //プロジェクトウィンドウ全検索_プログレスバー用レイヤー数把握
    for (i=1; i<=app.project.items.length; i++){
        itm = app.project.item(i);
        if(itm instanceof CompItem)greCnt+=itm.numLayers;
    }//プログレスバー用レイヤー数把握
}//function f_gresBar

//プログレスバー回し_全プロジェクトアイテム数基準
function f_greSpin_Itm(i,itm){
    wobj.grePnl.bar.Dialog.value = i/app.project.items.length*100; //プログレスバーの進行状況更新(全体の進行状況) 
    wobj.grePnl.info.st.text=Math.floor(i/app.project.items.length*100)+"% - 完了\r"+itm.name;
}

//プログレスバー回し_全レイヤー数基準
function f_greSpin_Lyr(j,itm){
    wobj.grePnl.bar.Dialog.value = (j+resumeNum)/greCnt*100; //プログレスバーの進行状況更新(全体の進行状況) 
    wobj.grePnl.info.st.text=Math.floor( (j+resumeNum)/greCnt*100 )+"% - 完了\r"+lyr.name;
    if(j==itm.numLayers){resumeNum+=itm.numLayers};//プログレスバー用
}//function greSpin

function f_gresDef(ttl){
    //プログレスバーの進行状況更新(全体の進行状況) 
    wobj.grePnl.bar.Dialog.value = 0;
    wobj.grePnl.info.st.text=ttl+" 完了\r";
    wobj.grePnl.text=gretxDef;
}

function GUI_win1(ttl,size,st){
    var w = new Window("dialog",scName+ttl);
    w.center();
    var st1 = w.add("statictext", size, "", {multiline: true } );
    st1.text=st;
    w.show();
}

function GUI_win2(ttl,size,st){
    var w = new Window("palette",scName+ttl);
    w.center();
    var st1 = w.add("edittext", size, "", {multiline: true } );
    st1.text=st;
    w.aa=function(){}
    w.show();
}

以前の記事の03-1.前半の処理:tsvをセルごとに分けた風の(JSON風)配列にすると考え方は同じ。「tsv」コンポジション内のタブ区切りテキストである「[^]tsvLayer」「[$]tsvLayer」それぞれをタブと改行でsplitし、スプレッドシートライクな配列に分けて格納します。1セルが1配列に入るイメージです。A1セルがcellData[0][0]に、B1セルがcellData[0][1]に、A2セルがcellData[1][0]に…分けます。

tsvテキストをスプレッドシートのセル風に配列にする部分

個別に。

//tsvコンポジション tsvLayerテキストレイヤーがあれば読み込み
function f_tsvToCell(tsv){

後半でtsvに使うテキストレイヤーの簡易チェックも入れました。

1セル(配列)ごとにソーステキストに適した状態に整形する部分

aep内のコンポジションを全検索し、適応するテキストレイヤー名があれば一つ右のセルであるcellData[n][n+1]をソーステキストに引っ張ってくるのは前回と同じ。拡張した機能として「^」で始まるレイヤー名のテキスト処理「タグ変換モード」と、付随する「ハッシュタグでの整形機能」を実行する処理を追加しています。

下記27行目からの if(lyrNameTagHash!=null){ 内で、一旦ハッシュタグ付きの文字列とハッシュタグを分離し、ハッシュタグに応じた変換処理を行い、戻します。

function f_comTxtReplace(){
    ttl="コメント変換";

後半の「$」から始まるレイヤー名のテキスト処理は単純な入れ替えなので前回のアイディアと同じです。

オプション機能1:tsv確認

tsvコンポジションを開かなくても簡易的に確認できたら便利かなと思い実装しました。特に便利さは理解できませんでした。失敗です。

オプション機能2:リンクレイヤー確認

aep内で準備したスプレッドシートとリンクさせたいレイヤー名を一覧化するスクリプト。わざわざレイヤー名を一つずつ抜き出していては気が狂うので、これで開いたウィンドウから内容をスプレッドシートにコピペすれば解決。

置換用の「$テキスト」とタグ変換の「^[タグ]」テキストを分けた上で被りを解決して列挙します。

オプション機能3:リンクNGチェック

aep内のリンク用レイヤー準備が中途半端になっているものをリストアップします。例えば”^”で始まるレイヤー名なのに”[]”で囲まれているタグが無い場合や、aep内にリンク用のテキストレイヤー名が見つかったのにtsvレイヤーに使われてない場合など。レイヤーも選択状態になるので、修正しやすいかなと思います。

スプレッドシート準備がちゃんと行われているかの逐次チェックと、最終的にaepに準備したリンクレイヤーとスプレッドシートが一致してるかのチェック用に使えます。

ダウンロード

架空童謡15「虫歯の菌にも五分の魂」前のページ

チュートリアル動画用の曲を作りました「CHOO TORIAL!」次のページ

ピックアップ記事

  1. フリーランスの開業届提出は開業freeeでとにかく簡単に
  2. なぜ?After Effectsのレイヤーをエクスプレッションで効率化
  3. amazonのスポンサー商品(広告)を非表示にするブックマークレット「amazO…
  4. YouTubeで一時停止中のコントローラーを非表示にするブックマークレット
  5. なぜ?After Effectsの操作を「スクリプト」で効率化

関連記事

  1. スクリプト

    選択レイヤーのソーステキストを編集するスクリプト「textLayerEditor.jsx」

    選択した全テキストレイヤーのソーステキストを取り込み、テキストエリア上…

  2. スクリプト

    アイソメトリックビューを簡易的に実現するスクリプト「isometricCamera.jsx」

    インフォグラフィックスにも相性のいいアイソメトリックビュー風カメラを手…

  3. スクリプト

    ウィグルを簡単に適用するスクリプト「posWigglr.jsx」

    位置へのウィグルエクスプレッション適用時に、スライダーを噛ませることで…

  4. スクリプト

    裏面を非表示にするスクリプト「hideBackface.jsx」

    「3Dレイヤーの裏面を非表示にするエクスプレッション」をスクリプト化…

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

CAPTCHA