Repository: streamlink/streamlink

Issue:
trace logging with ffmpeg - ImportError: sys.meta_path is None, Python is likely shutting down
## Bug Report

- [X] This is a bug report and I have read the contribution guidelines.

### Description

If you use `-l trace` with a FFmpeg Stream, there will be an error when the player is closed or the stream ends.

error might be related to https://github.com/streamlink/streamlink/pull/3273 and `def formatTime`

### Reproduction steps / Explicit stream URLs to test

1. open a muxed ffmpeg video with loglevel trace
2. `streamlink "https://www.youtube.com/watch?v=VaU6GR8OHNU" best -l trace`
3. close the player or wait until the video is over

### Log output

working 9ac40cb075de93dfc90f8f616a649e9cd1ad32eb

```
$ streamlink "https://www.youtube.com/watch?v=VaU6GR8OHNU" -l trace
[16:50:38,702][cli][debug] OS:         Linux-5.8.0-29-generic-x86_64-with-glibc2.32
[16:50:38,702][cli][debug] Python:     3.8.6
[16:50:38,702][cli][debug] Streamlink: 1.7.0+20.g9ac40cb
[16:50:38,702][cli][debug] Requests(2.25.0), Socks(1.7.1), Websocket(0.57.0)
[16:50:38,702][cli][info] Found matching plugin youtube for URL https://www.youtube.com/watch?v=VaU6GR8OHNU
[16:50:38,702][plugin.youtube][debug] Video ID from URL
[16:50:38,702][plugin.youtube][debug] Using video ID: VaU6GR8OHNU
[16:50:39,757][plugin.youtube][debug] get_video_info - 1: Found data
[16:50:39,759][plugin.youtube][debug] MuxedStream: v 135 a 251 = 480p
[16:50:39,759][plugin.youtube][debug] MuxedStream: v 133 a 251 = 240p
[16:50:39,759][plugin.youtube][debug] MuxedStream: v 160 a 251 = 144p
[16:50:39,759][cli][info] Available streams: audio_mp4a, audio_opus, 144p (worst), 240p, 360p, 480p (best)
[16:50:39,759][cli][info] Opening stream: 480p (muxed-stream)
[16:50:39,759][stream.ffmpegmux][debug] Opening http substream
[16:50:39,859][stream.ffmpegmux][debug] Opening http substream
[16:50:39,947][stream.ffmpegmux][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/ffmpeg-9457-466 -i /tmp/ffmpeg-9457-333 -c:v copy -c:a copy -map 0 -map 1 -f matroska pipe:1
[16:50:39,947][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9457-466
[16:50:39,948][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9457-333
[16:50:39,950][cli][debug] Pre-buffering 8192 bytes
[16:50:40,002][cli][info] Starting player: /usr/bin/mpv
[16:50:40,002][cli.output][debug] Opening subprocess: /usr/bin/mpv "--title=Christmas Music 2020 🎅 Top Christmas Songs Playlist 2020 🎄 Best Christmas Songs Ever" -
[16:50:40,505][cli][debug] Writing stream to output
[16:50:46,869][cli][info] Player closed
[16:50:46,869][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:50:46,870][stream.ffmpegmux][debug] Pipe copy complete: /tmp/ffmpeg-9457-466
[16:50:46,873][stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-9457-333
[16:50:46,954][stream.ffmpegmux][debug] Closed all the substreams
[16:50:46,954][cli][info] Stream ended
[16:50:46,954][cli][info] Closing currently open stream...
[16:50:46,954][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:50:46,954][stream.ffmpegmux][debug] Closed all the substreams
[16:50:46,969][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:50:46,969][stream.ffmpegmux][debug] Closed all the substreams
```

broken 70dc809b8128287f35405ac4279bdea3fa2edddc

```
$ streamlink "https://www.youtube.com/watch?v=VaU6GR8OHNU" -l trace
[16:51:11.763092][cli][debug] OS:         Linux-5.8.0-29-generic-x86_64-with-glibc2.32
[16:51:11.763170][cli][debug] Python:     3.8.6
[16:51:11.763193][cli][debug] Streamlink: 1.7.0+21.g70dc809
[16:51:11.763212][cli][debug] Requests(2.25.0), Socks(1.7.1), Websocket(0.57.0)
[16:51:11.763245][cli][info] Found matching plugin youtube for URL https://www.youtube.com/watch?v=VaU6GR8OHNU
[16:51:11.763275][plugin.youtube][debug] Video ID from URL
[16:51:11.763295][plugin.youtube][debug] Using video ID: VaU6GR8OHNU
[16:51:12.669559][plugin.youtube][debug] get_video_info - 1: Found data
[16:51:12.670741][plugin.youtube][debug] MuxedStream: v 135 a 251 = 480p
[16:51:12.670780][plugin.youtube][debug] MuxedStream: v 133 a 251 = 240p
[16:51:12.670804][plugin.youtube][debug] MuxedStream: v 160 a 251 = 144p
[16:51:12.671317][cli][info] Available streams: audio_mp4a, audio_opus, 144p (worst), 240p, 360p, 480p (best)
[16:51:12.671355][cli][info] Opening stream: 480p (muxed-stream)
[16:51:12.671386][stream.ffmpegmux][debug] Opening http substream
[16:51:12.750200][stream.ffmpegmux][debug] Opening http substream
[16:51:12.813040][stream.ffmpegmux][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/ffmpeg-9607-784 -i /tmp/ffmpeg-9607-270 -c:v copy -c:a copy -map 0 -map 1 -f matroska pipe:1
[16:51:12.813226][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9607-784
[16:51:12.813344][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9607-270
[16:51:12.815867][cli][debug] Pre-buffering 8192 bytes
[16:51:12.875782][cli][info] Starting player: /usr/bin/mpv
[16:51:12.876152][cli.output][debug] Opening subprocess: /usr/bin/mpv "--title=Christmas Music 2020 🎅 Top Christmas Songs Playlist 2020 🎄 Best Christmas Songs Ever" -
[16:51:13.378673][cli][debug] Writing stream to output
[16:51:19.777326][cli][info] Player closed
[16:51:19.777459][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:51:19.779916][stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-9607-270
[16:51:19.780000][stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-9607-784
[16:51:26.824121][stream.ffmpegmux][debug] Closed all the substreams
[16:51:26.824246][cli][info] Stream ended
[16:51:26.824434][cli][info] Closing currently open stream...
[16:51:26.824477][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:51:26.824539][stream.ffmpegmux][debug] Closed all the substreams
--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.8/logging/__init__.py", line 1081, in emit
    msg = self.format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 925, in format
    return fmt.format(record)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 54, in format
    return super().format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 666, in format
    record.asctime = self.formatTime(record, self.datefmt)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 41, in formatTime
    return tdt.strftime(datefmt or self.default_time_format)
ImportError: sys.meta_path is None, Python is likely shutting down
Call stack:
  File "venv3/lib/python3.8/site-packages/streamlink/stream/ffmpegmux.py", line 158, in close
    log.debug("Closing ffmpeg thread")
Message: 'Closing ffmpeg thread'
Arguments: ()
--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.8/logging/__init__.py", line 1081, in emit
    msg = self.format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 925, in format
    return fmt.format(record)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 54, in format
    return super().format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 666, in format
    record.asctime = self.formatTime(record, self.datefmt)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 41, in formatTime
    return tdt.strftime(datefmt or self.default_time_format)
ImportError: sys.meta_path is None, Python is likely shutting down
Call stack:
  File "venv3/lib/python3.8/site-packages/streamlink/stream/ffmpegmux.py", line 169, in close
    log.debug("Closed all the substreams")
Message: 'Closed all the substreams'
Arguments: ()
```

latest 3e8fbe63b89dee2d3a2111a88e185be0dbb54b8d

```
$ streamlink "https://www.youtube.com/watch?v=VaU6GR8OHNU" -l trace
[16:54:12.507281][cli][debug] OS:         Linux-5.8.0-29-generic-x86_64-with-glibc2.32
[16:54:12.507359][cli][debug] Python:     3.8.6
[16:54:12.507383][cli][debug] Streamlink: 1.7.0+78.g3e8fbe6
[16:54:12.507402][cli][debug] Requests(2.25.0), Socks(1.7.1), Websocket(0.57.0)
[16:54:12.507437][cli][info] Found matching plugin youtube for URL https://www.youtube.com/watch?v=VaU6GR8OHNU
[16:54:12.507464][plugin.youtube][debug] Video ID from URL
[16:54:12.507485][plugin.youtube][debug] Using video ID: VaU6GR8OHNU
[16:54:13.673312][plugin.youtube][debug] get_video_info - 1: Found data
[16:54:13.674497][plugin.youtube][debug] MuxedStream: v 135 a 251 = 480p
[16:54:13.674534][plugin.youtube][debug] MuxedStream: v 133 a 251 = 240p
[16:54:13.674558][plugin.youtube][debug] MuxedStream: v 160 a 251 = 144p
[16:54:13.675102][cli][info] Available streams: audio_mp4a, audio_opus, 144p (worst), 240p, 360p, 480p (best)
[16:54:13.675143][cli][info] Opening stream: 480p (muxed-stream)
[16:54:13.675175][stream.ffmpegmux][debug] Opening http substream
[16:54:13.753371][stream.ffmpegmux][debug] Opening http substream
[16:54:13.833386][stream.ffmpegmux][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/ffmpeg-9811-893 -i /tmp/ffmpeg-9811-933 -c:v copy -c:a copy -map 0 -map 1 -f matroska pipe:1
[16:54:13.833582][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9811-893
[16:54:13.833781][stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-9811-933
[16:54:13.836427][cli][debug] Pre-buffering 8192 bytes
[16:54:13.894765][cli][info] Starting player: /usr/bin/mpv
[16:54:13.895198][cli.output][debug] Opening subprocess: /usr/bin/mpv "--title=Christmas Music 2020 🎅 Top Christmas Songs Playlist 2020 🎄 Best Christmas Songs Ever" -
[16:54:14.398240][cli][debug] Writing stream to output
[16:54:34.826911][cli][info] Player closed
[16:54:34.827058][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:54:34.829721][stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-9811-893
[16:54:34.829823][stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-9811-933
[16:54:34.941941][stream.ffmpegmux][debug] Closed all the substreams
[16:54:34.942041][cli][info] Stream ended
[16:54:34.942232][cli][info] Closing currently open stream...
[16:54:34.942273][stream.ffmpegmux][debug] Closing ffmpeg thread
[16:54:34.942333][stream.ffmpegmux][debug] Closed all the substreams
--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.8/logging/__init__.py", line 1081, in emit
    msg = self.format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 925, in format
    return fmt.format(record)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 53, in format
    return super().format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 666, in format
    record.asctime = self.formatTime(record, self.datefmt)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 40, in formatTime
    return tdt.strftime(datefmt or self.default_time_format)
ImportError: sys.meta_path is None, Python is likely shutting down
Call stack:
  File "venv3/lib/python3.8/site-packages/streamlink/stream/ffmpegmux.py", line 156, in close
    log.debug("Closing ffmpeg thread")
Message: 'Closing ffmpeg thread'
Arguments: ()
--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.8/logging/__init__.py", line 1081, in emit
    msg = self.format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 925, in format
    return fmt.format(record)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 53, in format
    return super().format(record)
  File "/usr/lib/python3.8/logging/__init__.py", line 666, in format
    record.asctime = self.formatTime(record, self.datefmt)
  File "venv3/lib/python3.8/site-packages/streamlink/logger.py", line 40, in formatTime
    return tdt.strftime(datefmt or self.default_time_format)
ImportError: sys.meta_path is None, Python is likely shutting down
Call stack:
  File "venv3/lib/python3.8/site-packages/streamlink/stream/ffmpegmux.py", line 167, in close
    log.debug("Closed all the substreams")
Message: 'Closed all the substreams'
Arguments: ()
```



Discussion:
The issue is not caused by 70dc809. It's a side-effect of a different issue in the `FFMPEGMuxer` class, which causes `datetime.strftime` that's being used by the logger since 70dc809 to fail.

I think the problem is that `FFMPEGMuxer.close()` gets called twice by `streamlink_cli.main` and the second logging attempt raises the exception:
1. https://github.com/streamlink/streamlink/blob/3e8fbe63b89dee2d3a2111a88e185be0dbb54b8d/src/streamlink_cli/main.py#L384
2. https://github.com/streamlink/streamlink/blob/3e8fbe63b89dee2d3a2111a88e185be0dbb54b8d/src/streamlink_cli/main.py#L1056

`FFMPEGMuxer` inherits from `io.IOBase`, so `close()` should ([call its super method?](https://docs.python.org/3/library/io.html#io.IOBase.close) and) check if it's already closed at the beginning and just return if it's true. That fixes the issue in a simple test here at least.

Btw, is there a reason why the FFMPEG subprocess is killed immediately with a SIGKILL instead of a SIGTERM first and then with a SIGKILL if it doesn't respond after a certain time? The output will probably be corrupted by just SIGKILL-ing its process, which is a problem when writing the output to a file instead of a player's stdin.

Resolving patch:
diff --git a/src/streamlink/stream/ffmpegmux.py b/src/streamlink/stream/ffmpegmux.py
--- a/src/streamlink/stream/ffmpegmux.py
+++ b/src/streamlink/stream/ffmpegmux.py
@@ -153,6 +153,9 @@ def read(self, size=-1):
         return data
 
     def close(self):
+        if self.closed:
+            return
+
         log.debug("Closing ffmpeg thread")
         if self.process:
             # kill ffmpeg
@@ -161,10 +164,13 @@ def close(self):
 
             # close the streams
             for stream in self.streams:
-                if hasattr(stream, "close"):
+                if hasattr(stream, "close") and callable(stream.close):
                     stream.close()
 
             log.debug("Closed all the substreams")
+
         if self.close_errorlog:
             self.errorlog.close()
             self.errorlog = None
+
+        super().close()


Test patch: