diff --git a/src/Directory.packages.props b/src/Directory.packages.props index 46ac85d..5ce8323 100644 --- a/src/Directory.packages.props +++ b/src/Directory.packages.props @@ -4,6 +4,8 @@ + + diff --git a/src/StreamShorts.Console/Program.cs b/src/StreamShorts.Console/Program.cs index 7dcaacd..941144b 100644 --- a/src/StreamShorts.Console/Program.cs +++ b/src/StreamShorts.Console/Program.cs @@ -9,11 +9,16 @@ using FFMpegCore; using FFMpegCore.Enums; using FFMpegCore.Pipes; +using Microsoft.Extensions.Configuration; + using NAudio.Wave; using Whisper.net; using Whisper.net.Ggml; +var config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .Build(); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -105,35 +110,73 @@ foreach (var (i, segment) in segments.Select((s, index) => (index, s))) // Step 5: Send the transcription to LLM for analysis // TODO: Explore this prompt further...seems break // when transcription is long -var prompt = $""" -You are an expert in identifying engaging segments from YouTube live stream transcriptions that are suitable for creating short videos. You will be provided with a transcription of a YouTube live stream. Your task is to analyze the transcription and identify potential segments that would make compelling short videos. +var prompt = $$""" +I need your help to transform my YouTube live stream transcript into engaging YouTube Shorts. Act as my content editor and pinpoint **all potential candidate segments** that are perfect for short-form video. I'm looking for clips that are: -For each segment you identify, you should create an object with the following attributes: +  * **Funny:** Moments that will make viewers laugh. +  * **Informative:** Sections packed with valuable information or tips. +  * **Insightful:** Portions offering unique perspectives or 'aha\!' moments. -title: A concise and catchy title for the short video. -description: A brief description of the short video's content. -explanation: Explain why this segment would make a good short video (e.g., it's funny, informative, controversial, etc.). -start_time: The timestamp in the format HH:MM:SS where the segment begins in the original live stream. -end_time: The timestamp in the format HH:MM:SS where the segment ends in the original live stream. -Your response must be a JSON array of these objects. The JSON array should be the only output. Do not include any introductory or explanatory text outside of the JSON array. +For each suggested short, please provide: -Here is the transcription of the YouTube live stream: +  * The **start time** of the initial segment and the **end time** of the final segment. The duration of each short should be no longer than 3 minutes, but **aim for durations between 15 seconds and 60 seconds**. However, the short **must be as long as necessary to capture the complete thought or idea**, even if it means exceeding the target range or extending slightly to capture all necessary dialogue. +  * A concise **title** that grabs attention. +  * A brief **description** highlighting the short's content and its appeal. +  * An **explanation** of why this particular segment is suitable for a YouTube Short, focusing on its potential for discoverability and engagement. -{completeTranscription} +Please format your response as a JSON array of objects with the following structure: + +```json +{ +  ""title"": ""string"", +  "start_time": "string", +  "end_time": "string", +  "description": "string", +  "explanation": "string" +} +``` + +Here is the transcript of my YouTube live stream: + +{{completeTranscription}} """; +var apiKey = config["GeminiApiKey"]; + +if (string.IsNullOrWhiteSpace(apiKey)) +{ + throw new InvalidOperationException("GeminiApiKey is not configured in appsettings.json."); +} + using var client = new HttpClient() { Timeout = TimeSpan.FromMinutes(30) }; -using var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:11434/api/generate") + +var requestUrl = $"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent?key={apiKey}"; +using var request = new HttpRequestMessage(HttpMethod.Post, requestUrl) { Content = new StringContent( JsonSerializer.Serialize(new { - model = "llama3.1:latest", - prompt, - stream = false + contents = new[] + { + new + { + role = "user", + parts = new[] + { + new + { + text = prompt + } + }, + } + }, + generationConfig = new + { + responseMimeType = "application/json", + } }), Encoding.UTF8, "application/json" @@ -142,7 +185,13 @@ using var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:11 var response = await client.SendAsync(request); var responseContent = await response.Content.ReadAsStringAsync(); var responseJson = JsonSerializer.Deserialize(responseContent); -var analysis = JsonSerializer.Deserialize>(responseJson?.Response ?? string.Empty); +var candidatesText = responseJson? + .Candidates? + .FirstOrDefault()? + .Content + .Parts?.FirstOrDefault()? + .Text; +var analysis = JsonSerializer.Deserialize>(candidatesText ?? string.Empty); if (analysis is null) { @@ -181,6 +230,21 @@ record LLMAnalysis( ); record LLMResponse( - [property: JsonPropertyName("response")] - string Response + [property: JsonPropertyName("candidates")] + Candidate[] Candidates +); + +record Candidate( + [property: JsonPropertyName("content")] + Content Content +); + +record Content( + [property: JsonPropertyName("parts")] + Part[] Parts +); + +record Part( + [property: JsonPropertyName("text")] + string Text ); \ No newline at end of file diff --git a/src/StreamShorts.Console/StreamShorts.Console.csproj b/src/StreamShorts.Console/StreamShorts.Console.csproj index 38b1fd9..9e98856 100644 --- a/src/StreamShorts.Console/StreamShorts.Console.csproj +++ b/src/StreamShorts.Console/StreamShorts.Console.csproj @@ -6,8 +6,15 @@ + + + + + PreserveNewest + +