ファイルアップロード・ダウンロード =============== ## ファイルアップロード Webページ上で動作するプログラムを開発していると,ローカルに有るファイルをアップロードしたい場面があります.そこで,ローカルファイルをアップロードして,Webページ内のJavaScriptからファイル内のデータを取り扱う方法を説明します.まず,ファイルをアップロードするためのinput要素を記述します. ```html hl ``` 実際には,以下のように表示されます. 続いて,以下のように要素を取得しておきます. ```javascript runnable console editable // 要素の取得 var element = document.querySelector( '#file_form' ); console.log( element ); ``` 次に,上記input要素に対してファイルがドロップされたことを示す```change```イベントの処理を記載します.※以下のプログラムを実行しても,その時点では何も表示されません.上にあるinput要素にファイルをドラッグ&ドロップすると,その情報が表示されます. 処理の流れとしては,(1) input要素にファイルがドロップされる → (2) ファイルを読み取る → (3) 予め書いておいたファイルの読み取りが終わった際の処理という順に実行されます.(3)の内部処理では,読み取ったファイルの内容はreader.resultに代入されています.下記サンプルでは,ファイルの内容を表示しています. ```javascript runnable console editable // (1) input要素にファイルがドロップされた場合の処理 element.addEventListener('change', async (ev) => { var file = element.files[0]; // input要素のfilesがファイル情報です.複数ファイルに対応するため,配列になっています. var reader = new FileReader(); // (3) 下にある(2)の処理が終わった=ファイルを読み取った後の処理. reader.addEventListener('load', () => { console.log( reader.result ); // 読み取ったファイルの内容を表示 }); // (2) 実際にフィアルを読み取る処理 reader.readAsText(file); }); ``` ## バイナリファイルアップロード テキストファイルをアップロードできるようになったので,次はバイナリファイルをアップロードしてみたいと思います. テキストファイルと違いArrayBufferとして読み込むため,Viewを介してアクセスする必要があります. まずは以下のようにinput要素を作成します. ```html hl ``` 実際には,以下のように表示されます. 続いて,以下のように要素を取得しておきます. ```javascript runnable console editable // 要素の取得 var element2 = document.querySelector( '#binary' ); console.log( element2 ); ``` 次に,上記input要素に対してファイルがドロップされたことを示す```change```イベントの処理を記載します.※以下のプログラムを実行しても,その時点では何も表示されません.上にあるinput要素にファイルをドラッグ&ドロップすると,その情報が表示されます. 処理の流れとしては,(1) input要素にファイルがドロップされる → (2) ファイルを読み取る → (3) 予め書いておいたファイルの読み取りが終わった際の処理という順に実行されます.(3)の内部処理では,読み取ったファイルの内容はreader.resultに代入されています.1番目のプログラムでは,テキストデータとして読み込んでいましたが,今回はArrayBufferとして読み込んでいます.ArrayBufferは,そのままでは利用できないため,Viewとして符号なし8bit整数(Unsigned Int 8bit)を指定します.下記サンプルでは,ファイルの先頭10バイトを表示しています. ```javascript runnable console editable // 読み込んだデータを入れておく変数 var data2; // (1) input要素にファイルがドロップされた場合の処理 element2.addEventListener('change', async (ev) => { var file = element2.files[0]; // input要素のfilesがファイル情報です.複数ファイルに対応するため,配列になっています. var reader = new FileReader(); var size = file.size; // ファイルサイズ data = new ArrayBuffer( size ); // ArrayBufferの宣言 // (3) 下にある(2)の処理が終わった=ファイルを読み取った後の処理. reader.addEventListener('load', () => { data2 = reader.result; let view = new Uint8Array( data2 ); // viewの指定 for( let i=0; i < 10; i++ ) { console.log( view[i] ); } }); // (2) 実際にフィアルを読み取る処理 reader.readAsArrayBuffer(file); }); ``` ## アップロードされたオーディオデータを再生する&グラフを描く バイナリファイルをアップロードできるようになったので,wavやmp3などのオーディオデータがアップロードされた後に,音声を再生したりグラフ描画を行いたいと思います. inputタグは特に変えなくてもよいのですが,受付できるファイルタイプ(MIMEタイプ)を指定できるので,ここではaudioデータを指定します.なお,MIMEタイプは,通常```audio/wav```や```audio/mpeg```のように指定しますが,inputタグの場合は/の左側だけ指定することができます.この指定は,「ファイルを選択」ボタンを押した際にオーディオデータのみ表示されるようになりますが,ドラッグ&ドロップに対しては特に効果はありません. まずは以下のようにinput要素を作成します.また,音声データを再生するボタン,および,グラフ描画するボタンを付けておきます. ```html hl ``` 実際には,以下のように表示されます.下の方に有る4つのプログラムすべてを「Run」してから,上のinput要素に音声データをドロップしてください. ```javascript runnable console editable // 要素の取得 var element3 = document.querySelector( '#audio1' ); var play1 = document.querySelector( '#play1' ); var draw1 = document.querySelector( '#draw1' ); var graph1 = document.querySelector( '#graph1' ); console.log( [element3, play1, draw1, graph1] ); ``` バイナリファイルを読み込むところまでは,先の例と同じですが,今回は表示せずにdata3に代入するところまで行います. ```javascript runnable console editable // 読み込んだデータを入れておく変数 var data3; // ArrayBuffer型 var buffer3; // AudioBuffer型 // (1) input要素にファイルがドロップされた場合の処理 element3.addEventListener('change', async (ev) => { var file = element3.files[0]; // input要素のfilesがファイル情報です.複数ファイルに対応するため,配列になっています. var reader = new FileReader(); var size = file.size; // ファイルサイズ console.log( "size=" + size ); // (3) 下にある(2)の処理が終わった=ファイルを読み取った後の処理. reader.addEventListener('load', () => { data3 = reader.result; // data3の型はArrayBufferです }); // (2) 実際にフィアルを読み取る処理 reader.readAsArrayBuffer(file); }); ``` 続いて,「再生する」ボタンが押された際に,読み込んだファイルを音声に変換して再生する処理を記述します.特にチェックをしていないですが,ファイルを読み込んでからボタンをクリックするようにしてください. ```javascript runnable editable // 再生するボタンがクリックされた際の処理 play1.addEventListener('click', () => { let audioCtx = new window.AudioContext || window.webktAudioContext(); let source = audioCtx.createBufferSource(); // decodeAudioDataで音声ファイルを音声データに変換します audioCtx.decodeAudioData( data3 ) .then( buffer => { buffer3 = buffer; // buffer3の型はAudioBufferです // 以下は音声を再生する処理です.再生する必要がなければ不要です. source.buffer = buffer; source.connect( audioCtx.destination ); source.start(); }); }) ``` ここまでの処理で,buffer3に音声データが代入されました.buffer3はAudioBuffer型なので,内部的に以下の情報を持ちます. 変数名 | 内容 --|-- sampleRate | サンプリングレート(1秒あたりのサンプル数) length | データ数 duration | 音声データの秒数 numberOfChannels | チャンネル数 ```javascript runnable console editable // 描画するボタンが押された際の処理 draw1.addEventListener('click', () => { console.log( "データ数 = " + buffer3.length ); console.log( "サンプリングレート = " + buffer3.sampleRate + "Hz" ); console.log( "秒数 = " + buffer3.duration + " 秒" ); console.log( "チャンネル数 = " + buffer3.numberOfChannels ); let size = buffer3.length; let wave = buffer3.getChannelData(0); // 最初のチャンネルのデータを取得 let ctx = graph1.getContext('2d'); ctx.beginPath(); ctx.moveTo( 0, 128 ); let x = 0; for( i=0; i < 65536*2; i+=2*65536/512 ) { ctx.lineTo( x, 128-wave[i]*256 ); x+=1; } ctx.stroke(); }); ``` ## アップロードされたオーディオデータをテキスト形式で取得する wavやmp3などのオーディオファイルを読み込んで個々のデータを取得できるようになったので,次はそのデータをテキスト形式でダウンロードしてみましょう. まずは以下のようにinput要素を作成します.また,データ変換用のボタンや音声データをダウンロードするリンク(a要素)を設置するためのdiv要素を付けておきます. ```html hl ``` 実際には,以下のように表示されます.リンク用のdiv要素は表示されません. まずは,以下の3つのプログラムを実行して,オーディオデータをアップロードしてください.その後,変換するボタンをクリックすると,チャンネルごとにテキスト化されたデータをダウンロードすることができます. なお,ダウンロードするデータは,-1.0〜1.0の実数をテキスト化しているため,若干精度が悪いです. まずは要素を取得しておきます. ```javascript runnable console editable // 要素の取得 var element4 = document.querySelector( '#audio2' ); var convert1 = document.querySelector( '#convert1' ); console.log( [element4, convert1] ); ``` バイナリファイルを読み込むところまでは,先の例と同じですが,今回は表示せずにdata4に代入するところまで行います. ```javascript runnable console editable // 読み込んだデータを入れておく変数 var data4; // (1) input要素にファイルがドロップされた場合の処理 element4.addEventListener('change', async (ev) => { var file = element4.files[0]; // input要素のfilesがファイル情報です.複数ファイルに対応するため,配列になっています. var reader = new FileReader(); var size = file.size; // ファイルサイズ data = new ArrayBuffer( size ); // ArrayBufferの宣言 console.log( "size=" + size ); // (3) 下にある(2)の処理が終わった=ファイルを読み取った後の処理. reader.addEventListener('load', () => { data4 = reader.result; }); // (2) 実際にフィアルを読み取る処理 reader.readAsArrayBuffer(file); }); ``` 続いて,「変換する」ボタンが押された際に,読み込んだファイルを変換してダウンロード用のリンクを生成する処理を記述します.特にチェックをしていないですが,ファイルを読み込んでからボタンをクリックするようにしてください. ```javascript runnable editable console // 再生するボタンがクリックされた際の処理 console.log(convert1); convert1.addEventListener('click', async () => { let audioCtx = new window.AudioContext || window.webktAudioContext(); let source = audioCtx.createBufferSource(); // decodeAudioDataで音声ファイルを音声データに変換します audioCtx.decodeAudioData( data4 ) .then( buffer => { let output = ''; // チャンネル数分のデータとダウンロード用のリンクを作成します. for( let i=0; i < buffer.numberOfChannels; i++ ) { let size = buffer.length; let wave = buffer.getChannelData(i); // データ数の分だけ繰り返してテキスト化します. for( data of wave ) { output += data + '\n'; } // Blob型のデータを作ります.内容は変数outputで,CSV形式とします. let blob = new Blob([output], {type:'text/csv'}); let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'chan_' + i + '.csv'; link.text = 'チャンネル' + i; let links = document.querySelector('#links'); links.appendChild( link ); } }); }) ```