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>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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.')
|
||||
Reference in New Issue
Block a user