Electron でファイル名を連番でリネームするアプリをつくる(ダウンロード可)

さてさて、少し前に公開した記事 Electron 画像をドラッグ&ドロップするだけでリサイズするアプリをつくる を書いていて感じていたことがあります。

それは、「Electronを使えば日々の業務を効率化するデスクトップアプリを簡単につくれる!」というものでした。

ここで重要なのが、「簡単に」という部分です。

もちろんデスクトップアプリはプログラミング言語の王者C言語やJavaを使えばネイティブで高速なアプリをつくることはできますが、さすがにElectronほど敷居が低いものではありません。

業務改善アプリを作ろうとして時間がなくなり、他の作業に影響がでてしまうようなことはしたくはありませんよね。

そこで、今回のテーマ「連番リネーム」するアプリをElectronでつくってみることにしました。

ぜひElectronの学習に役立ててくださいね。

※ 開発環境: Electron 2.0

開発するアプリの内容

今回は開発するElectronアプリの詳細は次のとおりです。

  • フォルダを選択したら、その中にあるファイルすべてをリネームする
  • ただし、拡張子が複数ある場合は、その拡張子ごとに連番を振っていく(※1)
  • 連番の数字を何桁にするのかを選べるようにする
  • すでに同名のファイルがあっても上書きしない(※2)
  • 並べ替え順は、名前(昇順/降順)と変更日時(昇順/降順)

※1: 次のように連番を振っていきます。

  • 001.png, 002.png, 003.png ・・・
  • 001.jpg, 002.jpg, 003.jpg ・・・

※2: つまり、ファイル数が減ることはありません。

では、実際に開発をしていきましょう!

Electronをインストールする

インストールは、初心者向き!electron で簡単なメモ帳をつくってみよう“electronのインストール” に詳しく書いてあるので、そちらを見てください。

フォルダ名はrename_filesです。

アプリの設定をする

main.jsを開いてアプリの設定をします。
ウィンドウは画面トップ中央で開くようにします。(詳しい説明を見たい人は、Electron 画像をドラッグ&ドロップするだけでリサイズするアプリをつくる “アプリの設定をする” をご覧ください。)

const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow

let mainWindow

function createWindow () {
    
    const screen = electron.screen.getPrimaryDisplay()
    const width = 350
    const height = 300
    const x = parseInt(screen.workAreaSize.width*0.5 - width*0.5)

    mainWindow = new BrowserWindow({
        width: width,
        height: height,
        x: x,
        y: 0,
        autoHideMenuBar: true
    })

// 残りは省略

レイアウトをつくる

では、まずはアプリのレイアウトを作っていきます。
今回もレイアウトにはbootstrap 4を使うので以下のコマンドでインストールします。

npm i bootstrap --save

そして、bootstrapを使ってHTMLでレイアウトを作ります。

<link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css">
<div id="app" class="container">
    <div style="padding:15px 0;">
        <label>
        <span class="btn btn-light">
            フォルダを選択
            <input type="file" style="display:none" webkitdirectory>
        </span>
        </label>
    </div>
    <table class="table">
        <tr>
            <td>連番数字:</td>
            <td>
                <select class="form-control">
                    <option>4桁</option>
                </select>
            </td>
        </tr>
        <tr>
            <td>並べ替え順:</td>
            <td>
                <select class="form-control">
                    <option>名前(昇順)</option>
                </select>
            </td>
        </tr>
    </table>
    <div class="text-muted">(ここにメッセージ表示)</div>
    <button style="position:absolute;right:15px;bottom:15px;" class="btn btn-danger">開始する</button>
</div>

これを実行するとこうなります。

JavaScript部分をつくる

Vueの基本形をつくる

今回もJavaScriptフレームワークにVueを使いますので次のコマンドでインストールしましょう。

npm i vue --save

そして、index.html内にidをつけVueを読み込みます。

<div id="app" class="container">
<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>

    new Vue({
        el: '#app'
    })

</script>

フォルダ選択部分

ではアプリの一番上にある “フォルダを選択” ボタンがクリックされ、リネームするフォルダが選択されたときのコードです。

まず、dataに選択されたフォルダのパスを格納する変数をつくります。

data: {
    folderPath: ''
},

そして、<input>タグにchangeイベントをつけ、onFolderChange()が実行されるようにします。

<input type="file" style="display:none" @change="onFolderChanged($event)" webkitdirectory>

最後にonFolderChanged()の本体です。

methods: {
    onFolderChanged(e) {
        const files = e.target.files
        if(files.length > 0) {
            const folder = files[0]
            this.folderPath = folder.path
        } else {
            this.folderPath = ''
        }
    }
}

この中では、フォルダが選択させれていれば、folderPathにそのパスを、そうでなければ空白を格納しています。

連番数字を選択する部分

続いて、連番数字を選択する部分です。

まずはdataに選択されている桁を管理するdigit(初期値4)とその選択肢digitOptionsを追加します。

data: {
    folderPath: '',
    digit: 4,
    digitOptions: [3, 4, 5, 6]
},

次にdigit<select>タグへバインディングします。

<select class="form-control" v-model="digit">

そして、最後にv-forを使って選択肢を作ります。

<option v-for="digitOption in digitOptions" :value="digitOption">{{ digitOption }}桁</option>

これで以下のような選択肢ができます。

並べ替え順を選択する部分

こちらもまず、選択された並べ替えタイプを管理するsortとその選択肢のsortOptionsを変数登録します。

data: {
    // 省略
    sort: 0,
    sortOptions: [
        { value: 'name asc', text: '名前(昇順)' },
        { value: 'name desc', text: '名前(降順)' },
        { value: 'modified asc', text: '変更日時(昇順)' },
        { value: 'modified desc', text: '変更日時(昇順)' },
    ]
},

そして、<select>タグにsortをバインディングさせ、

<select class="form-control" v-model="sort">

v-forを使って選択肢をつくります。

<option v-for="(sortOption,index) in sortOptions" :value="index" v-text="sortOption.text"></option>

これを実行するとこうなります。

メッセージ部分をつくる

続いてリネームの完了やエラーメッセージを表示する部分をつくっていきましょう。

といってもdatamessageを登録し、それをバインディングするだけです。

data: {
    // 省略
    message: ''
},
<div class="text-muted" v-text="message"></div>

リネームする部分をつくる

では本題のファイル名をリネームする部分です。

クリックイベントをつける

まずはボタンがクリックされたときのクリックイベントでrename()が実行されるようにします。

<button style="position:absolute;right:15px;bottom:15px;" class="btn btn-danger" @click="rename()">開始する</button>

そして、rename()内は次のようにします。

rename() {
    this.message = ''
    if(this.folderPath == '') {
        this.message = '[エラー] フォルダを選択してください。'
    } else if(confirm('連番リネームを実行しますか?')) {
        // ここでファイル名を変更
    }
},

ちなみに、フォルダが選択されてない場合はエラーが出るようになっています。

指定されたフォルダから全ファイル名を取得する

では、指定されたフォルダからすべてのファイル名を取得し、さらに選択された並び替え順でそのファイル名を並べかえたリストを取得してみましょう。

まずはElectron(NodeJS)でファイルとパスを操作するパッケージfspathを読み込みます。

const fs = require('fs')
const path = require('path')

そしてファイル名を取得するメソッドは、getFileNames()です。

rename() {
    this.message = ''
    if(this.folderPath == '') {
        this.message = '[エラー] フォルダを選択してください。'
    } else if(confirm('連番リネームを実行しますか?')) {
        const fileNames = this.getFileNames()
    }
},

そして次がその中身ですが、少し長いコードになったため、getFileNames()本体と、並べ替えをするsortFiles()に分割しています。それぞれ見ていきましょう。

(getFileNames)

getFileNames() {
    const dir = this.folderPath
    const files = fs.readdirSync(dir)
        .filter((name) => {
            return (fs.statSync(dir +'/'+ name).isFile())
        })
        .map((name) => {
            return {
                name: name,
                time: fs.statSync(dir +'/'+ name).mtime.getTime()
            }
        })
    const sortedFiles = this.sortFiles(files)
    let fileNames = []
    sortedFiles.map((file) => {
        fileNames.push(file.name)
    })
    return fileNames
},

まず、fs.readdirSync()を使ってファイル名を取得します。ただし、このファイル名リストの中にはフォルダも含まれているのでfilterを使ってフォルダ形式のものを除外。そして、並べ替えのためにname(ファイル名)とtime(変更日時)を持ったデータへ変換します。

そして、sortFiles()で並べ替えをして(次の項目で詳しく説明します)、最後にそこからファイル名だけを抜き出してreturnしています。

(sortFiles)

続いて並べ替え部分です。

sortFiles(files) {
    const sort = this.sortOptions[this.sort].value
    let sorted
    if(sort == 'name asc') {
        sorted = files.sort((a, b) => {
            return (b.name < a.name) ? 1 : -1
        })
    } else if(sort == 'name desc') {
        sorted = files.sort((a, b) => {
            return (b.name < a.name) ? -1 : 1
        })
    } else if(sort == 'modified asc') {
        sorted = files.sort((a, b) => {
            return a.time - b.time
        })
    } else if(sort == 'modified desc') {
        sorted = files.sort((a, b) => {
            return b.time - a.time
        })
    }
    return sorted
}

まず始めに並べ替えの方式(例えば、name ascなど)を取得し各並べ替え計算を行っています。

上から、

  • 名前(昇順)
  • 名前(降順)
  • 変更日時(昇順)
  • 変更日時(降順)

です。

リネームする

では、実際にファイルをリネームする部分です。

// 省略

const fileNames = this.getFileNames()
const dir = this.folderPath
let numbers = {}
for(let fileName of fileNames) {
    let originalPath = dir +'/'+ fileName
    let tempPath = dir +'/temp-'+ fileName
    fs.renameSync(originalPath, tempPath)
}
for(let i in fileNames) {
    let fileName = fileNames[i]
    let originalPath = dir +'/temp-'+ fileName
    let extension = path.extname(fileName)
    if(numbers[extension] == undefined) {
        numbers[extension] = 0
    }
    numbers[extension]++
    let fileNumber = numbers[extension]
    let newFileName = this.numberFileName(fileNumber, extension)
    let newPath = dir +'/'+ newFileName
    fs.renameSync(originalPath, newPath)
}
this.message = 'リネームが完了しました!'
// 省略

まず、最初のforループで一旦全てのファイル名にtemp-をつけてリネームします。これは、新しい名前と同名のファイルがあると上書きされてしまうため、これを回避するための処理です。(本当はforループは1回でいけますが、どうしても説明が難しくなってしまうので、よりシンプルな方法を選びました。もし処理スピードをあげたい場合は、ご自身でリファクタリングしてください。m_ _m)

そして、新しい番号をつけたファイル名にリネームします。

ここで、重要なのが、numbersというオブジェクトです。
これは、拡張子ごとに番号を管理していて、同じ拡張子のファイルが見つかると数字を1つ足してそこから新しいファイル名をつくるようにしています。

{
    '.jpg': 1,
    '.png': 5,
    '.pdf': 3
}

教材ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、あくまでプログラムの紹介のために作成したものですので、実際問題あまりテストしていません。そのため重要なファイルでは試さないようお願いいたします。

Electron でファイル名を連番リネーム