#COMPILE EXE #DIM ALL #INCLUDE ONCE "Win32Api.inc" #INCLUDE ONCE "AppData.inc" #RESOURCE "ickvdo.pbr" $VER = "1.2" ' CHANGELOG: ' v1.2 (2025-07-04) display cleaner video names in vlc ' v1.1 (2025-06-09) make video search recursive in subfolders ' v1.0 (2025-01-28) initial release '------------------------------------------------------------------------------ ' Global variables '------------------------------------------------------------------------------ GLOBAL gStartPath AS STRING ' path to start search from GLOBAL gFiles() AS STRING ' array for found files GLOBAL gFileCount AS LONG ' counter for found files GLOBAL gMatch() AS STRING ' array for matching files GLOBAL gMatchCount AS LONG ' counter for matching files GLOBAL gHistoNb AS LONG ' counter for "ickvdo.000" files GLOBAL gHistoIdx() AS LONG ' search history > index in "ickvdo.000" files GLOBAL gHistoUri() AS STRING ' search history > URIs GLOBAL gHistoLst() AS STRING ' search history > searches GLOBAL gList() AS STRING ' combobox array for current folder GLOBAL gAbort AS LONG ' to enable cancel operation GLOBAL gBusy AS LONG ' signal that the search is running '------------------------------------------------------------------------------ ' Used to subclass the combobox and trap the Del key: '------------------------------------------------------------------------------ GLOBAL ghDlg, ghCbFocus, ghCBList, gOldProc AS DWORD '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ ' Lib "AES256.dll" prototypes '------------------------------------------------------------------------------ DECLARE FUNCTION Encrypt LIB "AES256.DLL" ALIAS "Encrypt" (BYVAL sText AS STRING,BYVAL sPassword AS STRING) AS STRING DECLARE FUNCTION Decrypt LIB "AES256.DLL" ALIAS "Decrypt" (BYVAL sText AS STRING,BYVAL sPassword AS STRING) AS STRING '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ MACRO SECONDS = * 1000 '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ FUNCTION EXISTS(BYVAL f AS STRING) AS LONG LOCAL i AS LONG i = GETATTR(f) FUNCTION = (ERRCLEAR = 0) END FUNCTION '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ FUNCTION Grab_and_Decrypt_File(BYVAL file AS STRING) AS STRING LOCAL enc AS STRING LOCAL ff AS LONG ff = FREEFILE OPEN file FOR BINARY ACCESS READ LOCK SHARED AS #ff GET$ #ff, LOF(#ff), enc CLOSE #ff FUNCTION = Decrypt(enc, "mougino.free.fr/ickvdo") END FUNCTION '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB Encrypt_and_Write_File(BYVAL file AS STRING, BYVAL dec AS STRING) LOCAL enc AS STRING LOCAL ff AS LONG enc = Encrypt(dec, "mougino.free.fr/ickvdo") ff = FREEFILE OPEN file FOR BINARY ACCESS WRITE LOCK WRITE AS #ff PUT$ #ff, enc CLOSE #ff END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB GetAllHistoryLists() ' History lists are stored in "%RoamingAppData%\ickvdo.001" to "ickvdo.999" ' Contents are encrypted with AES256. ' All files contain: ' - "C:\Target\Folder\" (ending with "\") ' - followed by "|" ' - followed by searchTerm1 + $LF + SearchTerm2 + $LF + ... LOCAL db, dbl, buf AS STRING LOCAL n AS LONG gHistoNb = 0 n = -1 DO INCR gHistoNb ' Handle manual backups if needed dbl = CURDIR$ + "\ickvdo." + FORMAT$(gHistoNb, "000") ' local DB db = RoamingAppData() + "ickvdo." + FORMAT$(gHistoNb, "000") ' roaming DB IF EXISTS(dbl) AND NOT EXISTS(db) THEN NAME dbl AS db ' Locate history databases in %RoamingAppData% IF EXISTS(db) THEN buf = Grab_and_Decrypt_File(db) IF LEN(buf) > 0 THEN ' useful content (able to decrypt) INCR n REDIM PRESERVE gHistoIdx(n) gHistoIdx(n) = gHistoNb REDIM PRESERVE gHistoUri(n) gHistoUri(n) = PARSE$(buf, "|", 1) REDIM PRESERVE gHistoLst(n) gHistoLst(n) = PARSE$(buf, "|", 2) ' all search terms separated by $LF END IF END IF LOOP UNTIL ISFALSE EXISTS(db) DECR gHistoNb END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB PopulateCombo(BYVAL hDlg AS DWORD) LOCAL e AS STRING LOCAL i AS LONG ARRAY SCAN gHistoUri(), COLLATE UCASE, =gStartPath, TO i IF i <> 0 THEN e = gHistoLst(i-1) REDIM gList(PARSECOUNT(e,$LF)) PARSE e, gList(), $LF END IF CONTROL ADD COMBOBOX, hDlg, 99, gList(), 18, 32, 244, 120 CONTROL SET FOCUS hDlg, 99 END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB AddToHistory(BYVAL searchTerm AS STRING) LOCAL dec, db AS STRING LOCAL i, n AS LONG ARRAY SCAN gHistoUri(), COLLATE UCASE, =gStartPath, TO i ' No history yet for this folder > create one IF i = 0 THEN dec = gStartPath + "|" + searchTerm n = gHistoNb + 1 ' Existing history for this folder ELSE ' Only add search term if it doesn't already exists IF INSTR ($LF+LCASE$(gHistoLst(i-1))+$LF, _ $LF+LCASE$(searchTerm)+$LF) <> 0 THEN EXIT SUB ELSE dec = gHistoUri(i-1) + "|" + gHistoLst(i-1) + $LF + searchTerm n = gHistoIdx(i-1) END IF END IF db = RoamingAppData() + "ickvdo." + FORMAT$(n, "000") Encrypt_and_Write_File db, dec END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB RemoveFromHistory(BYVAL searchTerm AS STRING) LOCAL dec, db AS STRING LOCAL i, j AS LONG ARRAY SCAN gHistoUri(), COLLATE UCASE, =gStartPath, TO i ' No history yet for this folder (should not happen) > cancel IF i = 0 THEN EXIT SUB ' Existing history for this folder > search index of term j = INSTR ($LF+LCASE$(gHistoLst(i-1))+$LF, _ $LF+LCASE$(searchTerm)+$LF) ' Term not present (should not happen) > cancel IF j = 0 THEN EXIT SUB IF j = 1 THEN INCR j ' Term present > remove it! gHistoLst(i-1) = LEFT$(gHistoLst(i-1), MAX(0,j-2)) _ + MID$(gHistoLst(i-1), j+LEN(searchTerm)) ' Update DB dec = gHistoUri(i-1) + "|" + gHistoLst(i-1) db = RoamingAppData() + "ickvdo." + FORMAT$(gHistoIdx(i-1), "000") Encrypt_and_Write_File db, dec END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB BackupHisto() LOCAL i AS LONG FOR i = LBOUND(gHistoIdx) TO UBOUND(gHistoIdx) KILL CURDIR$ + "\ickvdo." + FORMAT$(gHistoIdx(i),"000") FILECOPY RoamingAppData() + "ickvdo." + FORMAT$(gHistoIdx(i),"000"), _ CURDIR$ + "\ickvdo." + FORMAT$(gHistoIdx(i),"000") NEXT ?"Backup done.", %MB_ICONINFORMATION, EXE.NAME$ END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ FUNCTION PBMAIN LOCAL c, y AS LONG gStartPath = TRIM$(COMMAND$,$DQ) IF gStartPath = "/backup" THEN ' Special CLI option GetAllHistoryLists() BackupHisto() EXIT FUNCTION ELSEIF gStartPath = "" THEN ' No argument ?"Usage: "+$CRLF+$CRLF _ +"- By command line:"+$CRLF _ +"ickvdo "+$DQ+"C:\Video\Target\"+$DQ _ +$CRLF+$CRLF _ +"- or drag & drop a folder onto this executable" _ , %MB_ICONINFORMATION, "ickvdo (I seek a video)" EXIT FUNCTION END IF gStartPath = RTRIM$(gStartPath, "\") + "\" ' Create dialog DIALOG NEW PIXELS, 0, "ickvdo (I seek a video)",,, 280, 120, _ %WS_CAPTION OR %WS_MINIMIZEBOX OR %WS_SYSMENU, 0 TO gHdlg DIALOG SET ICON gHdlg, "AICO" CONTROL ADD GRAPHIC, gHdlg, 88, "", 0, 0, 280, 120 GRAPHIC ATTACH gHdlg, 88 ' Gray background FOR y = 0 TO 280*2 c = 125+150*(y+140)/(280*2) GRAPHIC LINE (0,y)-(y,0), RGB(c,c,c) NEXT ' Magnifying glass FOR y = 1 TO 6 GRAPHIC BOX (11+y/2,84+y/2)-(37-y/2,109-y/2), 120, %GRAY+RND(-30,30) GRAPHIC LINE (19-y/2,105)-(2-y/2,130), %GRAY+RND(-30,30) GRAPHIC LINE (17-y,110)-(-y,135), %GRAY+RND(-30,30) NEXT GRAPHIC BOX (10,83)-(38,110), 100, %GRAY+RND(-30,30) ' left reflection GRAPHIC LINE (14,95)-(19,100), %GRAY+RND(-30,30) GRAPHIC LINE (16,94)-(20,98), %GRAY+RND(-30,30) ' right reflection GRAPHIC LINE (28,94)-(33,99), %GRAY+RND(-30,30) GRAPHIC LINE (30,93)-(34,97), %GRAY+RND(-30,30) ' running lines GRAPHIC LINE (40,88)-(47,88), %GRAY+RND(-30,30) GRAPHIC LINE (40,92)-(57,92), %GRAY+RND(-30,30) GRAPHIC LINE (40,96)-(67,96), %GRAY+RND(-30,30) ' version GRAPHIC SET POS (27,107) GRAPHIC COLOR %GRAY, -2 GRAPHIC PRINT "v" + $VER ' Search result labels CONTROL ADD LABEL, gHdlg, 100, "", 18, 62, 70, 20 CONTROL SET COLOR gHdlg, 100, -1, -2 CONTROL ADD LABEL, gHdlg, 101, "", 88, 57, 182, 20 CONTROL SET COLOR gHdlg, 101, -1, -2 ' About label CONTROL ADD LABEL, gHdlg, 102, "[?]", 75, 91, 14, 20, %SS_NOTIFY CONTROL SET COLOR gHdlg, 102, %BLUE, -2 ' Path label CONTROL ADD LABEL, gHdlg, 103, RTRIM$(gStartPath,"\"), 22, 12, 240, 14, %SS_PATHELLIPSIS CONTROL SET COLOR gHdlg, 103, -1, -2 CONTROL ADD BUTTON, gHdlg, %IDOK, "&Search", 94, 88, 80, 22 CONTROL ADD BUTTON, gHdlg, %IDCANCEL, "E&xit", 182, 88, 80, 22 DIALOG SHOW MODAL gHdlg CALL ProcMain END FUNCTION '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ CALLBACK FUNCTION ProcMain () AS LONG LOCAL txt, r AS STRING LOCAL hCB, hFnt AS DWORD SELECT CASE CB.MSG CASE %WM_INITDIALOG RANDOMIZE TIMER KILL EXE.PATH$ + "\temp.m3u8" ' Remove any previous M3U8 SetTimer(CB.HNDL, 0, 50, 0) CASE %WM_TIMER ' Scan 'history' databases KillTimer(CB.HNDL, 0) GetAllHistoryLists() PopulateCombo CB.HNDL ' Create populated combobox hCB = GetDlgItem(CB.HNDL, 99) ghCbFocus = GetWindow(hCB, %GW_CHILD) ' The Edit part is child of CB %CBS_DROPDOWN CASE %WM_CTLCOLORLISTBOX ' sent when a combobox (CB) list needs repainting IF GetFocus = ghCbFocus THEN ' ensure we get correct CB list IF gOldProc = 0 THEN ghCBList = CB.LPARAM ' CB list handle - we can subclass (CB listbox is tricky - owner/parent is not CB) gOldProc = SetWindowLong(ghCbFocus, %GWL_WNDPROC, CODEPTR(CBProc)) END IF END IF CASE %WM_SETCURSOR ' change cursor to link-hand when hovering over controls IF GetDlgCtrlId(CB.WPARAM) = 102 THEN ' [?] SetCursor LoadCursor(%NULL, BYVAL %IDC_HAND) SetWindowLong CB.HNDL, %dwl_msgresult, 1 FUNCTION = 1 END IF CASE %WM_COMMAND IF CB.CTLMSG <> %BN_CLICKED THEN EXIT SELECT IF CB.CTL = %IDOK THEN SeekStart CB.HNDL ' Start search procedure CONTROL GET TEXT CB.HNDL, 99 TO txt DoFilter(txt) CONTROL SET TEXT CB.HNDL, 100, "Files found:" r = FORMAT$(gMatchCount) + " / " + FORMAT$(gFileCount) IF gMatchCount = 0 THEN CONTROL SET COLOR CB.HNDL, 101, %RED, -2 ELSE CONTROL SET COLOR CB.HNDL, 101, %RGB_DARKGREEN, -2 END IF FONT NEW "", 14 TO hFnt CONTROL SET FONT CB.HNDL, 101, hFnt CONTROL SET TEXT CB.HNDL, 101, r DIALOG DOEVENTS SLEEP 650 CONTROL SET FOCUS CB.HNDL, %IDCANCEL IF gMatchCount > 0 THEN CreateM3U8() AddToHistory(txt) IF EXISTS("vlcrun.exe") THEN ShellExecute %NULL, "open", "vlcrun.exe", EXE.PATH$+"\temp.m3u8", "", %SW_SHOW ELSE ShellExecute %NULL, "open", EXE.PATH$+"\temp.m3u8", "", "", %SW_SHOW END IF DIALOG SHOW STATE CB.HNDL, %SW_HIDE ' hide the main dialog SLEEP 45 SECONDS DO ' wait until the M3U8 is released, then remove it! KILL EXE.PATH$ + "\temp.m3u8" SLEEP 500 LOOP UNTIL ISFALSE EXISTS(EXE.PATH$ + "\temp.m3u8") END IF DIALOG END CB.HNDL ELSEIF CB.CTL = %IDCANCEL THEN DIALOG END CB.HNDL ELSEIF CB.CTL = 102 THEN ' About ShellExecute %NULL, "open", "http://mougino.free.fr/freeware", "", "", %SW_SHOW END IF CASE %WM_DESTROY gAbort = 1 ' Cancel eventual recursive action on exit IF gOldProc THEN SetWindowLong ghCBList, %GWL_WNDPROC, gOldProc END SELECT END FUNCTION '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ ' Combobox list subclass procedure '------------------------------------------------------------------------------ FUNCTION CBProc(BYVAL hWnd AS DWORD, BYVAL wMsg AS DWORD, _ BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG LOCAL LBitem AS LONG LOCAL e AS STRING IF wMsg = %WM_KEYUP AND wParam = %VK_DELETE THEN LBitem = SendMessage(ghCBList, %LB_GETCURSEL, 0, 0) COMBOBOX GET TEXT ghDlg, 99, 1+LBitem TO e RemoveFromHistory e COMBOBOX DELETE ghDlg, 99, 1+LBitem END IF FUNCTION = CallWindowProc(gOldProc, hWnd, wMsg, wParam, lParam) END FUNCTION '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB CreateM3U8() LOCAL i AS LONG LOCAL ff AS LONG LOCAL fo AS STRING fo = RTRIM$(gStartPath, "\") + "\" REPLACE "\" WITH "/" IN fo REPLACE " " WITH "%20" IN fo ff = FREEFILE OPEN EXE.PATH$ + "\temp.m3u8" FOR OUTPUT AS #ff PRINT #ff, "#EXTM3U" FOR i = LBOUND(gMatch) TO UBOUND(gMatch) PRINT #ff, "#EXTINF:-1," + RTRIM$(gStartPath, "\") + "\" + gMatch(i) REPLACE "\" WITH "/" IN gMatch(i) REPLACE " " WITH "%20" IN gMatch(i) PRINT #ff, "file:///" + fo + gMatch(i) NEXT CLOSE #ff END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ ' File search procedure '------------------------------------------------------------------------------ SUB ReadDirs (BYVAL Path AS STRING) LOCAL hSearch AS DWORD ' Search handle LOCAL WFD AS WIN32_FIND_DATA ' FindFirstFile structure LOCAL curpath AS ASCIIZ * %MAX_PATH ' What to search for curpath = Path + "*" ' This is what we want to find hSearch = FindFirstFile(curpath, WFD) ' Get search handle, if success IF hSearch <> %INVALID_HANDLE_VALUE THEN ' Loop through directory for files DO IF (WFD.dwFileAttributes AND %FILE_ATTRIBUTE_DIRECTORY) _ ' If not directory bit is set <> %FILE_ATTRIBUTE_DIRECTORY THEN ' (files only here...) IF gFileCount MOD 200 = 0 THEN REDIM PRESERVE gFiles(gFileCount + 200) AS GLOBAL STRING DIALOG DOEVENTS ' let the system "breathe" .. IF gAbort THEN EXIT DO ' Has user cancelled? END IF gFiles(gFileCount) = REMOVE$(Path, gStartPath) + WFD.cFileName INCR gFileCount ELSEIF WFD.cFileName <> "." AND WFD.cFileName <> ".." THEN ' Not these.. ReadDirs Path + RTRIM$(WFD.cFileName, $NUL) + "\" ' Recursive call for Subdirs END IF LOOP WHILE FindNextFile(hSearch, WFD) FindClose hSearch END IF END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ SUB DoFilter(BYVAL txt AS STRING) LOCAL i AS LONG LOCAL fn AS STRING txt = LCASE$(txt) ' case insensitive search REDIM gMatch(0) gMatchCount = 0 FOR i = LBOUND(gFiles) TO UBOUND(gFiles) fn = LCASE$(PATHNAME$(NAME, gFiles(i))) IF INSTR(fn, txt) > 0 THEN INCR gMatchCount IF gMatchCount MOD 20 = 1 THEN REDIM PRESERVE gMatch(gMatchCount+20) gMatch(gMatchCount-1) = gFiles(i) END IF NEXT REDIM PRESERVE gMatch(gMatchCount-1) END SUB '------------------------------------------------------------------------------ '------------------------------------------------------------------------------ ' Start and end point of a file search action '------------------------------------------------------------------------------ SUB SeekStart (BYVAL hDlg AS DWORD) LOCAL txt AS STRING, t AS SINGLE DIALOG DOEVENTS CONTROL GET TEXT hDlg, %IDOK TO txt IF txt = "&Cancel" THEN ' User has cancelled action gAbort = 1 gBusy = %FALSE DIALOG DOEVENTS EXIT SUB END IF ' Else: Go! t = TIMER gBusy = %TRUE gAbort = 0 gFileCount = 0 REDIM gFiles(0) AS GLOBAL STRING ' Clear array ' Disable system menu's close item MENU SET STATE GetSystemMenu(hDlg, 0), %SC_CLOSE, %MF_GRAYED ' Change Search button into a Cancel-button CONTROL SET TEXT hDlg, %IDOK, "&Cancel" ' Disable other controls CONTROL DISABLE hDlg, 99 CONTROL DISABLE hDlg, %IDCANCEL DIALOG DOEVENTS ' Terminate with backslash gStartPath = RTRIM$(gStartPath, ANY "\/") + "\" ' Call the search procedure ReadDirs gStartPath ' Resize array REDIM PRESERVE gFiles(gFileCount - 1) AS GLOBAL STRING ' Time taken t = TIMER - t 'control set text hDlg, 100, format$(gFileCount)+" found in "+FORMAT$(t,"0.000")+"s" ' Make it search-button again CONTROL SET TEXT hDlg, %IDOK, "&Search" ' Reenable controls CONTROL ENABLE hDlg, %IDCANCEL CONTROL ENABLE hDlg, 99 ' Reenable system menu's close item MENU SET STATE GetSystemMenu(hDlg, 0), %SC_CLOSE, %MF_ENABLED ' Allow the GUI to work fully again. gBusy = %FALSE DIALOG DOEVENTS END SUB '------------------------------------------------------------------------------