ムービーファイルから特定のチャプターだけを抜き出す方法

mp4などの動画ファイルから,特定の章(チャプター)を切り出す方法です.

切り出しはffmpegを使うと簡単かつ高速です.音声・画質の劣化もありません.

手順1) チャプター情報の確認

ffmpeg付属のffprobeコマンドを使いチャプターの一覧を確認します

$ ffprobe -i 動画ファイル名 

チャプターの一覧は次のような書式になります

  Chapters:
    Chapter #0:0: start 開始時刻, end 終了時刻
      Metadata:
        title           : タイトル1
    Chapter #0:0: start 開始時刻, end 終了時刻
      Metadata:
        title           : タイトル2

これでチャプターを抽出したいチャプターの開始時刻,終了時刻を確認します

手順2) 切り出し

開始時刻,終了時刻を指定して,ffmpegコマンドを使います

$ ffmpeg  -ss 開始時刻  -to 終了時刻  -i 元動画のファイル名 -c copy -map 0 -map_chapters -1  新しい動画のファイル名

"-c copy"で,動画の再圧縮なしで新しい動画を作成します.これで画像・音声の劣化は回避できます

補足

シェルスクリプトで上記処理を自動化する際は"-show_chapters"や " -print_format csv " オプションを使うと便利です

$ ffprobe -i 元動画のファイル名 -show_chapters

"-show_chapters"を付与すると,以下の出力が得られます

[CHAPTER]
id=0
time_base=1/10000000
start=開始時刻(単位 time_base)
start_time=開始時刻(単位 秒)
end=終了フレーム番号(単位 time_base)
end_time=終了時刻(単位 秒)
TAG:title=タイトル
[/CHAPTER]

ffprobeは詳細情報を大量に /dev/stderr に出力します."2>/dev/null"をつけます

$ ffprobe -i 元動画のファイル名 -show_chapters 2>/dev/null


さらに"-print_format csv"をつけると,カンマ(,)区切りのテキスト形式に書式が変わります

$ ffprobe -i 元動画のファイル名 -show_chapters -print_format csv 2>/dev/null

シェルスクリプトの場合はcutコマンドでパースすると良いでしょう

$ ffprobe -I 元動画のファイル名 -show_chapters -print_format csv  2>/dev/null  | cut -d ',' -f "5,7,8"
$ ffprobe -I 元動画のファイル名 -show_chapters -print_format csv  2>/dev/null  \
    | cut -d ',' -f "5,7,8" \
    | sed -e 's|,| |g' \
    | while read start end title; do 
   echo $start  $end $title
done

python / numpy で特異値分解(SVD)を使って一般化逆行列を計算する

一般化逆行列(擬似逆行列,最小二乗法に対応するやつ)を python で計算するサンプル

特異値分解(SVD)が必要なので,実装は numpy を使います

c++/eigen で実装したコード https://pyopyopyo.hatenablog.com/entry/2021/09/07/090000python版になります

import numpy as np

A = np.random.random((3,3))

# SVDを使って,A を U @ S @ Vh の形に分解する
U,S,Vh = np.linalg.svd(A)

# 検算. U @ np.diag(S) @ Vh は A と一致する
assert np.allclose(U@np.diag(S)@Vh, A)

# 一般化逆行列を計算する
#  A = U @ np.diag(S) @ Vh
# だから
# A+ = Vh.T @ np.diag(1./S) @ U.T
#
Ainv = Vh.T @ np.diag(1./S) @ U.T

# 検算.  A と A+ の積 m は単位行列に一致する
m = A@Ainv
assert np.allclose(m, np.identity(3))

C++/Eigen で特異値分解(SVD)を使って一般化逆行列を計算する

一般化逆行列(擬似逆行列,最小二乗法に対応するやつ)をc++で計算するサンプル

特異値分解(SVD)が必要なので,実装は Eigen を使います

python/numpy の実装も用意しました.こちらです https://pyopyopyo.hatenablog.com/entry/2021/09/14/161955

#include <Eigen/Dense>
#include <iostream>
#include <random>
#include <assert.h>

int
main()
{

    //ランダムな 3x3 行列を生成

    std::random_device rd;
    std::mt19937 gen(rd());
    std::normal_distribution<float> dis(0, 10);
    //ラムダ式で乱数を生成,生成した値を行列の各要素に格納する
    Eigen::MatrixXf A = Eigen::MatrixXf::NullaryExpr(3,3,  
                              [&](){return dis(gen);});

    // std::cout << "A: " << A << std::endl;

    Eigen::JacobiSVD<Eigen::MatrixXf> svd(A,
					  Eigen::ComputeFullU
					  | Eigen::ComputeFullV);

    //std::cout << "U: " << svd.matrixU() << std::endl;
    //std::cout << "S: " << svd.singularValues()  << std::endl;
    //std::cout << "V: " << svd.matrixV() << std::endl;


    // 一般化逆行列を計算する
    //
    //
    //  svdで  行列A を U * S * V.T と分解しているから
    //
    //  一般化逆行列 A+  は V * S.T * U.T

    Eigen::VectorXf s = svd.singularValues();
    s = s.array().inverse();

    // 一般化逆行列の変数名は Ainv 
    Eigen::MatrixXf Ainv =
                svd.matrixV()
             * s.asDiagonal()  
             * svd.matrixU().transpose();

    // A と A+ をかけると単位行列になる
    Eigen::MatrixXf m= A * Ainv;
    std::cout << m << std::endl;

    // 実際は,計算誤差があるので「ほぼ」単位行列になる.
    // そこで mとIdentity(3,3)が「ほぼ」等しいかEigenの isApprox() で確認
    // 結果は assert()でチェック

    assert( Eigen::MatrixXf::Identity(3,3).isApprox(m) );

    return 0;

/dev/null みたいな std::ostream

/dev/null みたいな std::ostream を作る方法.

方法1 : std::ostreamを継承して自前の null stream を作成する

以下のURLで紹介されている方法
stackoverflow.com

#include <iostream>

class NulStreambuf : public std::streambuf
{
    char dummyBuffer[ 64 ];
protected:
    virtual int overflow( int c )
    {
        setp( dummyBuffer, dummyBuffer + sizeof( dummyBuffer ) );
        return c == traits_type::eof()?'\0':c;
    }
};
class NulOStream : private NulStreambuf, public std::ostream
{
public:
    NulOStream() : std::ostream( this ) {}
    NulStreambuf* rdbuf() const { return (NulStreambuf*)this; }
};

int main()
{
    bool  output_enabled=false;

    NulOStream nos;
    std::ostream &os = (output_enabled)?std::cout : nos;

    os << "Hello" << std::endl;
    os << "c++" << std::endl;

    return 0;
}

方法2:boostの boost::iostreams::null_sink を使う

boostに実装がある.詳細は boost/iostreams/device/null.hpp あたりを読めばわかるので,割愛

方法3: マクロで回避

実際のコードは,多くの場合以下のような形になる

bool output_enabled = false;

if (output_enabled)
    std::cout << "Hello" << std::end;

if (output_enabled)
    std::cout << "c++" << std::end;

と言うわけで

#define LOG if (output_enabled) std::cout 

bool output_enabled = false;

LOG << "Hello" << std::end;
LOG << "c++" << std::end;

実はこれだけで解決する


こう言う形でもいいかも

#define LOG if (x) std::cout 

bool output_enabled = false;

LOG(output_enabled) << "Hello" << std::end;
LOG(output_enabled) << "c++" << std::end;

Linuxの起動が遅い原因の調査方法(その2)

Linuxの起動がものすごく遅い.5分ぐらい掛かる.原因を調査,解決したので手順をまとめておきます.

起動時のログを見る方法

systemdにはシステム起動時のログを分析する機能があります.使い方も簡単

まずは systemd-analyze を実行します

$ systemd-analyze 
Startup finished in 16.337s (firmware) + 2.699s (loader) + 2.958s (kernel) + 5min 1.771s (userspace) = 5min 23.767s 
graphical.target reached after 5min 1.764s in userspace

起動には5分23秒かかっていて,そのうち userspace の処理で 5分1秒も掛かっていることが解ります.たしかに遅い.

より詳細なログを見ます.systemd-analyze に blame オプションを付けます.

$ systemd-analyze  blame

以下のような表示がでました.

5min 400ms ifupdown-wait-online.service
   12.723s ifupdown-pre.service
   10.659s dev-sdc3.device
    5.322s NetworkManager-wait-online.service
    4.624s networking.service
    2.605s udisks2.service
    2.523s smartmontools.service
    1.533s man-db.service
    1.108s vmware.service
     629ms logrotate.service
     480ms apt-daily-upgrade.service
     474ms apt-daily.service
     243ms upower.service
     200ms systemd-journal-flush.service
     156ms vmware-USBArbitrator.service
     120ms exim4.service
      98ms user@1000.service
      92ms nmbd.service
      82ms accounts-daemon.service
      71ms systemd-udev-trigger.service
      65ms avahi-daemon.service
      64ms smbd.service
      62ms fancontrol.service
      60ms NetworkManager.service
      58ms polkit.service
      54ms winbind.service
      53ms systemd-logind.service
      53ms apache2.service
      52ms systemd-journald.service
      49ms rtkit-daemon.service
      49ms systemd-sysusers.service
      47ms nvidia-persistenced.service
      45ms keyboard-setup.service
      45ms home.mount
      44ms wpa_supplicant.service
      43ms dictd.service
      42ms phpsessionclean.service
      39ms systemd-udevd.service
      38ms ModemManager.service
      36ms etc-setserial.service
      34ms lm-sensors.service
      34ms exim4-base.service
      33ms setserial.service
      33ms rng-tools-debian.service
       以下省略

問題があるのは先頭の1行

5min 400ms ifupdown-wait-online.service

この部分です. ifupdown-wait-online.service が猛烈に遅いようです.

サービスの詳細を調べる方法

ifupdown-wait-online.service とは一体何者でしょうか? 調べてみます

最初にサービスの稼働状況を確認します

$ systemctl status ifupdown-wait-online.service

結果

● ifupdown-wait-online.service - Wait for network to be configured by ifupdown
     Loaded: loaded (/lib/systemd/system/ifupdown-wait-online.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sat 2021-07-31 16:27:14 JST; 24h ago
    Process: 332 ExecStart=/lib/ifupdown/wait-online.sh (code=exited, status=1/FAILURE)
   Main PID: 332 (code=exited, status=1/FAILURE)
        CPU: 372ms

サービスはエラーで停止しています

サービスの定義ファイル /lib/systemd/system/ifupdown-wait-online.service の中身を眺めてみます

cat /lib/systemd/system/ifupdown-wait-online.service
[Unit]
Description=Wait for network to be configured by ifupdown
DefaultDependencies=no
Before=network-online.target
ConditionFileIsExecutable=/sbin/ifup

[Service]
Type=oneshot
ExecStart=/lib/ifupdown/wait-online.sh
RemainAfterExit=yes

[Install]
WantedBy=network-online.target

ExecStart=/lib/ifupdown/wait-online.sh ですから, 実体は /lib/ifupdown/wait-online.sh .拡張子的に sh のシェルスクリプトです

$ cat /lib/ifupdown/wait-online.sh

長いので,詳細は割愛.wait-online.shの冒頭は以下のように実装されています

#!/bin/sh
set -e

WAIT_ONLINE_METHOD="ifup"
WAIT_ONLINE_IFACE=""
WAIT_ONLINE_ADDRESS=""
WAIT_ONLINE_TIMEOUT=300

[ -f /etc/default/networking ] && . /etc/default/networking

9行目で,設定ファイルは /etc/default/networking であることがわかります.
(4行目から7行目で,デフォルト値を設定./etc/default/networkingの内容で適宜値を変更する実装になっています)

/lib/ifupdown/wait-online.sh のデバッグ

デバッグするために,とりあえず -x オプションを指定してスクリプトを実行してみます

$ sudo /bin/sh -x /lib/ifupdown/wait-online.sh

結論だけ言うと,以下のコマンドで処理が止まっていました.

$ /sbin/ifquery --state eth1

原因

  1. /lib/ifupdown/wait-online.sh はネットワークインタフェースが利用可能になるまで wait するサービス
  2. このマシンでは eth0 と eth1 の2つのネットワークインタフェースが存在した
  3. しかし eth1 は使用していなかった(実験用の予備で普段はLANケーブルを外している)
  4. /lib/ifupdown/wait-online.sh は eth1がupするまで wait処理をおこない,時間切れで TIMEDOUTエラーで異常終了していた

ということでした

対処

eth1 の wait処理は不要なので /etc/default/networkingを編集して eth1は無視するように設定を変更しました.

WAIT_ONLINE_IFACE="eth0"

まとめ

  • 起動時のログは systemd-analyze で確認できる
  • 詳細なログは systemd-analyze blame で確認できる
  • 問題があるスクリプトが特定できたら,"-x"オプションを使ってデバッグする

関連するエントリ

同様の手順で別トラブルを解決したエントリもあります.併せてご覧ください!
pyopyopyo.hatenablog.com

debian/changelog からバージョン番号を取り出す方法

debianのソースパッケージから,生成されるバイナリパッケージのバージョン情報を抽出するには dpkg-parsechangelog を使うのが簡単.

dpkg-parsechangelog  --file path/to/changelog  --show-field Version 
dpkg-parsechangelog  --file path/to/changelog  --show-field Source 

"--show-field"で,抽出したい情報のフィールド名を指定する

フィールド名とdebian/changelogの書式の対応は以下の通り

Source (Version) Distribution; Urgency

-- Maintainer (Date)

あと Changelogunix timestamp を取る場合は

dpkg-parsechangelog  --file path/to/changelog  --show-field Timestamp 

とすれば良い.

さすがデビアン.よくできている.

pythonで自分のIPアドレスを調べる方法

pythonでローカルのネットワークインタフェースのIPアドレスを調べる方法

def get_local_ip_address():
    from socket import socket, AF_INET, SOCK_DGRAM
    s = socket(AF_INET, SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        ipaddr = s.getsockname()[0]
    except Exception:
        ipaddr = '127.0.0.1'
    finally:
        s.close()
    return ipaddr


print( get_local_ip_address() )

python標準ライブラリのsocketを使って実装しているので,別途モジュールをインストールする必要は無い