diff --git a/.gitignore b/.gitignore index fdd3a64..eff93f7 100644 --- a/.gitignore +++ b/.gitignore @@ -411,7 +411,6 @@ FodyWeavers.xsd ## Visual studio for Mac ## - # globs Makefile.in *.userprefs @@ -438,7 +437,6 @@ test-results/ # Icon must end with two \r Icon - # Thumbnails ._* diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a7cd9d..9f320c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableRoslynAnalyzers": true, - "dotnet.defaultSolution": "media-encoding.sln" + "dotnet.defaultSolution": "RipSharp.sln", + "chat.tools.terminal.autoApprove": { + "gh": true + } } diff --git a/EXAMPLES.md b/EXAMPLES.md index b6457fb..7251717 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -107,8 +107,6 @@ dotnet run --project src/RipSharp -- --mode tv --disc disc:1 --title "Friends" - ## Advanced Examples - - ## Specific Scenarios ### 4K UltraHD Blu-Ray diff --git a/README.md b/README.md index d86110e..2071499 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,51 @@ macOS: If no config file exists, RipSharp creates one in the first personal location above (for example, `$XDG_CONFIG_HOME/ripsharp/config.yaml` on Linux). +## Theming + +Themes are loaded from a YAML file located under a `themes` subdirectory in the config directory and bound to options. On startup, RipSharp writes bundled themes into that directory if they are missing and does not overwrite existing files. Set the theme name in your config file: + +```yaml +theme: "catppuccin mocha" +``` + +Built-in themes: + +- "catppuccin latte" +- "catppuccin frappe" +- "catppuccin macchiato" +- "catppuccin mocha" +- "dracula" +- "nord" +- "tokyo-night" +- "gruvbox dark" +- "gruvbox light" + +Theme file format (YAML): + +```yaml +theme: + colors: + success: "#94e2d5" + error: "#f38ba8" + warning: "#f9e2af" + info: "#89b4fa" + accent: "#89dceb" + muted: "#6c7086" + highlight: "#cba6f7" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" +``` + ## Building ```bash diff --git a/src/RipSharp.Tests/Core/ThemeFileLocatorTests.cs b/src/RipSharp.Tests/Core/ThemeFileLocatorTests.cs new file mode 100644 index 0000000..fca6d49 --- /dev/null +++ b/src/RipSharp.Tests/Core/ThemeFileLocatorTests.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.IO; + +using AwesomeAssertions; + +using Xunit; + +namespace BugZapperLabs.RipSharp.Tests.Core; + +public class ThemeFileLocatorTests +{ + [Fact] + public void ResolveThemePath_WhenRelative_ReturnsThemePathUnderConfigThemes() + { + var configPath = Path.Combine("/home/tester", ".config", "ripsharp", "config.yaml"); + + var result = ThemeFileLocator.ResolveThemePath("custom.yaml", configPath); + + result.Should().Be(Path.Combine("/home/tester", ".config", "ripsharp", "themes", "custom.yaml")); + } + + [Theory] + [InlineData("catppuccin mocha", "catppuccin-mocha.yaml")] + [InlineData("catppuccin-mocha", "catppuccin-mocha.yaml")] + [InlineData("catppuccin_mocha", "catppuccin-mocha.yaml")] + public void ResolveThemePath_WhenNameProvided_NormalizesToFileName(string themeName, string expectedFileName) + { + var configPath = Path.Combine("/home/tester", ".config", "ripsharp", "config.yaml"); + + var result = ThemeFileLocator.ResolveThemePath(themeName, configPath); + + result.Should().Be(Path.Combine("/home/tester", ".config", "ripsharp", "themes", expectedFileName)); + } + + [Fact] + public void ResolveThemePath_WhenMissing_UsesDefaultThemeFile() + { + var configPath = Path.Combine("/home/tester", ".config", "ripsharp", "config.yaml"); + + var result = ThemeFileLocator.ResolveThemePath(null, configPath); + + result.Should().Be(Path.Combine("/home/tester", ".config", "ripsharp", "themes", "catppuccin-mocha.yaml")); + } + + [Fact] + public void EnsureBundledThemeFiles_WritesThemesIntoConfigThemesDirectory() + { + var configPath = Path.Combine("/home/tester", ".config", "ripsharp", "config.yaml"); + var files = new Dictionary(); + var directories = new HashSet(); + var bundledThemes = new Dictionary + { + ["catppuccin-mocha.yaml"] = "theme:\n colors:\n", + ["catppuccin-latte.yaml"] = "theme:\n colors:\n" + }; + + ThemeFileLocator.EnsureBundledThemeFiles( + configPath, + path => files.ContainsKey(path), + (path, content) => files[path] = content, + path => directories.Add(path), + bundledThemes); + + var expectedThemeDir = Path.Combine("/home/tester", ".config", "ripsharp", "themes"); + var expectedMochaPath = Path.Combine(expectedThemeDir, "catppuccin-mocha.yaml"); + var expectedLattePath = Path.Combine(expectedThemeDir, "catppuccin-latte.yaml"); + + directories.Should().Contain(expectedThemeDir); + files.Should().ContainKey(expectedMochaPath); + files.Should().ContainKey(expectedLattePath); + files[expectedMochaPath].Should().NotBeNullOrWhiteSpace(); + files[expectedLattePath].Should().NotBeNullOrWhiteSpace(); + } +} diff --git a/src/RipSharp.Tests/Metadata/MetadataServiceTests.cs b/src/RipSharp.Tests/Metadata/MetadataServiceTests.cs index d435730..6a064b3 100644 --- a/src/RipSharp.Tests/Metadata/MetadataServiceTests.cs +++ b/src/RipSharp.Tests/Metadata/MetadataServiceTests.cs @@ -24,7 +24,7 @@ public async Task LookupAsync_Fallbacks_WhenNoApiKeys() Environment.SetEnvironmentVariable("TMDB_API_KEY", null); var notifier = Substitute.For(); var providers = new List(); - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); var md = await svc.LookupAsync("SIMPSONS_WS", isTv: false, year: null); @@ -46,7 +46,7 @@ public async Task LookupAsync_ReturnsFromFirstProvider_WhenMatch() provider2.Name.Returns("Provider2"); var providers = new List { provider1, provider2 }; - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); var result = await svc.LookupAsync("test", isTv: false, year: null); @@ -70,7 +70,7 @@ public async Task LookupAsync_TriesSecondProvider_WhenFirstReturnsNull() provider2.LookupAsync("test", false, null).Returns(new ContentMetadata { Title = "Test Movie", Year = 2021, Type = "movie" }); var providers = new List { provider1, provider2 }; - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); var result = await svc.LookupAsync("test", isTv: false, year: null); @@ -91,7 +91,7 @@ public async Task LookupAsync_UsesTitleVariations_WhenOriginalFails() provider.LookupAsync("MOVIE_TITLE", Arg.Any(), Arg.Any()).Returns(new ContentMetadata { Title = "Movie Title", Year = 2023, Type = "movie" }); var providers = new List { provider }; - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); var result = await svc.LookupAsync("MOVIE_TITLE_2023", isTv: false, year: null); @@ -112,7 +112,7 @@ public async Task LookupAsync_ShowsDifferentMessage_ForSimplifiedTitle() provider.LookupAsync("SIMPSONS", Arg.Any(), Arg.Any()).Returns(new ContentMetadata { Title = "The Simpsons", Year = 1989, Type = "tv" }); var providers = new List { provider }; - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); await svc.LookupAsync("SIMPSONS_WS", isTv: true, year: null); @@ -131,7 +131,7 @@ public async Task LookupAsync_ShowsNormalMessage_ForOriginalTitle() provider.LookupAsync("Test Movie", Arg.Any(), Arg.Any()).Returns(new ContentMetadata { Title = "Test Movie", Year = 2020, Type = "movie" }); var providers = new List { provider }; - var svc = new MetadataService(providers, notifier); + var svc = new MetadataService(providers, notifier, ThemeProvider.CreateDefault()); await svc.LookupAsync("Test Movie", isTv: false, year: null); diff --git a/src/RipSharp.Tests/Services/DiscRipperTitleSuffixTests.cs b/src/RipSharp.Tests/Services/DiscRipperTitleSuffixTests.cs index 2b68261..2bee07c 100644 --- a/src/RipSharp.Tests/Services/DiscRipperTitleSuffixTests.cs +++ b/src/RipSharp.Tests/Services/DiscRipperTitleSuffixTests.cs @@ -92,8 +92,9 @@ private static DiscRipper CreateRipper(IEncoderService encoder) var userPrompt = Substitute.For(); var episodeTitles = Substitute.For(); var progressDisplay = Substitute.For(); + var theme = ThemeProvider.CreateDefault(); - return new DiscRipper(scanner, encoder, metadataService, makeMkv, notifier, userPrompt, episodeTitles, progressDisplay); + return new DiscRipper(scanner, encoder, metadataService, makeMkv, notifier, userPrompt, episodeTitles, progressDisplay, theme); } private static async Task> InvokeBuildTitlePlansAsync( diff --git a/src/RipSharp.Tests/Utilities/ThemeProviderTests.cs b/src/RipSharp.Tests/Utilities/ThemeProviderTests.cs new file mode 100644 index 0000000..d52a7fd --- /dev/null +++ b/src/RipSharp.Tests/Utilities/ThemeProviderTests.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Options; + +using Spectre.Console; + +using Xunit; + +namespace BugZapperLabs.RipSharp.Tests.Utilities; + +public class ThemeProviderTests +{ + [Fact] + public void Colors_AreLoadedFromOptions() + { + var options = Options.Create(new ThemeOptions + { + Colors = new ThemeColors + { + Success = "#010203" + } + }); + + var provider = new ThemeProvider(options); + + provider.SuccessColor.Should().Be(new Color(1, 2, 3)); + } + + [Fact] + public void Emojis_AreLoadedFromOptions() + { + var options = Options.Create(new ThemeOptions + { + Emojis = new ThemeEmojis + { + Warning = "!" + } + }); + + var provider = new ThemeProvider(options); + + provider.Emojis.Warning.Should().Be("!"); + } +} diff --git a/src/RipSharp/Core/ConfigFileLocator.cs b/src/RipSharp/Core/ConfigFileLocator.cs index baee8f2..1d14b91 100644 --- a/src/RipSharp/Core/ConfigFileLocator.cs +++ b/src/RipSharp/Core/ConfigFileLocator.cs @@ -24,14 +24,15 @@ internal static class ConfigFileLocator " default_path: \"disc:0\"\n" + " default_temp_dir: \"/tmp/makemkv\"\n\n" + "output:\n" + - " movies_dir: \"~/Movies\"\n" + - " tv_dir: \"~/TV\"\n\n" + + " movies_dir: \"~/Videos/Movies\"\n" + + " tv_dir: \"~/Videos/TV\"\n\n" + "encoding:\n" + " include_english_subtitles: true\n" + " include_stereo_audio: true\n" + " include_surround_audio: true\n\n" + "metadata:\n" + - " lookup_enabled: true\n"; + " lookup_enabled: true\n\n" + + "theme: \"catppuccin mocha\"\n"; internal static ConfigSearchContext CreateContext() { diff --git a/src/RipSharp/Core/Program.cs b/src/RipSharp/Core/Program.cs index 8cd4471..72080e8 100644 --- a/src/RipSharp/Core/Program.cs +++ b/src/RipSharp/Core/Program.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; - namespace BugZapperLabs.RipSharp.Core; public class Program @@ -36,16 +35,18 @@ public static async Task Main(string[] args) private static async Task RunAsync(string[] args, CursorManager cursorManager) { var options = RipOptions.ParseArgs(args); + var defaultTheme = ThemeProvider.CreateDefault(); + var consoleWriter = new ConsoleWriter(defaultTheme); if (options.ShowHelp) { - RipOptions.DisplayHelp(new ConsoleWriter()); + RipOptions.DisplayHelp(consoleWriter); return 0; } if (options.ShowVersion) { - Console.WriteLine($"ripsharp {GetVersion()}"); + consoleWriter.Plain($"ripsharp {GetVersion()}"); return 0; } @@ -56,7 +57,7 @@ private static async Task RunAsync(string[] args, CursorManager cursorManag if (missingTools.Count > 0) { - var prereqWriter = new ConsoleWriter(); + var prereqWriter = consoleWriter; prereqWriter.Error("Missing required prerequisites:"); foreach (var tool in missingTools) { @@ -105,11 +106,32 @@ private static async Task RunAsync(string[] args, CursorManager cursorManag cfg.AddYamlFile(configPath, optional: false, reloadOnChange: true); } + ThemeFileLocator.EnsureBundledThemeFiles( + configPath, + File.Exists, + File.WriteAllText, + path => Directory.CreateDirectory(path)); + + var tempConfig = cfg.Build(); + var themeSetting = tempConfig.GetValue("theme"); + if (string.IsNullOrWhiteSpace(themeSetting)) + { + themeSetting = tempConfig.GetSection("theme").GetValue("path"); + } + var resolvedThemePath = ThemeFileLocator.ResolveThemePath(themeSetting, configPath); + + if (!string.IsNullOrWhiteSpace(resolvedThemePath)) + { + cfg.AddYamlFile(resolvedThemePath, optional: true, reloadOnChange: true); + } + cfg.AddEnvironmentVariables(); }) .ConfigureServices((ctx, services) => { services.Configure(ctx.Configuration); + services.Configure(ctx.Configuration.GetSection("theme")); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -155,6 +177,7 @@ private static async Task RunAsync(string[] args, CursorManager cursorManag var ripper = host.Services.GetRequiredService(); var writer = host.Services.GetRequiredService(); + var theme = host.Services.GetRequiredService(); List files; try @@ -163,7 +186,7 @@ private static async Task RunAsync(string[] args, CursorManager cursorManag } catch (OperationCanceledException) { - writer.Warning("\nāš ļø Operation interrupted by user"); + writer.Warning($"\n{theme.Emojis.Warning} Operation interrupted by user"); return 130; } diff --git a/src/RipSharp/Core/ThemeFileLocator.cs b/src/RipSharp/Core/ThemeFileLocator.cs new file mode 100644 index 0000000..5c3e01b --- /dev/null +++ b/src/RipSharp/Core/ThemeFileLocator.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace BugZapperLabs.RipSharp.Core; + +internal static class ThemeFileLocator +{ + internal const string ThemeDirectoryName = "themes"; + internal const string DefaultThemeFileName = "catppuccin-mocha.yaml"; + internal const string EmbeddedThemeResourcePrefix = "BugZapperLabs.RipSharp.themes."; + + internal static string? ResolveThemePath(string? themePath, string? configPath) + { + var baseDir = GetConfigDirectory(configPath) ?? AppContext.BaseDirectory; + + if (string.IsNullOrWhiteSpace(themePath)) + { + return Path.Combine(baseDir, ThemeDirectoryName, DefaultThemeFileName); + } + + if (Path.IsPathRooted(themePath)) + { + return themePath; + } + + var fileName = NormalizeThemeFileName(themePath); + return Path.Combine(baseDir, ThemeDirectoryName, fileName); + } + + internal static void EnsureBundledThemeFiles( + string? configPath, + Func fileExists, + Action writeFile, + Action ensureDirectory, + IReadOnlyDictionary? bundledThemes = null) + { + var baseDir = GetConfigDirectory(configPath); + if (string.IsNullOrWhiteSpace(baseDir)) + { + return; + } + + var themes = bundledThemes ?? ReadEmbeddedThemes(); + if (themes.Count == 0) + { + return; + } + + var themeDir = Path.Combine(baseDir, ThemeDirectoryName); + ensureDirectory(themeDir); + + foreach (var theme in themes) + { + if (string.IsNullOrWhiteSpace(theme.Key) || string.IsNullOrWhiteSpace(theme.Value)) + { + continue; + } + + var themePath = Path.Combine(themeDir, theme.Key); + if (fileExists(themePath)) + { + continue; + } + + writeFile(themePath, theme.Value); + } + } + + internal static string? GetConfigDirectory(string? configPath) + { + return string.IsNullOrWhiteSpace(configPath) + ? null + : Path.GetDirectoryName(configPath); + } + + private static string NormalizeThemeFileName(string themeName) + { + var trimmed = themeName.Trim(); + if (string.IsNullOrWhiteSpace(trimmed)) + { + return DefaultThemeFileName; + } + + var normalized = trimmed + .Replace('_', '-') + .Replace(' ', '-') + .ToLowerInvariant(); + + if (!normalized.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase)) + { + normalized += ".yaml"; + } + + return normalized; + } + + private static IReadOnlyDictionary ReadEmbeddedThemes() + { + var assembly = Assembly.GetExecutingAssembly(); + var resources = assembly.GetManifestResourceNames() + .Where(name => name.StartsWith(EmbeddedThemeResourcePrefix, StringComparison.Ordinal)) + .Where(name => name.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + var themes = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var resourceName in resources) + { + var fileName = resourceName.Substring(EmbeddedThemeResourcePrefix.Length); + if (string.IsNullOrWhiteSpace(fileName)) + { + continue; + } + + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream is null) + { + continue; + } + + using var reader = new StreamReader(stream); + var contents = reader.ReadToEnd(); + if (string.IsNullOrWhiteSpace(contents)) + { + continue; + } + + themes[fileName] = contents; + } + + return themes; + } +} diff --git a/src/RipSharp/Core/ThemeOptions.cs b/src/RipSharp/Core/ThemeOptions.cs new file mode 100644 index 0000000..597d01e --- /dev/null +++ b/src/RipSharp/Core/ThemeOptions.cs @@ -0,0 +1,33 @@ +namespace BugZapperLabs.RipSharp.Core; + +public class ThemeOptions +{ + public string? Path { get; set; } + public ThemeColors Colors { get; set; } = new(); + public ThemeEmojis Emojis { get; set; } = new(); +} + +public class ThemeColors +{ + public string Success { get; set; } = "#94e2d5"; + public string Error { get; set; } = "#f38ba8"; + public string Warning { get; set; } = "#f9e2af"; + public string Info { get; set; } = "#89b4fa"; + public string Accent { get; set; } = "#89dceb"; + public string Muted { get; set; } = "#6c7086"; + public string Highlight { get; set; } = "#cba6f7"; +} + +public class ThemeEmojis +{ + public string Success { get; set; } = "āœ“"; + public string Error { get; set; } = "āŒ"; + public string Warning { get; set; } = "āš ļø"; + public string InsertDisc { get; set; } = "šŸ’æ"; + public string DiscDetected { get; set; } = "šŸ“€"; + public string Scan { get; set; } = "šŸ”"; + public string DiscType { get; set; } = "šŸ’½"; + public string TitleFound { get; set; } = "šŸŽžļø"; + public string Tv { get; set; } = "šŸ“ŗ"; + public string Movie { get; set; } = "šŸŽ¬"; +} diff --git a/src/RipSharp/Core/appsettings.yaml b/src/RipSharp/Core/appsettings.yaml index 7ef3a8d..3b7a5ed 100644 --- a/src/RipSharp/Core/appsettings.yaml +++ b/src/RipSharp/Core/appsettings.yaml @@ -13,3 +13,5 @@ encoding: metadata: lookup_enabled: true + +theme: "catppuccin mocha" diff --git a/src/RipSharp/MakeMkv/MakeMkvOutputHandler.cs b/src/RipSharp/MakeMkv/MakeMkvOutputHandler.cs index 93f599e..7e701b9 100644 --- a/src/RipSharp/MakeMkv/MakeMkvOutputHandler.cs +++ b/src/RipSharp/MakeMkv/MakeMkvOutputHandler.cs @@ -11,11 +11,12 @@ public class MakeMkvOutputHandler private readonly string _progressLogPath; private readonly string _rawLogPath; private readonly IConsoleWriter _writer; + private readonly IThemeProvider _theme; public double LastBytesProcessed { get; private set; } public double LastProgressFraction { get; private set; } - public MakeMkvOutputHandler(long expectedBytes, int index, int totalTitles, IProgressTask? task, string progressLogPath, string rawLogPath, IConsoleWriter writer) + public MakeMkvOutputHandler(long expectedBytes, int index, int totalTitles, IProgressTask? task, string progressLogPath, string rawLogPath, IConsoleWriter writer, IThemeProvider theme) { _expectedBytes = expectedBytes; _index = index; @@ -24,6 +25,7 @@ public MakeMkvOutputHandler(long expectedBytes, int index, int totalTitles, IPro _progressLogPath = progressLogPath; _rawLogPath = rawLogPath; _writer = writer; + _theme = theme; } public void HandleLine(string line) @@ -81,7 +83,7 @@ public void HandleLine(string line) if (!string.IsNullOrEmpty(caption)) { if (_task != null) - _task.Description = $"[{ConsoleColors.Success}]{caption} ({_index + 1}/{_totalTitles})[/]"; + _task.Description = $"[{_theme.Colors.Success}]{caption} ({_index + 1}/{_totalTitles})[/]"; } TryAppend(_progressLogPath, $"PRGC {caption}\n"); } diff --git a/src/RipSharp/MakeMkv/ScanOutputHandler.cs b/src/RipSharp/MakeMkv/ScanOutputHandler.cs index 30eed56..bd7663e 100644 --- a/src/RipSharp/MakeMkv/ScanOutputHandler.cs +++ b/src/RipSharp/MakeMkv/ScanOutputHandler.cs @@ -5,6 +5,7 @@ namespace BugZapperLabs.RipSharp.MakeMkv; public class ScanOutputHandler { private readonly IConsoleWriter _notifier; + private readonly IThemeProvider _theme; private readonly List _titles; private string? _discName; private string? _discType; @@ -14,9 +15,10 @@ public class ScanOutputHandler private int _titleAddedCount; private readonly HashSet _printedTitles = new(); - public ScanOutputHandler(IConsoleWriter notifier, List titles) + public ScanOutputHandler(IConsoleWriter notifier, IThemeProvider theme, List titles) { _notifier = notifier; + _theme = theme; _titles = titles; } @@ -37,11 +39,11 @@ private void HandleProgressMessages(string line) { if (line.StartsWith("MSG:") && line.Contains("insert disc")) { - _notifier.Warning("šŸ’æ Insert a disc into the drive..."); + _notifier.Warning($"{_theme.Emojis.InsertDisc} Insert a disc into the drive..."); } else if (line.StartsWith("DRV:0,") && !line.Contains(",256,") && !_discDetectedPrinted) { - _notifier.Success("šŸ“€ Disc detected in drive..."); + _notifier.Success($"{_theme.Emojis.DiscDetected} Disc detected in drive..."); _discDetectedPrinted = true; } else if (line.StartsWith("MSG:1005,")) @@ -62,7 +64,7 @@ private void HandleProgressMessages(string line) } else if (line.StartsWith("MSG:") && (line.Contains("Scanning") || line.Contains("scanning")) && !_scanningStarted) { - _notifier.Info("šŸ” Scanning disc structure..."); + _notifier.Info($"{_theme.Emojis.Scan} Scanning disc structure..."); _scanningStarted = true; } else if (line.StartsWith("MSG:3307,")) @@ -72,7 +74,7 @@ private void HandleProgressMessages(string line) var titleMatch = Regex.Match(line, @"title #(\d+)"); if (fileMatch.Success && titleMatch.Success) { - _notifier.Success($" āœ“ Added title #{titleMatch.Groups[1].Value}: {fileMatch.Groups[1].Value}"); + _notifier.Success($" {_theme.Emojis.Success} Added title #{titleMatch.Groups[1].Value}: {fileMatch.Groups[1].Value}"); } } else if (line.StartsWith("MSG:3309,")) @@ -96,11 +98,11 @@ private void HandleProgressMessages(string line) var message = MakeMkvProtocol.ExtractQuoted(line); if (!string.IsNullOrWhiteSpace(message)) { - _notifier.Error($"āŒ {message}"); + _notifier.Error($"{_theme.Emojis.Error} {message}"); } else { - _notifier.Error($"āŒ {line}"); + _notifier.Error($"{_theme.Emojis.Error} {line}"); } } else if (line.StartsWith("CINFO:1,")) @@ -108,7 +110,7 @@ private void HandleProgressMessages(string line) var dtype = MakeMkvProtocol.ExtractQuoted(line); if (!string.IsNullOrWhiteSpace(dtype)) { - _notifier.Accent($"šŸ’½ Disc type: {dtype}"); + _notifier.Accent($"{_theme.Emojis.DiscType} Disc type: {dtype}"); } } else if (line.StartsWith("TINFO:")) @@ -119,7 +121,7 @@ private void HandleProgressMessages(string line) var tname = MakeMkvProtocol.ExtractQuoted(line); if (!string.IsNullOrWhiteSpace(tname) && _printedTitles.Add(tname!)) { - _notifier.Highlight($"šŸŽžļø Title found: {tname}"); + _notifier.Highlight($"{_theme.Emojis.TitleFound} Title found: {tname}"); } } } diff --git a/src/RipSharp/Metadata/MetadataService.cs b/src/RipSharp/Metadata/MetadataService.cs index 3649593..ed754dc 100644 --- a/src/RipSharp/Metadata/MetadataService.cs +++ b/src/RipSharp/Metadata/MetadataService.cs @@ -4,10 +4,12 @@ public class MetadataService : IMetadataService { private readonly List _providers; private readonly IConsoleWriter _notifier; + private readonly IThemeProvider _theme; - public MetadataService(IEnumerable providers, IConsoleWriter notifier) + public MetadataService(IEnumerable providers, IConsoleWriter notifier, IThemeProvider theme) { _notifier = notifier; + _theme = theme; _providers = providers.ToList(); } @@ -23,15 +25,15 @@ public MetadataService(IEnumerable providers, IConsoleWriter if (result != null) { if (titleVariation != title) - _notifier.Success($"āœ“ {provider.Name} {(isTv ? "TV" : "Movie")} lookup found using simplified title '{titleVariation}': '{result.Title}'" + (result.Year.HasValue ? $" ({result.Year.Value})" : "")); + _notifier.Success($"{_theme.Emojis.Success} {provider.Name} {(isTv ? "TV" : "Movie")} lookup found using simplified title '{titleVariation}': '{result.Title}'" + (result.Year.HasValue ? $" ({result.Year.Value})" : "")); else - _notifier.Success($"āœ“ {provider.Name} {(isTv ? "TV" : "Movie")} lookup found: '{result.Title}'" + (result.Year.HasValue ? $" ({result.Year.Value})" : "")); + _notifier.Success($"{_theme.Emojis.Success} {provider.Name} {(isTv ? "TV" : "Movie")} lookup found: '{result.Title}'" + (result.Year.HasValue ? $" ({result.Year.Value})" : "")); return result; } } } - _notifier.Warning($"āš ļø No metadata found from available providers for '{title}'. Using disc title as fallback."); + _notifier.Warning($"{_theme.Emojis.Warning} No metadata found from available providers for '{title}'. Using disc title as fallback."); return new ContentMetadata { Title = title, Year = year, Type = isTv ? "tv" : "movie" }; } } diff --git a/src/RipSharp/RipSharp.csproj b/src/RipSharp/RipSharp.csproj index 34dc00f..474c7cb 100644 --- a/src/RipSharp/RipSharp.csproj +++ b/src/RipSharp/RipSharp.csproj @@ -22,5 +22,9 @@ + + + + diff --git a/src/RipSharp/Services/DiscRipper.cs b/src/RipSharp/Services/DiscRipper.cs index ed2fb18..4ba5c3d 100644 --- a/src/RipSharp/Services/DiscRipper.cs +++ b/src/RipSharp/Services/DiscRipper.cs @@ -1,6 +1,5 @@ using System.Threading.Channels; - namespace BugZapperLabs.RipSharp.Services; // Job records for channel communication @@ -27,6 +26,7 @@ public class DiscRipper : IDiscRipper private readonly IUserPrompt _userPrompt; private readonly ITvEpisodeTitleProvider _episodeTitles; private readonly IProgressDisplay _progressDisplay; + private readonly IThemeProvider _theme; private record TitlePlan( int TitleId, @@ -38,7 +38,7 @@ private record TitlePlan( string? VersionSuffix, string DisplayName); - public DiscRipper(IDiscScanner scanner, IEncoderService encoder, IMetadataService metadata, IMakeMkvService makeMkv, IConsoleWriter notifier, IUserPrompt userPrompt, ITvEpisodeTitleProvider episodeTitles, IProgressDisplay progressDisplay) + public DiscRipper(IDiscScanner scanner, IEncoderService encoder, IMetadataService metadata, IMakeMkvService makeMkv, IConsoleWriter notifier, IUserPrompt userPrompt, ITvEpisodeTitleProvider episodeTitles, IProgressDisplay progressDisplay, IThemeProvider theme) { _scanner = scanner; _encoder = encoder; @@ -48,6 +48,7 @@ public DiscRipper(IDiscScanner scanner, IEncoderService encoder, IMetadataServic _userPrompt = userPrompt; _episodeTitles = episodeTitles; _progressDisplay = progressDisplay; + _theme = theme; } public async Task> ProcessDiscAsync(RipOptions options, CancellationToken cancellationToken = default) @@ -216,7 +217,7 @@ private static void PrepareDirectories(RipOptions options) if (discInfo.DetectedContentType.HasValue && discInfo.DetectionConfidence >= minConfidenceThreshold) { var contentType = discInfo.DetectedContentType.Value ? "TV series" : "movie"; - var emoji = discInfo.DetectedContentType.Value ? "\ud83d\udcfa " : "\ud83c\udfac "; // šŸ“ŗ for TV, šŸŽ¬ for movie + var emoji = discInfo.DetectedContentType.Value ? $"{_theme.Emojis.Tv} " : $"{_theme.Emojis.Movie} "; var confidencePercent = (int)(discInfo.DetectionConfidence * 100); _notifier.Info($"{emoji}Detected as {contentType} (confidence: {confidencePercent}%)"); options.Tv = discInfo.DetectedContentType.Value; @@ -281,7 +282,7 @@ await _progressDisplay.ExecuteAsync(async ctx => { var expectedBytes = titleInfo?.ReportedSizeBytes ?? 0; var maxValue = expectedBytes > 0 ? expectedBytes : 100; - var task = ctx.AddTask($"[{ConsoleColors.Success}]Title {idx + 1} ({idx + 1}/{totalTitles})[/]", maxValue); + var task = ctx.AddTask($"[{_theme.Colors.Success}]Title {idx + 1} ({idx + 1}/{totalTitles})[/]", maxValue); bool ripDone = false; var pollTask = Task.Run(async () => @@ -313,7 +314,7 @@ await _progressDisplay.ExecuteAsync(async ctx => }); var rawLogPath = Path.Combine(options.Temp!, $"makemkv_title_{titleId:D2}.log"); - var handler = new MakeMkvOutputHandler(expectedBytes, idx, totalTitles, task, progressLogPath, rawLogPath, _notifier); + var handler = new MakeMkvOutputHandler(expectedBytes, idx, totalTitles, task, progressLogPath, rawLogPath, _notifier, _theme); var exit = await _makeMkv.RipTitleAsync(options.Disc, titleId, options.Temp!, onOutput: handler.HandleLine, onError: errLine => @@ -329,7 +330,7 @@ await _progressDisplay.ExecuteAsync(async ctx => if (exit != 0) { - task.Description = $"[{ConsoleColors.Error}]Failed: Title {titleId}[/]"; + task.Description = $"[{_theme.Colors.Error}]Failed: Title {titleId}[/]"; task.StopTask(); _notifier.Error($"Failed to rip title {titleId}"); return; @@ -444,7 +445,7 @@ private async Task RipProducerAsync(Channel ripChannel, DiscInfo discInf var expectedBytes = titleInfo?.ReportedSizeBytes ?? 0; var durationSeconds = titleInfo?.DurationSeconds ?? 0; var rawLogPath = Path.Combine(options.Temp!, $"makemkv_title_{titleId:D2}.log"); - var handler = new MakeMkvOutputHandler(expectedBytes, idx, totalTitles, null, progressLogPath, rawLogPath, _notifier); + var handler = new MakeMkvOutputHandler(expectedBytes, idx, totalTitles, null, progressLogPath, rawLogPath, _notifier, _theme); bool ripDone = false; double observedMaxBytes = 1; // prevent divide-by-zero diff --git a/src/RipSharp/Services/DiscScanner.cs b/src/RipSharp/Services/DiscScanner.cs index fc4a053..9f53190 100644 --- a/src/RipSharp/Services/DiscScanner.cs +++ b/src/RipSharp/Services/DiscScanner.cs @@ -7,18 +7,20 @@ public class DiscScanner : IDiscScanner private readonly IProcessRunner _runner; private readonly IConsoleWriter _notifier; private readonly IDiscTypeDetector _typeDetector; + private readonly IThemeProvider _theme; - public DiscScanner(IProcessRunner runner, IConsoleWriter notifier, IDiscTypeDetector typeDetector) + public DiscScanner(IProcessRunner runner, IConsoleWriter notifier, IDiscTypeDetector typeDetector, IThemeProvider theme) { _runner = runner; _notifier = notifier; _typeDetector = typeDetector; + _theme = theme; } public async Task ScanDiscAsync(string discPath) { var titles = new List(); - var handler = new ScanOutputHandler(_notifier, titles); + var handler = new ScanOutputHandler(_notifier, _theme, titles); var exit = await _runner.RunAsync("makemkvcon", $"-r --robot info {discPath}", onOutput: handler.HandleLine); @@ -27,7 +29,7 @@ public DiscScanner(IProcessRunner runner, IConsoleWriter notifier, IDiscTypeDete if (handler.TitleAddedCount > 0) { - _notifier.Success($"āœ“ Scan complete - found {handler.TitleAddedCount} title(s)"); + _notifier.Success($"{_theme.Emojis.Success} Scan complete - found {handler.TitleAddedCount} title(s)"); } return BuildDiscInfo(handler.DiscName, handler.DiscType, titles); @@ -77,8 +79,6 @@ public List IdentifyMainContent(DiscInfo info, bool isTv) } } - - private static int ParseDurationToSeconds(string? s) { if (string.IsNullOrEmpty(s)) return 0; diff --git a/src/RipSharp/Services/EncoderService.cs b/src/RipSharp/Services/EncoderService.cs index 79449b7..16eece1 100644 --- a/src/RipSharp/Services/EncoderService.cs +++ b/src/RipSharp/Services/EncoderService.cs @@ -1,6 +1,5 @@ using System.Text.Json; - namespace BugZapperLabs.RipSharp.Services; public class EncoderService : IEncoderService diff --git a/src/RipSharp/Utilities/ConsoleColors.cs b/src/RipSharp/Utilities/ConsoleColors.cs deleted file mode 100644 index 8c337c3..0000000 --- a/src/RipSharp/Utilities/ConsoleColors.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Spectre.Console; - -namespace BugZapperLabs.RipSharp.Utilities; - -/// -/// Consistent color palette for Spectre.Console markup throughout the application. -/// Using Catppuccin Mocha color scheme. -/// -public static class ConsoleColors -{ - // Primary colors - public const string Success = "#94e2d5"; // Teal - for success messages and progress - public const string Error = "#f38ba8"; // Red - for errors and failures - public const string Warning = "#f9e2af"; // Yellow - for warnings and alerts - public const string Info = "#89b4fa"; // Blue - for informational messages - public const string Accent = "#89dceb"; // Sky - for highlights and accents - public const string Muted = "#6c7086"; // Overlay2 - for dim/secondary text - public const string Highlight = "#cba6f7"; // Mauve - for special highlights - - // Utility - public const string Bold = "bold"; -} - -public static class CustomColors -{ - public static readonly Color Success = new Color(148, 226, 213); // #94e2d5 - public static readonly Color Error = new Color(243, 139, 168); // #f38ba8 - public static readonly Color Warning = new Color(249, 226, 175); // #f9e2af - public static readonly Color Info = new Color(137, 180, 250); // #89b4fa - public static readonly Color Accent = new Color(137, 220, 235); // #89dceb - public static readonly Color Muted = new Color(108, 112, 134); // #6c7086 - public static readonly Color Highlight = new Color(203, 166, 247); // #cba6f7 -} diff --git a/src/RipSharp/Utilities/ConsoleWriter.cs b/src/RipSharp/Utilities/ConsoleWriter.cs index 92c9f98..980e955 100644 --- a/src/RipSharp/Utilities/ConsoleWriter.cs +++ b/src/RipSharp/Utilities/ConsoleWriter.cs @@ -7,15 +7,22 @@ namespace BugZapperLabs.RipSharp.Utilities; /// public class ConsoleWriter : IConsoleWriter { + private readonly IThemeProvider _theme; + + public ConsoleWriter(IThemeProvider theme) + { + _theme = theme; + } + private static void WriteColored(string color, string message) => AnsiConsole.MarkupLine($"[{color}]{Markup.Escape(message)}[/]"); - public void Info(string message) => WriteColored(ConsoleColors.Info, message); - public void Success(string message) => WriteColored(ConsoleColors.Success, message); - public void Warning(string message) => WriteColored(ConsoleColors.Warning, message); - public void Error(string message) => WriteColored(ConsoleColors.Error, message); - public void Muted(string message) => WriteColored(ConsoleColors.Muted, message); - public void Accent(string message) => WriteColored(ConsoleColors.Accent, message); - public void Highlight(string message) => WriteColored(ConsoleColors.Highlight, message); + public void Info(string message) => WriteColored(_theme.Colors.Info, message); + public void Success(string message) => WriteColored(_theme.Colors.Success, message); + public void Warning(string message) => WriteColored(_theme.Colors.Warning, message); + public void Error(string message) => WriteColored(_theme.Colors.Error, message); + public void Muted(string message) => WriteColored(_theme.Colors.Muted, message); + public void Accent(string message) => WriteColored(_theme.Colors.Accent, message); + public void Highlight(string message) => WriteColored(_theme.Colors.Highlight, message); public void Plain(string message) => AnsiConsole.WriteLine(Markup.Escape(message)); } diff --git a/src/RipSharp/Utilities/SpectreProgressDisplay.cs b/src/RipSharp/Utilities/SpectreProgressDisplay.cs index 05b361c..2f1d9bd 100644 --- a/src/RipSharp/Utilities/SpectreProgressDisplay.cs +++ b/src/RipSharp/Utilities/SpectreProgressDisplay.cs @@ -8,6 +8,13 @@ namespace BugZapperLabs.RipSharp.Utilities; /// public class SpectreProgressDisplay : IProgressDisplay { + private readonly IThemeProvider _theme; + + public SpectreProgressDisplay(IThemeProvider theme) + { + _theme = theme; + } + public async Task ExecuteAsync(Func action) { var liveContext = new LiveProgressContext(); @@ -49,7 +56,7 @@ await AnsiConsole.Live(Render(liveContext)) }); } - private static IRenderable Render(LiveProgressContext ctx) + private IRenderable Render(LiveProgressContext ctx) { var (ripTask, encodeTask, overallTask) = ctx.GetLatest(); @@ -57,7 +64,7 @@ private static IRenderable Render(LiveProgressContext ctx) { Header = new PanelHeader("Ripping", Justify.Left), Border = BoxBorder.Rounded, - BorderStyle = CustomColors.Success, + BorderStyle = _theme.SuccessColor, Expand = true }; @@ -65,7 +72,7 @@ private static IRenderable Render(LiveProgressContext ctx) { Header = new PanelHeader("Encoding", Justify.Left), Border = BoxBorder.Rounded, - BorderStyle = CustomColors.Highlight, + BorderStyle = _theme.HighlightColor, Expand = true }; @@ -73,7 +80,7 @@ private static IRenderable Render(LiveProgressContext ctx) { Header = new PanelHeader("Overall Progress", Justify.Left), Border = BoxBorder.Rounded, - BorderStyle = CustomColors.Accent, + BorderStyle = _theme.AccentColor, Expand = true }; @@ -86,11 +93,11 @@ private static IRenderable Render(LiveProgressContext ctx) return grid; } - private static IRenderable RenderTask(LiveTask? task, string label) + private IRenderable RenderTask(LiveTask? task, string label) { if (task == null) { - return new Text($"Waiting for {label.ToLower()} to start...", new Style(CustomColors.Muted)); + return new Text($"Waiting for {label.ToLower()} to start...", new Style(_theme.MutedColor)); } var percent = task.MaxValue > 0 ? Math.Clamp((double)task.Value / task.MaxValue, 0, 1) : 0; @@ -120,13 +127,13 @@ private static IRenderable RenderTask(LiveTask? task, string label) var timeInfo = $"{elapsedStr} / {remainingStr}"; // Combine filled and empty bars with different colors - var barRenderable = new Markup($"[{ConsoleColors.Success}]{filledBar}[/][{ConsoleColors.Muted}]{emptyBar}[/]"); + var barRenderable = new Markup($"[{_theme.Colors.Success}]{filledBar}[/][{_theme.Colors.Muted}]{emptyBar}[/]"); var progressBar = new Columns(new IRenderable[] { barRenderable, - new Text($"{pctText}%", CustomColors.Accent), - new Text($" {timeInfo}", CustomColors.Muted) + new Text($"{pctText}%", _theme.AccentColor), + new Text($" {timeInfo}", _theme.MutedColor) }); var messages = task.GetRecentMessages(5); @@ -138,7 +145,7 @@ private static IRenderable RenderTask(LiveTask? task, string label) var rows = new List { progressBar }; foreach (var msg in messages) { - rows.Add(new Text(msg, new Style(CustomColors.Muted))); + rows.Add(new Text(msg, new Style(_theme.MutedColor))); } return new Rows(rows); diff --git a/src/RipSharp/Utilities/ThemeProvider.cs b/src/RipSharp/Utilities/ThemeProvider.cs new file mode 100644 index 0000000..fe41cf2 --- /dev/null +++ b/src/RipSharp/Utilities/ThemeProvider.cs @@ -0,0 +1,72 @@ +using System.Globalization; + +using BugZapperLabs.RipSharp.Core; + +using Microsoft.Extensions.Options; + +using Spectre.Console; + +namespace BugZapperLabs.RipSharp.Utilities; + +public interface IThemeProvider +{ + ThemeColors Colors { get; } + ThemeEmojis Emojis { get; } + Color SuccessColor { get; } + Color ErrorColor { get; } + Color WarningColor { get; } + Color InfoColor { get; } + Color AccentColor { get; } + Color MutedColor { get; } + Color HighlightColor { get; } +} + +public class ThemeProvider : IThemeProvider +{ + private readonly ThemeOptions _options; + + public ThemeProvider(IOptions options) + { + _options = options.Value ?? new ThemeOptions(); + } + + public static IThemeProvider CreateDefault() + { + return new ThemeProvider(Options.Create(new ThemeOptions())); + } + + public ThemeColors Colors => _options.Colors; + public ThemeEmojis Emojis => _options.Emojis; + + public Color SuccessColor => ParseHexColor(Colors.Success, new Color(148, 226, 213)); + public Color ErrorColor => ParseHexColor(Colors.Error, new Color(243, 139, 168)); + public Color WarningColor => ParseHexColor(Colors.Warning, new Color(249, 226, 175)); + public Color InfoColor => ParseHexColor(Colors.Info, new Color(137, 180, 250)); + public Color AccentColor => ParseHexColor(Colors.Accent, new Color(137, 220, 235)); + public Color MutedColor => ParseHexColor(Colors.Muted, new Color(108, 112, 134)); + public Color HighlightColor => ParseHexColor(Colors.Highlight, new Color(203, 166, 247)); + + private static Color ParseHexColor(string? hex, Color fallback) + { + if (string.IsNullOrWhiteSpace(hex)) + { + return fallback; + } + + var trimmed = hex.Trim().TrimStart('#'); + if (trimmed.Length != 6) + { + return fallback; + } + + if (!int.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var rgb)) + { + return fallback; + } + + var r = (byte)((rgb >> 16) & 0xFF); + var g = (byte)((rgb >> 8) & 0xFF); + var b = (byte)(rgb & 0xFF); + return new Color(r, g, b); + } +} diff --git a/src/RipSharp/themes/catppuccin-frappe.yaml b/src/RipSharp/themes/catppuccin-frappe.yaml new file mode 100644 index 0000000..54aa891 --- /dev/null +++ b/src/RipSharp/themes/catppuccin-frappe.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#81c8be" + error: "#e78284" + warning: "#e5c890" + info: "#8caaee" + accent: "#99d1db" + muted: "#737994" + highlight: "#ca9ee6" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/catppuccin-latte.yaml b/src/RipSharp/themes/catppuccin-latte.yaml new file mode 100644 index 0000000..8592d85 --- /dev/null +++ b/src/RipSharp/themes/catppuccin-latte.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#179299" + error: "#d20f39" + warning: "#df8e1d" + info: "#1e66f5" + accent: "#04a5e5" + muted: "#9ca0b0" + highlight: "#8839ef" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/catppuccin-macchiato.yaml b/src/RipSharp/themes/catppuccin-macchiato.yaml new file mode 100644 index 0000000..524c4be --- /dev/null +++ b/src/RipSharp/themes/catppuccin-macchiato.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#8bd5ca" + error: "#ed8796" + warning: "#eed49f" + info: "#8aadf4" + accent: "#91d7e3" + muted: "#6e738d" + highlight: "#c6a0f6" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/catppuccin-mocha.yaml b/src/RipSharp/themes/catppuccin-mocha.yaml new file mode 100644 index 0000000..639d115 --- /dev/null +++ b/src/RipSharp/themes/catppuccin-mocha.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#94e2d5" + error: "#f38ba8" + warning: "#f9e2af" + info: "#89b4fa" + accent: "#89dceb" + muted: "#6c7086" + highlight: "#cba6f7" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/dracula.yaml b/src/RipSharp/themes/dracula.yaml new file mode 100644 index 0000000..600153f --- /dev/null +++ b/src/RipSharp/themes/dracula.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#50fa7b" + error: "#ff5555" + warning: "#f1fa8c" + info: "#8be9fd" + accent: "#bd93f9" + muted: "#6272a4" + highlight: "#ff79c6" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/gruvbox-dark.yaml b/src/RipSharp/themes/gruvbox-dark.yaml new file mode 100644 index 0000000..49befaf --- /dev/null +++ b/src/RipSharp/themes/gruvbox-dark.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#b8bb26" + error: "#fb4934" + warning: "#fabd2f" + info: "#83a598" + accent: "#8ec07c" + muted: "#928374" + highlight: "#d3869b" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/gruvbox-light.yaml b/src/RipSharp/themes/gruvbox-light.yaml new file mode 100644 index 0000000..c077967 --- /dev/null +++ b/src/RipSharp/themes/gruvbox-light.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#79740e" + error: "#9d0006" + warning: "#b57614" + info: "#076678" + accent: "#427b58" + muted: "#928374" + highlight: "#8f3f71" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/nord.yaml b/src/RipSharp/themes/nord.yaml new file mode 100644 index 0000000..4e3f58f --- /dev/null +++ b/src/RipSharp/themes/nord.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#a3be8c" + error: "#bf616a" + warning: "#ebcb8b" + info: "#88c0d0" + accent: "#81a1c1" + muted: "#4c566a" + highlight: "#b48ead" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬" diff --git a/src/RipSharp/themes/tokyo-night.yaml b/src/RipSharp/themes/tokyo-night.yaml new file mode 100644 index 0000000..fb476ea --- /dev/null +++ b/src/RipSharp/themes/tokyo-night.yaml @@ -0,0 +1,20 @@ +theme: + colors: + success: "#9ece6a" + error: "#f7768e" + warning: "#e0af68" + info: "#7aa2f7" + accent: "#bb9af7" + muted: "#787c99" + highlight: "#73daca" + emojis: + success: "āœ“" + error: "āŒ" + warning: "āš ļø" + insert_disc: "šŸ’æ" + disc_detected: "šŸ“€" + scan: "šŸ”" + disc_type: "šŸ’½" + title_found: "šŸŽžļø" + tv: "šŸ“ŗ" + movie: "šŸŽ¬"