Teams会議で発言時間を集計する (VTT対応版)

# 会議の PDCA 

「相手の話を聞けていたかな？」
「ファシリテーション、うまく回っていたかな？」
「与えられた時間内で発表できたかな？」

確認してみよう (**C**heck)

お試しは [こちら](https://keen-souffle-6e6b20.netlify.app/) からどうぞ

# 手順

1. [コード](https://qiita.com/Tomonobu3110/items/47c7652eed62c0cffb51#%E3%82%B3%E3%83%BC%E3%83%89) をテキストエディタなどにコピペしてHTMLファイル形式で保存する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/2e9c5b78-99b2-2114-d350-33fef3142693.png)

1. 会議で 文字起こしを開始 する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/8ca349f2-7b3e-30b8-4049-b0cf33f26d52.png)

1. 会議が終われば、文字起こしを .vttとしてダウンロード する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/25650578-8acb-c044-4d4c-298c94365375.png)

1. 1 で保存したHTMLをブラウザで開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/7fd89d5c-a2c6-f618-06e7-6d953a6f487c.png)

1. ファイルを選択 を押して、3 で保存したVTTファイルを指定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/be2edcd7-8a40-a999-0a00-5cb96314e022.png)

以上

# コード

```HTML
<!DOCTYPE html>
<html>
<head>
  <title>Teams会議 発話時間統計</title>
<style>
th, td {
  border: 1px solid gray;
  padding: 4px;
}
table {
  border-collapse: collapse;
}
</style>
</head>
<body>
  <input type="file" accept=".txt,.vtt" id="fileInput">
  <p><b>ファイル統計</b></p>
  <div id="statistics"></div>
  <p><b>発話時間統計</b></p>
  <div id="speaking"></div>

<script>
document.getElementById('fileInput').addEventListener('change', handleFileUpload);

function handleFileUpload(event) {
  const file = event.target.files[0];

  const reader = new FileReader();
  reader.onload = function(event) {
    const fileContent = event.target.result;
    parseAndDisplayStatistics(fileContent);
  };
  reader.readAsText(file);
}

function parseAndDisplayStatistics(fileContent) {
  const lines = fileContent.split('\n');
  const lineCount = lines.length;
  const wordCount = countWords(lines);

  const statisticsDiv = document.getElementById('statistics');
  statisticsDiv.innerHTML = `行数: ${lineCount}<br>単語数: ${wordCount}`;

  const hash = lines[0].startsWith("WEBVTT") ? countSpeakingTimeVtt(lines) : countSpeakingTimeTxt(lines);
  const sorted = Object.fromEntries(Object.entries(hash).sort((a, b) => b[1] - a[1]));
  console.log(hash);
  console.log(sorted);

  var html = '<table><tr><th>発話者<th>発話時間<th>ms</tr>';
  for (let key in sorted) {
    if (hash.hasOwnProperty(key)) {
      const value = hash[key];
      // ハッシュの各要素に対して行いたい処理をここに記述
      const hours   = Math.floor( value / (1000 * 60 * 60));
      const minutes = Math.floor((value % (1000 * 60 * 60)) / (1000 * 60));
      const seconds = Math.floor((value % (1000 * 60)) / 1000);
      const remainingMilliseconds = value % 1000;
      html += `<tr><td>${key}<td>${hours}時間 ${minutes}分 ${seconds}秒<td align=right>${value} ms</tr>`;
      console.log(`Key: ${key}, Value: ${value}`);
    }
  }
  document.getElementById('speaking').innerHTML = html + '</table>';
}

function countWords(lines) {
  let wordCount = 0;
  for (let i = 0; i < lines.length; i++) {
    const words = lines[i].trim().split(/\s+/);
    wordCount += words.length;
  }
  return wordCount;
}

// 発話時間を話者ごとに集計する (.txt)
// 入力フォーマット：
//   0:0:1.130 --> 0:0:2.700
//   発話者の名前
//   あお疲れ様です。
function countSpeakingTimeTxt(lines) {
  var speakingTime = {}; // empty hash
  for (let i = 0; i + 2 < lines.length; i += 3) {
    console.log('line : ' + i);
    // 1st line (time)
    const resultArray = parseTimeLine(lines[i]);
    console.log("begin : " + resultArray[0]);
    console.log("end   : " + resultArray[1]);

    const time_begin = parseTimeString(resultArray[0]);
    const time_end   = parseTimeString(resultArray[1]);
    const elapsed_ms = getElapsedMilliSeconds(time_begin, time_end);
    console.log(elapsed_ms + "ms");
    
    // 2nd line (speaker name)
    console.log("speaker : " + lines[i + 1]);
    const total  = speakingTime[lines[i + 1]];
    const update = total ? total + elapsed_ms : elapsed_ms;
    speakingTime[lines[i + 1]] = update;

    // 3rd line (speaking contents - ignore)
  }
  return speakingTime;
}

// 発話時間を話者ごとに集計する (.vtt)
// 入力フォーマット：
//   WEBVTT
//   { 空行 }
//   00:00:00.000 --> 00:00:00.560
//   <v 発話者の名前>ええ？</v>
//   { 空行 }
function countSpeakingTimeVtt(lines) {
  var speakingTime = {}; // empty hash
  for (let i = 2; i + 1 < lines.length; i += 3) {
    console.log('line : ' + i);
    // 1st line (time)
    const resultArray = parseTimeLine(lines[i]);
    console.log("begin : " + resultArray[0]);
    console.log("end   : " + resultArray[1]);

    const time_begin = parseTimeString(resultArray[0]);
    const time_end   = parseTimeString(resultArray[1]);
    const elapsed_ms = getElapsedMilliSeconds(time_begin, time_end);
    console.log(elapsed_ms + "ms");
    
    // 2nd line (speaker name + contents)
    const [str1, str2] = lines[i + 1].split('>');
    const speaker = str1.substring(3);
    console.log("speaker : " + speaker);
    const total  = speakingTime[speaker];
    const update = total ? total + elapsed_ms : elapsed_ms;
    speakingTime[speaker] = update;

    // 3rd line (empty line - ignore)
  }
  return speakingTime;
}

function parseTimeLine(timeLineString) {
  const [t1, allow, t2] = timeLineString.split(' ');
  return [t1, t2];
}

function parseTimeString(timeString) {
  const [hour, minute, second, millisecond] = timeString.split(/[:.]/);
  const currentDate = new Date();
  currentDate.setHours(hour);
  currentDate.setMinutes(minute);
  currentDate.setSeconds(second);
  currentDate.setMilliseconds(millisecond);
  return currentDate;
}

function getElapsedMilliSeconds(date1, date2) {
  return Math.abs(date2 - date1);
}
</script>
</body>
</html>
```