Skip to content

Commit 18d37d9

Browse files
committed
Release v0.1.2
1 parent 9a159d0 commit 18d37d9

File tree

17 files changed

+303
-268
lines changed

17 files changed

+303
-268
lines changed

docs/DEVLOG.md

Lines changed: 0 additions & 149 deletions
This file was deleted.

electron/main.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import https from 'https'
66
import { URL } from 'url'
77

88
const isMac = process.platform === 'darwin'
9+
const VIDEO_EXTS = ['.mp4', '.mkv', '.avi', '.webm', '.mov', '.wmv']
10+
const AUDIO_EXTS = ['.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg', '.opus', '.wma']
11+
const MEDIA_EXTS = [...VIDEO_EXTS, ...AUDIO_EXTS]
12+
const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp']
913

1014
let mainWindow: BrowserWindow | null = null
1115

@@ -69,7 +73,7 @@ ipcMain.handle('dialog:openVideo', async () => {
6973
const result = await dialog.showOpenDialog(mainWindow!, {
7074
properties: ['openFile'],
7175
filters: [
72-
{ name: 'Videos', extensions: ['mp4', 'mkv', 'avi', 'webm', 'mov', 'wmv'] },
76+
{ name: 'Media', extensions: MEDIA_EXTS.map((ext) => ext.slice(1)) },
7377
],
7478
})
7579
if (result.canceled) return null
@@ -87,8 +91,7 @@ ipcMain.handle('dialog:openFolder', async () => {
8791
// File system operations
8892
ipcMain.handle('fs:readDir', async (_event, dirPath: string) => {
8993
try {
90-
const videoExts = ['.mp4', '.mkv', '.avi', '.webm', '.mov', '.wmv']
91-
const files: Array<{ name: string; path: string; hasScript: boolean; relativePath: string }> = []
94+
const files: Array<{ name: string; path: string; type: 'video' | 'audio'; hasScript: boolean; relativePath: string }> = []
9295

9396
function scanDir(dir: string, prefix: string) {
9497
let entries: fs.Dirent[]
@@ -103,12 +106,13 @@ ipcMain.handle('fs:readDir', async (_event, dirPath: string) => {
103106
scanDir(fullPath, prefix ? prefix + '/' + entry.name : entry.name)
104107
} else if (entry.isFile()) {
105108
const ext = path.extname(entry.name).toLowerCase()
106-
if (videoExts.includes(ext)) {
109+
if (MEDIA_EXTS.includes(ext)) {
107110
const baseName = path.basename(entry.name, ext)
108111
const scriptPath = path.join(dir, baseName + '.funscript')
109112
files.push({
110113
name: entry.name,
111114
path: fullPath,
115+
type: VIDEO_EXTS.includes(ext) ? 'video' : 'audio',
112116
hasScript: fs.existsSync(scriptPath),
113117
relativePath: prefix ? prefix + '/' + entry.name : entry.name,
114118
})
@@ -164,12 +168,64 @@ ipcMain.handle('fs:getVideoUrl', async (_event, filePath: string) => {
164168
return `file:///${filePath.replace(/\\/g, '/')}`
165169
})
166170

171+
ipcMain.handle('fs:findArtwork', async (_event, mediaPath: string) => {
172+
try {
173+
return findArtworkForMedia(mediaPath)
174+
} catch {
175+
return null
176+
}
177+
})
178+
167179
// ============================================================
168180
// NAS (WebDAV / FTP) Service
169181
// ============================================================
170182

171-
const VIDEO_EXTS = ['.mp4', '.mkv', '.avi', '.webm', '.mov', '.wmv']
172-
const NAS_EXTS = [...VIDEO_EXTS, '.funscript']
183+
const NAS_EXTS = [...MEDIA_EXTS, '.funscript']
184+
185+
function findArtworkForMedia(mediaPath: string): string | null {
186+
const dir = path.dirname(mediaPath)
187+
const ext = path.extname(mediaPath)
188+
const baseName = path.basename(mediaPath, ext).toLowerCase()
189+
190+
let entries: string[]
191+
try {
192+
entries = fs.readdirSync(dir)
193+
} catch {
194+
return null
195+
}
196+
197+
const images = entries
198+
.filter((name) => IMAGE_EXTS.includes(path.extname(name).toLowerCase()))
199+
.sort((a, b) => a.localeCompare(b))
200+
201+
if (images.length === 0) return null
202+
203+
const sameBase = images.find((name) => path.basename(name, path.extname(name)).toLowerCase() === baseName)
204+
if (sameBase) return path.join(dir, sameBase)
205+
206+
const priorityKeywords = ['cover', 'folder', 'front', 'poster', 'preview', 'artwork', 'album', 'thumb']
207+
const scored = images
208+
.map((name) => {
209+
const stem = path.basename(name, path.extname(name)).toLowerCase()
210+
let score = 0
211+
212+
if (stem.includes(baseName)) score += 100
213+
for (const keyword of priorityKeywords) {
214+
if (stem === keyword) score += 80
215+
else if (stem.startsWith(keyword) || stem.endsWith(keyword)) score += 60
216+
else if (stem.includes(keyword)) score += 40
217+
}
218+
219+
return { name, score }
220+
})
221+
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
222+
223+
if (scored[0]?.score > 0) {
224+
return path.join(dir, scored[0].name)
225+
}
226+
227+
return path.join(dir, images[0])
228+
}
173229

174230
// ---- WebDAV helpers (raw HTTP) ----
175231

electron/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
1717
readFunscript: (videoPath: string, scriptFolder?: string) => ipcRenderer.invoke('fs:readFunscript', videoPath, scriptFolder),
1818
saveFunscript: (videoPath: string, data: string) => ipcRenderer.invoke('fs:saveFunscript', videoPath, data),
1919
getVideoUrl: (filePath: string) => ipcRenderer.invoke('fs:getVideoUrl', filePath),
20+
findArtwork: (mediaPath: string) => ipcRenderer.invoke('fs:findArtwork', mediaPath),
2021

2122
// NAS operations
2223
nasWebdavConnect: (url: string, username: string, password: string) =>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptplayer-plus",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "ScriptPlayer+ - Funscript video player with Handy integration",
55
"main": "dist-electron/main.js",
66
"scripts": {

scripts/set-icon.js

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,8 @@
1-
const { execSync } = require('child_process')
2-
const path = require('path')
31
const fs = require('fs')
4-
const glob = require('path')
5-
6-
// Find rcedit in electron-builder cache
7-
const cacheDir = path.join(process.env.LOCALAPPDATA || '', 'electron-builder', 'Cache', 'winCodeSign')
8-
let rceditPath = null
9-
10-
if (fs.existsSync(cacheDir)) {
11-
for (const entry of fs.readdirSync(cacheDir)) {
12-
const candidate = path.join(cacheDir, entry, 'rcedit-x64.exe')
13-
if (fs.existsSync(candidate)) {
14-
rceditPath = candidate
15-
break
16-
}
17-
}
18-
}
2+
const path = require('path')
3+
const ResEdit = require('resedit')
194

20-
if (!rceditPath) {
21-
console.log('[set-icon] rcedit not found, skipping icon update')
22-
process.exit(0)
23-
}
5+
const pkg = require('../package.json')
246

257
const exe = path.join(__dirname, '..', 'release', 'win-unpacked', 'ScriptPlayerPlus.exe')
268
const ico = path.join(__dirname, '..', 'public', 'icon.ico')
@@ -30,6 +12,53 @@ if (!fs.existsSync(exe)) {
3012
process.exit(0)
3113
}
3214

33-
console.log('[set-icon] Applying icon to', exe)
34-
execSync(`"${rceditPath}" "${exe}" --set-icon "${ico}"`, { stdio: 'inherit' })
15+
function normalizeVersion(version) {
16+
const parts = String(version).split('.').map((part) => Number.parseInt(part, 10) || 0)
17+
while (parts.length < 4) parts.push(0)
18+
return parts.slice(0, 4).join('.')
19+
}
20+
21+
const version = normalizeVersion(pkg.version)
22+
const lang = 1033
23+
const codepage = 1200
24+
25+
console.log('[set-icon] Updating executable resources for', exe)
26+
27+
const exeData = fs.readFileSync(exe)
28+
const executable = ResEdit.NtExecutable.from(exeData)
29+
const resources = ResEdit.NtExecutableResource.from(executable)
30+
const iconFile = ResEdit.Data.IconFile.from(fs.readFileSync(ico))
31+
const iconGroups = ResEdit.Resource.IconGroupEntry.fromEntries(resources.entries)
32+
const versionInfos = ResEdit.Resource.VersionInfo.fromEntries(resources.entries)
33+
34+
ResEdit.Resource.IconGroupEntry.replaceIconsForResource(
35+
resources.entries,
36+
iconGroups[0]?.id ?? 101,
37+
iconGroups[0]?.lang ?? lang,
38+
iconFile.icons.map((item) => item.data)
39+
)
40+
41+
const versionInfo = versionInfos[0] ?? ResEdit.Resource.VersionInfo.createEmpty()
42+
versionInfo.lang = versionInfo.lang || lang
43+
versionInfo.setFileVersion(version, lang)
44+
versionInfo.setProductVersion(version, lang)
45+
versionInfo.setStringValues(
46+
{ lang, codepage },
47+
{
48+
CompanyName: 'ScriptPlayerPlus',
49+
FileDescription: pkg.description,
50+
FileVersion: version,
51+
InternalName: pkg.build.productName,
52+
OriginalFilename: 'ScriptPlayerPlus.exe',
53+
ProductName: pkg.build.productName,
54+
ProductVersion: version,
55+
}
56+
)
57+
versionInfo.outputToResourceEntries(resources.entries)
58+
59+
resources.outputResource(executable)
60+
const outputPath = `${exe}.tmp`
61+
fs.writeFileSync(outputPath, Buffer.from(executable.generate()))
62+
fs.renameSync(outputPath, exe)
63+
3564
console.log('[set-icon] Done!')

0 commit comments

Comments
 (0)