Repository: commaai/openpilot
File: tools/clip/run.py (Python)

Code before:
    procs = []
    try:
      xvfb_proc = start_proc(xvfb_cmd, env)
      procs.append(xvfb_proc)
      ui_proc = start_proc(ui_cmd, env)
      procs.append(ui_proc)
      replay_proc = start_proc(replay_cmd, env)
      procs.append(replay_proc)

      logger.info('waiting for replay to begin (loading segments, may take a while)...')
      wait_for_frames(procs)

      logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...')
      time.sleep(SECONDS_TO_WARM)
      for proc in procs:
        check_for_failure(proc)

      ffmpeg_proc = start_proc(ffmpeg_cmd, env)
      procs.append(ffmpeg_proc)

      logger.info(f'recording in progress ({duration}s)...')
      ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS)
      for proc in procs:
        check_for_failure(proc)
      logger.info(f'recording complete: {Path(out).resolve()}')
    finally:
      for p in reversed(procs):
        if p.poll() is None:
          p.terminate()
      for p in reversed(procs):
        try:
          p.wait(timeout=5)
        except TimeoutExpired:
          p.kill()
          logger.warning(f"Process {p.args} did not terminate in time; killed.")


def main():
  p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai')
  validate_env(p)
  route_group = p.add_mutually_exclusive_group(required=True)
  route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})')
  route_group.add_argument('--demo', help='use the demo route', action='store_true')
  p.add_argument('-d', '--data-dir', help='local directory where route data is stored')
  p.add_argument('-e', '--end', help='stop clipping at <end> seconds', type=int)
  p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.)
  p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT)
  p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}')
  p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high')
  p.add_argument('-x', '--speed', help='record the clip at this speed multiple', type=int, default=1)
  p.add_argument('-s', '--start', help='start clipping at <start> seconds', type=int)

Review comment (refactor):
let's make a little helper (and put it in `common/`) to do this in a context manager:
```python
with Proc('./replay'), proc('./ui')...:
	<code>
```

Code after:
      logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...')
      time.sleep(SECONDS_TO_WARM)
      check_for_failure(procs)
      with managed_proc(ffmpeg_cmd, env) as ffmpeg_proc:
        procs.append(ffmpeg_proc)
        logger.info(f'recording in progress ({duration}s)...')
        ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS)
        check_for_failure(procs)
        logger.info(f'recording complete: {Path(out).resolve()}')


def main():
  p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai')
  validate_env(p)
  route_group = p.add_mutually_exclusive_group(required=True)
  route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})')
  route_group.add_argument('--demo', help='use the demo route', action='store_true')
  p.add_argument('-d', '--data-dir', help='local directory where route data is stored')
  p.add_argument('-e', '--end', help='stop clipping at <end> seconds', type=int)
  p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.)
  p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT)
  p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}')
  p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high')
  p.add_argument('-x', '--speed', help='record the clip at this speed multiple', type=int, default=1)
  p.add_argument('-s', '--start', help='start clipping at <start> seconds', type=int)
  p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title)
  args = parse_args(p)
  exit_code = 1
  try:
    clip(
      data_dir=args.data_dir,
      quality=args.quality,
      prefix=args.prefix,
      route=args.route,
      out=args.output,
      start=args.start,
      end=args.end,
      speed=args.speed,
      target_mb=args.file_size,
      title=args.title,
    )
    exit_code = 0
  except KeyboardInterrupt as e:
    logger.exception('interrupted by user', exc_info=e)
  except Exception as e:
    logger.exception('encountered error', exc_info=e)
  sys.exit(exit_code)


if __name__ == '__main__':
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s\t%(message)s')