学用大语言模型(8):通过Kimi批量更改截图文件名称
ztj100 2025-08-01 22:14 3 浏览 0 评论
经常截图,需要根据截图内容对文件更名。
包括没有文字的截图,获取图片可能传递的信息。
用kimi的api,实现了一个简单的应用。
比如:
获取指定文件夹的图片:
进行处理后:
借助ai,整理的主要代码:
// ------------------------------------------------------------
// File: Form1.cs
// 大语言模型文件管理器 – 支持 Moonshot(Kimi) Vision API
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 大语言模型文件管理器
{
public partial class Form1 : Form
{
private readonly KimiApiClient _kimiClient;
public Form1()
{
InitializeComponent();
// 如需从环境变量读取,可把 apiKey 设为 null
_kimiClient = new KimiApiClient("myKey");
InitializeListView();
}
#region UI 初始化
private void InitializeListView()
{
listView1.View = View.Details;
listView1.CheckBoxes = true;
listView1.Columns.Add("文件名", 800);
listView1.Columns.Add("状态", 200);
listView1.Columns.Add("识别结果", this.listView1.Width-1100);
}
#endregion
#region 事件处理
private void btnBrowse_Click(object sender, EventArgs e)
{
using var dlg = new FolderBrowserDialog();
if (dlg.ShowDialog() == DialogResult.OK)
LoadImagesFromFolder(dlg.SelectedPath);
}
private async void btnProcess_Click(object sender, EventArgs e)
{
if (listView1.CheckedItems.Count == 0)
{
MessageBox.Show("请先选择要处理的图片", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
btnProcess.Enabled = false;
progressBar1.Maximum = listView1.CheckedItems.Count;
progressBar1.Value = 0;
await ProcessCheckedItemsAsync();
btnProcess.Enabled = true;
MessageBox.Show("处理完成", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
#endregion
#region 业务逻辑
private void LoadImagesFromFolder(string folderPath)
{
listView1.Items.Clear();
var exts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ ".jpg",".jpeg",".png",".bmp",".gif",".tiff" };
try
{
foreach (var file in Directory.EnumerateFiles(folderPath)
.Where(f => exts.Contains(Path.GetExtension(f))))
{
var item = new ListViewItem(Path.GetFileName(file));
item.SubItems.Add("就绪");
item.SubItems.Add("");
item.Tag = file;
listView1.Items.Add(item);
}
}
catch (Exception ex)
{
MessageBox.Show(#34;加载图片时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async Task ProcessCheckedItemsAsync()
{
foreach (ListViewItem item in listView1.CheckedItems)
{
try
{
string imagePath = item.Tag.ToString();
item.SubItems[1].Text = "处理中";
listView1.Refresh();
var result = await _kimiClient.ProcessImageAsync(imagePath);
var shortName = await _kimiClient.SummarizeToFileNameAsync(result.RecognitionResult);
if (!string.IsNullOrEmpty(result.RecognitionResult))
{
item.SubItems[2].Text = shortName;
if (!string.IsNullOrEmpty(result.SuggestedFileName))
{
string dir = Path.GetDirectoryName(imagePath);
string ext = Path.GetExtension(imagePath);
string newName = #34;{shortName}{ext}";
string newPath = Path.Combine(dir, newName);
if (File.Exists(newPath))
newPath = Path.Combine(dir,
#34;{shortName}_{Guid.NewGuid():N[..8]}{ext}");
File.Move(imagePath, newPath);
item.Text = Path.GetFileName(newPath);
item.Tag = newPath;
item.SubItems[1].Text = "已重命名";
}
else
{
item.SubItems[1].Text = "命名失败";
}
}
else
{
item.SubItems[1].Text = "识别失败";
}
}
catch (Exception ex)
{
item.SubItems[1].Text = "错误";
item.SubItems[2].Text = ex.Message;
}
progressBar1.Value++;
await Task.Delay(50);
}
}
#endregion
}
#region 数据模型
public class ProcessResult
{
public string RecognitionResult { get; set; }
public string SuggestedFileName { get; set; }
}
#endregion
#region API 客户端
public class KimiApiClient : IDisposable
{
private readonly HttpClient _http;
private readonly string _apiKey;
private readonly string _endpoint;
private readonly string _model;
public KimiApiClient(string apiKey = null,
string endpoint = "https://api.moonshot.cn/v1/chat/completions",
string model = "moonshot-v1-8k-vision-preview")
{
_apiKey = apiKey ?? Environment.GetEnvironmentVariable("KIMI_API_KEY");
_endpoint = endpoint;
_model = model;
if (string.IsNullOrWhiteSpace(_apiKey))
throw new InvalidOperationException("API 密钥未设置");
_http = new HttpClient();
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<ProcessResult> ProcessImageAsync(string imagePath)
{
var result = new ProcessResult();
try
{
// 1. OCR
var ocr = await AskVisionAsync(imagePath,
"请完整识别图片中出现的所有文字,不要额外解释。只需要一句话。");
if (!string.IsNullOrWhiteSpace(ocr))
{
result.RecognitionResult = ocr.Trim();
result.SuggestedFileName = CleanFileName(ocr);
return result;
}
// 2. 描述
var desc = await AskVisionAsync(imagePath,
"请用简洁的中文描述图片中的主要内容,不要包含文件名信息。只需要一句话。");
if (!string.IsNullOrWhiteSpace(desc))
{
result.RecognitionResult = desc.Trim();
result.SuggestedFileName = CleanFileName(desc);
return result;
}
// 3. 兜底
result.RecognitionResult = "无法识别内容";
result.SuggestedFileName = #34;Kimi_{DateTime.Now:yyyyMMddHHmmss}";
return result;
}
catch (Exception ex)
{
result.RecognitionResult = #34;错误: {ex.Message}";
return result;
}
}
/// <summary>
/// 把识别结果文本提交给大模型,返回 ≤50 字符的合法文件名片段
/// </summary>
public async Task<string> SummarizeToFileNameAsync(string recognitionResult)
{
if (string.IsNullOrWhiteSpace(recognitionResult))
return "未知";
const int maxChars = 50;
var prompt =
#34;请把以下文本归纳为不超过{maxChars}个字符的简短概要," +
#34;只保留关键中文信息,不要出现标点符号,不要包括逗号、句号、空格等,不要包括其他特殊字符,仅仅是全中文的有意义的概述。\n\n{recognitionResult}";
var payload = new
{
model = "moonshot-v1-8k", // 普通文本模型即可
messages = new[]
{
new { role = "user", content = prompt }
},
max_tokens = 32,
temperature = 0.1
};
var json = JsonSerializer.Serialize(payload,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
using var content = new StringContent(json, Encoding.UTF8, "application/json");
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15));
var resp = await _http.PostAsync(_endpoint, content, cts.Token);
resp.EnsureSuccessStatusCode();
var respJson = await resp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(respJson);
var summary = doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString()?
.Trim() ?? "未知";
// 再做一次本地合法性清理
var invalid = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(summary.Length);
foreach (var c in summary.Take(maxChars))
sb.Append(invalid.Contains(c) ? '_' : c);
string cleaned = sb.ToString().Trim('_', '-', ' ');
cleaned = cleaned.Replace(" ", "");
return string.IsNullOrEmpty(cleaned) ? "未知" : cleaned;
}
#region 内部方法
private async Task<string> AskVisionAsync(string imagePath, string prompt)
{
var bytes = await File.ReadAllBytesAsync(imagePath);
var b64 = Convert.ToBase64String(bytes);
var mime = MimeMapping.GetMimeType(imagePath);
var payload = new
{
model = _model,
messages = new[]
{
new
{
role = "user",
content = new object[]
{
new { type = "text", text = prompt },
new { type = "image_url",
image_url = new { url = #34;data:{mime};base64,{b64}" } }
}
}
},
max_tokens = 300,
temperature = 0.1
};
var json = JsonSerializer.Serialize(payload,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
var body = new StringContent(json, Encoding.UTF8, "application/json");
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(30));
var resp = await _http.PostAsync(_endpoint, body, cts.Token);
resp.EnsureSuccessStatusCode();
var respJson = await resp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(respJson);
return doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString();
}
private static string CleanFileName(string input)
{
if (string.IsNullOrWhiteSpace(input)) return null;
var invalid = Path.GetInvalidFileNameChars();
var sb = new StringBuilder();
foreach (var c in input)
{
if (invalid.Contains(c))
sb.Append('_');
else
sb.Append(c);
}
var cleaned = sb.ToString().Trim();
if (cleaned.Length > 200) cleaned = cleaned[..200].Trim();
while (cleaned.EndsWith('.') || cleaned.EndsWith(' '))
cleaned = cleaned[..^1];
return cleaned;
}
#endregion
public void Dispose() => _http?.Dispose();
}
internal static class MimeMapping
{
public static string GetMimeType(string path) =>
Path.GetExtension(path).ToLowerInvariant() switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".bmp" => "image/bmp",
".gif" => "image/gif",
".tiff" => "image/tiff",
_ => "application/octet-stream"
};
}
#endregion
}
相关推荐
- Jquery 详细用法
-
1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...
- 前端开发79条知识点汇总
-
1.css禁用鼠标事件2.get/post的理解和他们之间的区别http超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP的工作方式是客户机与服务器之间的请求-应答协议。...
- js基础面试题92-130道题目
-
92.说说你对作用域链的理解参考答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。...
- Web前端必备基础知识点,百万网友:牛逼
-
1、Web中的常见攻击方式1.SQL注入------常见的安全性问题。解决方案:前端页面需要校验用户的输入数据(限制用户输入的类型、范围、格式、长度),不能只靠后端去校验用户数据。一来可以提高后端处理...
- 事件——《JS高级程序设计》
-
一、事件流1.事件流描述的是从页面中接收事件的顺序2.事件冒泡(eventbubble):事件从开始时由最具体的元素(就是嵌套最深的那个节点)开始,逐级向上传播到较为不具体的节点(就是Docu...
- 前端开发中79条不可忽视的知识点汇总
-
过往一些不足的地方,通过博客,好好总结一下。1.css禁用鼠标事件...
- Chrome 开发工具之Network
-
经常会听到比如"为什么我的js代码没执行啊?","我明明发送了请求,为什么反应?","我这个网站怎么加载的这么慢?"这类的问题,那么问题既然存在,就需要去解决它,需要解决它,首先我们得找对导致问题的原...
- 轻量级 React.js 虚拟美化滚动条组件RScroll
-
前几天有给大家分享一个Vue自定义滚动条组件VScroll。今天再分享一个最新开发的ReactPC端模拟滚动条组件RScroll。...
- 一文解读JavaScript事件对象和表单对象
-
前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScrip...
- Python函数参数黑科技:*args与**kwargs深度解析
-
90%的Python程序员不知道,可变参数设计竟能决定函数的灵活性和扩展性!掌握这些技巧,让你的函数适应任何场景!一、函数参数设计的三大进阶技巧...
- 深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名
-
在现代计算领域,信息安全逐渐成为焦点话题。密码学,作为信息保护的关键技术之一,允许我们加密(保密)和解密(解密)数据。...
- 阿里Nacos惊爆安全漏洞,火速升级!(附修复建议)
-
前言好,我是threedr3am,我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentitykey-value修复机制,依然存在绕过问题,在nacos开启了...
- Python模块:zoneinfo时区支持详解
-
一、知识导图二、知识讲解(一)zoneinfo模块概述...
- Golang开发的一些注意事项(一)
-
1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...
- Python鼠标与键盘自动化指南:从入门到进阶——键盘篇
-
`pynput`是一个用于控制和监控鼠标和键盘的Python库...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)