MrMartin
MrMartin

Reputation: 467

Detect if video is black and white in bash

I have a folder with hundreds of films, and I'd like to separate the color ones from black and white. Is there a bash command to do this for general video files?

I already extract a frame:

ffmpeg -ss 00:15:00 -i vid.mp4 -t 1 -r 1/1 image.bmp

How can I check if the image has a color component?

Upvotes: 0

Views: 1169

Answers (2)

Harry
Harry

Reputation: 1263

I never found out why video processing questions are answered on SO but as they typically are not closed, i'll do my best... As this is a developer board, i cannot recommend any ready commandline tool to use for your bash command, nor do i know any. Also i cannot give a bash only solution because i do not know how to process binary data in bash.

To get out if an image is grey or not, you'll need to check each pixel for it's color and "guess" if it is kind of grey. As others say in the comments, you will need to analyze multiple pictures of each video to get a more accurete result. For this you could possibly use the scene change detection filter of ffmpeg but thats another topic.

I'd start by resizing the image to save processing power, e.g. to 4x4 pixels. Also make sure you guarantee the colorspace or better pix_format is known so you know what a pixel looks like.

Using this ffmpeg line, you would extract one frame in 4x4 pixels to raw RGB24:

ffmpeg -i D:\smpte.mxf -pix_fmt rgb24 -t 1 -r 1/1 -vf scale=4:4 -f rawvideo d:\out_color.raw

The resulting file contains exactly 48 bytes, 16 pixels each 3 bytes, representing R,G,B color. To check if all pixels are gray, you need to compare the difference between R G and B. Typically R G and B have the same value when they are gray, but in reality you will need to allow some more fuzzy matching, e.g. if all values are the same +-10.

Some example perl code:

use strict;
use warnings;

my $fuzz = 10;
my $inputfile ="d:\\out_grey.raw";
die "input file is not an RGB24 raw picture." if ( (-s $inputfile) %3 != 0);
open (my $fh,$inputfile);
binmode $fh;

my $colordetected = 0;

for (my $i=0;$i< -s $inputfile;$i+=3){
    my ($R,$G,$B);
    read ($fh,$R,1);
    $R = ord($R);
    read ($fh,$B,1);
    $B = ord($B);
    read ($fh,$G,1);
    $G = ord($G);
     if ( $R >= $B-$fuzz  and  $R <= $B+$fuzz and  $B >= $G-$fuzz and  $B <= $G+$fuzz )  {
       #this pixel seems gray
     }else{
        $colordetected ++,
     } 
}

if ($colordetected != 0){
    print "There seem to be colors in this image"
}

Upvotes: 2

Richard L
Richard L

Reputation: 119

I'm probably horribly late to the party, but this works for me. Amend unique colours test count from 10 to suit your own videos if needed. Assumes the centre frame is representative of the overall video colour/BW situation. Its not perfect but does a fair job of separation.

#!/bin/bash

mkdir -p BW COL temp_frames

for video in *.mp4 *.m4v; do
    if [[ -f "$video" ]]; then # Extract a frame from the middle of the video using ffmpeg
    ffmpeg -loglevel error -i "$video" -ss "$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video" | awk '{print int($1/2)}')" -vframes 1 -q:v 2 -f image2 -update 1 "temp_frames/test_frame.jpg"
    count=$(convert temp_frames/test_frame.jpg -unique-colors - | wc -l)

        if [ "$count" -gt 10 ]; then
        echo "Moving color video: $video to COL/"
        \gmv -b "$video" COL/
    else
        echo "$video has $count unique colours"
        echo "Moving black and white video: $video to BW/"
        \gmv -b "$video" BW/
    fi

    # Clean up temp frames
    \rm -f temp_frames/*
    fi
done

Upvotes: 1

Related Questions