Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
136 changes: 136 additions & 0 deletions assets/src/blocks/godam-player/components/AITranscription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { BaseControl, Button, Spinner, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

/**
* Internal dependencies
*/
import { ReactComponent as TranscriptIcon } from '../../../images/ai-transcript.svg';

/**
* AI Transcription component for GoDAM video block.
*
* @param {Object} props Component props.
* @param {number} props.attachmentId The attachment ID.
* @param {number} props.instanceId The instance ID for unique control IDs.
* @param {boolean} props.hasTranscription Whether transcription already exists.
* @param {Function} props.onTranscriptionComplete Callback when transcription is generated.
*
* @return {JSX.Element} The AI Transcription component.
*/
export default function AITranscription( {
attachmentId,
instanceId,
hasTranscription = false,
onTranscriptionComplete,
} ) {
const [ isLoading, setIsLoading ] = useState( false );
const [ notice, setNotice ] = useState( null );

/**
* Handle generate transcription button click.
*/
const handleGenerateTranscription = async () => {
setIsLoading( true );
setNotice( null );

try {
const response = await apiFetch( {
path: '/godam/v1/ai-transcription/process',
method: 'POST',
data: {
attachment_id: attachmentId,
},
} );

if ( response.success ) {
// Call the callback to update tracks in parent component
if ( onTranscriptionComplete ) {
await onTranscriptionComplete();
}
} else {
throw new Error(
response.message ||
__( 'Failed to generate transcription.', 'godam' ),
);
}
} catch ( error ) {
setNotice( {
type: 'error',
message:
error.message ||
__( 'An error occurred while generating transcription.', 'godam' ),
} );
} finally {
setIsLoading( false );
}
};

// Don't show anything if attachment ID is invalid.
if ( ! attachmentId || isNaN( Number( attachmentId ) ) ) {
return null;
}

let buttonLabel = __( 'Generate AI Transcription', 'godam' );
if ( isLoading ) {
buttonLabel = __( 'Generating…', 'godam' );
} else if ( hasTranscription ) {
buttonLabel = __( 'Transcription Generated', 'godam' );
}

const helpText = __(
'Generate AI-powered captions for the video. Once generated, captions will be available automatically in the player.',
'godam',
);

const isButtonDisabled = hasTranscription || isLoading;

return (
<BaseControl
id={ `video-block__ai-transcription-${ instanceId }` }
label={ __( 'AI Transcription', 'godam' ) }
help={ helpText }
__nextHasNoMarginBottom
>
<div className="ai-transcription-control__content">
<Button
__next40pxDefaultSize
variant="primary"
onClick={ handleGenerateTranscription }
disabled={ isButtonDisabled }
icon={ isLoading && <Spinner /> }
isBusy={ isLoading }
>
<span className="ai-transcription-control__button-content">
{ buttonLabel }
<TranscriptIcon
aria-label={ __( 'AI transcription icon', 'godam' ) }
role="img"
className={
`ai-transcription-control__icon` +
( isButtonDisabled
? ' ai-transcription-control__icon--disabled'
: '' )
}
/>
</span>
</Button>

{ notice && (
<div className="ai-transcription-control__notice-wrapper">
<Notice
status={ notice.type }
isDismissible={ false }
>
{ notice.message }
</Notice>
</div>
) }
</div>
</BaseControl>
);
}
74 changes: 72 additions & 2 deletions assets/src/blocks/godam-player/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
useBlockProps,
InnerBlocks,
} from '@wordpress/block-editor';
import { useRef, useEffect, useState, useMemo } from '@wordpress/element';
import { useRef, useEffect, useState, useMemo, useCallback } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { __, _x, sprintf } from '@wordpress/i18n';
import { useInstanceId } from '@wordpress/compose';
Expand All @@ -43,6 +43,7 @@ import Video from './VideoJS';
import TracksEditor from './track-uploader';
import { Caption } from './caption';
import VideoSEOModal from './components/VideoSEOModal.js';
import AITranscription from './components/AITranscription';
import { appendTimezoneOffsetToUTC, isSEODataEmpty, secondsToISO8601 } from './utils/index.js';
import './editor.scss';
import { ReactComponent as icon } from '../../images/godam-video-filled.svg';
Expand Down Expand Up @@ -132,6 +133,49 @@ function VideoEdit( {

const dispatch = useDispatch();

/**
* Check if transcription already exists for this video.
*/
const checkExistingTranscription = useCallback( async () => {
try {
const response = await apiFetch( {
path: `/godam/v1/ai-transcription/get?attachment_id=${ id }`,
method: 'GET',
} );

if ( response.success && response.transcription_status === 'Transcribed' ) {
const transcriptPath = response?.transcript_path || '';

// Build tracks array with AI transcription.
const existingTracks = Array.isArray( tracks ) ? tracks : [];

// Check if this transcription path already exists in tracks.
const transcriptAlreadyExists = existingTracks.some(
( track ) => track.src === transcriptPath,
);

// Only add if not already present.
if ( ! transcriptAlreadyExists ) {
const newTrack = {
src: transcriptPath,
kind: 'subtitles',
label: 'English',
srclang: 'en',
};

setAttributes( { tracks: [ ...existingTracks, newTrack ] } );
}

return true; // Return true if transcription exists
}

return false; // Return false if no transcription
} catch ( error ) {
// Transcription doesn't exist yet, which is fine.
return false; // Return false on error
}
}, [ id, setAttributes, tracks ] );

// Memoize video options to prevent unnecessary rerenders.
const videoOptions = useMemo( () => ( {
controls,
Expand All @@ -150,7 +194,17 @@ function VideoEdit( {
muted,
poster: poster || defaultPoster,
sources,
tracks: tracks || [],
aspectRatio: '16:9',
controlBar: {
playToggle: true,
volumePanel: true,
currentTimeDisplay: true,
timeDivider: true,
durationDisplay: true,
fullscreenToggle: true,
subsCapsButton: true, // Enable captions button
},
// VHS (HLS/DASH) initial configuration to prefer a ~14 Mbps start.
// This only affects the initial bandwidth guess; VHS will continue to measure actual throughput and adapt.
html5: {
Expand All @@ -160,7 +214,7 @@ function VideoEdit( {
limitRenditionByPlayerDimensions: false, // don't cap by video element size
},
},
} ), [ controls, autoplay, preload, loop, muted, poster, defaultPoster, sources ] );
} ), [ controls, autoplay, preload, loop, muted, poster, defaultPoster, sources, tracks ] );

// Memoize the video component to prevent rerenders.
const videoComponent = useMemo( () => (
Expand Down Expand Up @@ -330,6 +384,15 @@ function VideoEdit( {
}
}, [ id, src, attributes.seo, isVideoSelecting, setAttributes ] );

// Check if transcription already exists on mount.
useEffect( () => {
if ( ! id || isInsideQueryLoop ) {
return;
}

checkExistingTranscription();
}, [ id, isInsideQueryLoop, checkExistingTranscription ] );

function onSelectVideo( media ) {
// Set flag to prevent backward compatibility logic during video selection
setIsVideoSelecting( true );
Expand Down Expand Up @@ -735,6 +798,13 @@ function VideoEdit( {
</BaseControl>
) }

<AITranscription
attachmentId={ id }
instanceId={ instanceId }
hasTranscription={ tracks && tracks.length > 0 }
onTranscriptionComplete={ checkExistingTranscription }
/>

<BaseControl
id={ `video-block__video-seo-${ instanceId }` }
label={ __( 'SEO Settings', 'godam' ) }
Expand Down
28 changes: 28 additions & 0 deletions assets/src/blocks/godam-player/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,31 @@
position: relative;
aspect-ratio: 16 / 9;
}

.ai-transcription-control {

&__content {
margin-top: 8px;
}

&__button-content {
display: inline-flex;
align-items: center;
gap: 6px;
}

&__icon {
width: 16px;
height: 16px;
opacity: 1;
transition: opacity 0.15s ease;
}

&__icon--disabled {
opacity: 0.5;
}

&__notice-wrapper {
margin-top: 8px;
}
}
14 changes: 14 additions & 0 deletions assets/src/images/ai-transcript.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions inc/classes/class-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
use RTGODAM\Inc\REST_API\Release_Post;
use RTGODAM\Inc\Gravity_Forms;
use RTGODAM\Inc\REST_API\MetForm;
use RTGODAM\Inc\REST_API\AI_Transcription;


use RTGODAM\Inc\Shortcodes\GoDAM_Player;
Expand Down Expand Up @@ -174,6 +175,7 @@ public function load_rest_api() {
Site::get_instance();
Video_Migration::get_instance();
Release_Post::get_instance();
AI_Transcription::get_instance();
}

/**
Expand Down
Loading
Loading