Add support for Telegram Sticker (.tgs) files (#1762)
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled

This commit is contained in:
Copilot
2025-09-22 04:57:56 +08:00
committed by GitHub
parent 1711874779
commit ad95955666
7 changed files with 254 additions and 4 deletions

View File

@@ -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<Dictionary<string, object>>(jsonString);

View File

@@ -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 <http://www.gnu.org/licenses/>.
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
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<string> GetPath()
{
return await Task.FromResult(new Uri(Path).AbsolutePath);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
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<Dictionary<string, object>>(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);
}
}

View File

@@ -20,6 +20,7 @@ using QuickLook.Common.Plugin;
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 @@ internal static class WebHandler
{
".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 @@ internal static class WebHandler
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 @@ internal static class WebHandler
".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 @@ internal static class WebHandler
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 @@ internal static class WebHandler
".svg" => new SvgImagePanel(),
".svga" => new SvgaImagePanel(metaWeb),
".lottie" or ".json" => new LottieImagePanel(),
".tgs" => new TgsImagePanel(),
_ => throw new NotSupportedException($"Unsupported file type: {ext}")
};

View File

@@ -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)