世のお父さんのために子供のスポーツ動画にクロマキー合成するタイマーを作りました

# 概要

スポーツシーズンですが、世のお父さん方は、子供達が活躍する所をせっせとビデオ撮影し、寝る間も惜しんで編集作業に勤しんでいることと思います。同情します。

そんなお父さんの動画クオリティーをアップさせるタイマーを用意しました。
https://www.exabugs.com/timer/index.html

![スクリーンショット 2018-09-30 11.08.33.png](https://qiita-image-store.s3.amazonaws.com/0/69410/31da91d4-1cbb-df48-dad4-7064da24402c.png)

これでカッコいい動画を作成すれば、嫁や子供達から「パパ素敵！」と言われること間違いなし！やったね！

なお、本記事では iMovie と QuickTime を使用します

# 利用方法
## タイマー使い方
 - ブラウザで上記URLにアクセス。(Chrome/Safariは動作確認済み)
 - 「リセット」でタイマーが出現し、「スタート」で開始します。
 - 設定はローカルに保存されますが、「クリア」を押すと初期化されます。
 - 緑色背景はアスペクト比 16:9 です。
 - 設定項目(左から)
    - 試合時間を「分」単位で指定
    - cd : カウントダウンかどうか。ONの場合は試合時間をカウントダウンします。
    - abs : 円形プログレスバーの一周(360度)を60分とする(サッカー方式？)か、試合時間とするか。
    - タイマーの大きさを画面幅との比率で指定
    - プログレスバーの色を指定。RGB値かred,blueのようなウェブカラー名。  
      WEB色見本 https://www.colordic.org/
    - プログレスバーの前景の幅を指定
    - プログレスバーの背景の幅を指定
    - 背景色を指定。クロマキー用途には緑か青。LT大会で使うには白がいいでしょう。
    - タイマーのフォント。  
      Googleウェブフォントを使用しています。
    - タイマーのフォントサイズ
    - タイマーの水平位置
    - タイマーの垂直位置
    - 緑色部分の幅と高さ。アスペクト比16:9になっています。

## タイマー動画作成
 - Mac の場合は QuickTime Player を起動し、ファイル - 新規画面収録 でタイマー動画を収録します。
 - 収録開始すると以下が表示されるので、緑色部分に収録範囲を合わせて下さい。
![スクリーンショット 2018-09-30 12.15.31.png](https://qiita-image-store.s3.amazonaws.com/0/69410/bb6bd87f-9144-7129-105e-ffadb2e0ccf3.png)



## 動画編集

### 1. タイマーの合成
 - iMovie を起動して、スポーツ動画とタイマー動画の両方をマイメディアに取り込みます
 - 「グリーン/ブルースクリーン」を選択すると、クロマキー合成されます。

![スクリーンショット_2018-09-30_12_29_24.jpg](https://qiita-image-store.s3.amazonaws.com/0/69410/5f11dafe-edbd-5ad5-09b6-10fcd3c5f7b8.jpeg)

 - タイムや怪我人の救護等で時計の進行が止まる場合は、iMovieでは「フリーズフレーム」でタイマー動画を止めるとよいでしょう。
 - ここまでで一旦、合成された動画をエクスポートします。

### 2. ハイライトシーンの編集
 - 1.の動画を開いて、ハイライトシーンを編集します。


# プログラム
 - 一応コードも貼っておきます。
 - 簡単のため一つのHTMLに内包しています。
 - 肝心の円形プログレスバーの部分はcirclifulを使わせて頂きました。  
   https://github.com/pguso/jquery-plugin-circliful

```html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>Timer</title>
        <link href="css/jquery.circliful.css" rel="stylesheet" type="text/css" />
        <link href='https://fonts.googleapis.com/css?family=Roboto:900,400' rel='stylesheet' type='text/css'>
        <link href='https://fonts.googleapis.com/css?family=Rajdhani:900,400' rel='stylesheet' type='text/css'>
        <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
        <script src="js/jquery.circliful.min.js"></script>
        <style type="text/css">
            body {
                margin: 0;
                font-size: small;
            }

            div {
                margin: 3px;
            }

            button, input, select {
                font-size: 100%;
            }
        </style>
    </head>
    <body>

        <div id="container">
            <div id="header">
                <button id="RESET">リセット</button>
                <button id="START">スタート</button>

                <input id="total" size="2">分
                <input id="countdown" type="checkbox">cd
                <input id="abs" type="checkbox">abs
                <input id="size" size="2">%
                <input id="color" size="9">
                <input id="fgborder" size="2">
                <input id="bgborder" size="2">

                <select id="bg">
                    <option value="#ffffff">白</option>
                    <option value="#00ff00">緑</option>
                    <option value="#0000ff">青</option>
                    <option value="#000000">黒</option>
                </select>

                <select id="font_family">
                    <option value="Roboto" selected>Roboto</option>
                    <option value="Rajdhani">Rajdhani</option>
                </select>
                <input id="font_size" size="3">px

                <select id="horizontal">
                    <option value="0">左</option>
                    <option value="1">中</option>
                    <option value="2">右</option>
                </select>

                <select id="vertical">
                    <option value="0">上</option>
                    <option value="1">中</option>
                    <option value="2">下</option>
                </select>

                <span id="divsize"></span>
                <button id="CLEAR">クリア</button>
            </div>
            <div id="contents">
                <div id="clock"></div>
            </div>
            <div id="footer">
            </div>
        </div>

        <script>

          function time(s) {
            var min = Math.floor(s / 60);
            var sec = Math.floor(s % 60);
            return ("0" + min).slice(-2) + ":" + ("0" + sec).slice(-2);
          }

          function disp(prog, total, countdown) {
            var _t = prog;
            if (total < prog) {
              _t = prog - total;
            } else if (countdown) {
              _t = total - prog;
            }
            return time(_t / 1000);
          }

          function percent(prog, total, abs) {
            var hour = 60 * 60 * 1000;
            var all = abs ? hour : total;
            return Math.max((total - prog) / all, 0);
          }

          function shadow(color) {
            return [
              [2, 0], [-2, 0], [0, -2], [0, 2],
              [2, 2], [-2, 2], [2, -2], [-2, -2],
              [1, 2], [-1, 2], [1, -2], [-1, -2],
              [2, 1], [-2, 1], [2, -1], [-2, -1]
            ].map(function(i) {
              return color + " " + i.map(function(j) {
                return j + "px"
              }).join(" ")
            }).join(",");
          }

          function conf(total, abs, countdown) {

            var font_size = $('#font_size').val();
            var font_family = $('#font_family').val();

            var textStyle = [
              ["font-size", font_size + "px"],
              ["font-family", font_family],
              ["text-shadow", shadow("black")]
            ].map(function(i) {
              return i[0] + ": " + i[1] + ";"
            }).join(" ");

            var prog = 0;
            return {
              percent: Math.floor(percent(prog, total, abs) * 100),
              replacePercentageByText: '',
              text: disp(prog, total, countdown),
              textStyle: textStyle,
              foregroundBorderWidth: $("#fgborder").val(),
              backgroundBorderWidth: $("#bgborder").val(),
              foregroundColor: $("#color").val(),
              textColor: "white",
              backgroundColor: "white",
            }
          }

          ///////////////////////////////////////////////////

          var storage = localStorage;

          function saveText(id) {
            storage.setItem(id, $("#" + id).val());
          }

          function saveCheck(id) {
            var val = $("#" + id).prop("checked");
            storage.setItem(id, val);
          }

          function loadText(id, def) {
            var val = storage.getItem(id);
            val = (val === null ? def : val);
            $("#" + id).val(val).change();
          }

          function loadCheck(id, def) {
            var val = storage.getItem(id);
            val = (val === null ? def : (val === "false" ? false : true));
            $("#" + id).prop("checked", val).change();
          }

          function save() {
            saveCheck("countdown");
            saveCheck("abs");
            saveText("total");
            saveText("size");
            saveText("color");
            saveText("bg");
            saveText("font_family");
            saveText("font_size");
            saveText("horizontal");
            saveText("vertical");
            saveText("fgborder");
            saveText("bgborder");
          }

          function load() {
            loadCheck("countdown", true);
            loadCheck("abs", true);
            loadText("total", 45);
            loadText("size", 15);
            loadText("color", "#3498DB");
            loadText("bg", "#00ff00");
            loadText("font_family", "Roboto");
            loadText("font_size", 36);
            loadText("horizontal", 0);
            loadText("vertical", 0);
            loadText("fgborder", 11);
            loadText("bgborder", 15);
          }

          function clear() {
            storage.clear();
            load();
          }

          ///////////////////////////////////////////////////

          var data = {};

          function stop() {
            clearInterval(data.timer);
            delete data.timer;
          }

          function reset() {
            stop();
            save();

            data.total = Number($("#total").val()) * 60 * 1000;
            data.countdown = $("#countdown").prop("checked");
            data.abs = $("#abs").prop("checked");

            var countdown = data.countdown;
            var total = data.total;
            var abs = data.abs;

            $('#clock').remove();
            $('#contents').append('<div id="clock"></div>');

            $('#clock').circliful(conf(total, abs, countdown));

            var svg = $("#clock").children("svg").eq(0);

            data.svg = {
              container: svg,
              text: svg.children("text").eq(0),
              circle: svg.children("circle").eq(1)
            };

            resize();
          }

          function start() {
            stop();

            data.start = Date.now();

            data.timer = setInterval(function() {
              var countdown = data.countdown;
              var total = data.total;
              var abs = data.abs;
              var prog = Date.now() - data.start;

              var degree = Math.floor(percent(prog, total, abs) * 360);

              data.svg.text.text(disp(prog, total, countdown));
              data.svg.circle.css("stroke-dasharray", degree + ", 20000");
            }, 200);
          }

          $('#RESET').click(reset);
          $('#START').click(start);
          $('#CLEAR').click(clear);

          ///////////////////////////////////////////////////

          $("#bg").change(function() {
            $("#contents").css('background-color', $(this).val());
          });

          function resize() {

            var contents = $("#contents");
            contents.height(Math.floor(contents.width() / 16 * 9));

            $("#divsize").text("(" + contents.width() + "x" + contents.height() + ")");

            if (data.svg) {
              var ratio = $("#size").val() / 100;

              var size = contents.width() * ratio;
              var left = (contents.width() - size) / 2 * $("#horizontal").val();
              var top = (contents.height() - size) / 2 * $("#vertical").val();

              var svg = data.svg.container;
              svg.css('width', size);
              svg.css('padding-left', left);
              svg.css('padding-top', top);
            }
          }

          $("#horizontal").change(resize);
          $("#vertical").change(resize);
          $(window).resize(resize);

          ///////////////////////////////////////////////////

          load();
          resize();

        </script>
    </body>
</html>
```