動画のスライドを抽出する

動画の全スライドを抽出して PDF 化する

スライドの抽出処理

ffmpeg で画像切り出しをした所で下記の記事を発見した。

qiita.com

ありがたや〜早速動かしてみる。

必要なパッケージのインストール

brew install ffmpeg imagemagick
touch movie2slide.sh

movie2slide.sh

#!/bin/bash

for command in ffmpeg convert diff
do
    which command$
    if [ $? != 0 ]; then
        echo "$command not found."
        exit 1
    fi
done

PSNR_threadshold=30
SLIDE_PREFIX=slide
RATE=1
isRATEdecimal=$( echo "$RATE > 1" | bc )
PHOTOFORMAT=jpg

moviefile=$1
pathname_tmp=${moviefile// /_}
pathname=${pathname_tmp%.*}
workdirname="${pathname}.$( date +"slide_%Y%m%d_%H%M%S" )"
mkdir $workdirname

ffmpeg -i "$1" -map 0:1 -vn -ac 2 -acodec pcm_s16le  -f wav ${workdirname}/audio.wav
ffmpeg -i "$1" -map 0:1 -vn -ac 2 -acodec libmp3lame -f wav ${workdirname}/audio.mp3

ffmpeg -i "$1" -r ${RATE} ${workdirname}/${SLIDE_PREFIX}.%d.png

cd ${workdirname}
finalslide_number=$( ls | wc -l )

merge_and_delete_duplicated_photos() {
    local same_picture_begin=$1
    local same_picture_end=$2
    local j

    if [ "${same_picture_begin}" != "${same_picture_end}" ];then
        if [ "${SEQUENCE}" != "" ]; then
            convert -evaluate-sequence ${SEQUENCE} $(
                for j in $( seq ${same_picture_begin} ${same_picture_end} ) ; do
                echo ${SLIDE_PREFIX}.$j.png
                done
            ) zmedian.${PHOTOFORMAT}
        fi

        for j in $( seq $((same_picture_begin+1)) ${same_picture_end} ) ; do
            rm ${SLIDE_PREFIX}.$j.png
        done

        if [ -e zmedian.${PHOTOFORMAT} ]; then
            mv zmedian.${PHOTOFORMAT} ${SLIDE_PREFIX}.${same_picture_begin}.${PHOTOFORMAT}
        fi
    fi

    if [ "${PHOTOFORMAT}" != "png" ]; then
        convert ${SLIDE_PREFIX}.${same_picture_begin}.png ${SLIDE_PREFIX}.${same_picture_begin}.${PHOTOFORMAT}
        rm ${SLIDE_PREFIX}.${same_picture_begin}.png
    else
        pngquant ${SLIDE_PREFIX}.${same_picture_begin}.png
        mv ${SLIDE_PREFIX}.${same_picture_begin}-fs8.png ${SLIDE_PREFIX}.${same_picture_begin}.png
    fi
}

detect_duplicated_photos() {

    local same_picture_begin=1
    local same_picture_end=1
    local i

    for i in $( seq 1 ${finalslide_number} ); do

        filename_current=${SLIDE_PREFIX}.$i.png
        filename_next=${SLIDE_PREFIX}.$((i+1)).png

        filename_start=${SLIDE_PREFIX}.${same_picture_begin}.png

        if [ -e $filename_current ] && [ -e $filename_next ]; then


            diff -b $filename_start $filename_next > /dev/null

            if [ $? -eq 0 ]; then

                PSNR=0
            else

                PSNR=$( compare -metric PSNR $filename_start $filename_next zdiff.png 2>&1 )
                rm zdiff.png


                PSNR=${PSNR%.*}
            fi

            echo -en "$filename_start -> $filename_next : PSNR = " $PSNR


            if [ $PSNR -eq 0 ] || [ $PSNR -ge $PSNR_threadshold ]; then
                echo -n " : delete"
                same_picture_end=$((i+1))
            else
                echo -n " : convert to ${PHOTOFORMAT}"
                merge_and_delete_duplicated_photos $same_picture_begin $same_picture_end

                same_picture_begin=$((i+1))
                same_picture_end=$((i+1))
            fi
            echo ""
        fi
    done

    merge_and_delete_duplicated_photos $same_picture_begin $same_picture_end
}

rename_filename_with_timestamped() {

    for file in $( ls ${SLIDE_PREFIX}.*.${PHOTOFORMAT} )
    do
        filename_tmp=${file%%.${PHOTOFORMAT}}
        number=${filename_tmp##${SLIDE_PREFIX}.}
        number=$(( number + 0 ))

        raw_seconds=$( echo "scale=2; $number/$RATE" | bc )
          hour=$( echo "scale=0; $raw_seconds/3600"            | bc )
        minute=$( echo "scale=0;($raw_seconds -$hour*3600)/60" | bc )
        second=$( echo "scale=0;($raw_seconds -$hour*3600 -$minute*60)*2/2" | bc )

        if [ $isRATEdecimal == 1 ]; then
            millisecond=$( echo "scale=0; ($raw_seconds -$hour*3600 -$minute*60 -$second)*100" | bc )
            timestamp=$(printf "%02d.%02d.%02d.%02d" ${hour%.*} ${minute%.*} ${second%.*} ${millisecond%.*} )
        else
            timestamp=$(printf "%02d.%02d.%02d" $hour $minute $second )
        fi
        mv $file ${SLIDE_PREFIX}.$timestamp.${PHOTOFORMAT}
    done

    for file in $( ls ${SLIDE_PREFIX}.*.${PHOTOFORMAT} ) ; do touch $file ; done
}

detect_duplicated_photos
rename_filename_with_timestamped

実行

zsh ./movie2slide.sh {xxxxx}.mp4

僕の環境で 1 時間の動画を変換するのに 20 分ほどかかった。 また、途中ディレクトリの容量を観察してみると 10GB を超えていたので容量には注意。 最初に出来上がった PNG ファイル 4200 枚が 重複分を削除し、614 枚まで減った。 最終的に 43.8MB に落ち着いた。

PDF 化処理

こちらの記事に大枠はありました。

qiita.com

ありがたや〜。

必要なパッケージのインストール

pip3 install img2pdf
pip3 install natsort
touch convert2pdf.py

img2pdf が brew パッケージにあれば 1 つのシェルスクリプトファイルだけで完結するのだが、仕方なし。 記事では出力された画像がソートされていないのでパッケージを追加して PDF を作成。

convert2pdf.py

import os
import img2pdf
from PIL import Image

if __name__ == '__main__':
    pdf_FileName = "./output.pdf" # Export PDF filename
    png_Folder = "./jpg/" # Input Images Directory
    extension  = ".jpg" # convert only jpg files

    with open(pdf_FileName,"wb") as f:
        f.write(img2pdf.convert([Image.open(jpg_Folder+j).filename for j in natsorted(os.listdir(jpg_Folder))if j.endswith(extension)]))