上传 HAR 文件,自动提取 SVG 图标并转换为 256x256 PNG 高清图片
首页 友情 登录
上传 HAR 文件,自动提取 SVG 图标并转换为 256x256 PNG 高清图片

本文最后更新于10天前,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

您阅读本篇文章共花了: 

set_time_limit(0);

ini_set('memory_limit', '1024M');

$baseDir = __DIR__ . '/har_tasks';

if (!is_dir($baseDir)) {

mkdir($baseDir, 0777, true);

}

function json_response($data) {

header('Content-Type: application/json; charset=utf-8');

echo json_encode($data, JSON_UNESCAPED_UNICODE);

exit;

}

function safe_task_id($id) {

return preg_replace('/^a-zA-Z0-9_-/', '', $id);

}

function task_path($taskId) {

global $baseDir;

return $baseDir . '/' . safe_task_id($taskId);

}

function progress_file($taskId) {

return task_path($taskId) . '/progress.json';

}

function save_progress($taskId, $data) {

file_put_contents(progress_file($taskId), json_encode($data, JSON_UNESCAPED_UNICODE

JSON_PRETTY_PRINT));

}

function read_progress($taskId) {

$file = progress_file($taskId);

if (!file_exists($file)) return null;

return json_decode(file_get_contents($file), true);

}

function extract_svg_links_from_har($file) {

$json = file_get_contents($file);

$data = json_decode($json, true);

if (!$data

empty($data'log''entries')) {

return [];

}

$links = [];

foreach ($data'log''entries' as $entry) {

$url = $entry'request''url' ?? '';

if (!$url) continue;

$path = parse_url($url, PHP_URL_PATH);

if (

strpos($url, 'https://cdn.file.mixinnet.cn/icon') === 0 &&

stripos($path, '.svg') !== false

) {

$links[] = $url;

}

}

return array_values(array_unique($links));

}

function curl_download($url, $savePath, $maxRetries = 3) {

$retryCount = 0;

while ($retryCount < $maxRetries) {

$fp = fopen($savePath, 'w');

if (!$fp) {

return false, '无法创建文件';

}

$ch = curl_init();

curl_setopt_array($ch, [

CURLOPT_URL => $url,

CURLOPT_FILE => $fp,

CURLOPT_FOLLOWLOCATION => true,

CURLOPT_TIMEOUT => 60,

CURLOPT_CONNECTTIMEOUT => 15,

CURLOPT_SSL_VERIFYPEER => false,

CURLOPT_SSL_VERIFYHOST => false,

CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36',

CURLOPT_HTTPHEADER => [

'Accept: image/svg+xml,image/*,*/*;q=0.8',

'Accept-Language: zh-CN,zh;q=0.9',

'Referer: https://sc.mixinnet.cn/',

],

]);

$ok = curl_exec($ch);

$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

$err = curl_error($ch);

curl_close($ch);

fclose($fp);

if ($ok && $code < 400 && file_exists($savePath) && filesize($savePath) > 0) {

// 验证 SVG 内容

$content = file_get_contents($savePath, false, null, 0, 1000);

if (stripos($content, 'stripos($content, 'xml') !== false) {

return true, null;

} else {

@unlink($savePath);

return false, '下载的内容不是有效的 SVG 格式';

}

}

@unlink($savePath);

$retryCount++;

if ($retryCount < $maxRetries) {

usleep(500000); // 等待 0.5 秒后重试

}

}

return false, "下载失败,已重试 {$maxRetries} 次。HTTP: {$code}, Error: {$err}";

}

// 方法1: 使用 rsvg-convert (推荐)

function svg_to_png_rsvg($svgPath, $pngPath, $size = 256) {

// 检查 rsvg-convert 是否可用

$checkCmd = "command -v rsvg-convert";

exec($checkCmd, $checkOutput, $checkRet);

if ($checkRet !== 0) {

throw new Exception('rsvg-convert 未安装');

}

// 使用 rsvg-convert 转换

$cmd = sprintf(

'rsvg-convert -w %d -h %d --keep-aspect-ratio --background-color=transparent %s -o %s 2>&1',

intval($size),

intval($size),

escapeshellarg($svgPath),

escapeshellarg($pngPath)

);

exec($cmd, $output, $ret);

if ($ret !== 0

!file_exists($pngPath)

filesize($pngPath) <= 0) {

$errorMsg = implode("n", $output);

throw new Exception("rsvg-convert 转换失败: {$errorMsg}");

}

// 验证 PNG 文件

$pngContent = file_get_contents($pngPath, false, null, 0, 100);

if (strpos($pngContent, 'PNG') === false) {

throw new Exception('生成的文件不是有效的 PNG 格式');

}

return true;

}

// 方法2: 使用 ImageMagick

function svg_to_png_imagick($svgPath, $pngPath, $size = 256) {

if (!extension_loaded('imagick')) {

throw new Exception('ImageMagick 扩展未加载');

}

try {

$imagick = new Imagick();

$imagick->setResolution(300, 300);

$imagick->readImage($svgPath);

$imagick->setImageFormat('png');

$imagick->resizeImage($size, $size, Imagick::FILTER_LANCZOS, 1);

$imagick->writeImage($pngPath);

$imagick->clear();

if (!file_exists($pngPath)

filesize($pngPath) <= 0) {

throw new Exception('ImageMagick 生成 PNG 失败');

}

return true;

} catch (Exception $e) {

throw new Exception('ImageMagick 转换失败: ' . $e->getMessage());

}

}

// 方法3: 使用 GD + 外部工具(备选)

function svg_to_png_gd($svgPath, $pngPath, $size = 256) {

// 创建一个临时 PNG 文件

$tempPng = dirname($pngPath) . '/temp_' . uniqid() . '.png';

// 尝试使用 rsvg-convert

$cmd = sprintf(

'rsvg-convert -w %d -h %d %s -o %s 2>&1',

intval($size),

intval($size),

escapeshellarg($svgPath),

escapeshellarg($tempPng)

);

exec($cmd, $output, $ret);

if ($ret === 0 && file_exists($tempPng) && filesize($tempPng) > 0) {

rename($tempPng, $pngPath);

return true;

}

if (file_exists($tempPng)) {

@unlink($tempPng);

}

throw new Exception('GD 转换失败,请安装 rsvg-convert 或 ImageMagick');

}

// 主转换函数(自动选择最佳方法)

function svg_to_png_hd($svgPath, $pngPath, $size = 256) {

$errors = [];

// 尝试方法1: rsvg-convert

try {

return svg_to_png_rsvg($svgPath, $pngPath, $size);

} catch (Exception $e) {

$errors[] = $e->getMessage();

}

// 尝试方法2: ImageMagick

try {

return svg_to_png_imagick($svgPath, $pngPath, $size);

} catch (Exception $e) {

$errors[] = $e->getMessage();

}

// 尝试方法3: GD (备选)

try {

return svg_to_png_gd($svgPath, $pngPath, $size);

} catch (Exception $e) {

$errors[] = $e->getMessage();

}

// 所有方法都失败

throw new Exception('所有转换方法都失败了: ' . implode('; ', $errors));

}

function clean_filename_from_url($url, $index) {

$path = parse_url($url, PHP_URL_PATH);

$name = basename($path);

$name = preg_replace('/.svg$/i', '', $name);

$name = preg_replace('/^a-zA-Z0-9_-/', '_', $name);

if ($name === '') {

$name = 'svg_' . str_pad((string)$index, 4, '0', STR_PAD_LEFT);

}

return str_pad((string)$index, 4, '0', STR_PAD_LEFT) . '_' . $name;

}

function process_task($taskId, $pngSize) {

$dir = task_path($taskId);

$harFile = $dir . '/input.har';

$svgDir = $dir . '/svg';

$pngDir = $dir . '/png';

$zipFile = $dir . '/result.zip';

if (!is_dir($svgDir)) mkdir($svgDir, 0777, true);

if (!is_dir($pngDir)) mkdir($pngDir, 0777, true);

$links = extract_svg_links_from_har($harFile);

$total = count($links);

if ($total === 0) {

save_progress($taskId, [

'status' => 'error',

'message' => 'HAR 文件中未找到 https://cdn.file.mixinnet.cn/icon 下的 SVG 链接',

'total' => 0,

'downloaded' => 0,

'converted' => 0,

'failed' => 0,

'zip_ready' => false,

]);

return;

}

$progress = [

'status' => 'running',

'message' => '开始下载 SVG',

'total' => $total,

'downloaded' => 0,

'converted' => 0,

'failed' => 0,

'zip_ready' => false,

'download_url' => '',

];

save_progress($taskId, $progress);

$downloadedFiles = [];

foreach ($links as $i => $url) {

$index = $i + 1;

$name = clean_filename_from_url($url, $index);

$svgPath = $svgDir . '/' . $name . '.svg';

$ok, $err = curl_download($url, $svgPath);

if (!$ok) {

$progress'failed'++;

$progress'message' = "下载失败:{$url} - {$err}";

save_progress($taskId, $progress);

continue;

}

$downloadedFiles[] = [

'name' => $name,

'svg' => $svgPath,

'url' => $url,

];

$progress'downloaded'++;

$progress'message' = "正在下载 SVG:{$progress'downloaded'} / {$total}";

save_progress($taskId, $progress);

}

$progress'message' = '开始高清转换 PNG (256x256)';

save_progress($taskId, $progress);

foreach ($downloadedFiles as $item) {

try {

$pngPath = $pngDir . '/' . $item'name' . '.png';

svg_to_png_hd($item'svg', $pngPath, 256); // 固定使用 256x256

$progress'converted'++;

$progress'message' = "正在转换 PNG:{$progress'converted'} / " . count($downloadedFiles);

save_progress($taskId, $progress);

} catch (Throwable $e) {

$progress'failed'++;

$progress'message' = '转换失败:' . $e->getMessage();

save_progress($taskId, $progress);

// 记录详细错误日志

$errorLog = $dir . '/error.log';

$logEntry = date('Y-m-d H:i:s') . " - {$item'name'}: " . $e->getMessage() . "n";

file_put_contents($errorLog, $logEntry, FILE_APPEND);

}

}

$progress'message' = '正在打包 ZIP';

save_progress($taskId, $progress);

$zip = new ZipArchive();

if ($zip->open($zipFile, ZipArchive::CREATE

ZipArchive::OVERWRITE) !== true) {

$progress'status' = 'error';

$progress'message' = '创建 ZIP 失败';

save_progress($taskId, $progress);

return;

}

// 添加 PNG 文件

$pngFiles = glob($pngDir . '/*.png');

if (count($pngFiles) > 0) {

foreach ($pngFiles as $file) {

$zip->addFile($file, 'png/' . basename($file));

}

}

// 添加 SVG 文件

$svgFiles = glob($svgDir . '/*.svg');

if (count($svgFiles) > 0) {

foreach ($svgFiles as $file) {

$zip->addFile($file, 'svg/' . basename($file));

}

}

$info = "总 SVG 链接数:{$total}n";

$info .= "下载成功:{$progress'downloaded'}n";

$info .= "转换成功:{$progress'converted'}n";

$info .= "失败数量:{$progress'failed'}n";

$info .= "PNG 输出尺寸:256x256n";

$info .= "转换时间:" . date('Y-m-d H:i:s') . "nn";

$info .= "SVG 链接列表:n" . implode("n", $links) . "n";

$zip->addFromString('readme.txt', $info);

$zip->close();

$progress'status' = 'done';

$progress'message' = '处理完成,共 ' . $progress'converted' . ' 个 PNG 文件';

$progress'zip_ready' = true;

$progress'download_url' = '?action=download&task_id=' . urlencode($taskId);

save_progress($taskId, $progress);

}

$action = $_GET'action' ?? '';

if ($action === 'upload') {

if (empty($_FILES'har''tmp_name')) {

json_response('success' => false, 'message' => '请选择 HAR 文件');

}

$taskId = date('YmdHis') . '_' . mt_rand(1000, 9999);

$dir = task_path($taskId);

mkdir($dir, 0777, true);

move_uploaded_file($_FILES'har''tmp_name', $dir . '/input.har');

save_progress($taskId, [

'status' => 'queued',

'message' => 'HAR 上传成功,等待处理',

'total' => 0,

'downloaded' => 0,

'converted' => 0,

'failed' => 0,

'zip_ready' => false,

]);

json_response('success' => true, 'task_id' => $taskId);

}

if ($action === 'process') {

$taskId = $_POST'task_id' ?? '';

$dir = task_path($taskId);

if (!$taskId

!is_dir($dir)) {

json_response('success' => false, 'message' => '任务不存在');

}

process_task($taskId, 256);

json_response('success' => true);

}

if ($action === 'progress') {

$taskId = $_GET'task_id' ?? '';

$progress = read_progress($taskId);

if (!$progress) {

json_response('success' => false, 'message' => '进度不存在');

}

$progress'success' = true;

json_response($progress);

}

if ($action === 'check_deps') {

$deps = [

'rsvg_convert' => false,

'imagick' => false,

'gd' => false,

];

// 检查 rsvg-convert

exec("command -v rsvg-convert", $output, $ret);

$deps'rsvg_convert' = ($ret === 0);

// 检查 imagick

$deps'imagick' = extension_loaded('imagick');

// 检查 gd

$deps'gd' = extension_loaded('gd');

json_response('success' => true, 'dependencies' => $deps);

}

if ($action === 'download') {

$taskId = $_GET'task_id' ?? '';

$zipFile = task_path($taskId) . '/result.zip';

if (!file_exists($zipFile)) {

die('ZIP 文件不存在');

}

header('Content-Type: application/zip');

header('Content-Disposition: attachment; filename="svg_png_' . safe_task_id($taskId) . '.zip"');

header('Content-Length: ' . filesize($zipFile));

readfile($zipFile);

exit;

}

?>

HAR SVG 转 PNG (256x256) 工具

* { box-sizing: border-box; }

body {

margin: 0;

min-height: 100vh;

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, "Microsoft YaHei", sans-serif;

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

display: flex;

align-items: center;

justify-content: center;

padding: 20px;

}

.container {

width: 800px;

max-width: 100%;

background: #fff;

border-radius: 20px;

padding: 40px;

box-shadow: 0 20px 60px rgba(0,0,0,0.3);

}

h1 {

margin: 0 0 10px 0;

text-align: center;

font-size: 28px;

color: #333;

}

.desc {

text-align: center;

color: #666;

margin-bottom: 30px;

line-height: 1.6;

}

.upload-box {

border: 2px dashed #667eea;

background: #f8f9ff;

border-radius: 16px;

padding: 40px 20px;

text-align: center;

transition: all 0.3s;

cursor: pointer;

}

.upload-box.dragover {

background: #eef2ff;

border-color: #764ba2;

}

.upload-icon {

font-size: 48px;

margin-bottom: 10px;

}

.upload-btn {

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

border: none;

border-radius: 10px;

padding: 12px 30px;

color: white;

font-size: 16px;

cursor: pointer;

margin-top: 10px;

transition: transform 0.2s;

}

.upload-btn:hover {

transform: translateY(-2px);

}

.file-name {

margin-top: 15px;

color: #666;

font-size: 14px;

}

.start-btn {

width: 100%;

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

border: none;

border-radius: 10px;

padding: 14px;

color: white;

font-size: 16px;

font-weight: bold;

cursor: pointer;

margin-top: 20px;

transition: transform 0.2s;

}

.start-btn:hover:not(:disabled) {

transform: translateY(-2px);

}

.start-btn:disabled {

opacity: 0.5;

cursor: not-allowed;

}

.progress-area {

display: none;

margin-top: 30px;

padding: 20px;

background: #f8f9fa;

border-radius: 12px;

}

.status-text {

color: #333;

margin-bottom: 15px;

font-size: 14px;

font-weight: bold;

}

.progress-row {

margin-bottom: 15px;

}

.progress-label {

display: flex;

justify-content: space-between;

color: #666;

margin-bottom: 5px;

font-size: 13px;

}

.progress-bar {

height: 8px;

background: #e0e0e0;

border-radius: 10px;

overflow: hidden;

}

.progress-inner {

height: 100%;

width: 0%;

background: linear-gradient(90deg, #667eea, #764ba2);

transition: width 0.3s ease;

}

.progress-inner.convert {

background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);

}

.stats {

display: grid;

grid-template-columns: repeat(4, 1fr);

gap: 10px;

margin-top: 20px;

}

.stat {

background: white;

border-radius: 10px;

padding: 12px;

text-align: center;

box-shadow: 0 2px 4px rgba(0,0,0,0.1);

}

.stat strong {

display: block;

color: #333;

font-size: 24px;

}

.stat span {

color: #999;

font-size: 12px;

}

.download-btn {

display: none;

width: 100%;

background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);

border: none;

border-radius: 10px;

padding: 14px;

color: white;

font-size: 16px;

font-weight: bold;

text-decoration: none;

text-align: center;

margin-top: 20px;

transition: transform 0.2s;

}

.download-btn:hover {

transform: translateY(-2px);

}

.deps-check {

margin-top: 20px;

padding: 15px;

background: #fef3c7;

border-radius: 10px;

font-size: 13px;

}

.deps-check h4 {

margin: 0 0 10px 0;

color: #92400e;

}

.deps-check ul {

margin: 5px 0;

padding-left: 20px;

}

.deps-check li {

color: #78350f;

margin: 5px 0;

}

.deps-check .ok {

color: #10b981;

}

.deps-check .error {

color: #ef4444;

}

.tips {

margin-top: 20px;

padding: 15px;

background: #e0e7ff;

border-radius: 10px;

font-size: 13px;

line-height: 1.6;

}

🎨 HAR 提取 SVG → PNG

上传 HAR 文件,自动提取 SVG 图标并转换为 256x256 PNG 高清图片

📁

或拖拽文件到此区域

等待开始...

📥 下载进度

0%

🖼️ 转换进度

0%

0总数
0已下载
0已转换
0失败

📦 下载 ZIP 文件

🔧 系统依赖检查

检查中...

💡 提示:

• 转换后的 PNG 尺寸固定为 256x256 像素

• ZIP 包含原始 SVG 和转换后的 PNG 文件

• 支持 rsvg-convert、ImageMagick 多种转换方式

let selectedFile = null;

let taskId = null;

let polling = null;

const fileInput = document.getElementById('harFile');

const fileName = document.getElementById('fileName');

const startBtn = document.getElementById('startBtn');

const progressArea = document.getElementById('progressArea');

const statusText = document.getElementById('statusText');

const dropBox = document.getElementById('dropBox');

// 检查依赖

async function checkDependencies() {

try {

const res = await fetch('?action=check_deps');

const data = await res.json();

if (data.success) {

const deps = data.dependencies;

let html = '

    ';

html += </p><li>${deps.rsvg_convert ? '✅' : '❌'} rsvg-convert ${deps.rsvg_convert ? '(已安装)' : '(未安装 - 推荐安装)'}</li>;

html += </p><li>${deps.imagick ? '✅' : '⚠️'} ImageMagick ${deps.imagick ? '(已安装)' : '(未安装 - 可选)'}</li>;

html += </p><li>${deps.gd ? '✅' : '⚠️'} GD ${deps.gd ? '(已安装)' : '(未安装 - 可选)'}</li>;

html += '';

if (!deps.rsvg_convert) {

html += '

⚠️ 建议安装 rsvg-convert 以获得最佳转换效果:

';

html += '

Ubuntu/Debian: sudo apt-get install librsvg2-bin
';

html += 'CentOS/RHEL: sudo yum install librsvg2-tools

';

} else {

html += '

✅ 系统配置正常,可以开始转换

';

}

document.getElementById('depsStatus').innerHTML = html;

}

} catch (e) {

document.getElementById('depsStatus').innerHTML = '

无法检查依赖状态

';

}

}

fileInput.addEventListener('change', function () {

selectedFile = this.files0;

if (selectedFile) {

fileName.textContent = selectedFile.name;

startBtn.disabled = false;

}

});

dropBox.addEventListener('dragover', function (e) {

e.preventDefault();

dropBox.classList.add('dragover');

});

dropBox.addEventListener('dragleave', function () {

dropBox.classList.remove('dragover');

});

dropBox.addEventListener('drop', function (e) {

e.preventDefault();

dropBox.classList.remove('dragover');

selectedFile = e.dataTransfer.files0;

if (selectedFile && selectedFile.name.endsWith('.har')) {

fileName.textContent = selectedFile.name;

startBtn.disabled = false;

} else {

alert('请上传 .har 格式的文件');

}

});

startBtn.addEventListener('click', async function () {

if (!selectedFile) return;

startBtn.disabled = true;

progressArea.style.display = 'block';

statusText.textContent = '📤 正在上传 HAR 文件...';

const formData = new FormData();

formData.append('har', selectedFile);

try {

const uploadRes = await fetch('?action=upload', {

method: 'POST',

body: formData

});

const uploadData = await uploadRes.json();

if (!uploadData.success) {

alert(uploadData.message

'上传失败');

startBtn.disabled = false;

return;

}

taskId = uploadData.task_id;

// 开始轮询进度

if (polling) clearInterval(polling);

polling = setInterval(loadProgress, 1000);

// 启动后台处理

const processRes = await fetch('?action=process', {

method: 'POST',

headers: {'Content-Type': 'application/x-www-form-urlencoded'},

body: 'task_id=' + encodeURIComponent(taskId)

});

} catch (error) {

alert('上传失败: ' + error.message);

startBtn.disabled = false;

}

});

async function loadProgress() {

if (!taskId) return;

try {

const res = await fetch('?action=progress&task_id=' + encodeURIComponent(taskId));

const data = await res.json();

if (!data.success) return;

statusText.textContent = data.message

'处理中...';

const total = data.total

0;

const downloaded = data.downloaded

0;

const converted = data.converted

0;

const failed = data.failed

0;

const downloadPercent = total > 0 ? Math.round(downloaded / total * 100) : 0;

const convertPercent = downloaded > 0 ? Math.round(converted / downloaded * 100) : 0;

document.getElementById('downloadBar').style.width = downloadPercent + '%';

document.getElementById('convertBar').style.width = convertPercent + '%';

document.getElementById('downloadPercent').textContent = downloadPercent + '%';

document.getElementById('convertPercent').textContent = convertPercent + '%';

document.getElementById('totalNum').textContent = total;

document.getElementById('downloadNum').textContent = downloaded;

document.getElementById('convertNum').textContent = converted;

document.getElementById('failNum').textContent = failed;

if (data.status === 'done') {

clearInterval(polling);

const btn = document.getElementById('downloadBtn');

btn.style.display = 'block';

btn.href = data.download_url;

statusText.textContent = '✅ ' + data.message;

}

if (data.status === 'error') {

clearInterval(polling);

alert('处理失败: ' + data.message);

startBtn.disabled = false;

}

} catch (error) {

console.error('获取进度失败:', error);

}

}

// 页面加载时检查依赖

checkDependencies();

上一篇:青春如炬,照亮复兴征程
下一篇:关于本站

服务热线

0

我是描述

微信客服

微信客服