[FFmpeg] 動画に複数のぼかしを入れる

[M1 Mac, Big Sur 11.6.8]

FFmpegのコマンドがとにかくわかりにくいので、書き留めておきます。

開発中のビデオツールアプリに実装するには、このコマンドをC++で生成する必要があります。

FFmpeg関連の情報はネットに数多くありますが、どういったユーザー層なのか気になります。C/C++ユーザーよりもコマンドライン使いが多そうです。

ffmpeg -i out.mp4 -filter_complex \          --- (1)
 "[0:v]crop=265:154:648:103,boxblur=4[b0]; \ --- (2)
  [0:v]crop=94:111:572:267,boxblur=4[b1]; \  --- (3)
  [0:v][b0]overlay=648:103[ovr0]; \          --- (4)
  [ovr0][b1]overlay=572:267[ovr1]" \         --- (5)
-map "[ovr1]" out2.mp4                       --- (6)

(1) 入力ファイルはout.mp4。以下、-filter_complexを記す。
(2) 入力ファイル0番目の映像[0:v]について、x:648,y:103の位置からw:265,h:154のサイズでクロップし、レベル4のboxblur処理をする。これをb0とする。
(3) 入力ファイル0番目の映像について、x:572,y:267の位置からw:94,h:111のサイズでクロップし、レベル4のboxblur処理をする。これをb1とする。
(4) 入力ファイル0番目の映像とb0をx:648,y:103の位置で重ねる。これをovr0とする。
(5) ovr0とb1をx:572,y:267の位置で重ねる。これをovr1とする。
(6) ovr1をマッピングし、out2.mp4として出力する。

[C++] 180 FLTK : 動画の一部をぼかす FFmpeg

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

動画の一部(四角形領域)をぼかす機能を実装しました。

今のところ1ヶ所のみですが、複数の領域にモザイクをかけられるようにします。

// 計算ボタンのコールバック関数 変数x,yはmathライブラリで使われているためxx,yyを使用
void calcCB(Fl_Widget*, void*) {
    xa = (int)(xx1 * ((float)widthSub1/480));
    xb = (int)(xx2 * ((float)widthSub1/480));
    ya = (int)(yy1 * ((float)heightSub1/360));
    yb = (int)(yy2 * ((float)heightSub1/360));

    cout << "widthSub1 " << widthSub1 << " heightSub1 " << heightSub1 << endl;
    cout << "xx1 " << xx1 << " xx2 " << xx2 << " yy1 " << yy1 << " yy2 " << yy2 << endl;
    cout << "xa " << xa << " xb " << xb << " ya " << ya << " yb " << yb << endl;

    wa = xb - xa;
    ha = yb - ya;

    filter = to_string(wa) + ":" + to_string(ha) + ":" + to_string(xa) + ":" + to_string(ya);
    filter2 = to_string(xa) + ":" + to_string(ya);

    fil_input -> value(filter.c_str());
}
// フィルタボタンのコールバック関数
void filCB(Fl_Widget*, void*){
    const char* output = outputLine2 -> value();
    const char* output2 = out2_input -> value();

    string filtercmd = "ffmpeg -i " + string(output) + " -filter_complex \"[0:v]crop=" + filter + ",boxblur=4[fg];[0:v][fg]overlay=" + filter2 + "[v]\" -map \"[v]\" " + string(output2) + " 2> " + filterFile;

    // CMD追加
    cmdBuffer2 -> append(filtercmd.c_str());
    cmdBuffer2 -> append("\n");
    cout << "filtercmd: "<< filtercmd << endl;

    // cmdTextDisplay2にcmdを表示する
    cmdTextDisplay2->buffer(cmdBuffer2);

    // コマンド実行
    system(filtercmd.c_str());

    // browserにfilterFileの内容を表示する
    browser2 -> load(filterFile.c_str());
    int line_num = browser2->size();
    browser2 -> bottomline(line_num);

}

[C++] 179 FLTK : Fl_Box内の相対座標をマウスクリックで取得する Fl::event_x()

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

動画の一部をマスクする長方形の座標と大きさを把握するため、マウスをクリックしてx座標、y座標を取得できるようにしました。

マウス左ボタンを押すとx1とy1、離すとx2とy2を取得します。マウスをドラッグしている間は長方形を描画させたかったのですが、うまくできませんでした。このデータを元にマスク用の画像を作成し、重ねた状態で動画を合成する予定です。

#pragma once
#include <FLstd.h>
#include "cppstd.h"

class BoxXY : public Fl_Box {
    Fl_Input* input_line1;
    Fl_Input* input_line2;
    Fl_Input* input_line3;
    Fl_Input* input_line4;

    public:
        BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4);
    private:
        int handle(int);
};
#include "BoxXY.h"

BoxXY::BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4) : Fl_Box(FL_NO_BOX, x, y, width_input, height_input, "") 
{
    this->input_line1 = input1;
    this->input_line2 = input2;
    this->input_line3 = input3;
    this->input_line4 = input4;
}

int BoxXY::handle(int event){
    switch (event) {
        case FL_PUSH:{ // Box左上の座標は(65,190)
            int x1 = Fl::event_x() - 65;
            int y1 = Fl::event_y() - 190;

            input_line1->value(to_string(x1).c_str());
            input_line1->textsize(12);

            input_line2->value(to_string(y1).c_str());
            input_line2->textsize(12);

            return 1;
        }
        case FL_RELEASE:{
            int x2 = Fl::event_x() - 65;
            int y2 = Fl::event_y() - 190;

            input_line3->value(to_string(x2).c_str());
            input_line3->textsize(12);

            input_line4->value(to_string(y2).c_str());
            input_line4->textsize(12);

            return 1;
        }
        default:
            return Fl_Box::handle(event);
    }
}

[C++] 178 FLTK : 動画からフレームレートを取得 FFmpeg

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

開発中のビデオツールアプリにおいて、すでに分割したフレーム群から動画を作成できるようにしました。

さらに動画の一部をマスクする機能を実装して、このアプリはとりあえず完成としたいです。

// 解析ボタンのコールバック関数
void analyzeCB(Fl_Widget*, void*) {
    string bufferStr2;
    std::ifstream file;
    string fpsFile = "fps.txt";
    string buffer;

    fpsInput2 -> value("\0");
    // 対象ファイルパス取得
    const char* path = inputLine2->value();
    
    // path未入力の場合は既存の静止画群のFPSを取得し表示
    if (string(path) == ""){
        cout << "pathなし" << endl;

        // fps.txtからfps(分数)を取得
        file.open(fpsFile, std::ios::in);
        std::getline(file, buffer);
        string fps_str = buffer;

        vector<string> list_fps = spt2.splits(fps_str,"/");
        string top0 = list_fps[0];
        float top = stof(top0);
        string bottom0 = list_fps[1];
        float bottom = stof(bottom0);

        fps = top/bottom;
        cout << "fps "<< fps << endl;

        fpsInput2 -> value((to_string(fps)).c_str());
        fpsInput2 -> position(0);
        
        outputLine2 -> value("out.mp4");

        toImagesRbtn -> value(false);
        toVideoRbtn -> value(true);

        return;
    }

    cout << "path "<< path << endl;

    string path_str = string(path);
    bufferStr2 += "path_str: " + path_str + "\n";

    // path内半角スペースをアンダースコアへ置き換え
    path2 = underScoreReplace(string(path_str));
    bufferStr2 += "path2: " + path2 + "\n";

    // 元ファイルをリネーム
    rename(path, path2.c_str());

    // 出力ファイル名を作成
    string outputFile0 = spt2.splitJoin(path2, ".", 0, -2);
    outputFile0.pop_back();
    outputFile = outputFile0 + "_trimmed.mp4";
    cout << "outputFile " << outputFile << endl;

    outputLine2 -> value(outputFile.c_str());

    // フレームレート出力コマンドcmd作成
    string cmd = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffprobe -v error -select_streams v -show_entries stream=avg_frame_rate -of csv=p=0 " + path2;
    // fps.txtとしても出力
    string cmd2 = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffprobe -v error -select_streams v -show_entries stream=avg_frame_rate -of csv=p=0 " + path2 + " 1> " + fpsFile;

    cout << "cmd: "<< cmd << endl;

    cmdBuffer2 -> append(cmd.c_str());
    cmdBuffer2 -> append("\n");

    bufferStr2 += cmd + "\n";

    // cmdTextDisplay2にcmdを表示する
    cmdTextDisplay2->buffer(cmdBuffer2);

    // FPSを取得し表示
    showFPS(cmd);
    system(cmd2.c_str());

    textBuffer2 -> append(bufferStr2.c_str());
    textDisplay2 -> buffer(textBuffer2);
}

[C++] 177 FLTK : 動画編集機能を実装 FFmpeg

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

開発中のビデオツールアプリに動画編集機能を実装しました。元動画をフレームに分解して、Fl_Boxのビューアーで見つつ最初と最後のフレーム番号を指定。選択した静止画を結合し、動画を作成します。

コードの内容については次回以降紹介します。

[C++] 176 FLTK : 画像の強制リサイズ Fl_Image 

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ビデオツールアプリに画像を表示する際、Fl_Boxのサイズに強制的に合わせるようにしました。copy関数を使います。

本来はアスペクト比を保持したまま表示するのが妥当ですが、編集点を見極められれば十分なのでFl_Boxに合わせました。あくまで表示のための一時的処理であって元画像のサイズは変わりません。

extern Fl_Box *show_box2
extern Fl_Input *imageNum2, *imageSec2, *fpsInput2;
extern Fl_Slider *slider2;

// スライダーのコールバック関数
void slider_cb2(Fl_Widget*, void*) {
    // スライダーの数値を取得
    slider2 -> bounds(0, (double)(paths.size() -1));
    double value = slider2 -> value();
    num = (int)value;

    // 画像番号/画像数の表示
    string image_num = to_string(num +1) + "/" + to_string(paths.size());
    imageNum2 -> value(image_num.c_str());

    // 経過秒数の表示
    const char* fps = fpsInput2 -> value();
    string fps_str = string(fps);
    float sec = num/stof(fps_str);
    imageSec2 -> value((to_string(sec)).c_str());

    string path = paths[num];
    cout << "slider_path " << path << endl;

    // png画像の表示(480*360へ強制リサイズ)
    Fl_PNG_Image *png = new Fl_PNG_Image(path.c_str());
    Fl_Image* png_copy = png -> copy(480,360);

    show_box2 -> image(png_copy);
    show_box2 -> redraw();

    subWindow1->show();
    delete png; // メモリ解放

    cout << "rev_cb2完了" << endl;
}

[C++] 175 FLTK : FFmpegで静止画から動画を作成 / 動画編集機能の実装 GUI案

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

FFmpegで動画をフレームに分割し、選択した静止画から動画に戻すコマンドを作成しました。

分割した静止画のアスペクト比が強制的に1:1になっていますが、動画に戻すと元のアスペクト比になっているのでコマンドはそのままにしておきます。

# movファイルをpngファイルに分割(フレームレートは23.98、1枚目は00001.png)
ffmpeg -i xxx.mov -an -r 23.98 /images/%05d.png

# 00100.pngから連番600個の静止画を元にout.mp4を作成(フレームレートは23.98)
ffmpeg -r 23.98 -start_number 100 -i /images/%05d.png -vcodec libx264 -pix_fmt yuv420p -vframes 600 out.mp4

GUI案は以下の通りです。まあのんびり作っていきます。

[C++] 174 FLTK : 動画からフレームの切り出しおよびコマ送り Fl_PNG_Image

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ビデオツールアプリの動画編集機能実装に着手しました。

まずはFFmpegを使って動画をフレーム単位に分解し、GUIに表示させてコマ送りができるようにしました。

動画の分解でアスペクト比がおかしくなっているのでffmpegコマンドの修正が必要です。後は使い勝手がいいようにウィジェットの位置やサイズを調整します。

[C++] 173 FLTK : メニューの階層を増やす Fl_Menu_Item

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

開発中のビデオツールアプリでメニューの階層を2層に増やしました。

2層の場合はFl_Menu_Itemの最後に{0}を3つ付加する必要があります。1層では2つ付加します。

int main(int argc, char **argv) {
    static Fl_Menu_Item	items[] = {
    { "Menu", 0, 0, 0, FL_SUBMENU },
    { "Window切替", 0, 0, 0, FL_SUBMENU },
    { "Video Tool", 0, switchWindow0},
    { "FFmpeg Command Maker", 0, switchWindow1},
    { "Video Editor", 0, switchWindow2},
    { 0 },
    { 0 },
    { 0 }
    };

    Fl_Sys_Menu_Bar *menubar;
    menubar = new Fl_Sys_Menu_Bar(0, 0, 60, 20);
    menubar->box(FL_FLAT_BOX);
    menubar->menu(items);

    <以下略>

}

[C++] 172 FLTK : 別ウィンドウへの画面遷移 

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

開発中のビデオツールアプリで別ウィンドウへ画面遷移できるようにしました。

これができなければFLTKを使いこなせているとは言えない、と思いながらついつい放置してきましたが、ようやく本腰を入れて取り組みました。

初めは同一Fl_Windowでredrawさせようかとも考えたものの、コンストラクタ内で条件分岐するとウィンドウが真っ黒になり挫折、別WindowとしてSubWindowクラスを作成したらうまくできました。表示する位置が元ウィンドウと完全一致するように工夫を入れています。

着手2時間で挫折、昼寝中に解決方法を着想、起きて30分で実装という流れでした。職場なら2、3時間の昼寝はご法度でしょうが、プログラミングの場合は比較的長時間の馬上(ドライブ・乗り物他)・枕上(昼寝)・厠上(トイレ)の三上を認めてもらいたいものです。あと電話が鳴る環境も思考深化の妨げになるので厳禁です。

ゲームや動画鑑賞など、頭をリラックスさせて着想に導く方法は人それぞれなので、何でも取り入れてみるべきですね。着手して1回泥沼にハマる(周辺知識を一気に詰め込む作業でもある)ことが前提条件です。

Fl_Window *window;
SubWindow *subWindow;
int mode; // 0:メイン、1:サブ

void switchWindow(Fl_Widget*, void*){
    if (mode == 1){
        mode = 0;
        subWindow -> hide();

        int xs_win = subWindow->x_root();
        int ys_win = subWindow->y_root();
        cout<<"xs_win "<< xs_win <<" ys_win "<< ys_win <<endl;
        
        window->resize(xs_win,ys_win,720,480);
        window -> show();

    } else {
        mode = 1;
        window -> hide();

        int x_win = window->x_root();
        int y_win = window->y_root();
        cout<<"x_win "<< x_win <<" y_win "<< y_win <<endl;
        
        subWindow->resize(x_win,y_win,720,480);
        subWindow -> show();

    }
}

int main(int argc, char **argv) {
    static Fl_Menu_Item	items[] = {
    { "設定", 0, 0, 0, FL_SUBMENU },
    { "ウィンドウ切替", 0, switchWindow, 0, 0 },
    { 0 },
    { 0 }
    };
    Fl_Sys_Menu_Bar *menubar;
    menubar = new Fl_Sys_Menu_Bar(0, 0, 60, 20);
    menubar->box(FL_FLAT_BOX);
    menubar->menu(items);

    window = new Fl_Window(100,100,720,480,"Video Tool");
    window->color(fl_rgb_color(0,163,175));

    <中略>

    window->end();
    window->show(argc, argv);

    // SubWindow(最初は表示しない)
    int x_win = window->x_root();
    int y_win = window->y_root();

    subWindow = new SubWindow(720,480,"Video Tool 2nd");
    subWindow->resize(x_win,y_win,720,480);

    return Fl::run();
}
#pragma once
#include <FLstd.h> // 自製ヘッダファイル FLTKライブラリ
#include <cppstd.h> // 自製ヘッダファイル C++標準ライブラリ

class SubWindow: public Fl_Window
{
public:
	SubWindow(int w,int h,const char *title);
	~SubWindow();
};
#include <SubWindow.h>

SubWindow::SubWindow(int w,int h,const char *title=0)
	:Fl_Window(w,h,title){
	this->Fl_Widget::color(fl_rgb_color(65,154,202)); // ふじむらさき
}
SubWindow::~SubWindow(){}