Search code examples
pythonopencvwebcamvideo-recording

Why my code fails to save video files of frames capture from a webcam but I can save the frames as image files just fine?


I am trying to develop Python script that can capture and record frames from a webcam and also record audio from a microphone for certain seconds, and repeating until predetermined time to stop. The frames from webcam can be saved as jpg files just fine. The audio can be saved also as wav files with no problem. But the video files cannot be open and give error "This file isn't playable. That might be because the file type is unsupported, the file extension is incorrect, or the file is corrupt." I use OpenCV to get the frames and record the video and image files. I already installed ffmpeg on my computer (Windows 10) and there is no exception error when running my code.

Below is the script I made. The program is set to run for runningDur = 12 seconds. Each video file (.mp4) should be saved every 5 seconds with duration of 5 seconds (videoSaveIntv). A frame will be saved as an image file (.jpg) every 4 seconds (frameSaveIntv). Audio file will be saved every 5 seconds (audioSaveIntv) with duration of 4 seconds (audirorecdur).

import numpy as np
import cv2 as cv
from datetime import time, datetime, timedelta
import sounddevice as sd
from scipy.io.wavfile import write as audioWrite
from threading import Thread
import traceback


cap = cv.VideoCapture(0) # use this line for testing with local camera (webcam, etc.)

# Define the codec and create VideoWriter object
fourcc = cv.VideoWriter_fourcc(*'mp4v')
fps = 20.0
#frameSize = (640, 480)
frameSize = (cv.CAP_PROP_FRAME_WIDTH, cv.CAP_PROP_FRAME_HEIGHT)
# Define parameters for audio recording
audirorecdur = 4
fs= 44100
sd.default.samplerate = fs
sd.default.channels = 1
# flag to trigger video recording
flag1 = True
# flag to trigger frame capture
flag2 = True 
# flag to trigger audio recording
flag3 = True
# startTime1 is to help controlling when we will save a frame as an image file
startTime1 = datetime.now()
# startTime2 is to help controlling how long this script will run
startTime2 = startTime1
# startTime3 is to help controling how long each video file will be saved
startTime3 = startTime1
# startTime4 is to help controling how long each audio file will be saved
startTime4 = startTime1
# create video file object...... maybe not necessary for project purpose
#out1 = cv.VideoWriter("D:/pyvidrec/rec_%03d.mp4", fourcc, fps, frameSize)
# determine how long should we run this program in seconds, 86400 s = 1 day
runningDur = 12
# determine time length of each saved video file in seconds
videoSaveIntv = 5
# determine interval of frame save in seconds
frameSaveIntv = 4
# determine time interval of audio recording start time in seconds
audioSaveIntv = 5

def saveAudioFile():
    sd.wait()
    audioWrite(audiofilename, fs, audiorecording)  # Save as WAV file 

# start the main loop
try:
    while cap.isOpened():
        # get a frame from camera, ret will return True if a frame is received
        ret, frame = cap.read()
        # get current time
        checkTime = datetime.now()
        
        # if no frame received, stop this program
        if not ret:
            print("Can't receive frame (stream end?). Exiting ...")
            break

        # if the video save interval has passed, make flag1 = True
        if (checkTime - startTime3).total_seconds() >= videoSaveIntv:
            flag1 = True
        # if flag1 is True, create new video file
        if flag1 == True:
            out1 = cv.VideoWriter("D:/pyvidrec/%02d%02d%02d_%02d%02d.mp4" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute), fourcc, fps, frameSize)
            # reset startTime3 as checkTime
            startTime3 = checkTime
            flag1 = False
        # write the frame into the video file
        out1.write(frame)

        # if the frame save interval has passed since last time check, make the flag2 = True
        if (checkTime - startTime1).total_seconds() >= frameSaveIntv:
            flag2 = True
        # if flag2 is True, save current frame in an image file
        if flag2 == True:
            cv.imwrite("D:/pyvidrec/%02d%02d%02d_%02d%02d%02d.jpg" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute, checkTime.second),frame)
            # reset startTime1 as checkTime
            startTime1 = checkTime
            # turn flag to False
            flag2 = False

        # if the audio save interval has passed since last time check, make the flag3 = True
        if (checkTime - startTime4).total_seconds() >= audioSaveIntv:
            flag3 = True
            try:
                sd.stop()
                audioWrite(audiofilename, fs, audiorecording)
            except:
                traceback.print_exc()
            
        # if flag3 is True, start getting audio recording
        if flag3 == True:
            audiorecording = sd.rec(int(audirorecdur * fs))
            audiofilename = "D:/pyvidrec/%02d%02d%02d_%02d%02d%02d.wav" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute, checkTime.second)
            #saveAudioFileThread = Thread(target = saveAudioFile)
            #saveAudioFileThread.run()
            # reset startTime4 as checkTime
            startTime4 = checkTime
            # turn flag to False
            flag3 = False
        # if cv.waitKey(1) == ord('q'):
        #     break
    
        # if program has run for certain time, stop it
        if (checkTime - startTime2).total_seconds() >= runningDur:
            break

    # Release everything if job is finished
    cap.release()
    out1.release()

except KeyboardInterrupt:
    print("KeyboardInterrupt Exception is caught. Exiting program...")
    cap.release()
    out1.release()

except:
    traceback.print_exc()
    cap.release()
    out1.release()

This script gives output of 2 mp4 files, 3 jpg files, and 2 wav files. Only mp4 files cant be open. Please help me.


Solution

  • There are few issues that result a non-playable video files:

    • The expression frameSize = (cv.CAP_PROP_FRAME_WIDTH, cv.CAP_PROP_FRAME_HEIGHT) results frameSize = (3, 4).
      The correct syntax is getting the size from cap:

       frame_width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
       frame_height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
       frameSize = (frame_width, frame_height)
      
    • We have to execute out1.release() before opening a new MP4 video file.
      Before executing out1 = cv.VideoWriter, execute out1.release(), except for the first time.
      We may initialize out1 to None above the loop, and execute out1.release() if out1 is not None:

       if out1 is not None:
           out1.release()  # Close the video file, before opening a new one
       out1 = cv.VideoWriter("D:/pyvidrec ...
      

    Code sample without the audio:

    import numpy as np
    import cv2 as cv
    from datetime import time, datetime, timedelta
    #import sounddevice as sd
    #from scipy.io.wavfile import write as audioWrite
    #from threading import Thread
    import traceback
    
    
    cap = cv.VideoCapture(0) # use this line for testing with local camera (webcam, etc.)
    
    # Define the codec and create VideoWriter object
    fourcc = cv.VideoWriter_fourcc(*'mp4v')
    fps = 20.0
    #frameSize = (640, 480)
    #frameSize = (cv.CAP_PROP_FRAME_WIDTH, cv.CAP_PROP_FRAME_HEIGHT)
    frame_width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
    frame_height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
    frameSize = (frame_width, frame_height)
    
    # Define parameters for audio recording
    #audirorecdur = 4
    #fs= 44100
    #sd.default.samplerate = fs
    #sd.default.channels = 1
    # flag to trigger video recording
    flag1 = True
    # flag to trigger frame capture
    flag2 = True 
    # flag to trigger audio recording
    flag3 = True
    # startTime1 is to help controlling when we will save a frame as an image file
    startTime1 = datetime.now()
    # startTime2 is to help controlling how long this script will run
    startTime2 = startTime1
    # startTime3 is to help controling how long each video file will be saved
    startTime3 = startTime1
    # startTime4 is to help controling how long each audio file will be saved
    startTime4 = startTime1
    # create video file object...... maybe not necessary for project purpose
    #out1 = cv.VideoWriter("D:/pyvidrec/rec_%03d.mp4", fourcc, fps, frameSize)
    # determine how long should we run this program in seconds, 86400 s = 1 day
    runningDur = 12
    # determine time length of each saved video file in seconds
    videoSaveIntv = 5
    # determine interval of frame save in seconds
    frameSaveIntv = 4
    # determine time interval of audio recording start time in seconds
    audioSaveIntv = 5
    
    #def saveAudioFile():
    #    sd.wait()
    #    audioWrite(audiofilename, fs, audiorecording)  # Save as WAV file 
    
    out1 = None
    
    # start the main loop
    try:
        while cap.isOpened():
            # get a frame from camera, ret will return True if a frame is received
            ret, frame = cap.read()
            # get current time
            checkTime = datetime.now()
            
            # if no frame received, stop this program
            if not ret:
                print("Can't receive frame (stream end?). Exiting ...")
                break
    
            # if the video save interval has passed, make flag1 = True
            if (checkTime - startTime3).total_seconds() >= videoSaveIntv:
                flag1 = True
            # if flag1 is True, create new video file
            if flag1 == True:
                if out1 is not None:
                    out1.release()  # Close the video file, before opening a new one
                out1 = cv.VideoWriter("D:/pyvidrec/%02d%02d%02d_%02d%02d.mp4" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute), fourcc, fps, frameSize)
                # reset startTime3 as checkTime
                startTime3 = checkTime
                flag1 = False
    
            # write the frame into the video file
            if out1 is not None:
                out1.write(frame)
    
            # if the frame save interval has passed since last time check, make the flag2 = True
            if (checkTime - startTime1).total_seconds() >= frameSaveIntv:
                flag2 = True
            # if flag2 is True, save current frame in an image file
            if flag2 == True:
                cv.imwrite("D:/pyvidrec/%02d%02d%02d_%02d%02d%02d.jpg" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute, checkTime.second),frame)
                # reset startTime1 as checkTime
                startTime1 = checkTime
                # turn flag to False
                flag2 = False
    
            # if the audio save interval has passed since last time check, make the flag3 = True
            #if (checkTime - startTime4).total_seconds() >= audioSaveIntv:
            #    flag3 = True
            #    try:
            #        sd.stop()
            #        audioWrite(audiofilename, fs, audiorecording)
            #    except:
            #        traceback.print_exc()
                
            # if flag3 is True, start getting audio recording
            #if flag3 == True:
            #    audiorecording = sd.rec(int(audirorecdur * fs))
            #    audiofilename = "D:/pyvidrec/%02d%02d%02d_%02d%02d%02d.wav" % (checkTime.year-2000, checkTime.month, checkTime.day, checkTime.hour, checkTime.minute, checkTime.second)
            #    #saveAudioFileThread = Thread(target = saveAudioFile)
            #    #saveAudioFileThread.run()
            #    # reset startTime4 as checkTime
            #    startTime4 = checkTime
            #    # turn flag to False
            #    flag3 = False
            ## if cv.waitKey(1) == ord('q'):
            #     break
        
            # if program has run for certain time, stop it
            if (checkTime - startTime2).total_seconds() >= runningDur:
                break
    
        # Release everything if job is finished
        cap.release()
        out1.release()
    
    except KeyboardInterrupt:
        print("KeyboardInterrupt Exception is caught. Exiting program...")
        cap.release()
        out1.release()
    
    except:
        traceback.print_exc()
        cap.release()
        out1.release()
    

    Note:
    Next time you post a question, please post a minimal reproducible code sample - create a clean code sample that excludes all the parts that are not relevant to the problem (for example: the audio is not relevant to the video issue).