【第2回】ブラウザ上で動画をカット編集する

ブラウザ上で動画を表示し、指定した時間でカット編集する方法をメモしておきます。
動画編集はMoviePyというPythonライブラリを用いることとし、環境はAWSのcloud9で作成します。
全2回の構成で、第1回ではMoviePyを使って動画編集を行えるようにします。
第2回では、javascriptでカットする領域を表示するなどします。
間違い等あった場合は、コメントでご指摘いただけると幸いです。

[第1回](https://qiita.com/a_takeeeee/items/d16c5028c96420539333)はこちら。

##1. cut.htmlの作成(前回と同じ)
まずはcut.htmlを書いておきます。

```html:cut.html
{% load static %}

<html lang="ja">
    <head>
        <meta charset="Shift-JIS">
        <title>video edit</title>
        <link rel="stylesheet" href="{% static 'main.css' %}">
    </head>
    <body>
        <h1>カット編集画面</h1>
        <section class="cut_left">
            <video id=video controls src='{{ MEDIA_URL }}{{ record.Original }}'></video>
            {% for time in cuttime_list %}
            <div hidden class='cut_starttime'>{{ time.startTime }}</div>
            <div hidden class='cut_endtime'>{{ time.endTime }}</div>
            {% endfor %}
            <div hidden id='video_duration'>{{ video_duration }}</div>
            
            <div class="flagBox">
                {% for time in cuttime_list %}
                <div name="cutflag"></div>
                {% endfor %}
            </div>
            <div class="changeTime">
                <button id="changeStartTime">カット開始時間変更</button>
                <button id="changeEndTime">カット終了時間変更</button>
            </div>
            
            <form method="post">
            {% csrf_token %}
                <div class='edit_time'>
                    <input type='number' value=5 min=0 max={{ video_duration }} step='0.01' id='startTime' name='startTime'>
                    <input type='number' value=10 min=0 max={{ video_duration }} step='0.01' id='endTime' name='endTime'>
                </div>
                <div class='cut_operation'>
                    <input type="submit" name="cutCanceled" value="キャンセル">
                    <input type="submit" name="cutReset" id="cutReset" value="元に戻す">
                    <input type="submit" name="cutExecute" id="cutExecute" value="カットする">
                    <input type="submit" name="cutFinished" id="cutFinished" value="完了">
                </div>
            </form>
            <a href="/"><p>戻る</p></a>
        </section>
        <script src={% static 'cut.js' %}></script>
    </body>
</html>
```

##2. main.cssの作成

```css:main.css
body {
    color: #333;
    font-family: Verdana, sans-serif;
    margin: 10px;
}

.cut_left {
    float: left;
    width: 70vw;
}

.cut_left video {
    width: 63vw;
}

.flagBox{
    width:90%;
    height:10px;
    background-color: #C0C0C0;
    margin: 0;
    margin-top: 20px;
    position:relative;
}

.changeTime button {
    margin-top: 10px;
    width: 140px;
    
}

.edit_time input {
    margin-bottom: 10px;
    width: 140px;
}
```
main.cssでhtmlの表示を微調整します。
ここで重要なのは".flagBox"の部分です。
flagBoxは、動画の横のサイズと同じ長さのグレーのバーを表示するために記述しています。
最初の"width:90%"は、cut_leftに占める動画の大きさと同じにしています(63vw/70vw=0.9)。これにより、バーの長さが動画の横のサイズと同じになります。
後は高さと色の設定などです。

##3. cut.jsの作成
最後に、JavaScriptを記述していきます。
ここでは、カット開始/終了時間の変更や、カットする領域のバー形式での表示を担当します。

###3.1 動画の再生

```javascript:cut.js
// videoの取得
let video = document.getElementById('video');
// 自動再生
video.autoplay = false;
// ループ
video.loop = true;

// カット開始/終了時間を格納
let startTimeArray = [];
let endTimeArray = [];

// 現在の経過時間
video.addEventListener('timeupdate', function(e) {
    for (let i=0; i<endTimeArray.length; i++){
        if (video.currentTime > startTimeArray[i] && video.currentTime < endTimeArray[i]) {
            video.currentTime = endTimeArray[i];
        }
    }
});
```

最初の行で、cut.htmlでidを'video'としているタグを取得します。
自動再生=false、ループ=trueとしておきます。
また、カット開始/終了時間を格納しておく配列を作成し、動画再生中にカット開始時間に来たらカット終了時間まで飛ばすようにしています。

###3.2 カット開始/終了時間変更ボタンを押したときの挙動

```javascript:cut.js
// カット開始時間変更ボタンを押した時
let changeStartTime = document.getElementById("changeStartTime");
let startTimeNum = document.getElementById("startTime");
changeStartTime.addEventListener('click', function () {
    startTimeNum.value = Math.round(video.currentTime * 100) / 100;
});

// カット終了時間変更ボタンを押した時
let changeEndTime = document.getElementById("changeEndTime");
let endTimeNum = document.getElementById("endTime");
changeEndTime.addEventListener('click', function () {
    endTimeNum.value = Math.round(video.currentTime * 100) / 100;
});
```

cut.htmlで「カット開始時間変更」ボタンのidを"changeStartTime"、「カット開始時間」のidを"startTime"と指定しています。ボタンをクリックしたとき、「カット開始時間」のvalueを動画の現在時刻に変更しています。
カット終了時間についても同様です。

###3.3 カット領域の表示

```javascript:cut.js
// htmlから必要な要素を取得
let startClass = document.getElementsByClassName("cut_starttime");
let endClass = document.getElementsByClassName("cut_endtime");
let flagName = document.getElementsByName("cutflag");

// カットされている時間の集合
let cutBar = [];

// 動画再生時間
let duration = parseFloat(document.getElementById('video_duration').textContent);
let inverse_duration = 100 / duration;

// カット領域の表示
ShowCutArea();

function ShowCutArea()
{
    let startTimeID = "startTime";
    let endTimeID = "endTime";
    let flagID = "cutFlag";
    let startPer = [0]; // 0を入れておく
    let endPer = [0];   // 0を入れておく
    
    for (var i=0; i<startClass.length; i++){
        startClass[i].setAttribute("id", startTimeID + i);
        endClass[i].setAttribute("id", endTimeID + i);
        startTimeArray.push(document.getElementById(startTimeID + i).textContent);
        endTimeArray.push(document.getElementById(endTimeID + i).textContent);
    }
    
    for (var i=1; i<=startClass.length; i++){
        startPer[i] = startTimeArray[i-1] * inverse_duration;
        endPer[i] = endTimeArray[i-1] * inverse_duration;
        flagName[i-1].className = flagID + i;
        cutBar[i] = document.getElementsByClassName(flagID + i);
        cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%';
        cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%';
        cutBar[i][0].style.backgroundColor = "#000000";
        cutBar[i][0].style.height = 10 + "px";
        cutBar[i][0].style.cssFloat = "left";
    }
}
```
"ShowCutArea"関数でカット領域を表示します。
この中では同じfor文を2度回しています。
1回目のfor文では、カット開始/終了時間を取得しています。
cut.htmlで、カット開始時間に"cut_starttime"のclassを付けて非表示にしていました。
この値を取得するため、classに順番にstartTimeID1, startTimeID2…とIDを振り、startTimeArrayにこのIDのテキストをpushしていきます。
この操作により、startTimeArray, endTimeArrayにカット開始/終了時間を格納できました。

2回目のfor文では、cut.htmlのflagBox(main.cssによってグレーのバーとして表示されている)のうち、黒く表示する部分を決めています。
実際に色を変更しているのは以下の部分です。

```
        cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%';
        cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%';
        cutBar[i][0].style.backgroundColor = "#000000";
```
表示領域は%で表したいので、割合を計算してstartPer, endPerに入れています。
黒く表示する部分の左端はカット開始時間から1つ前のカット終了時間を引いた値、黒く表示する部分の長さはカット終了時間からカット開始時間を引いた値になります。
配列の長さだけ繰り返すことで、複数のカット時間に対応できるようになっています。

cut.js全体はこちらです。
順番等、変更している部分があります。

```javascript:cut.js
'use strict';

// videoの取得
let video = document.getElementById('video');
// 自動再生
video.autoplay = false;
// ループ
video.loop = true;

// カット開始/終了時間を格納
let startTimeArray = [];
let endTimeArray = [];

// htmlから必要な要素を取得
let startClass = document.getElementsByClassName("cut_starttime");
let endClass = document.getElementsByClassName("cut_endtime");
let flagName = document.getElementsByName("cutflag");

// カットされている時間の集合
let cutBar = [];

// 動画再生時間
let duration = parseFloat(document.getElementById('video_duration').textContent);
let inverse_duration = 100 / duration;

// カット領域の表示
ShowCutArea();

// 現在の経過時間
video.addEventListener('timeupdate', function(e) {
    for (let i=0; i<endTimeArray.length; i++){
        if (video.currentTime > startTimeArray[i] && video.currentTime < endTimeArray[i]) {
            video.currentTime = endTimeArray[i];
        }
    }
});

// カット開始時間変更ボタンを押した時
let changeStartTime = document.getElementById("changeStartTime");
let startTimeNum = document.getElementById("startTime");
changeStartTime.addEventListener('click', function () {
    startTimeNum.value = Number(Math.round(video.currentTime * 100) / 100);
});

// カット終了時間変更ボタンを押した時
let changeEndTime = document.getElementById("changeEndTime");
let endTimeNum = document.getElementById("endTime");
changeEndTime.addEventListener('click', function () {
    endTimeNum.value = Number(Math.round(video.currentTime * 100) / 100);
});

function ShowCutArea()
{
    let startTimeID = "startTime";
    let endTimeID = "endTime";
    let flagID = "cutFlag";
    let startPer = [0]; // 0を入れておく
    let endPer = [0];   // 0を入れておく
    
    for (var i=0; i<startClass.length; i++){
        startClass[i].setAttribute("id", startTimeID + i);
        endClass[i].setAttribute("id", endTimeID + i);
        startTimeArray.push(document.getElementById(startTimeID + i).textContent);
        endTimeArray.push(document.getElementById(endTimeID + i).textContent);
    }
    
    for (var i=1; i<=startClass.length; i++){
        startPer[i] = startTimeArray[i-1] * inverse_duration;
        endPer[i] = endTimeArray[i-1] * inverse_duration;
        flagName[i-1].className = flagID + i;
        cutBar[i] = document.getElementsByClassName(flagID + i);
        cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%';
        cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%';
        cutBar[i][0].style.backgroundColor = "#000000";
        cutBar[i][0].style.height = 10 + "px";
        cutBar[i][0].style.cssFloat = "left";
    }
}

```


##4. 最後に
以上でブラウザ上で動画のカット編集ができるようになりました。
まだまだ不十分な部分があるので、参考にしながら改良していただければと思います。