はじめに
こんにちは!qiita初投稿です。
自分は組み込み系を2年くらいやっているプログラマーです。
仕事では主にvisual c++ や c#.netを使ってます。
元々web系に興味があったので、組み込み系で行ったデスクトップアプリ制作の経験が活かせるelectronを勉強しています。
ある程度勉強して、node-lameというライブラリを使ってwavファイルをmp3にエンコードするちょっとしたアプリを作ってみたので解説してみます。
node-lameとは
lameというオープンソースのエンコーダーをnodejs環境で使えるようにしたライブラリのようです。
wavやmp2をmp3にエンコードしたり、デコードしたりと音声ファイルの変換ができるみたいです。
(node-lame公式画像)
wavなどの音声ファイルはヘッダー部分、データの部分とフォーマットが決まっていて、プログラムで読み込むときはヘッダーがどこまでか、データ部はどんな形式でサイズはどうなのかを考えて読み込むことになります。
データを正確に読み込むのはかなり大変でそれを変換するとなると一つなにかがずれるだけでエラーが起きたりしてかなり大変です。
このライブラリを使うだけで安全に読み込んでくれるのでかなり便利ですね。
基本的な使い方
エンコードのサンプルコードをちょこっと解説します。
1.まずrequireでnode-lameを読み込む
const Lame = require("node-lame").Lame;
2.出力先のパスとファイル名、ビットレートなどを設定してインスタンスを作成する
output:のあとにパスとファイル名を設定します。ここに適当な変数をセットしておくと、guiでユーザーが設定したパスとかを使えますね。
const encoder = new Lame({
output: "./audio-files/demo.mp3",
bitrate: 192,
}).setFile("./audio-files/demo.wav");
3.encode()またはdecode()で変換する
then()で異常系を書いておくと、成功したら「エンコードが終わりました」的なメッセージを出して、失敗したらエラーメッセージを出したりできますね。
encoder
.encode()
.then(() => {
// Encoding finished
})
.catch((error) => {
// Something went wrong
});
作ったもの
electronといえば、atom,vscodeなどですね。
組み込み系のプログラマーとしてはラズベリーパイのosの焼き込みに仕えるetcherがelectronを使ったソフトとして思い浮かびます。
etcherのUIはすごくシンプルで好きなので、etcherを参考にシンプルなUIを作ってみました。
githubリポジトリ
完全に見た目通りです。「SELECT WAV FILES」でwavファイルをダイアログで選択します。
それから 「ENCODE TO MP3!」 をクリックしたら選択したwavファイルがmp3に変換されて、出力先のパスに保存されます。
ソース
大まかな動作の流れはmainWindow.htmlからボタンの入力をmain.jsが受け取り、ダイアログを開いたり、変換をする。
変換が上手くいったらメッセージを表示するです。
main.js
const electron = require("electron");
const url = require('url');
const Lame = require("node-lame").Lame;
const {app, BrowserWindow, ipcMain, dialog, Menu} = electron;
const path = require('path');
let { cnvfilename,filename} = '';
let mainWindow;
app.on('ready', () => {
mainWindow = new BrowserWindow({
width: 650,
height: 500,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// Load html into window
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'mainWindow.html'),
protocol:'file:',
slashes: true
}));
// Quit app when closed
mainWindow.on('closed', function(){
app.quit();
});
mainWindow.removeMenu();
});
// Catch Convert
ipcMain.on('Convert',function(e){
// Replace wav to mp3 (WIP)
filename = filename.replace('wav', 'mp3');
// Set output location
const output = "./output/" + filename;
// Set target file by path
const encoder = new Lame({
output: output,
bitrate: 128,
}).setFile(cnvfilename);
// Encode by node-lame
encoder.encode()
.then(() => {
// Encoding finished
console.log("success");
e.reply('convert-reply', 'complete!')
})
.catch((error) => {
// Something went wrong
console.log(error);
});
});
// File-open dialog
ipcMain.handle('file-open', async (event) => {
const filepaths = dialog.showOpenDialogSync(mainWindow, {
buttonLabel: 'open',
filters: [
{ name: 'Audiofiles', extensions: ['audiofiles', 'wav'] },
],
properties:[
'openFile',
'createDirectory',
]
});
// when closes dialog without opening a file
if( filepaths === undefined ){
return({status: undefined});
}
// get files contents
try {
const filepath = filepaths[0];
cnvfilename = filepath;
filename = path.basename(filepath);
return({
status: true,
path: path,
name: filename
});
}
catch(error) {
return({status:false, message:error.message});
}
});
mainWindow.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>AudioConv</title>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="./css/main.css">
</head>
<body>
<div class="container">
<form>
<div class="row pt-160">
<div class="col s6 center-align">
<i class="medium material-icons icon-white">add_box</i>
<pre class="m-12" id="input-file"> </pre>
<button id="filesSelect" type="button" class="waves-effect waves-light btn">Select Wav files</button>
</div>
<div class="col s6 center-align">
<i class="medium material-icons icon-white">cached</i>
<pre class="m-12" id="after-convert"> </pre>
<button id="convert" type="button" class="waves-effect waves-light btn">Encode to MP3!</button>
</div>
</div>
</form>
</div>
<script>
const electron = require('electron');
const { ipcRenderer } = electron;
// Get html elements
const form = document.querySelector('form');
const BtnFilesSelect = document.getElementById('filesSelect');
const BtnConvert = document.getElementById('convert');
// Adding Eventlistner to each button
BtnFilesSelect.addEventListener('click', filesSelect);
BtnConvert.addEventListener('click', convert);
// File open dialog event
function filesSelect(e){
ipcRenderer.invoke('file-open')
.then((result) => {
if(result.status == undefined){
return(false);
}
if(!result.status){
alert('Could not open the file\n${result.message}');
return(false);
}
document.getElementById('input-file').innerHTML = result.name;
})
}
// Convert button
function convert(e){
ipcRenderer.send('Convert');
}
// Show complete message when converting is finished
ipcRenderer.on('convert-reply', (e, message) => {
document.getElementById('after-convert').innerHTML = message;
})
</script>
</body>
</html>
今後やりたいこと
とりあえずまずは変換できるファイルを増やしたいですね。
それからまだipcRendererやipcMainの仕様をしっかり把握していないのでその辺を掘り下げていきたいですね。
↧