#
# PronunciationOperations.py
#
# Class: PronunciationOperations
# Pronunciation operations for FieldWorks Language Explorer
# projects via SIL Language and Culture Model (LCM) API.
#
# Platform: Python.NET
# FieldWorks Version 9+
#
# Copyright 2025
#
import logging
logger = logging.getLogger(__name__)
import System
# Import BaseOperations parent class
from ..BaseOperations import BaseOperations, OperationsMethod, wrap_enumerable
# Import FLEx LCM types
from SIL.LCModel import (
ILexEntry,
ILexPronunciation,
ILexPronunciationFactory,
ICmFile,
ICmFileFactory,
)
from SIL.LCModel.Core.KernelInterfaces import ITsString
from SIL.LCModel.Core.Text import TsStringUtils
# Import flexlibs exceptions
from ..FLExProject import (
FP_ParameterError,
)
# Import string utilities
from ..Shared.string_utils import normalize_text
[docs]
class PronunciationOperations(BaseOperations):
"""
This class provides operations for managing pronunciations in a FieldWorks project.
Pronunciations represent the phonetic representation of lexical entries, typically
using IPA (International Phonetic Alphabet) notation. Each pronunciation can have
forms in multiple writing systems (e.g., en-fonipa for IPA), associated audio files,
and optional CV (consonant-vowel) pattern location information.
This class should be accessed via FLExProject.Pronunciations property.
Usage::
from flexlibs2 import FLExProject
project = FLExProject()
project.OpenProject("my project", writeEnabled=True)
# Get all pronunciations for an entry
entry = list(project.LexiconAllEntries())[0]
for pron in project.Pronunciations.GetAll(entry):
ipa = project.Pronunciations.GetForm(pron, "en-fonipa")
print(f"IPA: {ipa}")
# Create a new pronunciation
pron = project.Pronunciations.Create(entry, "rʌn", "en-fonipa")
# Add audio file
project.Pronunciations.AddMediaFile(pron, "/path/to/audio.wav")
# Get media files
media = project.Pronunciations.GetMediaFiles(pron)
print(f"Audio files: {len(media)}")
project.CloseProject()
"""
def __init__(self, project):
"""
Initialize PronunciationOperations with a FLExProject instance.
Args:
project: The FLExProject instance to operate on.
"""
super().__init__(project)
def _GetSequence(self, parent):
"""
Specify which sequence to reorder for pronunciations.
For Pronunciation, we reorder entry.PronunciationsOS
"""
return parent.PronunciationsOS
# --- Core CRUD Operations ---
@wrap_enumerable
@OperationsMethod
def GetAll(self, entry_or_hvo=None):
"""
Get all pronunciations for a lexical entry, or all pronunciations in the entire project.
Args:
entry_or_hvo: The ILexEntry object or HVO. If None, iterates all pronunciations
in the entire project.
Yields:
ILexPronunciation: Each pronunciation for the entry (or project).
Example:
>>> # Get pronunciations for specific entry
>>> entry = list(project.LexiconAllEntries())[0]
>>> for pron in project.Pronunciations.GetAll(entry):
... ipa = project.Pronunciations.GetForm(pron, "en-fonipa")
... print(f"IPA: {ipa}")
IPA: rʌn
IPA: ɹʌn
>>> # Get ALL pronunciations in entire project
>>> for pron in project.Pronunciations.GetAll():
... ipa = project.Pronunciations.GetForm(pron, "en-fonipa")
... print(f"IPA: {ipa}")
Notes:
- When entry_or_hvo is provided:
- Returns pronunciations in the order they appear in FLEx
- Returns empty generator if entry has no pronunciations
- Pronunciations are ordered collections (can be reordered)
- When entry_or_hvo is None:
- Iterates ALL entries in the project
- For each entry, yields all pronunciations
- Useful for project-wide pronunciation operations
See Also:
Create, Delete, Reorder
"""
if entry_or_hvo is None:
# Iterate ALL pronunciations in entire project
for entry in self.project.lexDB.Entries:
if hasattr(entry, "PronunciationsOS"):
for pronunciation in entry.PronunciationsOS:
yield pronunciation
else:
# Iterate pronunciations for specific entry
entry = self.__GetEntryObject(entry_or_hvo)
if hasattr(entry, "PronunciationsOS"):
for pronunciation in entry.PronunciationsOS:
yield pronunciation
@OperationsMethod
def Create(self, entry_or_hvo, form, wsHandle=None):
"""
Create a new pronunciation for a lexical entry.
Args:
entry_or_hvo: The ILexEntry object or HVO.
form (str): The pronunciation form (typically IPA notation).
wsHandle: Optional writing system handle. Use "en-fonipa" for IPA,
or defaults to vernacular WS.
Returns:
ILexPronunciation: The newly created pronunciation object.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If entry_or_hvo or form is None.
FP_ParameterError: If form is empty.
Example:
>>> entry = list(project.LexiconAllEntries())[0]
>>> # Create IPA pronunciation
>>> pron = project.Pronunciations.Create(entry, "rʌn", "en-fonipa")
>>> print(project.Pronunciations.GetForm(pron, "en-fonipa"))
rʌn
>>> # Create with default WS
>>> pron = project.Pronunciations.Create(entry, "run")
Notes:
- The pronunciation is added to the end of the entry's pronunciations list
- For IPA notation, use "en-fonipa" writing system (English IPA)
- Other IPA writing systems follow pattern: "{lang}-fonipa" (e.g., "fr-fonipa")
- Audio files can be added after creation using AddMediaFile()
- Pronunciation form is set immediately
See Also:
Delete, SetForm, AddMediaFile
"""
self._EnsureWriteEnabled()
self._ValidateParam(entry_or_hvo, "entry_or_hvo")
self._ValidateParam(form, "form")
self._ValidateStringNotEmpty(form, "form")
entry = self.__GetEntryObject(entry_or_hvo)
wsHandle = self.__WSHandleVern(wsHandle)
# Create the new pronunciation using the factory
factory = self.project.project.ServiceLocator.GetService(ILexPronunciationFactory)
with self._TransactionCM("Create pronunciation"):
pronunciation = factory.Create()
# Add to entry's pronunciations collection (must be done before setting properties)
if hasattr(entry, "PronunciationsOS"):
entry.PronunciationsOS.Add(pronunciation)
else:
raise FP_ParameterError("Entry does not support pronunciations")
# Set pronunciation form
mkstr = TsStringUtils.MakeString(form, wsHandle)
pronunciation.Form.set_String(wsHandle, mkstr)
return pronunciation
@OperationsMethod
def Delete(self, pronunciation_or_hvo):
"""
Delete a pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO to delete.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> entry = list(project.LexiconAllEntries())[0]
>>> pronunciations = list(project.Pronunciations.GetAll(entry))
>>> if pronunciations:
... project.Pronunciations.Delete(pronunciations[0])
Notes:
- Permanently removes the pronunciation from the database
- Associated media files are NOT automatically deleted from disk
- Cannot be undone
- The pronunciation is removed from the owning entry's collection
See Also:
Create, GetAll
"""
self._EnsureWriteEnabled()
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
# pronunciation.Owner is typed as ICmObject in LCM. pythonnet only
# surfaces attributes from the static type, so
# hasattr(entry, "PronunciationsOS") returns False without a cast
# and the Remove() silently never runs (issue #151).
entry = ILexEntry(pronunciation.Owner)
entry.PronunciationsOS.Remove(pronunciation)
@OperationsMethod
def Duplicate(self, item_or_hvo, insert_after=True, deep=False):
"""
Duplicate a pronunciation, creating a new copy with a new GUID.
Args:
item_or_hvo: The ILexPronunciation object or HVO to duplicate.
insert_after (bool): If True (default), insert after the source pronunciation.
If False, insert at end of parent's pronunciations list.
deep (bool): If True, also duplicate owned objects (media files).
If False (default), only copy simple properties and references.
Returns:
ILexPronunciation: The newly created duplicate pronunciation with a new GUID.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If item_or_hvo is None.
Example:
>>> entry = list(project.LexiconAllEntries())[0]
>>> pronunciations = list(project.Pronunciations.GetAll(entry))
>>> if pronunciations:
... # Shallow duplicate (no media files)
... dup = project.Pronunciations.Duplicate(pronunciations[0])
... print(f"Original: {project.Pronunciations.GetGuid(pronunciations[0])}")
... print(f"Duplicate: {project.Pronunciations.GetGuid(dup)}")
... ipa = project.Pronunciations.GetForm(dup, "en-fonipa")
... print(f"IPA: {ipa}")
Original: 12345678-1234-1234-1234-123456789abc
Duplicate: 87654321-4321-4321-4321-cba987654321
IPA: rʌn
...
... # Deep duplicate (includes media files)
... deep_dup = project.Pronunciations.Duplicate(pronunciations[0], deep=True)
... count = project.Pronunciations.GetMediaCount(deep_dup)
... print(f"Media files: {count}")
Notes:
- Factory.Create() automatically generates a new GUID
- insert_after=True preserves the original pronunciation's position/priority
- Simple properties copied: Form (MultiString)
- Reference properties copied: LocationRA
- Owned objects (deep=True): MediaFilesOS
- Media files are duplicated by creating new ICmFile objects with same properties
- LiftResidue is not copied (import-specific data)
See Also:
Create, Delete, GetGuid
"""
self._EnsureWriteEnabled()
self._ValidateParam(item_or_hvo, "item_or_hvo")
# Get source pronunciation and parent
source = self.__GetPronunciationObject(item_or_hvo)
# PronunciationsOS is declared only on ILexEntry; cast so pythonnet
# surfaces the typed collection accessor.
parent = ILexEntry(source.Owner)
# Create new pronunciation using factory (auto-generates new GUID)
factory = self.project.project.ServiceLocator.GetService(ILexPronunciationFactory)
with self._TransactionCM("Duplicate pronunciation"):
duplicate = factory.Create()
# Determine insertion position
if insert_after and hasattr(parent, "PronunciationsOS"):
# Insert after source pronunciation
source_index = parent.PronunciationsOS.IndexOf(source)
parent.PronunciationsOS.Insert(source_index + 1, duplicate)
else:
# Insert at end
if hasattr(parent, "PronunciationsOS"):
parent.PronunciationsOS.Add(duplicate)
# Copy simple MultiString properties (AFTER adding to parent)
# Use GetString/SetString instead of CopyAlternatives for safer copying
try:
duplicate.Form.CopyAlternatives(source.Form)
except (System.NullReferenceException, AttributeError):
# Fallback: copy string by string if CopyAlternatives fails
if hasattr(source, "Form") and source.Form:
for ws_id in source.Form.AvailableWritingSystems:
try:
form_string = source.Form.get_String(ws_id)
if form_string:
duplicate.Form.set_String(ws_id, form_string)
except Exception:
# Skip individual strings that fail to copy
pass
# Copy Reference Atomic (RA) properties
if hasattr(source, "LocationRA"):
duplicate.LocationRA = source.LocationRA
# Handle owned objects if deep=True
if deep and hasattr(source, "MediaFilesOS"):
# Duplicate media files
for media in source.MediaFilesOS:
media_factory = self.project.project.ServiceLocator.GetService(ICmFileFactory)
new_media = media_factory.Create()
duplicate.MediaFilesOS.Add(new_media)
# Copy media file properties
new_media.InternalPath = media.InternalPath
new_media.Description.CopyAlternatives(media.Description)
if hasattr(media, "Copyright"):
new_media.Copyright.CopyAlternatives(media.Copyright)
return duplicate
# ========== SYNC INTEGRATION METHODS ==========
@OperationsMethod
def GetSyncableProperties(self, item):
"""
Get all syncable properties of a pronunciation for comparison.
Args:
item: The ILexPronunciation object.
Returns:
dict: Dictionary mapping property names to their values.
"""
props = {}
# MultiString properties
# Form - the pronunciation form (typically IPA)
form_dict = {}
if hasattr(item, "Form"):
for ws_def in self.project.WritingSystems.GetAll():
text = normalize_text(ITsString(item.Form.get_String(ws_def.Handle)).Text)
if text:
form_dict[ws_def.Id] = text
props["Form"] = form_dict
# Reference Atomic (RA) properties
# LocationRA - CV pattern location
if hasattr(item, "LocationRA") and item.LocationRA:
props["LocationRA"] = str(item.LocationRA.Guid)
else:
props["LocationRA"] = None
# Note: MediaFilesOS is an Owning Sequence (OS) - not included
return props
@OperationsMethod
def CompareTo(self, item1, item2, ops1=None, ops2=None):
"""
Compare two pronunciations and return their differences.
Args:
item1: The first ILexPronunciation object.
item2: The second ILexPronunciation object.
ops1: Optional PronunciationOperations instance for item1.
ops2: Optional PronunciationOperations instance for item2.
Returns:
tuple: (is_different, differences_dict)
"""
ops1 = ops1 or self
ops2 = ops2 or self
props1 = ops1.GetSyncableProperties(item1)
props2 = ops2.GetSyncableProperties(item2)
differences = {}
all_keys = set(props1.keys()) | set(props2.keys())
for key in all_keys:
val1 = props1.get(key)
val2 = props2.get(key)
if val1 != val2:
differences[key] = (val1, val2)
is_different = len(differences) > 0
return is_different, differences
@OperationsMethod
def Reorder(self, entry_or_hvo, pronunciation_list):
"""
Reorder pronunciations for a lexical entry.
Args:
entry_or_hvo: The ILexEntry object or HVO.
pronunciation_list: List of ILexPronunciation objects in desired order.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If entry_or_hvo or pronunciation_list is None.
FP_ParameterError: If pronunciation_list is invalid or contains
pronunciations not owned by this entry.
Example:
>>> entry = list(project.LexiconAllEntries())[0]
>>> prons = list(project.Pronunciations.GetAll(entry))
>>> if len(prons) > 1:
... # Reverse the order
... project.Pronunciations.Reorder(entry, list(reversed(prons)))
Notes:
- All pronunciations in the list must belong to the entry
- The list must contain all current pronunciations (no partial reordering)
- Useful for prioritizing preferred pronunciations
- Order affects display in FLEx UI
See Also:
GetAll, Create
"""
self._EnsureWriteEnabled()
self._ValidateParam(entry_or_hvo, "entry_or_hvo")
self._ValidateParam(pronunciation_list, "pronunciation_list")
entry = self.__GetEntryObject(entry_or_hvo)
if not hasattr(entry, "PronunciationsOS"):
raise FP_ParameterError("Entry does not support pronunciations")
# Validate that all pronunciations belong to this entry
current_count = entry.PronunciationsOS.Count
if len(pronunciation_list) != current_count:
raise FP_ParameterError(f"Pronunciation list must contain all {current_count} pronunciations")
# Clear and re-add in new order
with self._TransactionCM("Reorder pronunciations"):
entry.PronunciationsOS.Clear()
for pron in pronunciation_list:
entry.PronunciationsOS.Add(pron)
# --- Form Management ---
@OperationsMethod
def GetForm(self, pronunciation_or_hvo, wsHandle=None):
"""
Get the pronunciation form (e.g., IPA transcription).
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
wsHandle: Optional writing system handle or tag (e.g., "en-fonipa" for IPA).
Defaults to vernacular WS.
Returns:
str: The pronunciation form text, or empty string if not set.
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> # Get IPA form
>>> ipa = project.Pronunciations.GetForm(pron, "en-fonipa")
>>> print(f"IPA: {ipa}")
IPA: rʌn
>>> # Get default WS form
>>> form = project.Pronunciations.GetForm(pron)
Notes:
- For IPA notation, use "{lang}-fonipa" writing system (e.g., "en-fonipa")
- Returns empty string if form is not set for the specified WS
- Can retrieve forms in multiple writing systems if set
- Pronunciation forms are typically phonetic transcriptions
See Also:
SetForm, Create
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
wsHandle = self.__WSHandleVern(wsHandle)
# MultiUnicodeAccessor
form = ITsString(pronunciation.Form.get_String(wsHandle)).Text
return self._NormalizeMultiString(form)
@OperationsMethod
def SetForm(self, pronunciation_or_hvo, text, wsHandle=None):
"""
Set the pronunciation form (e.g., IPA transcription).
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
text (str): The pronunciation form text (typically IPA notation).
wsHandle: Optional writing system handle or tag (e.g., "en-fonipa" for IPA).
Defaults to vernacular WS.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If pronunciation_or_hvo or text is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> # Set IPA form
>>> project.Pronunciations.SetForm(pron, "ɹʌn", "en-fonipa")
>>> # Update default WS form
>>> project.Pronunciations.SetForm(pron, "run")
Notes:
- For IPA notation, use "{lang}-fonipa" writing system (e.g., "en-fonipa")
- Can set forms in multiple writing systems
- Empty string clears the form for that writing system
- Forms can contain Unicode IPA characters
See Also:
GetForm, Create
"""
self._EnsureWriteEnabled()
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
self._ValidateParam(text, "text")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
wsHandle = self.__WSHandleVern(wsHandle)
# MultiUnicodeAccessor
mkstr = TsStringUtils.MakeString(text, wsHandle)
pronunciation.Form.set_String(wsHandle, mkstr)
# --- Media Files ---
@OperationsMethod
def GetMediaFiles(self, pronunciation_or_hvo):
"""
Get all media files (typically audio) associated with a pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
Returns:
list: List of media file objects (empty list if none).
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> media = project.Pronunciations.GetMediaFiles(pron)
>>> for m in media:
... print(f"Media: {m}")
Media: audio_recording.wav
Notes:
- Returns media file objects from MediaFilesOS collection
- Media files are typically audio recordings of pronunciation
- Can also include video or other media types
- File objects contain path and metadata information
- Empty list if no media files attached
See Also:
GetMediaCount, AddMediaFile, RemoveMediaFile
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
if hasattr(pronunciation, "MediaFilesOS"):
return list(pronunciation.MediaFilesOS)
return []
@OperationsMethod
def GetMediaCount(self, pronunciation_or_hvo):
"""
Get the count of media files associated with a pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
Returns:
int: The number of media files.
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> count = project.Pronunciations.GetMediaCount(pron)
>>> print(f"This pronunciation has {count} audio files")
This pronunciation has 2 audio files
Notes:
- More efficient than len(GetMediaFiles()) for just counting
- Returns 0 if no media files attached
- Includes all media types (audio, video, etc.)
See Also:
GetMediaFiles, AddMediaFile
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
if hasattr(pronunciation, "MediaFilesOS"):
return pronunciation.MediaFilesOS.Count
return 0
@OperationsMethod
def AddMediaFile(self, pronunciation_or_hvo, file_path, label=None):
"""
Add a media file (typically audio) to a pronunciation.
Copies the file to the project's LinkedFiles directory and creates
a media reference linked to the pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
file_path (str): Path to the external media file to import.
label (str, optional): Descriptive label for the media file.
Returns:
ICmFile: The created media file object with proper path.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If pronunciation_or_hvo or file_path is None.
FP_ParameterError: If file_path is empty or file doesn't exist.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> # Add audio recording
>>> media = project.Pronunciations.AddMediaFile(
... pron,
... "/path/to/audio.wav",
... label="Speaker pronunciation"
... )
>>> print(f"Added media file")
Added media file
Notes:
- File is copied to LinkedFiles/AudioVisual directory
- Unique filename generated if collision occurs (audio_1.wav, etc.)
- Creates ICmFile object with proper LinkedFiles path
- Supported formats: WAV, MP3, OGG, WMA (audio), MP4, AVI (video)
- Multiple media files can be added to one pronunciation
See Also:
RemoveMediaFile, GetMediaFiles, GetMediaCount, project.Media.CopyToProject
"""
self._EnsureWriteEnabled()
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
self._ValidateParam(file_path, "file_path")
self._ValidateStringNotEmpty(file_path, "file_path")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
# Use MediaOperations to properly copy file and create ICmFile
# Copy file to project and get ICmFile reference
media_file = self.project.Media.CopyToProject(file_path, internal_subdir="AudioVisual", label=label)
# Add to pronunciation's media collection
if hasattr(pronunciation, "MediaFilesOS"):
pronunciation.MediaFilesOS.Add(media_file)
else:
raise FP_ParameterError("Pronunciation does not support media files")
return media_file
@OperationsMethod
def RemoveMediaFile(self, pronunciation_or_hvo, media_or_hvo):
"""
Remove a media file from a pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
media_or_hvo: The ICmFile object or HVO to remove.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If pronunciation_or_hvo or media_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> media_files = project.Pronunciations.GetMediaFiles(pron)
>>> if media_files:
... project.Pronunciations.RemoveMediaFile(pron, media_files[0])
Notes:
- Removes the media file reference from the pronunciation
- Does NOT delete the actual file from disk
- The media file object is deleted from the database
- Cannot be undone
See Also:
AddMediaFile, GetMediaFiles
"""
self._EnsureWriteEnabled()
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
self._ValidateParam(media_or_hvo, "media_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
# Resolve media object
if isinstance(media_or_hvo, int):
media = self.project.Object(media_or_hvo)
else:
media = media_or_hvo
# Remove from collection
if hasattr(pronunciation, "MediaFilesOS"):
pronunciation.MediaFilesOS.Remove(media)
@OperationsMethod
def MoveMediaFile(self, media, from_pronunciation_or_hvo, to_pronunciation_or_hvo):
"""
Move a media file from one pronunciation to another pronunciation.
This is useful when reorganizing pronunciations or moving audio recordings to the
correct pronunciation variant.
Args:
media: ICmFile object to move
from_pronunciation_or_hvo: Source ILexPronunciation object or HVO
to_pronunciation_or_hvo: Destination ILexPronunciation object or HVO
Returns:
bool: True if media was successfully moved, False if source and destination
are the same pronunciation (no-op)
Raises:
FP_ReadOnlyError: If project not writable
FP_ParameterError: If media not in source pronunciation's collection
Example:
>>> # Move audio from one pronunciation to another
>>> entry = list(project.LexiconAllEntries())[0]
>>> pron1 = entry.PronunciationsOS[0] # IPA: [rʌn]
>>> pron2 = entry.PronunciationsOS[1] # IPA: [ɹʌn]
>>>
>>> media_files = project.Pronunciations.GetMediaFiles(pron1)
>>> # Move the audio to the correct pronunciation variant
>>> project.Pronunciations.MoveMediaFile(media_files[0], pron1, pron2)
>>>
>>> # Verify the move
>>> print(f"Pron 1 media: {project.Pronunciations.GetMediaCount(pron1)}")
>>> print(f"Pron 2 media: {project.Pronunciations.GetMediaCount(pron2)}")
>>> # Can also move between different entries
>>> entry2 = list(project.LexiconAllEntries())[1]
>>> other_pron = entry2.PronunciationsOS[0]
>>> project.Pronunciations.MoveMediaFile(media_files[1], pron1, other_pron)
Notes:
- Media is removed from source MediaFilesOS and added to destination
- File reference and description are preserved
- The physical media file is NOT moved/copied
- Cannot move to the same pronunciation (no-op, returns False)
- Media object's GUID remains the same
Warning:
- Moving media between different entries' pronunciations is allowed
- Ensure the audio is appropriate for the target pronunciation
- The media will be associated with the new pronunciation
See Also:
AddMediaFile, RemoveMediaFile, GetMediaFiles
"""
self._EnsureWriteEnabled()
from_pron = (
self.__GetPronunciationObject(from_pronunciation_or_hvo)
if isinstance(from_pronunciation_or_hvo, int)
else from_pronunciation_or_hvo
)
to_pron = (
self.__GetPronunciationObject(to_pronunciation_or_hvo)
if isinstance(to_pronunciation_or_hvo, int)
else to_pronunciation_or_hvo
)
# Can't move to same pronunciation
if from_pron == to_pron:
logger.warning("Source and destination are the same pronunciation")
return False
# Verify media is in source collection
if media not in from_pron.MediaFilesOS:
raise FP_ParameterError("Media file not found in source pronunciation's media collection")
# Move the media (remove from source, add to destination)
with self._TransactionCM("Move media file"):
from_pron.MediaFilesOS.Remove(media)
to_pron.MediaFilesOS.Add(media)
logger.info(f"Moved media from pronunciation {from_pron.Guid} to pronunciation {to_pron.Guid}")
return True
# --- CV Pattern/Location ---
@OperationsMethod
def GetLocation(self, pronunciation_or_hvo):
"""
Get the CV pattern location for the pronunciation.
The location refers to where in a CV (consonant-vowel) pattern analysis
this pronunciation applies. This is used in phonological analysis.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
Returns:
object: The location object, or None if not set.
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> location = project.Pronunciations.GetLocation(pron)
>>> if location:
... print(f"CV Location: {location}")
Notes:
- Location is used for phonological CV pattern analysis
- Returns None if no location is set
- Location objects reference phonological environments
- Not commonly used in basic pronunciation entry
See Also:
SetLocation
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
if hasattr(pronunciation, "LocationRA"):
return pronunciation.LocationRA
return None
@OperationsMethod
def SetLocation(self, pronunciation_or_hvo, location):
"""
Set the CV pattern location for the pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
location: The location object (phonological environment) to set,
or None to clear.
Raises:
FP_ReadOnlyError: If the project is not opened with write enabled.
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> # Set location (requires a valid phonological environment object)
>>> # env = project.Environments.Find("word-final")
>>> # project.Pronunciations.SetLocation(pron, env)
>>> # Clear location
>>> project.Pronunciations.SetLocation(pron, None)
Notes:
- Location must be a valid phonological environment object
- Pass None to clear the location
- Used for phonological CV pattern analysis
- Not commonly used in basic pronunciation entry
See Also:
GetLocation
"""
self._EnsureWriteEnabled()
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
if hasattr(pronunciation, "LocationRA"):
pronunciation.LocationRA = location
# --- Utilities ---
@OperationsMethod
def GetOwningEntry(self, pronunciation_or_hvo):
"""
Get the lexical entry that owns this pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
Returns:
ILexEntry: The owning entry object.
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> owner = project.Pronunciations.GetOwningEntry(pron)
>>> headword = project.LexEntry.GetHeadword(owner)
>>> print(f"Pronunciation belongs to: {headword}")
Pronunciation belongs to: run
Notes:
- Returns the ILexEntry that contains this pronunciation
- Useful when you have a pronunciation object without context
- The owner is determined by the LCM ownership hierarchy
See Also:
GetGuid, GetAll
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
# Cast to declared return type ILexEntry. Raw pronunciation.Owner is
# typed as ICmObject in LCM; pythonnet only surfaces ILexEntry
# properties after the explicit interface cast.
return ILexEntry(pronunciation.Owner)
@OperationsMethod
def GetGuid(self, pronunciation_or_hvo):
"""
Get the GUID of a pronunciation.
Args:
pronunciation_or_hvo: The ILexPronunciation object or HVO.
Returns:
System.Guid: The GUID of the pronunciation.
Raises:
FP_NullParameterError: If pronunciation_or_hvo is None.
Example:
>>> pron = list(project.Pronunciations.GetAll(entry))[0]
>>> guid = project.Pronunciations.GetGuid(pron)
>>> print(f"Pronunciation GUID: {guid}")
Pronunciation GUID: 12345678-1234-1234-1234-123456789abc
Notes:
- GUIDs are globally unique identifiers
- Persistent across project versions
- Use for external references and tracking
- Same GUID across different copies of the project
See Also:
GetOwningEntry
"""
self._ValidateParam(pronunciation_or_hvo, "pronunciation_or_hvo")
pronunciation = self.__GetPronunciationObject(pronunciation_or_hvo)
return pronunciation.Guid
# --- Private Helper Methods ---
def __GetEntryObject(self, entry_or_hvo):
"""
Resolve HVO or object to ILexEntry.
Args:
entry_or_hvo: Either an ILexEntry object or an HVO (int).
Returns:
ILexEntry: The resolved entry object.
"""
if isinstance(entry_or_hvo, int):
return self.project.Object(entry_or_hvo)
return entry_or_hvo
def __GetPronunciationObject(self, pronunciation_or_hvo):
"""
Resolve HVO or object to ILexPronunciation.
Args:
pronunciation_or_hvo: Either an ILexPronunciation object or an HVO (int).
Returns:
ILexPronunciation: The resolved pronunciation object.
"""
if isinstance(pronunciation_or_hvo, int):
return self.project.Object(pronunciation_or_hvo)
return pronunciation_or_hvo
def __WSHandle(self, wsHandle):
"""
Get writing system handle, defaulting to vernacular WS.
Args:
wsHandle: Optional writing system handle or tag.
Returns:
int: The writing system handle.
"""
if wsHandle is None:
return self.project.project.DefaultVernWs
# If it's a string (like "en-fonipa"), convert to handle
if isinstance(wsHandle, str):
handle = self.project.WSHandle(wsHandle)
if handle is None:
# Fallback to default vernacular if WS not found
return self.project.project.DefaultVernWs
return handle
return wsHandle
def __WSHandleVern(self, wsHandle):
"""
Get writing system handle, defaulting to vernacular WS for pronunciation forms.
Args:
wsHandle: Optional writing system handle or tag (e.g., "en-fonipa").
Returns:
int: The writing system handle.
"""
return self.__WSHandle(wsHandle)