diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Lottie/LottieDetector.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Lottie/LottieDetector.cs index 6271b01ec..70d3cf7f5 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Lottie/LottieDetector.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Lottie/LottieDetector.cs @@ -22,12 +22,25 @@ namespace QuickLook.Plugin.ImageViewer.Webview.Lottie; internal static class LottieDetector { - public static bool IsVaild(string path) + public static bool IsVaildFile(string path) { try { var jsonString = File.ReadAllText(path); + return IsVaildContent(jsonString); + } + catch + { + // If any exception occurs, assume it's not a valid Lottie file + } + return false; + } + + public static bool IsVaildContent(string jsonString) + { + try + { // No exception will be thrown here var jsonLottie = LottieParser.Parse>(jsonString); diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsDetector.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsDetector.cs new file mode 100644 index 000000000..c741667df --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsDetector.cs @@ -0,0 +1,42 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Plugin.ImageViewer.Webview.Lottie; + +namespace QuickLook.Plugin.ImageViewer.Webview.Tgs; + +internal static class TgsDetector +{ + public static bool IsValidFile(string path) + { + try + { + // Extract JSON content from gzipped TGS file + var jsonString = TgsExtractor.GetJsonContent(path); + return IsVaildContent(jsonString); + } + catch + { + // If any exception occurs, assume it's not a valid TGS file + } + + return false; + } + + public static bool IsVaildContent(string jsonString) + => LottieDetector.IsVaildContent(jsonString); +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsExtractor.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsExtractor.cs new file mode 100644 index 000000000..df98f2221 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsExtractor.cs @@ -0,0 +1,33 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.IO; +using System.IO.Compression; +using System.Text; + +namespace QuickLook.Plugin.ImageViewer.Webview.Tgs; + +internal static class TgsExtractor +{ + public static string GetJsonContent(string path) + { + using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new StreamReader(gzipStream, Encoding.UTF8); + return reader.ReadToEnd(); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsImagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsImagePanel.cs new file mode 100644 index 000000000..10da24db9 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsImagePanel.cs @@ -0,0 +1,92 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using Microsoft.Web.WebView2.Core; +using QuickLook.Plugin.ImageViewer.Webview.Svg; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace QuickLook.Plugin.ImageViewer.Webview.Tgs; + +public class TgsImagePanel : SvgImagePanel +{ + public override void Preview(string path) + { + FallbackPath = Path.GetDirectoryName(path); + + ObjectForScripting ??= new TgsScriptHandler(path); + + _homePage = _resources["/lottie2html.html"]; + NavigateToUri(new Uri("file://quicklook/")); + } + + protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args) + { + try + { + var requestedUri = new Uri(args.Request.Uri); + + if ((requestedUri.Scheme == "https" || requestedUri.Scheme == "http") + && requestedUri.AbsolutePath.EndsWith(".tgs", StringComparison.OrdinalIgnoreCase)) + { + var localPath = Uri.UnescapeDataString($"{requestedUri.Authority}:{requestedUri.AbsolutePath}".Replace('/', '\\')); + + if (localPath.StartsWith(_fallbackPath, StringComparison.OrdinalIgnoreCase)) + { + if (File.Exists(localPath)) + { + var content = TgsExtractor.GetJsonContent(localPath); + byte[] byteArray = Encoding.UTF8.GetBytes(content); + var stream = new MemoryStream(byteArray); + var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse( + stream, 200, "OK", + $""" + Access-Control-Allow-Origin: * + Content-Type: {MimeTypes.GetMimeType()} + """ + ); + args.Response = response; + return; + } + } + } + } + catch (Exception e) + { + // We don't need to feel burdened by any exceptions + Debug.WriteLine(e); + } + + base.WebView_WebResourceRequested(sender, args); + } +} + +[ClassInterface(ClassInterfaceType.AutoDual)] +[ComVisible(true)] +public sealed class TgsScriptHandler(string path) +{ + public string Path { get; } = path; + + public async Task GetPath() + { + return await Task.FromResult(new Uri(Path).AbsolutePath); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsMetaProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsMetaProvider.cs new file mode 100644 index 000000000..7b51e4a54 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Tgs/TgsMetaProvider.cs @@ -0,0 +1,62 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Plugin.ImageViewer.Webview.Lottie; +using System.Collections.Generic; +using System.IO; +using System.Windows; + +namespace QuickLook.Plugin.ImageViewer.Webview.Tgs; + +public class TgsMetaProvider(string path) : IWebMetaProvider +{ + private readonly string _path = path; + private Size _size = Size.Empty; + + public Size GetSize() + { + if (_size != Size.Empty) + { + return _size; + } + + if (!File.Exists(_path)) + { + return _size; + } + + try + { + var jsonString = TgsExtractor.GetJsonContent(_path); + var jsonLottie = LottieParser.Parse>(jsonString); + + if (jsonLottie.ContainsKey("w") + && jsonLottie.ContainsKey("h") + && double.TryParse(jsonLottie["w"].ToString(), out double width) + && double.TryParse(jsonLottie["h"].ToString(), out double height)) + { + return _size = new Size(width, height); + } + } + catch + { + // That's fine, just return the default size. + } + + return new Size(800, 600); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs index e38a3660a..bc6487cb9 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs @@ -20,6 +20,7 @@ using QuickLook.Plugin.ImageViewer.Webview.Lottie; using QuickLook.Plugin.ImageViewer.Webview.Svg; using QuickLook.Plugin.ImageViewer.Webview.Svga; +using QuickLook.Plugin.ImageViewer.Webview.Tgs; using System; using System.IO; using System.Windows; @@ -37,7 +38,8 @@ public static bool TryCanHandle(string path) { ".svg" => SettingHelper.Get("RenderSvgWeb", true, "QuickLook.Plugin.ImageViewer"), ".svga" or ".lottie" => true, - ".json" => LottieDetector.IsVaild(path), // Check for Lottie files + ".tgs" => TgsDetector.IsValidFile(path), // Check for TGS files + ".json" => LottieDetector.IsVaildFile(path), // Check for Lottie files _ => false, }; } @@ -47,7 +49,7 @@ public static bool TryPrepare(string path, ContextObject context, out IWebMetaPr string ext = Path.GetExtension(path).ToLower(); if (ext == ".svg" || ext == ".svga" - || ext == ".lottie" || ext == ".json") + || ext == ".lottie" || ext == ".tgs" || ext == ".json") { if (ext == ".svg") { @@ -63,6 +65,7 @@ public static bool TryPrepare(string path, ContextObject context, out IWebMetaPr ".svg" => new SvgMetaProvider(path), ".svga" => new SvgaMetaProvider(path), ".lottie" or ".json" => new LottieMetaProvider(path), + ".tgs" => new TgsMetaProvider(path), _ => throw new NotSupportedException($"Unsupported file type: {ext}") }; var sizeSvg = metaWeb.GetSize(); @@ -85,7 +88,8 @@ public static bool TryView(string path, ContextObject context, IWebMetaProvider string ext = Path.GetExtension(path).ToLower(); if (ext == ".svg" || ext == ".svga" - || ext == ".lottie" || ext == ".json") + || ext == ".lottie" || ext == ".json" + || ext == ".tgs") { if (ext == ".svg") { @@ -101,6 +105,7 @@ public static bool TryView(string path, ContextObject context, IWebMetaProvider ".svg" => new SvgImagePanel(), ".svga" => new SvgaImagePanel(metaWeb), ".lottie" or ".json" => new LottieImagePanel(), + ".tgs" => new TgsImagePanel(), _ => throw new NotSupportedException($"Unsupported file type: {ext}") }; diff --git a/SUPPORTED_FORMATS.md b/SUPPORTED_FORMATS.md index 67412b9e7..a0c1c14f5 100644 --- a/SUPPORTED_FORMATS.md +++ b/SUPPORTED_FORMATS.md @@ -37,6 +37,7 @@ - `.jxl` (JPEG XL) - `.jxr` (JPEG XR) - `.k25`, `.kdc` (Kodak RAW image) +- `.lottie` (Lottie animation) - `.mdc` (MagicDraw UML) - `.mef` (Mamiya RAW image) - `.mos` (Leaf RAW image) @@ -60,7 +61,9 @@ - `.rw2`, `.rwl`, `.rwz` (Panasonic/Leica RAW image) - `.sr2`, `.srf`, `.srw` (Sony RAW image) - `.svg`, `.svgz` (Scalable Vector Graphics) +- `.svga` (SVGA animation) - `.tga` (Truevision TGA) +- `.tgs` (Telegram sticker, Lottie animation) - `.tif`, `.tiff` (Tagged Image File Format) - `.wdp` (Windows Media Photo) - `.webp` (WebP image)