diff --git a/.github/workflows/publish_console.yml b/.github/workflows/publish_console.yml index 7210d03..1d52733 100644 --- a/.github/workflows/publish_console.yml +++ b/.github/workflows/publish_console.yml @@ -5,6 +5,10 @@ on: paths: - src/StreamShorts.Console/** - src/StreamShorts.Console.Tests/** + - src/StreamShorts.Library/** + - src/StreamShorts.Library.Tests/** + - StreamShorts.sln + - Directory.Build.props branches: - main jobs: diff --git a/src/StreamShorts.Console/Commands/DefaultCommand.cs b/src/StreamShorts.Console/Commands/DefaultCommand.cs index f8f0e58..e469c7c 100644 --- a/src/StreamShorts.Console/Commands/DefaultCommand.cs +++ b/src/StreamShorts.Console/Commands/DefaultCommand.cs @@ -1,9 +1,8 @@ -using System.Text.Json; - -using StreamShorts.Library.Media.Video; - namespace StreamShorts.Console.Commands; +/// +/// The default command for processing video streams to create short clips. +/// internal sealed class DefaultCommand( IFileSystem fileSystem, IAnsiConsole console, @@ -28,8 +27,14 @@ internal sealed class DefaultCommand( private readonly IShortsCreator _shortsCreator = shortsCreator ?? throw new ArgumentNullException(nameof(shortsCreator)); private readonly TimeProvider _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + /// + /// Represents the settings for the default command. + /// internal class Settings : CommandSettings { + /// + /// Gets or sets the path to the stream. + /// [CommandArgument(0, "[Stream]")] [Description("The path to the stream")] public string Stream { get; init; } = string.Empty; @@ -103,7 +108,7 @@ internal sealed class DefaultCommand( ); _fileSystem.Directory.CreateDirectory(outputDirectoryPath); - + await _fileSystem.File.WriteAllTextAsync( _fileSystem.Path.Combine(outputDirectoryPath, "transcription.txt"), string.Join(Environment.NewLine, transcriptionSegments) diff --git a/src/StreamShorts.Console/Hosting/HostBuilderExtensions.cs b/src/StreamShorts.Console/Hosting/HostBuilderExtensions.cs index 374372e..28c1516 100644 --- a/src/StreamShorts.Console/Hosting/HostBuilderExtensions.cs +++ b/src/StreamShorts.Console/Hosting/HostBuilderExtensions.cs @@ -1,7 +1,15 @@ namespace StreamShorts.Console.Hosting; +/// +/// Provides extension methods for building command applications from host builders. +/// internal static class HostBuilderExtensions { + /// + /// Builds a command application from the host builder. + /// + /// The host builder. + /// A configured command application. public static CommandApp BuildApp(this IHostBuilder builder) { var registrar = new TypeRegistrar(builder); diff --git a/src/StreamShorts.Console/Hosting/TypeRegistrar.cs b/src/StreamShorts.Console/Hosting/TypeRegistrar.cs index e8bf08d..bf236e0 100644 --- a/src/StreamShorts.Console/Hosting/TypeRegistrar.cs +++ b/src/StreamShorts.Console/Hosting/TypeRegistrar.cs @@ -1,5 +1,9 @@ namespace StreamShorts.Console.Hosting; +/// +/// Provides type registration services for the dependency injection container. +/// +/// internal sealed class TypeRegistrar(IHostBuilder builder) : ITypeRegistrar { private readonly IHostBuilder _builder = builder; diff --git a/src/StreamShorts.Console/Hosting/TypeResolver.cs b/src/StreamShorts.Console/Hosting/TypeResolver.cs index ae3ac08..8814c1a 100644 --- a/src/StreamShorts.Console/Hosting/TypeResolver.cs +++ b/src/StreamShorts.Console/Hosting/TypeResolver.cs @@ -1,5 +1,9 @@ namespace StreamShorts.Console.Hosting; +/// +/// Provides type resolution services using the dependency injection container. +/// +/// internal sealed class TypeResolver(IHost provider) : ITypeResolver, IDisposable { private readonly IHost _host = provider ?? throw new ArgumentNullException(nameof(provider)); diff --git a/src/StreamShorts.Console/Program.cs b/src/StreamShorts.Console/Program.cs index 59d69cf..3da4dab 100644 --- a/src/StreamShorts.Console/Program.cs +++ b/src/StreamShorts.Console/Program.cs @@ -1,8 +1,4 @@ -using Microsoft.Extensions.Configuration; - -using StreamShorts.Library.Media.Video; - -Log.Logger = new LoggerConfiguration() +Log.Logger = new LoggerConfiguration() .WriteTo.File( formatter: new CompactJsonFormatter(), path: Path.Combine(AppContext.BaseDirectory, "logs", "log.jsonl"), @@ -42,7 +38,7 @@ try { throw new InvalidOperationException($"{keyOptionName} is not configured in appsettings.json."); } - + var clientFactory = sp.GetRequiredService(); return new GeminiAnalyzer(clientFactory, key, model); @@ -62,4 +58,4 @@ catch (Exception ex) finally { await Log.CloseAndFlushAsync(); -} +} \ No newline at end of file diff --git a/src/StreamShorts.Console/StreamShorts.Console.csproj b/src/StreamShorts.Console/StreamShorts.Console.csproj index ecca060..4b2629c 100644 --- a/src/StreamShorts.Console/StreamShorts.Console.csproj +++ b/src/StreamShorts.Console/StreamShorts.Console.csproj @@ -1,7 +1,14 @@  + StreamShorts.Console + StreamShorts.Console + A command-line interface for StreamShorts + 0.0.0 + Stevan Freeborn Exe + true + true diff --git a/src/StreamShorts.Console/Usings.cs b/src/StreamShorts.Console/Usings.cs index fd2723d..c076a35 100644 --- a/src/StreamShorts.Console/Usings.cs +++ b/src/StreamShorts.Console/Usings.cs @@ -1,7 +1,9 @@ global using System.ComponentModel; global using System.IO.Abstractions; global using System.Reflection; +global using System.Text.Json; +global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; @@ -18,4 +20,5 @@ global using StreamShorts.Console.Hosting; global using StreamShorts.Library.Analysis; global using StreamShorts.Library.Analysis.Gemini; global using StreamShorts.Library.Media.Audio; -global using StreamShorts.Library.Transcription; +global using StreamShorts.Library.Media.Video; +global using StreamShorts.Library.Transcription; \ No newline at end of file diff --git a/src/StreamShorts.Library/Analysis/FailedTranscriptAnalysisException.cs b/src/StreamShorts.Library/Analysis/FailedTranscriptAnalysisException.cs index c918499..eae15a9 100644 --- a/src/StreamShorts.Library/Analysis/FailedTranscriptAnalysisException.cs +++ b/src/StreamShorts.Library/Analysis/FailedTranscriptAnalysisException.cs @@ -28,4 +28,4 @@ public sealed class FailedTranscriptAnalysisException : Exception public FailedTranscriptAnalysisException(string message, Exception innerException) : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/src/StreamShorts.Library/Analysis/Gemini/Content.cs b/src/StreamShorts.Library/Analysis/Gemini/Content.cs index 66615a9..3810ab1 100644 --- a/src/StreamShorts.Library/Analysis/Gemini/Content.cs +++ b/src/StreamShorts.Library/Analysis/Gemini/Content.cs @@ -1,5 +1,10 @@ using System.Text.Json.Serialization; +/// +/// Represents content with role and parts for Gemini API. +/// +/// The role of the content (e.g., "user", "assistant"). +/// The array of content parts. internal record Content( [property: JsonPropertyName("role")] string Role, @@ -7,6 +12,10 @@ internal record Content( Part[] Parts ); +/// +/// Represents a part of content for Gemini API. +/// +/// The text content of the part. internal record Part( [property: JsonPropertyName("text")] string Text diff --git a/src/StreamShorts.Library/Analysis/Gemini/GenerateContentRequest.cs b/src/StreamShorts.Library/Analysis/Gemini/GenerateContentRequest.cs index f4bd08e..e6a2989 100644 --- a/src/StreamShorts.Library/Analysis/Gemini/GenerateContentRequest.cs +++ b/src/StreamShorts.Library/Analysis/Gemini/GenerateContentRequest.cs @@ -2,6 +2,11 @@ using System.Text.Json.Serialization; namespace StreamShorts.Library.Analysis.Gemini; +/// +/// Represents a request to generate content using Gemini. +/// +/// The content array for the request. +/// The generation configuration. internal record GenerateContentRequest( [property: JsonPropertyName("contents")] Content[] Contents, @@ -9,6 +14,10 @@ internal record GenerateContentRequest( GenerationConfig GenerationConfig ); +/// +/// Represents configuration for content generation. +/// +/// The MIME type for the response. internal record GenerationConfig( [property: JsonPropertyName("responseMimeType")] string ResponseMimeType diff --git a/src/StreamShorts.Library/Analysis/Gemini/GenerateContentResponse.cs b/src/StreamShorts.Library/Analysis/Gemini/GenerateContentResponse.cs index 4a45668..7a87edd 100644 --- a/src/StreamShorts.Library/Analysis/Gemini/GenerateContentResponse.cs +++ b/src/StreamShorts.Library/Analysis/Gemini/GenerateContentResponse.cs @@ -2,13 +2,20 @@ using System.Text.Json.Serialization; namespace StreamShorts.Library.Analysis.Gemini; +/// +/// Represents a response from the Gemini content generation API. +/// +/// The array of candidate responses. internal record GenerateContentResponse( [property: JsonPropertyName("candidates")] Candidate[] Candidates ); +/// +/// Represents a candidate response from content generation. +/// +/// The generated content. internal record Candidate( [property: JsonPropertyName("content")] Content Content -); - +); \ No newline at end of file diff --git a/src/StreamShorts.Library/Analysis/ShortCandidate.cs b/src/StreamShorts.Library/Analysis/ShortCandidate.cs index 42433cc..2828185 100644 --- a/src/StreamShorts.Library/Analysis/ShortCandidate.cs +++ b/src/StreamShorts.Library/Analysis/ShortCandidate.cs @@ -16,4 +16,4 @@ public record ShortCandidate( TimeSpan StartTime, [property: JsonPropertyName("end_time")] TimeSpan EndTime -); +); \ No newline at end of file diff --git a/src/StreamShorts.Library/Media/Audio/AudioExtractor.cs b/src/StreamShorts.Library/Media/Audio/AudioExtractor.cs index cd9d7d3..6e0a3f0 100644 --- a/src/StreamShorts.Library/Media/Audio/AudioExtractor.cs +++ b/src/StreamShorts.Library/Media/Audio/AudioExtractor.cs @@ -16,10 +16,10 @@ public sealed class AudioExtractor : IAudioExtractor } /// - /// Initializes a new instance of the class with a specified FFMpeg service. + /// Initializes a new instance of the class with a specified . /// /// The video service to use for audio extraction. - /// Thrown when the FFMpeg service is null. + /// Thrown when the video service is null. internal AudioExtractor(IVideoService videoService) { _videoService = videoService ?? throw new ArgumentNullException(nameof(videoService), $"{nameof(videoService)} cannot be null"); diff --git a/src/StreamShorts.Library/Media/FFMpegService.cs b/src/StreamShorts.Library/Media/FFMpegService.cs index 3f74a72..8ab00fd 100644 --- a/src/StreamShorts.Library/Media/FFMpegService.cs +++ b/src/StreamShorts.Library/Media/FFMpegService.cs @@ -23,8 +23,8 @@ internal sealed class FFMpegService : IVideoService } public async Task CreateClipFromVideoAsync( - string sourcePath, - string destinationPath, + string sourcePath, + string destinationPath, TimeSpan startTime, TimeSpan endTime, TimeSpan? buffer = null @@ -32,7 +32,7 @@ internal sealed class FFMpegService : IVideoService { var start = startTime - (buffer ?? TimeSpan.Zero); var end = endTime + (buffer ?? TimeSpan.Zero); - + await FFMpeg.SubVideoAsync(sourcePath, destinationPath, start, end) .ConfigureAwait(false); } diff --git a/src/StreamShorts.Library/Media/IVideoService.cs b/src/StreamShorts.Library/Media/IVideoService.cs index 2818580..2c5bef1 100644 --- a/src/StreamShorts.Library/Media/IVideoService.cs +++ b/src/StreamShorts.Library/Media/IVideoService.cs @@ -12,7 +12,7 @@ internal interface IVideoService /// The output audio stream. /// A task that represents the asynchronous operation. The task result indicates whether the extraction was successful. Task ExtractAudioFromVideoAsync(Stream video, Stream audio); - + /// /// Creates a clip from a video file based on the specified start and end times. /// diff --git a/src/StreamShorts.Library/Media/Video/ShortsCreator.cs b/src/StreamShorts.Library/Media/Video/ShortsCreator.cs index 04d7240..f314f0f 100644 --- a/src/StreamShorts.Library/Media/Video/ShortsCreator.cs +++ b/src/StreamShorts.Library/Media/Video/ShortsCreator.cs @@ -6,14 +6,22 @@ namespace StreamShorts.Library.Media.Video; /// Represents a service that creates video shorts. /// /// -public class ShortsCreator : IShortsCreator +public sealed class ShortsCreator : IShortsCreator { private readonly IVideoService _videoService = new FFMpegService(); + /// + /// Initializes a new instance of the class. + /// public ShortsCreator() { } + /// + /// Initializes a new instance of the class with a specified . + /// + /// The video service. + /// Thrown when the video service is null. internal ShortsCreator(IVideoService videoService) { _videoService = videoService ?? throw new ArgumentNullException(nameof(videoService), $"{nameof(videoService)} cannot be null"); @@ -25,17 +33,17 @@ public class ShortsCreator : IShortsCreator { throw new ArgumentNullException(nameof(sourcePath), $"{nameof(sourcePath)} cannot be null or whitespace"); } - + if (candidate is null) { throw new ArgumentNullException(nameof(candidate), $"{nameof(candidate)} cannot be null"); } - + if (string.IsNullOrWhiteSpace(destinationPath)) { throw new ArgumentNullException(nameof(destinationPath), $"{nameof(destinationPath)} cannot be null or whitespace"); } - + await _videoService.CreateClipFromVideoAsync(sourcePath, destinationPath, candidate.StartTime, candidate.EndTime, buffer) .ConfigureAwait(false); } diff --git a/src/StreamShorts.Library/Transcription/ITranscriber.cs b/src/StreamShorts.Library/Transcription/ITranscriber.cs index 207c854..7c4df20 100644 --- a/src/StreamShorts.Library/Transcription/ITranscriber.cs +++ b/src/StreamShorts.Library/Transcription/ITranscriber.cs @@ -12,4 +12,4 @@ public interface ITranscriber /// A cancellation token to cancel the operation. /// An where T is . IAsyncEnumerable TranscribeAsync(Stream audio, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeRegistrarTests.cs b/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeRegistrarTests.cs index 1b76528..30b61b7 100644 --- a/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeRegistrarTests.cs +++ b/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeRegistrarTests.cs @@ -1,6 +1,6 @@ namespace StreamShorts.Console.Tests.Unit.Hosting; -public class TypeRegistrarTests +internal class TypeRegistrarTests { [Fact] public void Constructor_WhenCalled_ItShouldNotThrowShould() diff --git a/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeResolverTests.cs b/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeResolverTests.cs index 8cfaf73..0533c80 100644 --- a/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeResolverTests.cs +++ b/tests/StreamShorts.Console.Tests/Unit/Hosting/TypeResolverTests.cs @@ -1,6 +1,6 @@ namespace StreamShorts.Console.Tests.Unit.Hosting; -public class TypeResolverTests +internal class TypeResolverTests { [Fact] public void Constructor_WhenCalledWithNullHost_ItShouldThrowArgumentNullException() diff --git a/tests/StreamShorts.Console.Tests/Usings.cs b/tests/StreamShorts.Console.Tests/Usings.cs index 7c8e815..a3ca487 100644 --- a/tests/StreamShorts.Console.Tests/Usings.cs +++ b/tests/StreamShorts.Console.Tests/Usings.cs @@ -5,4 +5,4 @@ global using Microsoft.Extensions.Hosting; global using Moq; -global using StreamShorts.Console.Hosting; +global using StreamShorts.Console.Hosting; \ No newline at end of file diff --git a/tests/StreamShorts.Library.Tests/Integration/Media/Audio/AudioExtractorTests.cs b/tests/StreamShorts.Library.Tests/Integration/Media/Audio/AudioExtractorTests.cs index 39ca12d..ab5bbba 100644 --- a/tests/StreamShorts.Library.Tests/Integration/Media/Audio/AudioExtractorTests.cs +++ b/tests/StreamShorts.Library.Tests/Integration/Media/Audio/AudioExtractorTests.cs @@ -1,6 +1,6 @@ namespace StreamShorts.Library.Tests.Integration.Media.Audio; -public class AudioExtractorTests +internal class AudioExtractorTests { private readonly AudioExtractor _sut = new(); diff --git a/tests/StreamShorts.Library.Tests/Unit/Media/Audio/AudioExtractorTests.cs b/tests/StreamShorts.Library.Tests/Unit/Media/Audio/AudioExtractorTests.cs index d70f76b..366bdd8 100644 --- a/tests/StreamShorts.Library.Tests/Unit/Media/Audio/AudioExtractorTests.cs +++ b/tests/StreamShorts.Library.Tests/Unit/Media/Audio/AudioExtractorTests.cs @@ -4,7 +4,7 @@ using StreamShorts.Library.Media; namespace StreamShorts.Library.Tests.Unit.Media.Audio; -public class AudioExtractorTests +internal class AudioExtractorTests { private readonly Mock _mockFfmpegService = new(); private readonly AudioExtractor _sut;