Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support the Text-to-Speech feature in the Classic Editor #518

Merged
merged 5 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions includes/Classifai/Providers/Azure/TextToSpeech.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Classifai\Providers\Azure;

use Classifai\Admin\SavePostHandler;
use Classifai\Providers\Provider;
use stdClass;
use WP_Http;
Expand Down Expand Up @@ -130,6 +131,8 @@ public function enqueue_editor_assets() {
public function register() {
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
add_action( 'rest_api_init', [ $this, 'add_synthesize_speech_meta_to_rest_api' ] );
add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] );
add_action( 'save_post', [ $this, 'save_post_metadata' ], 5 );
add_filter( 'the_content', [ $this, 'render_post_audio_controls' ] );
}

Expand Down Expand Up @@ -492,6 +495,108 @@ function( $post_type ) {
);
}

/**
* Add meta box to post types that support speech synthesis.
*
* @param string $post_type Post type.
*/
public function add_meta_box( $post_type ) {
if ( ! in_array( $post_type, $this->get_supported_post_types(), true ) ) {
return;
}

\add_meta_box(
'classifai-text-to-speech-meta-box',
__( 'ClassifAI Text to Speech Processing', 'classifai' ),
[ $this, 'render_meta_box' ],
null,
'side',
'low',
array( '__back_compat_meta_box' => true )
);
}

/**
* Render meta box content.
*
* @param \WP_Post $post WP_Post object.
*/
public function render_meta_box( $post ) {
wp_nonce_field( 'classifai_text_to_speech_meta_action', 'classifai_text_to_speech_meta' );

$process_content = get_post_meta( $post->ID, self::SYNTHESIZE_SPEECH_KEY, true );
$process_content = ( 'no' === $process_content ) ? 'no' : 'yes';

$post_type = get_post_type_object( get_post_type( $post ) );
$post_type_label = esc_html__( 'Post', 'classifai' );
if ( $post_type ) {
$post_type_label = $post_type->labels->singular_name;
}

$audio_id = get_post_meta( $post->ID, self::AUDIO_ID_KEY, true );
?>

<p>
<label for="classifai_synthesize_speech">
<input type="checkbox" value="yes" id="classifai_synthesize_speech" name="classifai_synthesize_speech" <?php checked( $process_content, 'yes' ); ?> />
<?php esc_html_e( 'Enable audio generation', 'classifai' ); ?>
</label>
<span class="description">
<?php
/* translators: %s Post type label */
printf( esc_html__( 'ClassifAI will generate audio for this %s when it is published or updated', 'classifai' ), esc_html( $post_type_label ) );
?>
</span>
</p>

<?php
if ( 'yes' === $process_content && $audio_id && wp_get_attachment_url( $audio_id ) ) {
$cache_busting_url = add_query_arg(
[
'ver' => time(),
],
wp_get_attachment_url( $audio_id )
);
?>

<p>
<audio id="classifai-audio-preview" controls controlslist="nodownload" src="<?php echo esc_url( $cache_busting_url ); ?>"></audio>
</p>

<?php
}

}

/**
* Process the meta box save.
*
* @param int $post_id Post ID.
*/
public function save_post_metadata( $post_id ) {
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) {
return;
}

if ( empty( $_POST['classifai_text_to_speech_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_text_to_speech_meta'] ) ), 'classifai_text_to_speech_meta_action' ) ) {
return;
}

$supported_post_types = $this->get_supported_post_types();
if ( ! in_array( get_post_type( $post_id ), $supported_post_types, true ) ) {
return;
}

if ( isset( $_POST['classifai_synthesize_speech'] ) && 'yes' === sanitize_text_field( wp_unslash( $_POST['classifai_synthesize_speech'] ) ) ) {
update_post_meta( $post_id, self::SYNTHESIZE_SPEECH_KEY, 'yes' );

$save_post_handler = new SavePostHandler();
$save_post_handler->synthesize_speech( $post_id );
} else {
update_post_meta( $post_id, self::SYNTHESIZE_SPEECH_KEY, 'no' );
}
}

/**
* Adds audio controls to the post that has speech sythesis enabled.
*
Expand Down
22 changes: 21 additions & 1 deletion src/js/language-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ import '../scss/language-processing.scss';
}
} )();

// Display "Classify Post" button only when "Process content on update" is unchecked (Classic Editor).
document.addEventListener( 'DOMContentLoaded', function () {
// Display "Classify Post" button only when "Process content on update" is unchecked (Classic Editor).
const classifaiNLUCheckbox = document.getElementById(
'_classifai_process_content'
);
Expand All @@ -364,4 +364,24 @@ document.addEventListener( 'DOMContentLoaded', function () {
} );
classifaiNLUCheckbox.dispatchEvent( new Event( 'change' ) );
}

// Display audio preview only when "Enable audio generation" is checked (Classic Editor).
const classifaiAudioGenerationCheckbox = document.getElementById(
'classifai_synthesize_speech'
);
const classifaiAudioPreview = document.getElementById(
'classifai-audio-preview'
);
if ( classifaiAudioGenerationCheckbox && classifaiAudioPreview ) {
classifaiAudioGenerationCheckbox.addEventListener(
'change',
function () {
if ( this.checked === true ) {
classifaiAudioPreview.style.display = 'block';
} else {
classifaiAudioPreview.style.display = 'none';
}
}
);
}
} );
9 changes: 9 additions & 0 deletions src/scss/language-processing.scss
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,12 @@
}
}
}

#classifai-text-to-speech-meta-box {
.description {
display: block;
margin-top: 8px;
font-size: 12px;
color: rgb(117, 117, 117);
}
}
76 changes: 63 additions & 13 deletions tests/cypress/integration/text-to-speech.test.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
describe( 'Microsoft Azure - Text to Speech', () => {
before( () => {
cy.login();
cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' );
cy.visit(
'/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech'
);
cy.get( '#azure_text_to_speech_post_types_post' ).check( 'post' );
cy.get( '#url' ).clear();
cy.get( '#url' ).type( 'https://service.com' );
cy.get( '#api_key' ).type( 'password' );
cy.get('#submit').click();
cy.get( '#submit' ).click();

cy.get( '#voice' ).select( 'en-AU-AnnetteNeural|Female' );
cy.get('#submit').click();
cy.get( '#submit' ).click();
} );

it( 'Generates audio from text', () => {
cy.createPost( {
title: 'Text to Speech test',
content: "This feature uses Microsoft's Text to Speech capabilities.",
content:
"This feature uses Microsoft's Text to Speech capabilities.",
} );

cy.get( 'button[aria-label="Close panel"]' ).click();
cy.get( 'button[data-label="Post"]' ).click();
cy.get( '.classifai-panel' ).click();
cy.get( '#classifai-audio-controls__preview-btn' ).should( 'exist' )
cy.get( '#classifai-audio-controls__preview-btn' ).should( 'exist' );
} );

it( 'Audio controls are visible if supported by post type', () => {
Expand All @@ -32,33 +35,80 @@ describe( 'Microsoft Azure - Text to Speech', () => {
it( 'a11y - aria-labels', () => {
cy.visit( '/text-to-speech-test/' );
cy.get( '.dashicons-controls-play' ).should( 'be.visible' );
cy.get( '.class-post-audio-controls' ).should( 'have.attr', 'aria-label', 'Play audio' );
cy.get( '.class-post-audio-controls' ).should(
'have.attr',
'aria-label',
'Play audio'
);

cy.get( '.class-post-audio-controls' ).click();

cy.get( '.dashicons-controls-play' ).should( 'not.be.visible' );
cy.get( '.class-post-audio-controls' ).should( 'have.attr', 'aria-label', 'Pause audio' );
cy.get( '.class-post-audio-controls' ).should(
'have.attr',
'aria-label',
'Pause audio'
);

cy.get( '.class-post-audio-controls' ).click();
cy.get( '.dashicons-controls-play' ).should( 'be.visible' );
cy.get( '.class-post-audio-controls' ).should( 'have.attr', 'aria-label', 'Play audio' );
cy.get( '.class-post-audio-controls' ).should(
'have.attr',
'aria-label',
'Play audio'
);
} );

it( 'a11y - keyboard accessibility', () => {
cy.visit( '/text-to-speech-test/' );
cy.get( '.class-post-audio-controls' ).tab( { shift: true } ).tab().type( '{enter}' );
cy.get( '.class-post-audio-controls' )
.tab( { shift: true } )
.tab()
.type( '{enter}' );
cy.get( '.dashicons-controls-pause' ).should( 'be.visible' );
cy.get( '.class-post-audio-controls' ).should( 'have.attr', 'aria-label', 'Pause audio' );
cy.get( '.class-post-audio-controls' ).should(
'have.attr',
'aria-label',
'Pause audio'
);

cy.get( '.class-post-audio-controls' ).type( '{enter}' );
cy.get( '.dashicons-controls-play' ).should( 'be.visible' );
cy.get( '.class-post-audio-controls' ).should( 'have.attr', 'aria-label', 'Play audio' );
cy.get( '.class-post-audio-controls' ).should(
'have.attr',
'aria-label',
'Play audio'
);
} );

it( 'Can see the enable button in a post (Classic Editor)', () => {
cy.visit( '/wp-admin/plugins.php' );
cy.get( '#activate-classic-editor' ).click();

cy.classicCreatePost( {
title: 'Text to Speech test classic',
content:
"This feature uses Microsoft's Text to Speech capabilities.",
postType: 'post',
} );

cy.get( '#classifai-text-to-speech-meta-box' ).should( 'exist' );
cy.get( '#classifai_synthesize_speech' ).check();
cy.get( '#classifai-audio-preview' ).should( 'exist' );

cy.visit( '/text-to-speech-test/' );
cy.get( '.class-post-audio-controls' ).should( 'be.visible' );

cy.visit( '/wp-admin/plugins.php' );
cy.get( '#deactivate-classic-editor' ).click();
} );

it( 'Disable support for post type Post', () => {
cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' );
cy.visit(
'/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech'
);
cy.get( '#azure_text_to_speech_post_types_post' ).uncheck( 'post' );
cy.get('#submit').click();
cy.get( '#submit' ).click();

cy.visit( '/text-to-speech-test/' );
cy.get( '.class-post-audio-controls' ).should( 'not.exist' );
Expand Down