vidgear: Small number of frames causes no output.

Description

I an trying to make a video from a low number of frames. However, the ffmpeg process is terminated before the writing is finished. I also add audio from a clip that has the same duration as the video made. It’s also possible this causes the last few frames of longer clips to go missing as well, but isn’t that noticable unless you pay attention at the end, expect for situation where chapters won’t show due to incomplete file closes. (Which happens with ffmpeg, copying such files with ffmpeg again will reintroduce the chapters in a video. Pardon the sidestep, but i have had that problem with WriteGear as well, so i strongly suspect it the same cause.)

(Note: In the example below, the clip doesn’t have audio but the result is still the same.)

Acknowledgment

Environment

  • VidGear version: 0.1.9-dev
  • Branch: Development
  • Python version: 3.6
  • pip version: Not relevant
  • Operating System and version: Windows 10

Expected Behavior

Expected video

Actual Behavior

0 byte file is made.

Possible Fix

The ffmpeg process is terminated when calling close() with the -i flag is supplied as a parameter, which i assume kills ffmpeg before it has finished processing the frames. There are many situations where slower encoders are picked where there closing might happen before a frame write happens.

Relevant line of code: https://github.com/abhiTronix/vidgear/blob/b9d11098045e864afcceaced18973a0080c756ce/vidgear/gears/writegear.py#L534-L535

Steps to reproduce

Debug setup In folder: vid.webm # Video where audio is copied from (14 Frames) frames # Folder with frames for vidgear (both video and frames can be uploaded)

Code:

from vidgear.gears import WriteGear
from PIL import Image
import os
import numpy as np
import vidgear

print(vidgear.version.__version__)
source = 'vid.webm'

output_params = {"-input_framerate": 29.97*2, # Double the framerate of the video, double frames in the frames folder.
                 '-i': source,
                 '-clones': ['-map', '0:v:0', '-map', '1:a?', '-map', '1:s?']}

writer = WriteGear(output_filename='test_out.mkv', logging=True, **output_params)
for image in os.listdir('frames'):
    img = Image.open(os.path.join('frames', image))
    writer.write(np.asarray(img))

writer.close()

Default ffmpeg is used, and let Writegear handle output format and everything

Logging:

0.1.9-dev
WriteGear :: DEBUG :: Compression Mode is enabled therefore checking for valid FFmpeg executables.
WriteGear :: DEBUG :: Output_params Dict: {'-input_framerate': '59.94', '-i': 'vid.webm', '-clones': ['-map', '0:v:0', '-map', '1:a?', '-map', '1:s?']}
Helper :: DEBUG :: FFmpeg Windows Download Path: C:\Users\thoma\AppData\Local\Temp
Helper :: DEBUG :: Final FFmpeg Path: C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe
Helper :: DEBUG :: FFmpeg validity Test Passed!
Helper :: DEBUG :: Found valid FFmpeg Version: `b'git-2020-07-16-d11cc74'` installed on this system
WriteGear :: DEBUG :: Found valid FFmpeg executables: `C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe`.
WriteGear :: DEBUG :: Compression Mode is configured properly!
WriteGear :: DEBUG :: InputFrame => Height:372 Width:390 Channels:3
WriteGear :: DEBUG :: Setting Input FrameRate = 59.94
WriteGear :: DEBUG :: Executing FFmpeg command: `C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe -y -f rawvideo -vcodec rawvideo -s 390x372 -pix_fmt bgr24 -framerate 59.94 -i - -i vid.webm -map 0:v:0 -map 1:a? -map 1:s? -vcodec libx264 -crf 18 -preset fast D:\AI_RRIN\RRIN\test_softmax\test_out.mkv`
ffmpeg version git-2020-07-16-d11cc74 Copyright (c) 2000-2020 the FFmpeg developers
  built with gcc 9.3.1 (GCC) 20200621
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libsrt --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libgsm --disable-w32threads --enable-libmfx --enable-ffnvcodec --enable-cuda-llvm --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt --enable-amf
  libavutil      56. 55.100 / 56. 55.100
  libavcodec     58. 96.100 / 58. 96.100
  libavformat    58. 48.100 / 58. 48.100
  libavdevice    58. 11.101 / 58. 11.101
  libavfilter     7. 87.100 /  7. 87.100
  libswscale      5.  8.100 /  5.  8.100
  libswresample   3.  8.100 /  3.  8.100
  libpostproc    55.  8.100 / 55.  8.100
Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 0.000000, bitrate: 208706 kb/s
    Stream #0:0: Video: rawvideo (BGR[24] / 0x18524742), bgr24, 390x372, 208706 kb/s, 59.94 tbr, 59.94 tbn, 59.94 tbc
Input #1, matroska,webm, from 'vid.webm':
  Metadata:
    title           : xbox_loop_test3.1
    PURL            : https://www.youtube.com/watch?v=FrL8YFNvEWM
    DATE            : 20171024
    COMPATIBLE_BRANDS: iso6avc1mp41
    MAJOR_BRAND     : dash
    MINOR_VERSION   : 0
    ARTIST          : Real Game Media
    DESCRIPTION     : Nice to see the original boot animation again after all these years. http://realgamemedia.com/original-xbox-backwards-compatibility-finally/ Link to US XBOX ...
    COMMENT         : Nice to see the original boot animation again after all these years. http://realgamemedia.com/original-xbox-backwards-compatibility-finally/ Link to US XBOX ...
    ENCODER         : Lavf58.45.100
  Duration: 00:00:00.50, start: 0.033000, bitrate: 358 kb/s
    Stream #1:0: Video: vp9 (Profile 0), yuv420p(tv), 410x412, SAR 1:1 DAR 205:206, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbc (default)
    Metadata:
      HANDLER_NAME    : VideoHandler
      ENCODER         : Lavc58.91.100 libvpx-vp9
      DURATION        : 00:00:00.500000000
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
[swscaler @ 00000168e82a5600] Warning: data is not aligned! This can lead to a speed loss
[libx264 @ 00000168e8244ac0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 00000168e8244ac0] profile High 4:4:4 Predictive, level 3.0, 4:4:4, 8-bit
[libx264 @ 00000168e8244ac0] 264 - core 160 - H.264/MPEG-4 AVC codec - Copyleft 2003-2020 - http://www.videolan.org/x264.html - options: cabac=1 ref=2 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=6 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=1 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=30 rc=crf mbtree=1 crf=18.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, matroska, to 'D:\AI_RRIN\RRIN\test_softmax\test_out.mkv':
  Metadata:
    encoder         : Lavf58.48.100
    Stream #0:0: Video: h264 (libx264) (H264 / 0x34363248), yuv444p, 390x372, q=-1--1, 59.94 fps, 1k tbn, 59.94 tbc
    Metadata:
      encoder         : Lavc58.96.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
WriteGear :: DEBUG :: Terminating WriteGear Processes.

Logging with the mentioned lines commented out: (which has working result)

0.1.9-dev
WriteGear :: DEBUG :: Compression Mode is enabled therefore checking for valid FFmpeg executables.
WriteGear :: DEBUG :: Output_params Dict: {'-input_framerate': '59.94', '-i': 'vid.webm', '-clones': ['-map', '0:v:0', '-map', '1:a?', '-map', '1:s?']}
Helper :: DEBUG :: FFmpeg Windows Download Path: C:\Users\thoma\AppData\Local\Temp
Helper :: DEBUG :: Final FFmpeg Path: C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe
Helper :: DEBUG :: FFmpeg validity Test Passed!
Helper :: DEBUG :: Found valid FFmpeg Version: `b'git-2020-07-16-d11cc74'` installed on this system
WriteGear :: DEBUG :: Found valid FFmpeg executables: `C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe`.
WriteGear :: DEBUG :: Compression Mode is configured properly!
WriteGear :: DEBUG :: InputFrame => Height:372 Width:390 Channels:3
WriteGear :: DEBUG :: Setting Input FrameRate = 59.94
WriteGear :: DEBUG :: Executing FFmpeg command: `C:\Users\thoma\AppData\Local\Temp\ffmpeg-latest-win64-static/bin/ffmpeg.exe -y -f rawvideo -vcodec rawvideo -s 390x372 -pix_fmt bgr24 -framerate 59.94 -i - -i vid.webm -map 0:v:0 -map 1:a? -map 1:s? -vcodec libx264 -crf 18 -preset fast D:\AI_RRIN\RRIN\test_softmax\test_out.mkv`
ffmpeg version git-2020-07-16-d11cc74 Copyright (c) 2000-2020 the FFmpeg developers
  built with gcc 9.3.1 (GCC) 20200621
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libsrt --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libgsm --disable-w32threads --enable-libmfx --enable-ffnvcodec --enable-cuda-llvm --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt --enable-amf
  libavutil      56. 55.100 / 56. 55.100
  libavcodec     58. 96.100 / 58. 96.100
  libavformat    58. 48.100 / 58. 48.100
  libavdevice    58. 11.101 / 58. 11.101
  libavfilter     7. 87.100 /  7. 87.100
  libswscale      5.  8.100 /  5.  8.100
  libswresample   3.  8.100 /  3.  8.100
  libpostproc    55.  8.100 / 55.  8.100
Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 0.000000, bitrate: 208706 kb/s
    Stream #0:0: Video: rawvideo (BGR[24] / 0x18524742), bgr24, 390x372, 208706 kb/s, 59.94 tbr, 59.94 tbn, 59.94 tbc
Input #1, matroska,webm, from 'vid.webm':
  Metadata:
    title           : xbox_loop_test3.1
    PURL            : https://www.youtube.com/watch?v=FrL8YFNvEWM
    DATE            : 20171024
    COMPATIBLE_BRANDS: iso6avc1mp41
    MAJOR_BRAND     : dash
    MINOR_VERSION   : 0
    ARTIST          : Real Game Media
    DESCRIPTION     : Nice to see the original boot animation again after all these years. http://realgamemedia.com/original-xbox-backwards-compatibility-finally/ Link to US XBOX ...
    COMMENT         : Nice to see the original boot animation again after all these years. http://realgamemedia.com/original-xbox-backwards-compatibility-finally/ Link to US XBOX ...
    ENCODER         : Lavf58.45.100
  Duration: 00:00:00.50, start: 0.033000, bitrate: 358 kb/s
    Stream #1:0: Video: vp9 (Profile 0), yuv420p(tv), 410x412, SAR 1:1 DAR 205:206, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbc (default)
    Metadata:
      HANDLER_NAME    : VideoHandler
      ENCODER         : Lavc58.91.100 libvpx-vp9
      DURATION        : 00:00:00.500000000
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
[swscaler @ 0000019397bc5600] Warning: data is not aligned! This can lead to a speed loss
[libx264 @ 0000019397b64ac0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0000019397b64ac0] profile High 4:4:4 Predictive, level 3.0, 4:4:4, 8-bit
[libx264 @ 0000019397b64ac0] 264 - core 160 - H.264/MPEG-4 AVC codec - Copyleft 2003-2020 - http://www.videolan.org/x264.html - options: cabac=1 ref=2 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=6 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=1 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=30 rc=crf mbtree=1 crf=18.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, matroska, to 'D:\AI_RRIN\RRIN\test_softmax\test_out.mkv':
  Metadata:
    encoder         : Lavf58.48.100
    Stream #0:0: Video: h264 (libx264) (H264 / 0x34363248), yuv444p, 390x372, q=-1--1, 59.94 fps, 1k tbn, 59.94 tbc
    Metadata:
      encoder         : Lavc58.96.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
WriteGear :: DEBUG :: Terminating WriteGear Processes.
frame=   27 fps=0.0 q=-1.0 Lsize=      49kB time=00:00:00.40 bitrate=1000.2kbits/s speed=2.17x    
video:48kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.693746%
[libx264 @ 0000019397b64ac0] frame I:2     Avg QP:21.34  size:  2958
[libx264 @ 0000019397b64ac0] frame P:9     Avg QP:21.42  size:  2278
[libx264 @ 0000019397b64ac0] frame B:16    Avg QP:20.98  size:  1388
[libx264 @ 0000019397b64ac0] consecutive B-frames: 11.1% 22.2% 22.2% 44.4%
[libx264 @ 0000019397b64ac0] mb I  I16..4: 46.4% 52.8%  0.8%
[libx264 @ 0000019397b64ac0] mb P  I16..4: 31.6% 33.1%  0.5%  P16..4: 17.3%  4.9%  1.0%  0.0%  0.0%    skip:11.7%
[libx264 @ 0000019397b64ac0] mb B  I16..4: 10.1%  8.4%  0.3%  B16..8: 28.5%  6.3%  0.2%  direct:11.0%  skip:35.1%  L0:49.8% L1:45.6% BI: 4.6%
[libx264 @ 0000019397b64ac0] 8x8 transform intra:49.4% inter:71.8%
[libx264 @ 0000019397b64ac0] coded y,u,v intra: 22.7% 10.8% 14.3% inter: 7.9% 6.6% 7.4%
[libx264 @ 0000019397b64ac0] i16 v,h,dc,p: 21% 16%  6% 58%
[libx264 @ 0000019397b64ac0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 27% 20% 17%  5%  8%  7%  7%  6%  3%
[libx264 @ 0000019397b64ac0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 18% 18%  3%  8%  8%  8%  5%  2%
[libx264 @ 0000019397b64ac0] Weighted P-Frames: Y:44.4% UV:22.2%
[libx264 @ 0000019397b64ac0] ref P L0: 76.8% 23.2%
[libx264 @ 0000019397b64ac0] ref B L0: 79.4% 20.6%
[libx264 @ 0000019397b64ac0] ref B L1: 92.1%  7.9%
[libx264 @ 0000019397b64ac0] kb/s:863.56

Take note how there is stuff printed after calling close in this last log, but not in the first one. While i do know printing doesn’t follow proper ordering, i do still believe this to be accurate here. After close is called, the ffmpeg process is terminated and won’t continue so nothing is printed.

When run, there is created a 0 byte file, the typical ones created by ffmpeg before frames have been written to it. Commenting out the code mentioned above, makes a proper video file.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Tested it quickly, and seems to be working.

Thanks again for the good work!

boolean needs to be changed to bool. Crashes otherwise since it’s not defined. There’s also an edge case if the user provides the termination flag, but not “-i” where that key is not removed. It’s not pretty, but the following code is a suggested fix:

# activate force termination
if "-disable_force_termination" in self.__output_parameters:
    if "-i" in self.__output_parameters:
        self.__force_termination = (
            self.__output_parameters["-disable_force_termination"]
            if isinstance(
                self.__output_parameters["-disable_force_termination"],
                bool,
            )
            else False
        )
    else:
        self.__force_termination = False

    del self.__output_parameters["-disable_force_termination"]  # clean
else:
    self.__force_termination = False

Edit: Flipped a boolean.

I’d could have made pull requests for these things, it just that i have no experience when it comes to creating pull requests and such.

While i can say i have a pretty decent understanding of writegear.py, i can’t say as much for the rest of VidGear. Streaming isn’t my strong side either, but I’m happy to have a look at it, just not sure how much i can actually do.

Used testing branch as development is removed at time of writing. Pardon the delayed response.

The option probably works, but ffmpeg crashes due to the option not being deleted from the output parameters before being passed to it.

Also, as per the docs in the referenced commit, the default usage is letting the user provide a boolean, but this is not checked in the actual code, only the existence of the termination key. This may lead a user to incorrectly assume that passing the flag with the a False value will cause the default behavior, when it does not. To solve both issues, i propose to change the following lines

https://github.com/abhiTronix/vidgear/blob/d76c8be8df075b3d0feef2c6de1b40f7706c5ffc/vidgear/gears/writegear.py#L183-L186

to the following:

if "-disable_force_termination" in self.__output_parameters:
    self.__force_termination = not self.__output_parameters.get("-disable_force_termination")
    del self.__output_parameters["-disable_force_termination"]

Edit: Removed some comment, reading old Stack Overflow for too old python versions is not always good.

@Thomasedv Give me few days, as I’m stacked up with work right now. I’ll notify you here when I’ll be pushing related commits.

That seems proper. I’d hold on implementing until one of us has tested the ffmpeg flag i mentioned , it might just do the job for us, and can be either manually or automatically added to the outout_params as well. And optionally be disabled just like termination. And I feel that if you can avoid termination altogether you’ll be handling stopping much better overall. I can do a test of that once I’m off work.

On the topic of time.sleep, I think it would be too arbitrary, in edge cases where encoding takes a long time, eg. AV1 encoding with vmaf tune, I think one frame can take upwards to 30 sec for example going by a post i saw earlier. Which may leave the user in the same situation.

If i recall, I added this .terminate() callback to address this bug which causes the undesired behavior where sound continues to record with a frozen picture (if live audio is used as source) on termination, and without this callback this problem persists

It’s what I assumed. I honestly don’t know exactly if that is possible, the best suggestion I have is to add a keyword argument to the close method that let’s users turn of the termination. Eg. Have a is_livestream=True which would support backwards compatibility and let a user set to false to let it finish at its own pace.

I would think ffmpeg has some potential options, I’m not at liberty to test for a while, but the -shortest flag might address that bug, assuming ffmpeg can understand its over once the stdin closes. I only Google from mobile so we’ll see, I can test with a random audio clip later.

vidgear_test.zip

Images and video is part of original xbox startup(minor flash warning in case of epilepsy), and the frames are the same video but interpolatated which is why the input has to be double the framerate of the video. This works well for anything else where frames are written. Frames extracted are processed by my original python script, but in this case I just wrote them as png directly instead of using WriteGear.

No audio in the original video, I didn’t add that when i made the short video. Shouldn’t matter for the case at hand, as the lack of audio shouldn’t prevent anything from being made. It’s my mistake however, but if you insist I can make a new example after work.

os.listdir() is sorted by filename on windows, but i added the sorted for good measure, same with the -r tag, i noticed ffmpeg could drop frames without it with my own settings. (because it assumed it was working realtime) But i just went barebones for the sake of the simple code.

And again, that i consistently have no output with .terminate() there, and correct video without it, seems pretty likely that the subprocess is killing it before it finished. On Windows it calls the win32 api which has the following documentation:

The TerminateProcess function is used to unconditionally cause a process to exit. The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess.

This function stops execution of all threads within the process and requests cancellation of all pending I/O. The terminated process cannot exit until all pending I/O has been completed or canceled. When a process terminates, its kernel object is not destroyed until all processes that have open handles to the process have released those handles.

TerminateProcess is asynchronous; it initiates termination and returns immediately. If you need to be sure the process has terminated, call the WaitForSingleObject function with a handle to the process.

A process cannot prevent itself from being terminated.

It’s a bit vague for me, but it sounds like it may prevent any IO from happening, not to mention stop ffmpeg from processing the last frames given to it, with any slow encoder it would take longer than the call to close after the last write call.