imageio / imageio-ffmpeg

FFMPEG wrapper for Python
BSD 2-Clause "Simplified" License
233 stars 52 forks source link

Unable to find a suitable output format for 'temp-file' #28

Closed ericnjogu closed 4 years ago

ericnjogu commented 4 years ago

I am trying to read a video file and write it to a socket using the code below and it is failing. I would appreciate your help:

async def write_video_to_stream(path_to_video, path_to_socket):
    video_reader = imageio.get_reader(path_to_video)
    sock = await create_socket(path_to_socket)
    _, socket_writer = await asyncio.open_unix_connection(sock=sock)
    img_writer = imageio.get_writer(socket_writer, format='FFMPEG', ffmpeg_log_level='debug')
    for frame in video_reader:
        img_writer.append_data(frame)
        #await socket_writer.drain()
    img_writer.close()
    socket_writer.close()

The code is failing with the stacktrace below (debug logging turned on):

(object-detection) mugo@eric-aspire:~/ai-object-detection/detection-visualization$ python socket_stream.py ~/Videos/dog-rescue-water.mp4 /tmp/sock2
ffmpeg version 4.1-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 6.3.0 (Debian 6.3.0-18+deb9u1) 20170516
  configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc-6 --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gray --enable-libaom --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100
Splitting the commandline.
Reading option '-y' ... matched as option 'y' (overwrite output files) with argument '1'.
Reading option '-f' ... matched as option 'f' (force format) with argument 'rawvideo'.
Reading option '-vcodec' ... matched as option 'vcodec' (force video codec ('copy' to copy stream)) with argument 'rawvideo'.
Reading option '-s' ... matched as option 's' (set frame size (WxH or abbreviation)) with argument '400x400'.
Reading option '-pix_fmt' ... matched as option 'pix_fmt' (set pixel format) with argument 'rgb24'.
Reading option '-r' ... matched as option 'r' (set frame rate (Hz value, fraction or abbreviation)) with argument '10.00'.
Reading option '-i' ... matched as input url with argument '-'.
Reading option '-an' ... matched as option 'an' (disable audio) with argument '1'.
Reading option '-vcodec' ... matched as option 'vcodec' (force video codec ('copy' to copy stream)) with argument 'libx264'.
Reading option '-pix_fmt' ... matched as option 'pix_fmt' (set pixel format) with argument 'yuv420p'.
Reading option '-crf' ... matched as AVOption 'crf' with argument '25'.
Reading option '-v' ... matched as option 'v' (set logging level) with argument 'debug'.
Reading option '/tmp/imageio_8sv61nse' ... matched as output url.
Finished splitting the commandline.
Parsing a group of options: global .
Applying option y (overwrite output files) with argument 1.
Applying option v (set logging level) with argument debug.
Successfully parsed a group of options.
Parsing a group of options: input url -.
Applying option f (force format) with argument rawvideo.
Applying option vcodec (force video codec ('copy' to copy stream)) with argument rawvideo.
Applying option s (set frame size (WxH or abbreviation)) with argument 400x400.
Applying option pix_fmt (set pixel format) with argument rgb24.
Applying option r (set frame rate (Hz value, fraction or abbreviation)) with argument 10.00.
Successfully parsed a group of options.
Opening an input file: -.
[rawvideo @ 0x6584d00] Opening 'pipe:' for reading
[pipe @ 0x6585680] Setting default whitelist 'crypto'
[rawvideo @ 0x6584d00] Before avformat_find_stream_info() pos: 0 bytes read:32768 seeks:0 nb_streams:1
[rawvideo @ 0x6584d00] All info found
[rawvideo @ 0x6584d00] After avformat_find_stream_info() pos: 480000 bytes read:480000 seeks:0 frames:1
Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 0.000000, bitrate: 38400 kb/s
    Stream #0:0, 1, 1/10: Video: rawvideo, 1 reference frame (RGB[24] / 0x18424752), rgb24, 400x400, 0/1, 38400 kb/s, 10 tbr, 10 tbn, 10 tbc
Successfully opened the file.
Parsing a group of options: output url /tmp/imageio_8sv61nse.
Applying option an (disable audio) with argument 1.
Applying option vcodec (force video codec ('copy' to copy stream)) with argument libx264.
Applying option pix_fmt (set pixel format) with argument yuv420p.
Successfully parsed a group of options.
Opening an output file: /tmp/imageio_8sv61nse.
[NULL @ 0x65870c0] Unable to find a suitable output format for '/tmp/imageio_8sv61nse'
/tmp/imageio_8sv61nse: Invalid argument
[AVIOContext @ 0x6585500] Statistics: 480000 bytes read, 0 seeks
Fatal read error on socket transport
protocol: <asyncio.streams.StreamReaderProtocol object at 0x7fa09efcc2b0>
transport: <_SelectorSocketTransport fd=7 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/asyncio/selector_events.py", line 801, in _read_ready__data_received
    data = self._sock.recv(self.max_size)
OSError: [Errno 22] Invalid argument
Traceback (most recent call last):
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio_ffmpeg/_io.py", line 411, in write_frames
    p.stdin.write(bb)
BrokenPipeError: [Errno 32] Broken pipe

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "socket_stream.py", line 33, in <module>
    asyncio.run(write_video_to_stream(ns.path_to_video, ns.path_to_socket))
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "socket_stream.py", line 11, in write_video_to_stream
    img_writer.append_data(frame)
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio/core/format.py", line 492, in append_data
    return self._append_data(im, total_meta)
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio/plugins/ffmpeg.py", line 572, in _append_data
    self._write_gen.send(im)
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio_ffmpeg/_io.py", line 418, in write_frames
    raise IOError(msg)
OSError: [Errno 32] Broken pipe

FFMPEG COMMAND:
/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio_ffmpeg/binaries/ffmpeg-linux64-v4.1 -y -f rawvideo -vcodec rawvideo -s 400x400 -pix_fmt rgb24 -r 10.00 -i - -an -vcodec libx264 -pix_fmt yuv420p -crf 25 -v debug /tmp/imageio_8sv61nse

FFMPEG STDERR OUTPUT:

Fatal Python error: could not acquire lock for <_io.BufferedReader name=11> at interpreter shutdown, possibly due to daemon threads

Thread 0x00007fa0a65a0700 (most recent call first):
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio_ffmpeg/_parsing.py", line 61 in run
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/threading.py", line 917 in _bootstrap_inner
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/threading.py", line 885 in _bootstrap

Current thread 0x00007fa0aad6d740 (most recent call first):
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/subprocess.py", line 1704 in _communicate
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/subprocess.py", line 939 in communicate
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio_ffmpeg/_io.py", line 193 in read_frames
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio/plugins/ffmpeg.py", line 343 in _close
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio/core/format.py", line 252 in close
  File "/home/mugo/miniconda3/envs/object-detection/lib/python3.7/site-packages/imageio/core/format.py", line 241 in __del__
Aborted (core dumped)
almarklein commented 4 years ago

Thanks for the report!

Imageio-ffmpeg only supports writing to a file on the file system, because it calls ffmpeg in a subprocess. Therefore imageio cannot write directly to a file-like object (like a socket). What happens is that ffmpeg is used to write the video to a temporary file, and then it's loaded and written to the file object. This happens about here in imageio/core/request.py.

Now, this error looks a bit like ffmpeg needs a filename extension to determine the output format, and the temp file does not have such an extension. This can be reproduced:

import imageio
import imageio_ffmpeg

ims = imageio.mimread("imageio:cockatoo.mp4", memtest="1000MB")
size = ims[0].shape[1], ims[0].shape[0]

gen = imageio_ffmpeg.write_frames("c:/users/almar/desktop/foo_video_no_ext", size)
gen.send(None)
for im in ims:
    gen.send(im)
gen.close()
almarklein commented 4 years ago

Now how to fix this. The quick version would be to use imageio_ffmpeg directly.

The longer version would probably be to change Request.get_local_filename() here to accept an optional argument to allow plugins to specify a file extension.

ericnjogu commented 4 years ago

Thanks for the response @almarklein and sorry for the long delay.

I tried out your suggestion and it appears that imageio_ffmpeg write_frames() only works with string paths. It would be nice to have it accept file like objects too. This way one could write to a socket.

Here's my code - https://github.com/kunadawa/object-detection-visualization/commit/0e142207add039d18e5ebbb1dd79b80b75cf3d84

almarklein commented 4 years ago

It would be nice to have it accept file like objects too

I agree, but this is not possible because we're calling ffmpeg in a subprocess and all we can do is give it a file path. It would likely be possible if we'd use ffmpeg as a library, but this makes packaging much harder; it was a deliberate choice to use ffmpeg as a subprocess.

almarklein commented 4 years ago

I created https://github.com/imageio/imageio/issues/509 in imageio to fix the issue with the temp file. But then I realized that it won't actually fix your problem: you want to be able to stream video to a file like object, and imageio-ffmpeg simply cannot do that. I've added a note to the readme that explains this.