Files
srt-streamer/scripts/build-icon.js
admin bc65186637 build: add Windows portable EXE packaging scripts
- scripts/build-icon.js — generates icon.png/ico/tray-icon.png
  from pure Node.js (no dependencies, zlib + PNG/ICO encoder)
- scripts/fix-wincodeSign.js — pre-populates electron-builder
  winCodeSign cache by extracting the .7z while ignoring macOS
  symlinks (which fail on Windows without SeCreateSymbolicLink)
- package.json: add fix-wincodeSign script, cross-env, electron-builder
  downgraded to v24 (stable portable target), CSC signing disabled

Build: npm run build:win → dist-electron/SRT-Streamer-Portable-1.0.0.exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 01:12:33 +03:00

154 lines
5.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Generates assets/icon.png and assets/icon.ico
* using only built-in Node.js (no external deps).
*
* ICO contains an embedded 256x256 PNG (Vista+ format).
*/
'use strict'
const zlib = require('zlib')
const fs = require('fs')
const path = require('path')
// ─── CRC32 for PNG chunks ─────────────────────────────────────────────────────
function crc32(buf) {
let crc = 0xFFFFFFFF
for (let i = 0; i < buf.length; i++) {
crc ^= buf[i]
for (let j = 0; j < 8; j++) crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1)
}
return (crc ^ 0xFFFFFFFF) >>> 0
}
function pngChunk(type, data) {
const t = Buffer.from(type, 'ascii')
const len = Buffer.alloc(4); len.writeUInt32BE(data.length)
const crcVal = Buffer.alloc(4); crcVal.writeUInt32BE(crc32(Buffer.concat([t, data])))
return Buffer.concat([len, t, data, crcVal])
}
// ─── Minimal PNG encoder (RGBA, no interlace) ─────────────────────────────────
function makePNG(size, drawFn) {
const stride = 1 + size * 4 // filter byte + RGBA per pixel
const raw = Buffer.alloc(size * stride)
for (let y = 0; y < size; y++) {
raw[y * stride] = 0 // filter: None
for (let x = 0; x < size; x++) {
const [r, g, b, a = 255] = drawFn(x, y, size)
const off = y * stride + 1 + x * 4
raw[off] = r; raw[off+1] = g; raw[off+2] = b; raw[off+3] = a
}
}
const ihdrData = Buffer.alloc(13)
ihdrData.writeUInt32BE(size, 0)
ihdrData.writeUInt32BE(size, 4)
ihdrData[8] = 8 // bit depth
ihdrData[9] = 6 // RGBA
ihdrData[10] = 0; ihdrData[11] = 0; ihdrData[12] = 0
return Buffer.concat([
Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a]), // PNG sig
pngChunk('IHDR', ihdrData),
pngChunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
pngChunk('IEND', Buffer.alloc(0))
])
}
// ─── Draw function ────────────────────────────────────────────────────────────
function drawIcon(x, y, S) {
const cx = S / 2, cy = S / 2
const r = S * 0.48 // outer radius
// Distance from center
const dx = x - cx, dy = y - cy
const dist = Math.sqrt(dx*dx + dy*dy)
// Outside circle → transparent
if (dist > r) return [0, 0, 0, 0]
// Background gradient: dark blue
const t = dist / r
const bg = [
Math.round(26 + t * 8),
Math.round(26 + t * 6),
Math.round(46 + t * 10),
255
]
// ── Play triangle ──────────────────────────────────────────────────────────
const tLeft = cx - S * 0.22
const tRight = cx + S * 0.28
const tTop = cy - S * 0.30
const tBot = cy + S * 0.30
// Point is inside triangle if: (x >= tLeft) AND left of right edge
const edgeTop = tLeft + (tRight - tLeft) * (y - tTop) / (tBot - tTop)
const edgeBot = tLeft + (tRight - tLeft) * (tBot - y) / (tBot - tTop)
const inTri = x >= tLeft && x <= edgeTop && x <= edgeBot
&& y >= tTop && y <= tBot
if (inTri) {
// Accent blue gradient
const fx = (x - tLeft) / (tRight - tLeft)
return [
Math.round(79 + fx * 50),
Math.round(142 + fx * 30),
Math.round(247 - fx * 50),
255
]
}
// ── Red "LIVE" dot (top-right) ─────────────────────────────────────────────
const dotX = cx + S * 0.25, dotY = cy - S * 0.26, dotR = S * 0.12
const ddx = x - dotX, ddy = y - dotY
const dotDist = Math.sqrt(ddx*ddx + ddy*ddy)
if (dotDist <= dotR) {
const inner = dotR * 0.45
if (dotDist <= inner) return [255, 255, 255, 255] // white center
return [239, 68, 68, 255] // red ring
}
return bg
}
// ─── Wrap PNG in ICO ──────────────────────────────────────────────────────────
function pngToIco(pngBuf, size) {
const header = Buffer.alloc(6)
header.writeUInt16LE(0, 0) // reserved
header.writeUInt16LE(1, 2) // type: icon
header.writeUInt16LE(1, 4) // image count
const entry = Buffer.alloc(16)
entry[0] = size >= 256 ? 0 : size // width (0 = 256)
entry[1] = size >= 256 ? 0 : size // height (0 = 256)
entry[2] = 0 // color count
entry[3] = 0 // reserved
entry.writeUInt16LE(1, 4) // planes
entry.writeUInt16LE(32, 6) // bit count
entry.writeUInt32LE(pngBuf.length, 8) // bytes in image
entry.writeUInt32LE(6 + 16, 12) // offset = header + 1 entry
return Buffer.concat([header, entry, pngBuf])
}
// ─── Main ─────────────────────────────────────────────────────────────────────
const assetsDir = path.join(__dirname, '..', 'assets')
if (!fs.existsSync(assetsDir)) fs.mkdirSync(assetsDir, { recursive: true })
console.log('Generating icon 256×256 PNG…')
const png256 = makePNG(256, drawIcon)
fs.writeFileSync(path.join(assetsDir, 'icon.png'), png256)
console.log(' ✓ assets/icon.png')
console.log('Wrapping in ICO…')
const ico = pngToIco(png256, 256)
fs.writeFileSync(path.join(assetsDir, 'icon.ico'), ico)
console.log(' ✓ assets/icon.ico')
// 16px tray icon
console.log('Generating tray icon 16×16…')
const png16 = makePNG(16, drawIcon)
fs.writeFileSync(path.join(assetsDir, 'tray-icon.png'), png16)
console.log(' ✓ assets/tray-icon.png')
console.log('Done.')