Skip to content

Commit 907d1a9

Browse files
authored
Highlight sentence during voiceover playback (oppia#22689)
* Creats helper modules. * Adds platform file for Azure TTS. * Removes unwanted files * Adds backend tests * Add functionality to regenerate voiceovers from exploration editor * Updates the branch with latest changes * Fixes backend tests * Updates docstring * Fixes backend tests * Fixes mypy checks and lint checks * Fixes lint issues * Adds backend test files in a shard. * Updates dummy voiceovers! * Adds backend coverage tests * updates typo + shards * temp * remove comma from backend shard * Adds saving functionality for generated voiceovers. * Updates method names and removes consoles * updates graph coloring after voiceover regeneration * Adds backend unit tests * Adds frontend tests * Adds frontend test coverage * Fixes mypy checks * Fixes ts checks * Hides feature behind feature flag. * Fixes mypy checks * Adds logic to highlight text during voiceover play * Adds backend coverage tests * Highlights in preview and player page * Fixes acceptance test * Adds confirmation modal for updating autogeneration suuport for voiceovers. * Hides autogenerate voiceover button for non curated explorations * Addresses review comments * Adds services to highlight sentences * Adds frontend tests * adds input variable * Updates modal information. * Fixes lint issues * Removes constants * Updates highlighting logic * Fixes lint issues * Adds voiceover accuracy rules * Adds logic to block voiceover addition/regeneration when there is no content. * Updates code to handle superscripts * Adds frontend tests coverage * Fixes frontend tests * Removes azure call dependencies * Fixes lint issues. * updates variable names * voiceover addition option for customization args * Adds backend test coverage * Fixes lint and TS issues * Commits changes for highlighting * restructures code * temp * Fixes acceptance tests * Fixes sentence highlight during language change * Removes redundant method * Adds frontend test coverage * Fixes lint checks * Fixes TS checks * Fixes backend test * Updates copyright year & fileoverview. * Fixes minute bugs * Fixes voiceover card loading issue * Blocks language accent support if language code is not present in constants. * Rephrases the names of some frontend tests * Fixes spelling
1 parent a5fbb37 commit 907d1a9

31 files changed

+1919
-86
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
/core/templates/services/entity-translations.services.ts @oppia/lace-frontend-reviewers
9999
/core/templates/services/entity-voiceovers.services.ts @oppia/lace-frontend-reviewers
100100
/core/templates/services/voiceover-language-management-service.ts @oppia/lace-frontend-reviewers
101+
/core/templates/services/automatic-voiceover-highlight-service.ts @oppia/lace-frontend-reviewers
101102

102103

103104
# Audio Voiceover project

assets/constants.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7752,7 +7752,6 @@ export default {
77527752
"CONTRIBUTOR_CERTIFICATE_HEIGHT": 1313,
77537753
"BRANCH_NAME": "",
77547754
"SHORT_COMMIT_HASH": "",
7755-
77567755
// Please consult the translation team before adding any entries here.
77577756
// These words improve the quality of automatic voiceovers.
77587757
"LANGUAGE_CODE_TO_MATH_SYMBOL_PRONUNCIATIONS": {
@@ -7804,5 +7803,14 @@ export default {
78047803
"^2": "تربيع",
78057804
"^3": "تكعيب"
78067805
}
7806+
},
7807+
// Please consult the translation team before adding any entries here.
7808+
// These punctuation marks are used to identify sentence boundaries during
7809+
// voiceover playback.
7810+
"LANGUAGE_CODE_TO_SENTENCE_ENDING_PUNCTUATION_MARKS": {
7811+
"ar": "؟!",
7812+
"en": ".!?",
7813+
"pt": ".!?",
7814+
"hi": "।!?"
78077815
}
78087816
} as const;

core/domain/voiceover_regeneration_services.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ def parse_html(html_content: str) -> str:
106106
Returns:
107107
str. The plain text retrieved from the HTML content.
108108
"""
109-
110109
soup = bs4.BeautifulSoup(html_content, 'html.parser')
111110
for custom_tag_element in ALLOWED_CUSTOM_OPPIA_RTE_TAGS:
112111
for element in soup.find_all(custom_tag_element):
@@ -167,7 +166,6 @@ def synthesize_voiceover_for_html_string(
167166
feconf.OPPIA_AUTOMATIC_VOICEOVER_PROVIDER
168167
)
169168
)
170-
171169
audio_offset_list: List[Dict[str, Union[str, float]]] = []
172170

173171
is_cached_model_used_for_voiceovers = False

core/platform/speech_synthesis/azure_speech_synthesis_services.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@
6161
</p>
6262
"""
6363

64-
# Standard arithmetic operators used to separate text with math expressions in
65-
# an SSML string.
66-
COMMONLY_USED_ARITHMETIC_EXPRESSIONS = [
67-
'+', ' - ', '*', ' / ', '×', '÷', '=']
68-
6964

7065
class WordBoundaryCollection:
7166
"""This class handles word boundary events to collect the time offsets
@@ -94,24 +89,6 @@ def word_boundary_event(self, event: speechsdk.SessionEventArgs) -> None:
9489
self.audio_offset_list.append(audio_offset_record)
9590

9691

97-
def is_mathematical_text(text: str) -> bool:
98-
"""The method determines whether the given text is mathematical by checking
99-
for the presence of common arithmetic operators.
100-
101-
Args:
102-
text: str. The text to be analyzed for the presence of arithmetic
103-
operators.
104-
105-
Returns:
106-
bool. A boolean value that signifies if the given text contains
107-
mathematical content.
108-
"""
109-
for expression in COMMONLY_USED_ARITHMETIC_EXPRESSIONS:
110-
if expression in text:
111-
return True
112-
return False
113-
114-
11592
def get_azure_voicecode_from_language_accent_code(
11693
language_accent_code: str
11794
) -> str:

core/platform/speech_synthesis/azure_speech_synthesis_services_test.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,15 +291,6 @@ def test_should_return_word_boundary_collection_correctly(self) -> None:
291291
word_boundary_collection.audio_offset_list,
292292
expected_word_boundary_collection)
293293

294-
def test_is_mathematical_text(self) -> None:
295-
math_text = 'Convert 5 + 3 to words.'
296-
self.assertTrue(
297-
azure_speech_synthesis_services.is_mathematical_text(math_text))
298-
299-
non_math_text = 'This is a test text.'
300-
self.assertFalse(
301-
azure_speech_synthesis_services.is_mathematical_text(non_math_text))
302-
303294
def _get_ssml_content(
304295
self, main_content: str, language_accent_code: str
305296
) -> str:

core/templates/pages/exploration-editor-page/translation-tab/voiceover-card/voiceover-card.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ <h4 class="voiceover-type-heading">Manual Voiceovers</h4>
6565
</div>
6666

6767
<div class="automatic-voiceovers"
68-
*ngIf="isVoiceoverAutogenerationSupportedForSelectedAccent && isAutomaticVoiceoverRegenerationFromExpFeatureEnabled()">
68+
*ngIf="shouldShowAutoVoiceoverRegenerationSection()">
6969
<h4 class="voiceover-type-heading">Automatic Voiceovers (generated using cloud service)</h4>
7070
<div class="voiceovers-component" *ngIf="automaticVoiceover">
7171
<button class="btn btn-secondary audio-button" (click)="playAndPauseVoiceover(automaticVoiceover.filename, 'auto')">

core/templates/pages/exploration-editor-page/translation-tab/voiceover-card/voiceover-card.component.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,4 +917,22 @@ describe('Voiceover card component', () => {
917917
component.isAutomaticVoiceoverRegenerationFromExpFeatureEnabled()
918918
).toBeTrue();
919919
});
920+
921+
it('should show voiceover regeneration section', () => {
922+
let explorationLinkedToStorySpy = spyOn(
923+
contextService,
924+
'isExplorationLinkedToStory'
925+
);
926+
component.isVoiceoverAutogenerationSupportedForSelectedAccent = true;
927+
spyOn(
928+
component,
929+
'isAutomaticVoiceoverRegenerationFromExpFeatureEnabled'
930+
).and.returnValue(true);
931+
932+
explorationLinkedToStorySpy.and.returnValue(true);
933+
expect(component.shouldShowAutoVoiceoverRegenerationSection()).toBeTrue();
934+
935+
explorationLinkedToStorySpy.and.returnValue(false);
936+
expect(component.shouldShowAutoVoiceoverRegenerationSection()).toBeFalse();
937+
});
920938
});

core/templates/pages/exploration-editor-page/translation-tab/voiceover-card/voiceover-card.component.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import {
2020
Component,
21+
ChangeDetectorRef,
2122
ElementRef,
2223
OnInit,
2324
ViewChild,
@@ -98,6 +99,7 @@ export class VoiceoverCardComponent implements OnInit, AfterViewChecked {
9899
constructor(
99100
private audioPlayerService: AudioPlayerService,
100101
private contextService: ContextService,
102+
private changeDetectorRef: ChangeDetectorRef,
101103
private translationLanguageService: TranslationLanguageService,
102104
private translationTabActiveContentIdService: TranslationTabActiveContentIdService,
103105
private ngbModal: NgbModal,
@@ -149,10 +151,6 @@ export class VoiceoverCardComponent implements OnInit, AfterViewChecked {
149151
}
150152
)
151153
);
152-
this.voiceoversAreLoaded =
153-
Object.keys(
154-
this.entityVoiceoversService.languageAccentCodeToEntityVoiceovers
155-
).length !== 0;
156154

157155
if (!this.entityVoiceoversService.getActiveLanguageAccentCode()) {
158156
this.voiceoversAreLoaded = true;
@@ -212,6 +210,11 @@ export class VoiceoverCardComponent implements OnInit, AfterViewChecked {
212210
} else {
213211
this.isGenerateAutomaticVoiceoverOptionEnabled = false;
214212
}
213+
this.isVoiceoverAutogenerationSupportedForSelectedAccent =
214+
this.voiceoverLanguageManagementService.isAutogenerationSupportedGivenLanguageAccent(
215+
this.languageAccentCode
216+
);
217+
this.changeDetectorRef.detectChanges();
215218
}
216219

217220
isAutomaticVoiceoverRegenerationFromExpFeatureEnabled(): boolean {
@@ -487,6 +490,18 @@ export class VoiceoverCardComponent implements OnInit, AfterViewChecked {
487490
}
488491
}
489492

493+
isExplorationLinkedToStory(): boolean {
494+
return this.contextService.isExplorationLinkedToStory();
495+
}
496+
497+
shouldShowAutoVoiceoverRegenerationSection(): boolean {
498+
return (
499+
this.isVoiceoverAutogenerationSupportedForSelectedAccent &&
500+
this.isAutomaticVoiceoverRegenerationFromExpFeatureEnabled() &&
501+
this.isExplorationLinkedToStory()
502+
);
503+
}
504+
490505
deleteManualVoiceover(): void {
491506
const modalRef = this.ngbModal.open(VoiceoverRemovalConfirmModalComponent, {
492507
backdrop: 'static',

core/templates/pages/exploration-player-page/layout-directives/audio-bar.component.spec.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ describe('Audio Bar Component', () => {
326326
expect(loadAndPlaySpy).toHaveBeenCalled();
327327
});
328328

329-
it('should be able update diplayable language accent code', () => {
329+
it('should be able to update displayable language accent code for manual voiceover', () => {
330330
let manualVoiceoverBackendDict: VoiceoverBackendDict = {
331331
filename: 'a.mp3',
332332
file_size_bytes: 200000,
@@ -336,6 +336,7 @@ describe('Audio Bar Component', () => {
336336
let contentIdToVoiceoversMappingBackendDict = {
337337
content0: {
338338
manual: manualVoiceoverBackendDict,
339+
auto: undefined,
339340
},
340341
};
341342
let entityId = 'exploration_1';
@@ -372,6 +373,53 @@ describe('Audio Bar Component', () => {
372373
expect(component.voiceoverToBePlayed.filename).toEqual('a.mp3');
373374
});
374375

376+
it('should be able to update displayable language accent code for auto voiceover', () => {
377+
let autoVoiceoverBackendDict: VoiceoverBackendDict = {
378+
filename: 'b.mp3',
379+
file_size_bytes: 200000,
380+
needs_update: false,
381+
duration_secs: 10.0,
382+
};
383+
let contentIdToVoiceoversMappingBackendDict = {
384+
content0: {
385+
manual: undefined,
386+
auto: autoVoiceoverBackendDict,
387+
},
388+
};
389+
let entityId = 'exploration_1';
390+
let entityType = 'exploration';
391+
let entityVersion = 1;
392+
let languageAccentCode = 'en-US';
393+
let entityVoiceoversBackendDict = {
394+
entity_id: entityId,
395+
entity_type: entityType,
396+
entity_version: entityVersion,
397+
language_accent_code: languageAccentCode,
398+
voiceovers_mapping: contentIdToVoiceoversMappingBackendDict,
399+
};
400+
let entityVoiceovers = EntityVoiceovers.createFromBackendDict(
401+
entityVoiceoversBackendDict
402+
);
403+
let languageAccentDecriptions = ['en-US', 'en-IN'];
404+
405+
spyOn(
406+
voiceoverPlayerService,
407+
'getLanguageAccentDescriptions'
408+
).and.returnValue(languageAccentDecriptions);
409+
spyOn(
410+
entityVoiceoversService,
411+
'getActiveEntityVoiceovers'
412+
).and.returnValue(entityVoiceovers);
413+
spyOn(playerPositionService, 'getCurrentStateName');
414+
spyOn(audioPreloaderService, 'restartAudioPreloader');
415+
voiceoverPlayerService.activeContentId = 'content0';
416+
417+
component.voiceoverToBePlayed = undefined;
418+
component.updateDisplayableLanguageAccentDescription();
419+
420+
expect(component.voiceoverToBePlayed.filename).toEqual('b.mp3');
421+
});
422+
375423
it('should pause uploaded voiceover when pause button is clicked', () => {
376424
component.voiceoverToBePlayed = Voiceover.createFromBackendDict({
377425
filename: 'audio-en.mp3',

core/templates/pages/exploration-player-page/layout-directives/audio-bar.component.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,21 @@ export class AudioBarComponent {
217217

218218
let contentId = this.voiceoverPlayerService.activeContentId;
219219

220-
this.voiceoverToBePlayed = entityVoiceover.getManualVoiceover(
220+
let manualVoiceover = entityVoiceover.getManualVoiceover(
221221
contentId
222222
) as Voiceover;
223+
let automaticVoiceover = entityVoiceover.getAutomaticVoiceover(
224+
contentId
225+
) as Voiceover;
226+
227+
if (manualVoiceover && manualVoiceover.needsUpdate === false) {
228+
this.voiceoverToBePlayed = manualVoiceover;
229+
} else if (automaticVoiceover && automaticVoiceover.needsUpdate === false) {
230+
this.voiceoverToBePlayed = automaticVoiceover;
231+
}
223232

233+
this.audioPreloaderService.contentIdsToVoiceovers =
234+
this.entityVoiceoversService.getAllContentIdsToVoiceovers();
224235
this.audioPreloaderService.restartAudioPreloader(
225236
this.playerPositionService.getCurrentStateName()
226237
);

0 commit comments

Comments
 (0)