elements to videos and render transcripts. * * @package Captionwright */ namespace Captionwright; defined( 'ABSPATH' ) || exit; /** * Insert caption tracks into a ' ) ) { return $html; } $default = (bool) settings()['default_on'] && false === stripos( $html, ' default' ); $add = ''; foreach ( $tracks as $lang => $t ) { if ( preg_match( '/srclang=["\']' . preg_quote( $lang, '/' ) . '["\']/i', $html ) ) { continue; } $add .= sprintf( '', esc_url( $t['url'] ), esc_attr( $lang ), esc_attr( $t['label'] ), $default ? ' default' : '' ); $default = false; } if ( '' === $add ) { return $html; } $pos = strripos( $html, '' ); return substr( $html, 0, $pos ) . $add . substr( $html, $pos ); } add_filter( 'render_block_core/video', function ( $html, $block ) { $id = isset( $block['attrs']['id'] ) ? (int) $block['attrs']['id'] : 0; if ( ! $id ) { return $html; } $html = inject_tracks( $html, $id ); if ( settings()['auto_transcript'] && get_tracks( $id ) ) { $html .= render_transcript( array( 'attachmentId' => $id ) ); } return $html; }, 10, 2 ); add_filter( 'wp_video_shortcode', function ( $output, $atts ) { foreach ( array( 'src', 'mp4', 'webm', 'm4v', 'ogv', 'mov' ) as $key ) { if ( ! empty( $atts[ $key ] ) ) { $id = attachment_url_to_postid( $atts[ $key ] ); if ( $id ) { return inject_tracks( $output, $id ); } } } return $output; }, 10, 2 ); /** * Format seconds as m:ss or h:mm:ss. * * @param float $s Seconds. * @return string */ function clock( $s ) { $s = (int) floor( $s ); return $s >= 3600 ? sprintf( '%d:%02d:%02d', intdiv( $s, 3600 ), intdiv( $s % 3600, 60 ), $s % 60 ) : sprintf( '%d:%02d', intdiv( $s, 60 ), $s % 60 ); } /** * Transcript HTML for an attachment. * * @param array $atts attachmentId, lang, collapsed, showTimes. * @param string $wrapper Extra wrapper attributes (from blocks). * @return string */ function render_transcript( $atts, $wrapper = '' ) { $id = isset( $atts['attachmentId'] ) ? (int) $atts['attachmentId'] : 0; $tracks = $id ? get_tracks( $id ) : array(); if ( ! $tracks ) { return ''; } $lang = isset( $atts['lang'] ) && isset( $tracks[ $atts['lang'] ] ) ? $atts['lang'] : (string) array_key_first( $tracks ); $cues = parse_vtt( read_track( $id, $lang ) ); if ( ! $cues ) { return ''; } $collapsed = ! isset( $atts['collapsed'] ) || (bool) $atts['collapsed']; $times = ! isset( $atts['showTimes'] ) || (bool) $atts['showTimes']; wp_enqueue_style( 'captionwright-transcript' ); wp_enqueue_script( 'captionwright-transcript-view' ); // Join cues into readable paragraphs: break after a long pause, or at the end of a // sentence once the paragraph has some length. $paras = array(); $cur = array(); $chars = 0; $prev = null; foreach ( $cues as $c ) { $sentence_end = $cur && preg_match( '/[.!?…]["\'”’)]*$/u', end( $cur )['text'] ); if ( $cur && ( ( null !== $prev && $c['start'] - $prev > 2.5 ) || ( $sentence_end && $chars > 280 ) ) ) { $paras[] = $cur; $cur = array(); $chars = 0; } $cur[] = $c; $chars += strlen( $c['text'] ); $prev = $c['end']; } if ( $cur ) { $paras[] = $cur; } $body = ''; foreach ( $paras as $p ) { $body .= '

'; if ( $times ) { $body .= sprintf( ' ', esc_attr( (string) $p[0]['start'] ), /* translators: %s: timestamp such as 1:05 */ esc_attr( sprintf( __( 'Play from %s', 'captionwright' ), clock( $p[0]['start'] ) ) ), esc_html( clock( $p[0]['start'] ) ) ); } foreach ( $p as $c ) { $body .= sprintf( '%3$s ', esc_attr( (string) $c['start'] ), esc_attr( (string) $c['end'] ), esc_html( $c['text'] ) ); } $body .= '

'; } $title = sprintf( /* translators: %s: media title */ __( 'Transcript: %s', 'captionwright' ), get_the_title( $id ) ); /** * Filters the media URL the transcript is linked to (used to find the player). * * @param string $url Attachment URL. * @param int $id Post ID holding the tracks. */ $url = (string) apply_filters( 'captionwright_media_url', (string) wp_get_attachment_url( $id ), $id ); return sprintf( '
%5$s
%6$s
', '' !== $wrapper ? $wrapper : 'class="captionwright-transcript"', esc_url( $url ), esc_attr( $lang ), $collapsed ? '' : ' open', esc_html( $title ), $body ); } add_action( 'init', function () { wp_register_style( 'captionwright-transcript', CAPTIONWRIGHT_URL . 'blocks/transcript/style.css', array(), CAPTIONWRIGHT_VERSION . '.' . filemtime( CAPTIONWRIGHT_DIR . 'blocks/transcript/style.css' ) ); wp_register_script( 'captionwright-transcript-view', CAPTIONWRIGHT_URL . 'blocks/transcript/view.js', array(), CAPTIONWRIGHT_VERSION . '.' . filemtime( CAPTIONWRIGHT_DIR . 'blocks/transcript/view.js' ), array( 'in_footer' => true, 'strategy' => 'defer', ) ); register_block_type( CAPTIONWRIGHT_DIR . 'blocks/transcript' ); add_shortcode( 'captionwright_transcript', function ( $atts ) { $atts = shortcode_atts( array( 'id' => 0, 'lang' => '', 'collapsed' => 'yes', 'times' => 'yes', ), $atts, 'captionwright_transcript' ); return render_transcript( array( 'attachmentId' => (int) $atts['id'], 'lang' => sanitize_key( $atts['lang'] ), 'collapsed' => 'no' !== $atts['collapsed'], 'showTimes' => 'no' !== $atts['times'], ) ); } ); } );