asciiframe 1.2.0

Watch any video in the terminal with ASCII
use std::{
	fs::{File, OpenOptions},
	io::{self, Write},
	path::Path,
	thread::sleep,
	time::{Duration, SystemTime},
};

use indicatif::ProgressBar;
use opencv::prelude::*;
use opencv::{core, imgproc, videoio};
use terminal_size::{terminal_size, Height, Width};

use crate::converter;
use crate::error::{Error, Result};

/// # Errors
///
/// Will return `Err` if video capture from file fails OR
/// accessing frame count fails OR
/// reading frame data fails OR
/// writing ascii data to a file fails
pub fn render_to_file(fin: &Path, fout: &Path, strategy: converter::Strategy) -> Result<()> {
	let mut capture = videoio::VideoCapture::from_file(fin.to_str().unwrap(), 0)?;
	let frame_count: u64 = capture.get(videoio::CAP_PROP_FRAME_COUNT)? as u64;
	let pb = ProgressBar::new(frame_count);

	File::create(fout)?.write_all(
		"#!/bin/bash\n# This file was auto-generated by asciiframe\necho -en '\033[2J' \n"
			.as_bytes(),
	)?;

	for _i in 0..frame_count {
		let mut frame = Mat::default();
		// CV_8UC3
		capture.read(&mut frame)?;

		let mut resized = Mat::default();

		if let Some((Width(w), Height(h))) = terminal_size() {
			imgproc::resize(
				&frame,
				&mut resized,
				core::Size {
					width: i32::from(w - 1),
					height: i32::from(h - 1),
				},
				0.0,
				0.0,
				imgproc::INTER_AREA,
			)?;

			render_frame_to_file(&resized, strategy, fout)?;
			pb.inc(1);
		} else {
			return Err(Error::from("Unable to get terminal size"));
		}
	}

	Ok(())
}

/// # Errors
///
/// Will return `Err` if video capture from file fails OR
/// accessing frame count fails OR
/// reading frame data fails OR
/// writing ascii to stdout fails
pub fn render_to_stdout(fin: &Path, strategy: converter::Strategy) -> Result<()> {
	let mut capture = videoio::VideoCapture::from_file(fin.to_str().unwrap(), 0)?;
	let frame_count: u64 = capture.get(videoio::CAP_PROP_FRAME_COUNT)? as u64;
	let time_d: f32 = (1.0 / capture.get(videoio::CAP_PROP_FPS)?) as f32;
	let stdout = io::stdout();
	let mut handle = stdout.lock();

	for _i in 0..frame_count {
		let start = SystemTime::now();

		let mut frame = Mat::default();
		// CV_8UC3
		capture.read(&mut frame)?;

		let mut resized = Mat::default();

		if let Some((Width(w), Height(h))) = terminal_size() {
			imgproc::resize(
				&frame,
				&mut resized,
				core::Size {
					width: i32::from(w - 1),
					height: i32::from(h - 1),
				},
				0.0,
				0.0,
				imgproc::INTER_AREA,
			)?;

			render_frame_stdout(&mut handle, &resized, strategy)?;

			let elapsed = start.elapsed().unwrap().as_secs_f32();
			if elapsed < time_d {
				sleep(Duration::from_millis(((time_d - elapsed) * 1000.0) as u64));
			}
		} else {
			return Err(Error::from("Unable to get terminal size"));
		}
	}

	Ok(())
}

fn render_frame_stdout(
	handle: &mut io::StdoutLock<'_>,
	frame: &Mat,
	strategy: converter::Strategy,
) -> Result<()> {
	write!(handle, "{esc}c", esc = 27 as char)?;
	write!(handle, "{}", converter::convert_frame(frame, strategy)?)?;

	Ok(())
}

fn render_frame_to_file(frame: &Mat, strategy: converter::Strategy, path: &Path) -> Result<()> {
	let mut fout = OpenOptions::new().append(true).open(path)?;
	fout.write_all(
		format!(
			"echo '{}'\nsleep 0.033\necho '\u{001b}[0;0H' \n",
			converter::convert_frame(frame, strategy)?
		)
		.as_bytes(),
	)?;

	Ok(())
}