@@ -6,6 +6,10 @@ import https from 'https'
66import { URL } from 'url'
77
88const 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
1014let 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
8892ipcMain . 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
0 commit comments