How to build a story generator application fo
In this post, I will show you in more detail how I crea...
.NET is also an open-source platform, with a growing community of developers contributing to its development.
Hello devs! I'm so excited to be part of the .NET MAUI UI July – 2025 calendar! I invite you to explore all the fabulous posts that other authors have published on topics related to .NET MAUI.
In this post, I'm going to show you how I created an application that connects to the SORA service of Azure OpenAI for generating stunning videos. This project is just a part of a more complex system that I developed some time ago that allows for automated YouTube video generation. Personally, it's a topic that I'm passionate about, and I found it interesting to share it with you. I should add that I made extensive use of the VS Code agent to speed up the demo development. Let's get started!
public partial class SettingsViewModel : ObservableObject
{
// Constants for Preferences keys
private const string AZURE_LLM_ENDPOINT_KEY = "AzureLlmEndpoint";
private const string AZURE_LLM_API_KEY = "AzureLlmApiKey";
private const string AZURE_LLM_DEPLOYMENT_KEY = "AzureLlmDeployment";
private const string SORA_ENDPOINT_KEY = "SoraEndpoint";
private const string SORA_API_KEY = "SoraApiKey";
private const string SORA_DEPLOYMENT_KEY = "SoraDeployment";
[ObservableProperty]
private string azureLlmEndpoint = string.Empty;
[ObservableProperty]
private string azureLlmApiKey = string.Empty;
[ObservableProperty]
private string azureLlmDeployment = string.Empty;
[ObservableProperty]
private string soraEndpoint = string.Empty;
[ObservableProperty]
private string soraApiKey = string.Empty;
[ObservableProperty]
private string soraDeployment = string.Empty;
[ObservableProperty]
private bool isAzureLlmApiKeyVisible = false;
[ObservableProperty]
private bool isSoraApiKeyVisible = false;
[ObservableProperty]
private string azureLlmApiKeyToggleIcon = "👁️";
[ObservableProperty]
private string soraApiKeyToggleIcon = "👁️";
[ObservableProperty]
private bool isSaving = false;
// Reference to the page for displaying alerts
private Page? _page;
public SettingsViewModel()
{
LoadSettings();
}
public void SetPage(Page page)
{
_page = page;
}
///
/// Loads saved settings from Preferences
///
public void LoadSettings()
{
AzureLlmEndpoint = Preferences.Default.Get(AZURE_LLM_ENDPOINT_KEY, string.Empty);
AzureLlmApiKey = Preferences.Default.Get(AZURE_LLM_API_KEY, string.Empty);
AzureLlmDeployment = Preferences.Default.Get(AZURE_LLM_DEPLOYMENT_KEY, string.Empty);
SoraEndpoint = Preferences.Default.Get(SORA_ENDPOINT_KEY, string.Empty);
SoraApiKey = Preferences.Default.Get(SORA_API_KEY, string.Empty);
SoraDeployment = Preferences.Default.Get(SORA_DEPLOYMENT_KEY, string.Empty);
}
///
/// Command to toggle visibility of Azure LLM API key
///
[RelayCommand]
private void ToggleAzureLlmApiKeyVisibility()
{
IsAzureLlmApiKeyVisible = !IsAzureLlmApiKeyVisible;
AzureLlmApiKeyToggleIcon = IsAzureLlmApiKeyVisible ? "🙈" : "👁️";
}
///
/// Command to toggle visibility of SORA API key
///
[RelayCommand]
private void ToggleSoraApiKeyVisibility()
{
IsSoraApiKeyVisible = !IsSoraApiKeyVisible;
SoraApiKeyToggleIcon = IsSoraApiKeyVisible ? "🙈" : "👁️";
}
///
/// Command to save all settings
///
[RelayCommand]
private async Task SaveSettings()
{
IsSaving = true;
try
{
// Validate settings before saving
if (!ValidateSettings())
{
return;
}
// Save Azure LLM settings
Preferences.Default.Set(AZURE_LLM_ENDPOINT_KEY, AzureLlmEndpoint.Trim());
Preferences.Default.Set(AZURE_LLM_API_KEY, AzureLlmApiKey.Trim());
Preferences.Default.Set(AZURE_LLM_DEPLOYMENT_KEY, AzureLlmDeployment.Trim());
// Save SORA settings
Preferences.Default.Set(SORA_ENDPOINT_KEY, SoraEndpoint.Trim());
Preferences.Default.Set(SORA_API_KEY, SoraApiKey.Trim());
Preferences.Default.Set(SORA_DEPLOYMENT_KEY, SoraDeployment.Trim());
if (_page != null)
{
await _page.DisplayAlert("Settings Saved", "All settings have been saved successfully!", "OK");
}
}
catch (Exception ex)
{
if (_page != null)
{
await _page.DisplayAlert("Error", $"Failed to save settings: {ex.Message}", "OK");
}
}
finally
{
IsSaving = false;
}
}
///
/// Command to clear all settings
///
[RelayCommand]
private async Task ClearSettings()
{
if (_page != null)
{
bool confirmed = await _page.DisplayAlert(
"Clear Settings",
"Are you sure you want to clear all settings? This action cannot be undone.",
"Clear",
"Cancel");
if (!confirmed)
return;
}
try
{
// Clear from Preferences
Preferences.Default.Remove(AZURE_LLM_ENDPOINT_KEY);
Preferences.Default.Remove(AZURE_LLM_API_KEY);
Preferences.Default.Remove(AZURE_LLM_DEPLOYMENT_KEY);
Preferences.Default.Remove(SORA_ENDPOINT_KEY);
Preferences.Default.Remove(SORA_API_KEY);
Preferences.Default.Remove(SORA_DEPLOYMENT_KEY);
// Clear from UI
AzureLlmEndpoint = string.Empty;
AzureLlmApiKey = string.Empty;
AzureLlmDeployment = string.Empty;
SoraEndpoint = string.Empty;
SoraApiKey = string.Empty;
SoraDeployment = string.Empty;
if (_page != null)
{
await _page.DisplayAlert("Settings Cleared", "All settings have been cleared successfully!", "OK");
}
}
catch (Exception ex)
{
if (_page != null)
{
await _page.DisplayAlert("Error", $"Failed to clear settings: {ex.Message}", "OK");
}
}
}
///
/// Command to test Azure LLM connection
///
[RelayCommand]
private async Task TestAzureLlmConnection()
{
if (string.IsNullOrWhiteSpace(AzureLlmEndpoint) ||
string.IsNullOrWhiteSpace(AzureLlmApiKey) ||
string.IsNullOrWhiteSpace(AzureLlmDeployment))
{
if (_page != null)
{
await _page.DisplayAlert("Incomplete Configuration",
"Please fill in all Azure LLM fields before testing.", "OK");
}
return;
}
// TODO: Implement actual connection test
if (_page != null)
{
await _page.DisplayAlert("Test Connection",
"Azure LLM connection test functionality will be implemented soon!", "OK");
}
}
///
/// Command to test SORA connection
///
[RelayCommand]
private async Task TestSoraConnection()
{
if (string.IsNullOrWhiteSpace(SoraEndpoint) ||
string.IsNullOrWhiteSpace(SoraApiKey) ||
string.IsNullOrWhiteSpace(SoraDeployment))
{
if (_page != null)
{
await _page.DisplayAlert("Incomplete Configuration",
"Please fill in all SORA fields before testing.", "OK");
}
return;
}
// TODO: Implement actual connection test
if (_page != null)
{
await _page.DisplayAlert("Test Connection",
"SORA connection test functionality will be implemented soon!", "OK");
}
}
///
/// Validates all settings before saving
///
private bool ValidateSettings()
{
var errors = new List();
// Validate Azure LLM settings
if (string.IsNullOrWhiteSpace(AzureLlmEndpoint))
errors.Add("Azure LLM Endpoint is required");
else if (!Uri.TryCreate(AzureLlmEndpoint, UriKind.Absolute, out _))
errors.Add("Azure LLM Endpoint must be a valid URL");
if (string.IsNullOrWhiteSpace(AzureLlmApiKey))
errors.Add("Azure LLM API Key is required");
if (string.IsNullOrWhiteSpace(AzureLlmDeployment))
errors.Add("Azure LLM Deployment name is required");
// Validate SORA settings
if (string.IsNullOrWhiteSpace(SoraEndpoint))
errors.Add("SORA Endpoint is required");
else if (!Uri.TryCreate(SoraEndpoint, UriKind.Absolute, out _))
errors.Add("SORA Endpoint must be a valid URL");
if (string.IsNullOrWhiteSpace(SoraApiKey))
errors.Add("SORA API Key is required");
if (string.IsNullOrWhiteSpace(SoraDeployment))
errors.Add("SORA Deployment name is required");
if (errors.Count > 0 && _page != null)
{
Task.Run(async () => await _page.DisplayAlert("Validation Error",
string.Join("\n", errors), "OK"));
return false;
}
return true;
}
///
/// Checks if all required settings are configured
///
public bool AreSettingsComplete =>
!string.IsNullOrWhiteSpace(AzureLlmEndpoint) &&
!string.IsNullOrWhiteSpace(AzureLlmApiKey) &&
!string.IsNullOrWhiteSpace(AzureLlmDeployment) &&
!string.IsNullOrWhiteSpace(SoraEndpoint) &&
!string.IsNullOrWhiteSpace(SoraApiKey) &&
!string.IsNullOrWhiteSpace(SoraDeployment);
///
/// Checks if Azure LLM settings are complete
///
public bool IsAzureLlmConfigComplete =>
!string.IsNullOrWhiteSpace(AzureLlmEndpoint) &&
!string.IsNullOrWhiteSpace(AzureLlmApiKey) &&
!string.IsNullOrWhiteSpace(AzureLlmDeployment);
///
/// Checks if SORA settings are complete
///
public bool IsSoraConfigComplete =>
!string.IsNullOrWhiteSpace(SoraEndpoint) &&
!string.IsNullOrWhiteSpace(SoraApiKey) &&
!string.IsNullOrWhiteSpace(SoraDeployment);
}
public partial class AddVideoIdeaViewModel : ObservableObject
{
private readonly IChatService _chatService;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanGenerateStory))]
private string videoIdea = string.Empty;
[ObservableProperty]
private string characterCountText = "0 characters";
[ObservableProperty]
private bool isEnhancementEnabled = false;
[ObservableProperty]
private string generateButtonText = "Generate Viral Story";
[ObservableProperty]
private string enhancementToggleIcon = "🤖";
[ObservableProperty]
private double enhancementToggleOpacity = 0.5;
[ObservableProperty]
private double enhancementToggleTranslationX = 0.0;
[ObservableProperty]
private Color enhancementToggleBorderColor = Colors.Gray;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanGenerateStory))]
private bool isGenerating = false;
// Video Configuration Properties
[ObservableProperty]
private string selectedResolution = "720";
[ObservableProperty]
private string selectedDuration = "10";
[ObservableProperty]
private string selectedOrientation = "Horizontal";
// Available options for pickers
public List ResolutionOptions { get; } = ["480", "720", "1080"];
public List DurationOptions { get; } = ["5", "10", "15", "20"];
public List OrientationOptions { get; } = ["Horizontal", "Vertical", "Square"];
// Reference to the page for displaying alerts
private Page? _page;
public AddVideoIdeaViewModel(IChatService chatService)
{
_chatService = chatService;
CheckChatServiceConfiguration();
}
public void SetPage(Page page)
{
_page = page;
}
///
/// Checks if chat service is configured and updates UI accordingly
///
private async void CheckChatServiceConfiguration()
{
try
{
var isConfigured = await _chatService.IsConfiguredAsync();
if (!isConfigured)
{
// Update UI to indicate that enhancement requires configuration
EnhancementToggleIcon = "⚙️";
}
}
catch
{
// If checking fails, assume not configured
EnhancementToggleIcon = "⚙️";
}
}
///
/// Updates character count when video idea text changes
///
partial void OnVideoIdeaChanged(string value)
{
var characterCount = string.IsNullOrEmpty(value) ? 0 : value.Length;
CharacterCountText = $"{characterCount} characters";
}
///
/// Updates UI elements when enhancement toggle state changes
///
partial void OnIsEnhancementEnabledChanged(bool value)
{
UpdateGenerateButtonText();
UpdateToggleAppearance();
}
///
/// Command to toggle AI enhancement feature
///
[RelayCommand]
private async Task ToggleEnhancement()
{
IsEnhancementEnabled = !IsEnhancementEnabled;
await AnimateToggle();
}
///
/// Command to generate viral story from user's idea
///
[RelayCommand]
private async Task GenerateViralStory()
{
if (string.IsNullOrWhiteSpace(VideoIdea))
{
if (_page != null)
{
await _page.DisplayAlert("Empty Idea", "Please enter your video idea before generating a viral story.", "OK");
}
return;
}
IsGenerating = true;
try
{
string finalVideoIdea = VideoIdea;
// Check if enhancement is enabled and service is configured
if (IsEnhancementEnabled)
{
var isConfigured = await _chatService.IsConfiguredAsync();
if (!isConfigured)
{
if (_page != null)
{
var result = await _page.DisplayAlert(
"Service Not Configured",
"AI enhancement requires Azure LLM configuration. Would you like to go to Settings to configure it?",
"Go to Settings",
"Continue without Enhancement");
if (result)
{
await Shell.Current.GoToAsync("//Settings");
return;
}
else
{
// Continue without enhancement
IsEnhancementEnabled = false;
await AnimateToggle();
}
}
}
else
{
// Enhance the idea using Azure LLM
try
{
finalVideoIdea = await _chatService.EnhancePromptAsync(VideoIdea);
// No DisplayAlert - keep loading while processing
Debug.WriteLine($"[AddVideoIdea] Idea enhanced successfully: {finalVideoIdea}");
}
catch (Exception ex)
{
Debug.WriteLine($"[AddVideoIdea] Enhancement failed: {ex.Message}");
if (_page != null)
{
await _page.DisplayAlert("Enhancement Failed",
$"Failed to enhance the idea: {ex.Message}\n\nContinuing with original idea.",
"OK");
}
}
}
}
// TODO: Store the final video idea and enhancement status for the VideoPromptsPage
// Pass the data through navigation parameters including video configuration
var (width, height) = GetVideoDimensions();
var duration = int.Parse(SelectedDuration);
var navigationParameters = new Dictionary
{
["VideoIdea"] = VideoIdea,
["EnhancedIdea"] = finalVideoIdea ?? string.Empty,
["WasEnhanced"] = (finalVideoIdea != VideoIdea && !string.IsNullOrEmpty(finalVideoIdea)).ToString(),
["VideoWidth"] = width.ToString(),
["VideoHeight"] = height.ToString(),
["VideoDuration"] = duration.ToString(),
["VideoOrientation"] = SelectedOrientation
};
// Navigate to VideoPromptsPage after processing
await Shell.Current.GoToAsync("//VideoPrompts", navigationParameters);
}
finally
{
IsGenerating = false;
}
}
///
/// Updates the generate button text based on enhancement state
///
private void UpdateGenerateButtonText()
{
GenerateButtonText = IsEnhancementEnabled
? "✨ Enhance & Generate"
: "Generate Viral Story";
}
///
/// Updates toggle visual appearance based on current state
///
private void UpdateToggleAppearance()
{
if (IsEnhancementEnabled)
{
EnhancementToggleIcon = "✨";
EnhancementToggleOpacity = 1.0;
EnhancementToggleTranslationX = 20.0;
EnhancementToggleBorderColor = Color.FromRgb(0x00, 0xFF, 0x33); // Green
}
else
{
EnhancementToggleIcon = "🤖";
EnhancementToggleOpacity = 0.5;
EnhancementToggleTranslationX = 0.0;
EnhancementToggleBorderColor = Colors.Gray;
}
}
///
/// Animates the enhancement toggle switch
///
private async Task AnimateToggle()
{
var animation = new Animation();
var targetTranslation = IsEnhancementEnabled ? 20.0 : 0.0;
var startTranslation = IsEnhancementEnabled ? 0.0 : 20.0;
// Translation animation
animation.Add(0, 1, new Animation(v =>
{
EnhancementToggleTranslationX = v;
}, startTranslation, targetTranslation));
// Color animation
animation.Add(0, 1, new Animation(v =>
{
if (IsEnhancementEnabled)
{
var color = Color.FromRgba(
(int)(0x66 + (0x00 - 0x66) * v), // Red component
(int)(0x66 + (0xFF - 0x66) * v), // Green component
(int)(0x66 + (0x33 - 0x66) * v), // Blue component
255);
EnhancementToggleBorderColor = color;
}
else
{
var grayValue = (int)(0x66 + (0xCC - 0x66) * v);
EnhancementToggleBorderColor = Color.FromRgb(grayValue, grayValue, grayValue);
}
}, 0, 1));
var completionSource = new TaskCompletionSource();
if (_page != null)
{
animation.Commit(_page, "ToggleAnimation", 16, 250,
Easing.CubicOut, (v, c) => completionSource.SetResult(true));
}
else
{
completionSource.SetResult(true);
}
await completionSource.Task;
}
///
/// Validates if the current idea is ready for generation
///
public bool CanGenerateStory => !string.IsNullOrWhiteSpace(VideoIdea) && !IsGenerating;
///
/// Gets video dimensions based on selected resolution and orientation
/// Using SORA-supported resolution combinations
///
/// Tuple with width and height values
private (int width, int height) GetVideoDimensions()
{
// SORA supported resolutions: (480, 480), (854, 480), (720, 720), (1280, 720), (1080, 1080), (1920, 1080)
return SelectedOrientation switch
{
"Horizontal" => SelectedResolution switch
{
"480" => (854, 480), // Horizontal 480p
"720" => (1280, 720), // Horizontal 720p (HD)
"1080" => (1920, 1080), // Horizontal 1080p (Full HD)
_ => (1280, 720) // Default to 720p horizontal
},
"Vertical" => SelectedResolution switch
{
"480" => (480, 854), // Vertical 480p
"720" => (720, 1280), // Vertical 720p
"1080" => (1080, 1920), // Vertical 1080p
_ => (720, 1280) // Default to 720p vertical
},
"Square" => SelectedResolution switch
{
"480" => (480, 480), // Square 480p
"720" => (720, 720), // Square 720p
"1080" => (1080, 1080), // Square 1080p
_ => (720, 720) // Default to 720p square
},
_ => (1280, 720) // Default to horizontal 720p
};
}
}
public partial class VideoPromptsViewModel : ObservableObject, IQueryAttributable
{
private readonly IChatService _chatService;
private readonly ISoraService _soraService;
[ObservableProperty]
private ObservableCollection prompts = new();
[ObservableProperty]
private int totalPrompts = 0;
[ObservableProperty]
private int editedPrompts = 0;
[ObservableProperty]
private string videoIdea = string.Empty;
[ObservableProperty]
private bool isLoading = false;
// Store original and enhanced ideas
private string _originalIdea = string.Empty;
private string _enhancedIdea = string.Empty;
private bool _wasEnhanced = false;
// Video configuration properties
private int _videoWidth = 1280; // Default horizontal 720p
private int _videoHeight = 720;
private int _videoDuration = 10; // Default 10 seconds
private string _videoOrientation = "Horizontal";
public VideoPromptsViewModel(IChatService chatService, ISoraService soraService)
{
_chatService = chatService;
_soraService = soraService;
// Don't load sample data by default anymore
}
// IQueryAttributable implementation
public void ApplyQueryAttributes(IDictionary query)
{
if (query.ContainsKey("VideoIdea"))
{
_originalIdea = query["VideoIdea"].ToString() ?? "";
}
if (query.ContainsKey("EnhancedIdea"))
{
_enhancedIdea = query["EnhancedIdea"].ToString() ?? "";
_wasEnhanced = !string.IsNullOrEmpty(_enhancedIdea);
}
if (query.ContainsKey("WasEnhanced"))
{
_wasEnhanced = bool.Parse(query["WasEnhanced"].ToString() ?? "false");
}
// Get video configuration parameters
if (query.ContainsKey("VideoWidth"))
{
int.TryParse(query["VideoWidth"].ToString(), out _videoWidth);
}
if (query.ContainsKey("VideoHeight"))
{
int.TryParse(query["VideoHeight"].ToString(), out _videoHeight);
}
if (query.ContainsKey("VideoDuration"))
{
int.TryParse(query["VideoDuration"].ToString(), out _videoDuration);
}
if (query.ContainsKey("VideoOrientation"))
{
_videoOrientation = query["VideoOrientation"].ToString() ?? "Horizontal";
}
// Set the display idea (enhanced if available, otherwise original)
VideoIdea = _wasEnhanced && !string.IsNullOrEmpty(_enhancedIdea) ? _enhancedIdea : _originalIdea;
// Generate prompts based on the video idea
_ = GeneratePromptsFromAI();
}
private async Task GeneratePromptsFromAI()
{
if (string.IsNullOrWhiteSpace(VideoIdea))
{
LoadSampleData(); // Fallback to sample data
return;
}
IsLoading = true;
Prompts.Clear();
try
{
var isConfigured = await _chatService.IsConfiguredAsync();
if (!isConfigured)
{
LoadSampleData(); // Fallback to sample data
return;
}
// Generate prompts using AI
var generatedPrompts = await _chatService.GenerateVideoPromptsAsync(VideoIdea, _wasEnhanced);
int promptId = 1;
foreach (var prompt in generatedPrompts)
{
Prompts.Add(new VideoPrompt
{
Id = promptId++,
Title = prompt.Title,
Content = prompt.Content,
IsEdited = false
});
}
UpdateStatistics();
}
catch (Exception)
{
// If AI generation fails, fall back to sample data
LoadSampleData();
}
finally
{
IsLoading = false;
}
}
private void LoadSampleData()
{
VideoIdea = "Dancing cat compilation with trending music";
Prompts.Add(new VideoPrompt
{
Id = 1,
Title = "Opening Hook",
Content = "Start with the most energetic cat dance move synchronized to the beat drop",
IsEdited = false
});
Prompts.Add(new VideoPrompt
{
Id = 2,
Title = "Main Content",
Content = "Sequence of 5-7 different cats dancing to trending audio, each for 3-4 seconds",
IsEdited = false
});
Prompts.Add(new VideoPrompt
{
Id = 3,
Title = "Engagement Boost",
Content = "Add text overlay: 'Which cat has the best moves? 👇 Comment below!'",
IsEdited = false
});
Prompts.Add(new VideoPrompt
{
Id = 4,
Title = "Call to Action",
Content = "End with 'Follow for more dancing pets!' with heart and dancing emojis",
IsEdited = false
});
UpdateStatistics();
}
[RelayCommand]
private async Task EditPrompt(VideoPrompt prompt)
{
try
{
var page = GetCurrentPage();
if (page == null) return;
var result = await page.DisplayPromptAsync(
"Edit Prompt",
$"Edit the {prompt.Title}:",
"Save",
"Cancel",
placeholder: prompt.Content,
initialValue: prompt.Content);
if (!string.IsNullOrEmpty(result) && result != prompt.Content)
{
if (!prompt.IsEdited)
{
prompt.IsEdited = true;
EditedPrompts++;
}
prompt.Content = result;
}
}
catch
{
// Handle any navigation errors silently
}
}
[RelayCommand]
private void DeletePrompt(VideoPrompt prompt)
{
if (prompt.IsEdited)
{
EditedPrompts--;
}
Prompts.Remove(prompt);
UpdateStatistics();
}
[RelayCommand]
private async Task RegeneratePrompt(VideoPrompt prompt)
{
// Simulate regeneration
await Task.Delay(1000);
var regeneratedContent = prompt.Title switch
{
"Opening Hook" => "Begin with an unexpected cat appearing from behind furniture, dancing immediately",
"Main Content" => "Create a montage of cats from different breeds, each showcasing unique dance styles",
"Engagement Boost" => "Add interactive text: 'Tap ❤️ if your cat can dance too!'",
"Call to Action" => "Conclude with 'Tag a friend who loves dancing cats!' with music note emojis",
_ => "Regenerated prompt content"
};
if (!prompt.IsEdited)
{
prompt.IsEdited = true;
EditedPrompts++;
}
prompt.Content = regeneratedContent;
}
[RelayCommand]
private async Task GenerateVideo()
{
try
{
Debug.WriteLine("[VideoPrompts] GenerateVideo started");
var page = GetCurrentPage();
// Check if SORA service is configured
var isConfigured = await _soraService.IsConfiguredAsync();
Debug.WriteLine($"[VideoPrompts] SORA configured: {isConfigured}");
if (!isConfigured)
{
Debug.WriteLine("[VideoPrompts] SORA not configured, showing alert");
if (page != null)
{
await page.DisplayAlert("Configuration Required",
"Please configure your SORA credentials in the settings page.",
"OK");
}
return;
}
// Activate loading UI (blocks all interaction)
IsLoading = true;
Debug.WriteLine("[VideoPrompts] Loading UI activated - blocking all interaction");
// Generate the combined prompt from all prompts
var combinedPrompt = string.Join(". ", Prompts.Select(p => p.Content));
if (string.IsNullOrWhiteSpace(combinedPrompt))
{
combinedPrompt = VideoIdea; // Fallback to original idea
}
Debug.WriteLine($"[VideoPrompts] Combined prompt: {combinedPrompt}");
Debug.WriteLine($"[VideoPrompts] Video configuration: {_videoWidth}x{_videoHeight}, {_videoDuration}s, {_videoOrientation}");
Debug.WriteLine("[VideoPrompts] Starting SORA video generation...");
// Generate video using SORA service with custom configuration
var videoFilePath = await _soraService.GenerateVideoAsync(combinedPrompt, _videoWidth, _videoHeight, _videoDuration);
Debug.WriteLine($"[VideoPrompts] ✅ Video generated successfully: {videoFilePath}");
Debug.WriteLine($"[VideoPrompts] File exists: {File.Exists(videoFilePath)}");
Debug.WriteLine($"[VideoPrompts] File size: {new FileInfo(videoFilePath).Length / 1024 / 1024:F2} MB");
// Prepare navigation parameters
var navigationParams = new Dictionary
{
["VideoTitle"] = VideoIdea,
["VideoSource"] = videoFilePath,
["OriginalPrompt"] = _originalIdea,
["EnhancedPrompt"] = _wasEnhanced ? _enhancedIdea : "",
["EnglishContent"] = combinedPrompt,
["HasEnhancedPrompt"] = _wasEnhanced.ToString()
};
Debug.WriteLine("[VideoPrompts] Navigation parameters:");
foreach (var kvp in navigationParams)
{
Debug.WriteLine($"[VideoPrompts] {kvp.Key}: {kvp.Value}");
}
// Navigate to video display page with real video
Debug.WriteLine("[VideoPrompts] Navigating to VideoDisplay page...");
await Shell.Current.GoToAsync("VideoDisplay", navigationParams);
// Show success message
if (page != null)
{
Debug.WriteLine("[VideoPrompts] Showing success message");
await page.DisplayAlert("Video Generated!",
$"Your viral video has been created successfully.\n\nFile: {Path.GetFileName(videoFilePath)}",
"Watch Video");
}
Debug.WriteLine("[VideoPrompts] GenerateVideo completed successfully");
}
catch (InvalidOperationException ex)
{
Debug.WriteLine($"[VideoPrompts] Configuration error: {ex.Message}");
var page = GetCurrentPage();
if (page != null)
{
await page.DisplayAlert("Configuration Error", ex.Message, "OK");
}
}
catch (TimeoutException)
{
Debug.WriteLine("[VideoPrompts] Timeout error");
var page = GetCurrentPage();
if (page != null)
{
await page.DisplayAlert("Timeout",
"Video generation took too long. Please try again.",
"OK");
}
}
catch (HttpRequestException ex)
{
Debug.WriteLine($"[VideoPrompts] HTTP error: {ex.Message}");
var page = GetCurrentPage();
if (page != null)
{
await page.DisplayAlert("Connection Error",
$"Failed to connect to the SORA service: {ex.Message}",
"OK");
}
}
catch (Exception ex)
{
Debug.WriteLine($"[VideoPrompts] Unexpected error: {ex.Message}");
Debug.WriteLine($"[VideoPrompts] Exception type: {ex.GetType().Name}");
Debug.WriteLine($"[VideoPrompts] Stack trace: {ex.StackTrace}");
var page = GetCurrentPage();
if (page != null)
{
await page.DisplayAlert("Error", $"An error occurred while generating the video: {ex.Message}", "OK");
}
}
finally
{
// Always deactivate loading UI regardless of success or failure
IsLoading = false;
Debug.WriteLine("[VideoPrompts] Loading UI deactivated - interaction restored");
}
}
private Page? GetCurrentPage()
{
try
{
return Application.Current?.Windows?.FirstOrDefault()?.Page;
}
catch
{
return null;
}
}
private void UpdateStatistics()
{
TotalPrompts = Prompts.Count;
EditedPrompts = Prompts.Count(p => p.IsEdited);
}
}
public partial class VideoPrompt : ObservableObject
{
[ObservableProperty]
private int id;
[ObservableProperty]
private string title = string.Empty;
[ObservableProperty]
private string content = string.Empty;
[ObservableProperty]
private bool isEdited;
}
public partial class VideoDisplayViewModel : ObservableObject, IQueryAttributable
{
private MediaElement? _mediaElement;
[ObservableProperty]
private string videoTitle = "Generated Viral Video";
[ObservableProperty]
private string videoSource = "";
[ObservableProperty]
private string videoDescription = "AI-generated viral video content";
[ObservableProperty]
private string videoDuration = "Loading...";
[ObservableProperty]
private string videoStatus = "Ready";
[ObservableProperty]
private string originalPrompt = "";
[ObservableProperty]
private string enhancedPrompt = "";
[ObservableProperty]
private string englishContent = "";
[ObservableProperty]
private bool hasEnhancedPrompt = false;
[ObservableProperty]
private bool isLoading = true;
public VideoDisplayViewModel()
{
// Sample data for demonstration
VideoTitle = "AI Generated Viral Video";
VideoDescription = "This video was generated using AI based on your creative prompt and enhanced with advanced algorithms.";
OriginalPrompt = "A cat playing with a ball of yarn";
EnhancedPrompt = "A playful orange tabby cat with bright green eyes gracefully pouncing and rolling around with a large, fluffy ball of rainbow-colored yarn in a cozy, sunlit living room with wooden floors and comfortable furniture.";
EnglishContent = "Watch as this adorable cat brings joy and entertainment through its playful interaction with colorful yarn. Perfect for social media sharing and guaranteed to make viewers smile!";
HasEnhancedPrompt = !string.IsNullOrEmpty(EnhancedPrompt);
// For demo purposes, you can use a sample video URL or local file
// VideoSource = "https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4";
}
public void SetMediaElement(MediaElement mediaElement)
{
_mediaElement = mediaElement;
// Subscribe to MediaElement events
if (_mediaElement != null)
{
_mediaElement.MediaOpened += OnMediaOpened;
_mediaElement.MediaEnded += OnMediaEnded;
_mediaElement.MediaFailed += OnMediaFailed;
}
}
private void OnMediaOpened(object? sender, EventArgs e)
{
if (_mediaElement != null)
{
VideoDuration = _mediaElement.Duration.ToString(@"mm\:ss");
VideoStatus = "Ready to play";
IsLoading = false; // Stop loading when media is ready
}
}
private void OnMediaEnded(object? sender, EventArgs e)
{
VideoStatus = "Playback completed";
}
private void OnMediaFailed(object? sender, EventArgs e)
{
VideoStatus = "Error loading video";
IsLoading = false; // Stop loading on error
}
[RelayCommand]
private void Play()
{
try
{
_mediaElement?.Play();
VideoStatus = "Playing";
}
catch (Exception ex)
{
Debug.WriteLine($"Error playing video: {ex.Message}");
VideoStatus = "Error playing video";
}
}
[RelayCommand]
private void Pause()
{
try
{
_mediaElement?.Pause();
VideoStatus = "Paused";
}
catch (Exception ex)
{
Debug.WriteLine($"Error pausing video: {ex.Message}");
}
}
[RelayCommand]
private void Stop()
{
try
{
_mediaElement?.Stop();
VideoStatus = "Stopped";
}
catch (Exception ex)
{
Debug.WriteLine($"Error stopping video: {ex.Message}");
}
}
[RelayCommand]
private async Task ShareVideo()
{
try
{
if (!string.IsNullOrEmpty(VideoSource))
{
await Shell.Current.DisplayAlert("Share Video", $"Sharing: {VideoTitle}\n\nCheck out this AI-generated viral video!", "OK");
}
else
{
await Shell.Current.DisplayAlert("Info", "No video available to share", "OK");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error sharing video: {ex.Message}");
await Shell.Current.DisplayAlert("Error", "Unable to share video", "OK");
}
}
[RelayCommand]
private async Task Download()
{
try
{
// In a real app, this would download the video file
await Shell.Current.DisplayAlert("Info", "Download functionality will be implemented with actual video generation service", "OK");
}
catch (Exception ex)
{
Debug.WriteLine($"Error downloading video: {ex.Message}");
await Shell.Current.DisplayAlert("Error", "Unable to download video", "OK");
}
}
[RelayCommand]
private async Task Back()
{
try
{
// Stop video before navigating back
_mediaElement?.Stop();
await Shell.Current.GoToAsync("..");
}
catch (Exception ex)
{
Debug.WriteLine($"Error navigating back: {ex.Message}");
}
}
// Method to set video data from external source
public void SetVideoData(string title, string source, string originalPrompt, string enhancedPrompt = "", string englishContent = "")
{
VideoTitle = title;
VideoSource = source;
OriginalPrompt = originalPrompt;
EnhancedPrompt = enhancedPrompt;
EnglishContent = englishContent;
HasEnhancedPrompt = !string.IsNullOrEmpty(enhancedPrompt);
}
// IQueryAttributable implementation
public void ApplyQueryAttributes(IDictionary query)
{
Debug.WriteLine("[VideoDisplay] ApplyQueryAttributes called");
Debug.WriteLine($"[VideoDisplay] Received {query.Count} parameters:");
// Start loading when receiving new video parameters
IsLoading = true;
foreach (var kvp in query)
{
Debug.WriteLine($"[VideoDisplay] {kvp.Key}: {kvp.Value}");
}
if (query.ContainsKey("VideoTitle"))
{
VideoTitle = query["VideoTitle"].ToString() ?? "";
Debug.WriteLine($"[VideoDisplay] Set VideoTitle: {VideoTitle}");
}
if (query.ContainsKey("VideoSource"))
{
var videoPath = query["VideoSource"].ToString() ?? "";
Debug.WriteLine($"[VideoDisplay] Received VideoSource: {videoPath}");
// Validate that the file exists
if (File.Exists(videoPath))
{
VideoSource = videoPath;
Debug.WriteLine($"[VideoDisplay] ✅ Video file exists, set VideoSource: {VideoSource}");
}
else
{
Debug.WriteLine($"[VideoDisplay] ❌ Video file not found: {videoPath}");
// Fallback to sample video for demo
VideoSource = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
Debug.WriteLine($"[VideoDisplay] Using fallback video: {VideoSource}");
}
}
else
{
Debug.WriteLine("[VideoDisplay] No VideoSource parameter received, using fallback");
VideoSource = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
}
if (query.ContainsKey("OriginalPrompt"))
{
OriginalPrompt = query["OriginalPrompt"].ToString() ?? "";
Debug.WriteLine($"[VideoDisplay] Set OriginalPrompt: {OriginalPrompt}");
}
if (query.ContainsKey("EnhancedPrompt"))
{
EnhancedPrompt = query["EnhancedPrompt"].ToString() ?? "";
HasEnhancedPrompt = !string.IsNullOrEmpty(EnhancedPrompt);
Debug.WriteLine($"[VideoDisplay] Set EnhancedPrompt: {EnhancedPrompt}");
Debug.WriteLine($"[VideoDisplay] HasEnhancedPrompt: {HasEnhancedPrompt}");
}
if (query.ContainsKey("HasEnhancedPrompt"))
{
if (bool.TryParse(query["HasEnhancedPrompt"].ToString(), out bool hasEnhanced))
{
HasEnhancedPrompt = hasEnhanced;
Debug.WriteLine($"[VideoDisplay] Set HasEnhancedPrompt from parameter: {HasEnhancedPrompt}");
}
}
if (query.ContainsKey("EnglishContent"))
{
EnglishContent = query["EnglishContent"].ToString() ?? "";
Debug.WriteLine($"[VideoDisplay] Set EnglishContent: {EnglishContent}");
}
VideoDescription = $"AI-generated viral video: {VideoTitle}";
Debug.WriteLine($"[VideoDisplay] Set VideoDescription: {VideoDescription}");
Debug.WriteLine("[VideoDisplay] ApplyQueryAttributes completed");
}
}
In this post, I will show you in more detail how I crea...
Hi Devs!Nice to have you around.Nice to have you around...