|string $atts Attributes. */ public static function shortcode( $atts ): string { $atts = shortcode_atts( array( 'id' => 0, 'player' => '0', 'timestamps' => '1', 'speakers' => '1', ), is_array( $atts ) ? $atts : array(), 'vocalstack_transcript' ); return self::render( array( 'mediaId' => (int) $atts['id'], 'showPlayer' => filter_var( $atts['player'], FILTER_VALIDATE_BOOLEAN ), 'showTimestamps' => filter_var( $atts['timestamps'], FILTER_VALIDATE_BOOLEAN ), 'showSpeakers' => filter_var( $atts['speakers'], FILTER_VALIDATE_BOOLEAN ), ) ); } /** * The transcript's HTML, or '' when there is none to show. * * @param array{mediaId?: int, showPlayer?: bool, showTimestamps?: bool, showSpeakers?: bool} $args What to show. * @param string $wrapper_attributes Extra attributes for the wrapper (from the block). */ public static function render( array $args, string $wrapper_attributes = '' ): string { $id = (int) ( $args['mediaId'] ?? 0 ); if ( ! $id || ! Media::is_transcribable( $id ) || ! self::is_viewable( $id ) ) { return ''; } $text = (string) get_post_meta( $id, Media::TRANSCRIPT, true ); if ( '' === $text ) { return ''; } $sentences = Media::sentences( $id ); // An edited transcript no longer matches its timings, so it shows as plain text. $timed = $sentences && Text::plain( $sentences ) === $text; $speakers = ! empty( $args['showSpeakers'] ) && $timed && count( array_unique( array_column( $sentences, 'speaker' ) ) ) > 1; $timestamps = ! empty( $args['showTimestamps'] ) && $timed; $url = (string) wp_get_attachment_url( $id ); $html = ''; if ( ! empty( $args['showPlayer'] ) ) { $tag = str_starts_with( (string) get_post_mime_type( $id ), 'video/' ) ? 'video' : 'audio'; $html .= sprintf( '<%1$s class="vocalstack-transcript__player" controls preload="metadata" src="%2$s">', $tag, esc_url( $url ) ); } $html .= '
'; if ( $timed ) { $last = null; foreach ( Text::paragraphs( $sentences ) as $paragraph ) { $first = $paragraph[0]; $html .= '

'; // Name the speaker when it changes, not on every pause. if ( $speakers && $first['speaker'] !== $last ) { $html .= '' . esc_html( self::speaker_label( $first['speaker'], $id ) ) . ' '; } $last = $first['speaker']; if ( $timestamps ) { $html .= sprintf( ' ', esc_attr( (string) $first['start'] ), esc_html( Text::clock( $first['start'] ) ) ); } foreach ( $paragraph as $s ) { $html .= sprintf( '%s ', esc_attr( (string) $s['start'] ), esc_attr( (string) $s['end'] ), esc_html( $s['text'] ) ); } $html .= '

'; } } else { $html .= wpautop( esc_html( $text ) ); } $html .= '
'; // The block's stylesheet; a shortcode needs it too. wp_enqueue_style( 'vocalstack-transcript-style' ); if ( $timed ) { Assets::enqueue( 'player' ); } return sprintf( '
%s
', '' !== $wrapper_attributes ? $wrapper_attributes : 'class="vocalstack-transcript"', $id, esc_url( $url ), $html ); } /** * Whether visitors may see this attachment's transcript: the same rule as * the attachment itself, which follows the post it is attached to. * * @param int $id Attachment ID. */ public static function is_viewable( int $id ): bool { if ( current_user_can( 'edit_post', $id ) ) { return true; } $parent = (int) wp_get_post_parent_id( $id ); // Unattached media counts as published (get_post_status() reports `publish`). return is_post_publicly_viewable( $parent ? $parent : $id ); } /** * A speaker's name. * * @param int $speaker VocalStack's speaker number, from 0. * @param int $id Attachment ID. */ private static function speaker_label( int $speaker, int $id ): string { /* translators: %d: speaker number, from 1. */ $label = sprintf( __( 'Speaker %d', 'vocalstack' ), $speaker + 1 ); /** * Filters a speaker's name in transcripts, to name the people speaking. * * @param string $label `Speaker 1`, `Speaker 2`, …. * @param int $speaker VocalStack's speaker number, from 0. * @param int $id Attachment ID. */ return (string) apply_filters( 'vocalstack_speaker_label', $label, $speaker, $id ); } }