class_docstr: Finite state ffmpeg `-i` command option file information parser.
  Is designed to parse the output fast, in one loop. Iterates line by
  line of the `ffmpeg -i <filename> [-f null -]` command output changing
  the internal state of the parser.
  
  Parameters
  ----------
  
  filename
    Name of the file parsed, only used to raise accurate error messages.
  
  infos
    Information returned by FFmpeg.
  
  fps_source
    Indicates what source data will be preferably used to retrieve fps data.
  
  check_duration
    Enable or disable the parsing of the duration of the file. Useful to
    skip the duration check, for example, for images.
  
  decode_file
    Indicates if the whole file has been decoded. The duration parsing strategy
    will differ depending on this argument.
class_docstr_tok: ["Finite","state","ffmpeg","`","-i","`","command","option","file","information","parser",".","Is","designed","to","parse","the","output","fast",",","in","one","loop",".","Iterates","line","by","line","of","the","`","ffmpeg","-i","<","filename",">","[","-f","null","-","]","`","command","output","changing","the","internal","state","of","the","parser",".","Parameters","--","--","--","--","--","filename","Name","of","the","file","parsed",",","only","used","to","raise","accurate","error","messages",".","infos","Information","returned","by","FFmpeg",".","fps_source","Indicates","what","source","data","will","be","preferably","used","to","retrieve","fps","data",".","check_duration","Enable","or","disable","the","parsing","of","the","duration","of","the","file",".","Useful","to","skip","the","duration","check",",","for","example",",","for","images",".","decode_file","Indicates","if","the","whole","file","has","been","decoded",".","The","duration","parsing","strategy","will","differ","depending","on","this","argument","."]
docstr: Parses the information returned by FFmpeg in stderr executing their binary
  for a file with ``-i`` option and returns a dictionary with all data needed
  by MoviePy.
docstr_tok: ["Parses","the","information","returned","by","FFmpeg","in","stderr","executing","their","binary","for","a","file","with","``","-i","``","option","and","returns","a","dictionary","with","all","data","needed","by","MoviePy","."]
code: def parse(self):
      """Parses the information returned by FFmpeg in stderr executing their binary
      for a file with ``-i`` option and returns a dictionary with all data needed
      by MoviePy.
      """
      # chapters by input file
      input_chapters = []
  
      for line in self.infos.splitlines()[1:]:
          if (
              self.duration_tag_separator == "time="
              and self.check_duration
              and "time=" in line
          ):
              # parse duration using file decodification
              self.result["duration"] = self.parse_duration(line)
          elif self._inside_output or line[0] != " ":
              if (
                  self.duration_tag_separator == "time="
                  and not self._inside_output
              ):
                  self._inside_output = True
              # skip lines like "At least one output file must be specified"
          elif not self._inside_file_metadata and line.startswith(
              "  Metadata:"
          ):
              # enter "  Metadata:" group
              self._inside_file_metadata = True
          elif line.startswith("  Duration:"):
              # exit "  Metadata:" group
              self._inside_file_metadata = False
              if (
                  self.check_duration
                  and self.duration_tag_separator == "Duration: "
              ):
                  self.result["duration"] = self.parse_duration(line)
  
              # parse global bitrate (in kb/s)
              bitrate_match = re.search(r"bitrate: (\d+) kb/s", line)
              self.result["bitrate"] = (
                  int(bitrate_match.group(1)) if bitrate_match else None
              )
  
              # parse start time (in seconds)
              start_match = re.search(r"start: (\d+\.?\d+)", line)
              self.result["start"] = (
                  float(start_match.group(1)) if start_match else None
              )
          elif self._inside_file_metadata:
              # file metadata line
              field, value = self.parse_metadata_field_value(line)
  
              # multiline metadata value parsing
              if field == "":
                  field = self._last_metadata_field_added
                  value = self.result["metadata"][field] + "\n" + value
              else:
                  self._last_metadata_field_added = field
              self.result["metadata"][field] = value
          elif line.lstrip().startswith("Stream "):
              # exit stream "    Metadata:"
              if self._current_stream:
                  self._current_input_file["streams"].append(
                      self._current_stream
                  )
  
              # get input number, stream number, language and type
              main_info_match = re.search(
                  r"^Stream\s#(\d+):(\d+)(?:\[\w+\])?\(?(\w+)?\)?:\s(\w+):",
                  line.lstrip(),
              )
              (
                  input_number,
                  stream_number,
                  language,
                  stream_type,
              ) = main_info_match.groups()
              input_number = int(input_number)
              stream_number = int(stream_number)
              stream_type_lower = stream_type.lower()
  
              if language == "und":
                  language = None
  
              # start builiding the current stream
              self._current_stream = {
                  "input_number": input_number,
                  "stream_number": stream_number,
                  "stream_type": stream_type_lower,
                  "language": language,
                  "default": not self._default_stream_found
                  or line.endswith("(default)"),
              }
              self._default_stream_found = True
  
              # for default streams, set their numbers globally, so it's
              # easy to get without iterating all
              if self._current_stream["default"]:
                  self.result[
                      f"default_{stream_type_lower}_input_number"
                  ] = input_number
                  self.result[
                      f"default_{stream_type_lower}_stream_number"
                  ] = stream_number
  
              # exit chapter
              if self._current_chapter:
                  input_chapters[input_number].append(
                      self._current_chapter
                  )
                  self._current_chapter = None
  
              if "input_number" not in self._current_input_file:
                  # first input file
                  self._current_input_file[
                      "input_number"
                  ] = input_number
              elif (
                  self._current_input_file["input_number"]
                  != input_number
              ):
                  # new input file
  
                  # include their chapters if there are for this input file
                  if len(input_chapters) >= input_number + 1:
                      self._current_input_file[
                          "chapters"
                      ] = input_chapters[input_number]
  
                  # add new input file to self.result
                  self.result["inputs"].append(self._current_input_file)
                  self._current_input_file = {
                      "input_number": input_number
                  }
  
              # parse relevant data by stream type
              try:
                  (
                      global_data,
                      stream_data,
                  ) = self.parse_data_by_stream_type(stream_type, line)
              except NotImplementedError as exc:
                  warnings.warn(
                      f"{str(exc)}\nffmpeg output:\n\n{self.infos}",
                      UserWarning,
                  )
              else:
                  self.result.update(global_data)
                  self._current_stream.update(stream_data)
          elif line.startswith("    Metadata:"):
              # enter group "    Metadata:"
              continue
          elif self._current_stream:
              # stream metadata line
              if "metadata" not in self._current_stream:
                  self._current_stream["metadata"] = {}
  
              field, value = self.parse_metadata_field_value(line)
  
              if self._current_stream["stream_type"] == "video":
                  field, value = self.video_metadata_type_casting(
                      field, value
                  )
                  if field == "rotate":
                      self.result["video_rotation"] = value
  
              # multiline metadata value parsing
              if field == "":
                  field = self._last_metadata_field_added
                  value = (
                      self._current_stream["metadata"][field]
                      + "\n"
                      + value
                  )
              else:
                  self._last_metadata_field_added = field
              self._current_stream["metadata"][field] = value
          elif line.startswith("    Chapter"):
              # Chapter data line
              if self._current_chapter:
                  # there is a previews chapter?
                  if (
                      len(input_chapters)
                      < self._current_chapter["input_number"] + 1
                  ):
                      input_chapters.append([])
                  # include in the chapters by input matrix
                  input_chapters[
                      self._current_chapter["input_number"]
                  ].append(self._current_chapter)
  
              # extract chapter data
              chapter_data_match = re.search(
                  r"^    Chapter #(\d+):(\d+): start (\d+\.?\d+?), end (\d+\.?\d+?)",
                  line,
              )
              (
                  input_number,
                  chapter_number,
                  start,
                  end,
              ) = chapter_data_match.groups()
  
              # start building the chapter
              self._current_chapter = {
                  "input_number": int(input_number),
                  "chapter_number": int(chapter_number),
                  "start": float(start),
                  "end": float(end),
              }
          elif self._current_chapter:
              # inside chapter metadata
              if "metadata" not in self._current_chapter:
                  self._current_chapter["metadata"] = {}
              field, value = self.parse_metadata_field_value(line)
  
              # multiline metadata value parsing
              if field == "":
                  field = self._last_metadata_field_added
                  value = (
                      self._current_chapter["metadata"][field]
                      + "\n"
                      + value
                  )
              else:
                  self._last_metadata_field_added = field
              self._current_chapter["metadata"][field] = value
  
      # last input file, must be included in self.result
      if self._current_input_file:
          self._current_input_file["streams"].append(
              self._current_stream
          )
          # include their chapters, if there are
          if (
              len(input_chapters)
              == self._current_input_file["input_number"] + 1
          ):
              self._current_input_file["chapters"] = input_chapters[
                  self._current_input_file["input_number"]
              ]
          self.result["inputs"].append(self._current_input_file)
  
      # some video duration utilities
      if self.result["video_found"] and self.check_duration:
          self.result["video_n_frames"] = int(
              self.result["duration"] * self.result["video_fps"]
          )
          self.result["video_duration"] = self.result["duration"]
      else:
          self.result["video_n_frames"] = 1
          self.result["video_duration"] = None
      # We could have also recomputed duration from the number of frames, as follows:
      # >>> result['video_duration'] = result['video_n_frames'] / result['video_fps']
  
      # not default audio found, assume first audio stream is the default
      if self.result["audio_found"] and not self.result.get(
          "audio_bitrate"
      ):
          self.result["audio_bitrate"] = None
          for streams_input in self.result["inputs"]:
              for stream in streams_input["streams"]:
                  if stream["stream_type"] == "audio" and stream.get(
                      "bitrate"
                  ):
                      self.result["audio_bitrate"] = stream["bitrate"]
                      break
  
              if self.result["audio_bitrate"] is not None:
                  break
  
      result = self.result
  
      # reset state of the parser
      self._reset_state()
  
      return result
  
imports: import os
  import re
  import subprocess
  import warnings
  import numpy
  from moviepy.config import FFMPEG_BINARY
  from moviepy.tools import convert_to_seconds, cross_platform_popen_params
label_desc: Use image node_id 3 for calling the FFmpegInfosParser obj's underlying member method code with example usage: obj.parse() and returns: result