This lets you have a custom reading time within Etch.

{this.fwReadingTime.display} - 3 minutes 12 seconds

{this.fwReadingTime.short} - 3m 12s

{this.fwReadingTime.minutes}  - 3

{this.fwReadingTime.seconds}  - 12

If you are creating a site for slower or faster readers, you can change the $wpm value. It does use the WordPress approach that accounts for (includes) images in the reading time calculation.

<?php 
add_filter('etch/dynamic_data/post', function ($data, $post_id) {
	$data['fwReadingTime'] = fw_get_wp_reading_time_for_etch($post_id);

	return $data;
}, 10, 2);

function fw_get_wp_reading_time_for_etch($post_id) {
	$post_id = (int) $post_id;

	if ($post_id < 1) {
		return [
			'display'      => '0 seconds',
			'short'        => '0s',
			'minutes'      => 0,
			'seconds'      => 0,
			'totalSeconds' => 0,
			'totalMinutes' => 0,
		];
	}

	$options = get_option('rt_reading_time_options', []);

	$wpm = isset($options['wpm']) ? (int) $options['wpm'] : 200;
	if ($wpm < 1) {
		$wpm = 200;
	}

	$content = (string) get_post_field('post_content', $post_id);
	$image_count = substr_count(strtolower($content), '<img ');

	/*
	 * Match plugin behavior:
	 * plugin strips shortcodes unless include_shortcodes is set.
	 */
	if (! isset($options['include_shortcodes'])) {
		$content = strip_shortcodes($content);
	}

	$content = wp_strip_all_tags($content);
	$word_count = count(preg_split('/\s+/', trim($content), -1, PREG_SPLIT_NO_EMPTY));

	/*
	 * Reuse the plugin's own image calculation method when available.
	 */
	$additional_words = 0;

	if (
		! empty($image_count) &&
		(
			! isset($options['exclude_images']) ||
			! $options['exclude_images']
		)
	) {
		global $reading_time_wp;

		if (
			isset($reading_time_wp) &&
			is_object($reading_time_wp) &&
			method_exists($reading_time_wp, 'rt_calculate_images')
		) {
			$additional_words = (int) round($reading_time_wp->rt_calculate_images($image_count, $wpm));
		} else {
			/*
			 * Fallback mirrors the plugin's public method:
			 * first image 12 sec, second 11 sec ... 10+ images 3 sec each.
			 */
			for ($i = 1; $i <= $image_count; $i++) {
				if ($i >= 10) {
					$additional_words += 3 * $wpm / 60;
				} else {
					$additional_words += (12 - ($i - 1)) * $wpm / 60;
				}
			}

			$additional_words = (int) round($additional_words);
		}
	}

	$total_words = $word_count + $additional_words;

	$total_seconds = (int) round(($total_words / $wpm) * 60);

	if ($total_seconds < 1) {
		$total_seconds = 1;
	}

	$total_minutes = (int) ceil($total_words / $wpm);
	$minutes = (int) floor($total_seconds / 60);
	$seconds = (int) ($total_seconds % 60);

	$parts = [];

	if ($minutes > 0) {
		$parts[] = sprintf(
			_n('%d minute', '%d minutes', $minutes, 'your-textdomain'),
			$minutes
		);
	}

	if ($seconds > 0 || $minutes === 0) {
		$parts[] = sprintf(
			_n('%d second', '%d seconds', $seconds, 'your-textdomain'),
			$seconds
		);
	}

	return [
		'display'      => implode(' ', $parts),
		'short'        => $minutes > 0 ? sprintf('%dm %02ds', $minutes, $seconds) : sprintf('%ds', $seconds),
		'minutes'      => $minutes,
		'seconds'      => $seconds,
		'totalSeconds' => $total_seconds,
		'totalMinutes' => $total_minutes,
	];
}