From 9891a302ac780e29fd1e1c0b0f659ec05ff2b436 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 12 Jul 2024 08:27:19 +0200 Subject: [PATCH] Big update --- src/basic/basic.h | 6 +- src/basic/filesystem.h | 9 +- src/basic/win32.cpp | 47 ++++- src/transcript_browser/loading_thread.cpp | 156 ++++++++-------- src/transcript_browser/main.cpp | 205 ++++++++++++++-------- 5 files changed, 261 insertions(+), 162 deletions(-) diff --git a/src/basic/basic.h b/src/basic/basic.h index b1e9ec2..7aa9b72 100644 --- a/src/basic/basic.h +++ b/src/basic/basic.h @@ -758,7 +758,11 @@ String16 ToString16(Allocator allocator, String string); wchar_t *ToWidechar(Allocator allocator, String string); int64_t WideLength(wchar_t *string); void NormalizePathInPlace(String s); - +String ChopLastSlash(String s); +String ChopLastPeriod(String s); +String SkipToLastSlash(String s); +String SkipToLastPeriod(String s); +String Copy(Allocator allocator, String string); /* Hash table implementation: Pointers to values diff --git a/src/basic/filesystem.h b/src/basic/filesystem.h index a225af9..731f272 100644 --- a/src/basic/filesystem.h +++ b/src/basic/filesystem.h @@ -19,15 +19,20 @@ struct FileIter { }; String ReadFile(Allocator arena, String path); +bool WriteFile(String path, String data); String GetAbsolutePath(Allocator arena, String relative); bool IsValid(const FileIter &it); void Advance(FileIter *it); FileIter IterateFiles(Allocator allocator, String path); bool InitOS(); +String GetExePath(Allocator allocator); +String GetExeDir(Allocator allocator); + struct Process { - bool is_valid; - char platform[32]; + bool is_valid; + String error_message; + char platform[32]; }; Process RunEx(String cmd); diff --git a/src/basic/win32.cpp b/src/basic/win32.cpp index 2f41ee3..23ec850 100644 --- a/src/basic/win32.cpp +++ b/src/basic/win32.cpp @@ -59,8 +59,8 @@ Process RunEx(String args) { NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); LocalFree(lpMsgBuf); - printf("Failed to create process \ncmd: %.*s\nwindows_message: %s", FmtString(args), (char *)lpMsgBuf); - Assert(!"Failed to create process"); + // @warning: leak! but we don't care + result.error_message = Format(GetSystemAllocator(), "Failed to create process | message: %s | cmd: %.*s", (char *)lpMsgBuf, FmtString(args)); } return result; } @@ -228,4 +228,45 @@ double get_time_in_micros(void) { clock_gettime(CLOCK_MONOTONIC, &spec); return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); } -#endif \ No newline at end of file +#endif + +bool WriteFile(String path, String data) { + bool result = false; + wchar_t wpath[1024]; + CreateWidecharFromChar(wpath, Lengthof(wpath), path.data, path.len); + + DWORD access = GENERIC_WRITE; + DWORD creation_disposition = CREATE_ALWAYS; + HANDLE handle = CreateFileW(wpath, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + DWORD bytes_written = 0; + Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files? + BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL); + if (error == TRUE) { + if (bytes_written == data.len) { + result = true; + } + } + CloseHandle(handle); + } + + return result; +} + +String GetExePath(Allocator allocator) { + wchar_t wbuffer[1024]; + DWORD wsize = GetModuleFileNameW(0, wbuffer, Lengthof(wbuffer)); + Assert(wsize != 0); + + String path = ToString(allocator, wbuffer, wsize); + NormalizePathInPlace(path); + return path; +} + +String GetExeDir(Allocator allocator) { + Scratch scratch; + String path = GetExePath(scratch); + path = ChopLastSlash(path); + path = Copy(allocator, path); + return path; +} diff --git a/src/transcript_browser/loading_thread.cpp b/src/transcript_browser/loading_thread.cpp index 512efd5..1856947 100644 --- a/src/transcript_browser/loading_thread.cpp +++ b/src/transcript_browser/loading_thread.cpp @@ -1,68 +1,31 @@ -struct TimeString { - uint16_t hour; - uint16_t minute; - uint16_t second; - String string; -}; - -Array ParseSrtFile(Arena *arena, String filename) { - String content = ReadFile(*arena, filename); - Array lines = Split(*arena, content, "\n"); - - IterRemove(lines) { - IterRemovePrepare(lines); - it = Trim(it); - if (it.len == 0) remove_item = true; - } - - long section_number = 1; - Array time_strings = {*arena}; - for (int i = 0; i < lines.len;) { - String it0 = lines[i++]; - long num = strtol(it0.data, NULL, 10); - Assert(section_number == num); - section_number += 1; - - TimeString item = {}; - String it1 = lines[i++]; - item.hour = (uint16_t)strtol(it1.data, NULL, 10); - item.minute = (uint16_t)strtol(it1.data + 3, NULL, 10); - item.second = (uint16_t)strtol(it1.data + 6, NULL, 10); - - String next_section_number = Format(*arena, "%d", section_number); - while (i < lines.len && lines[i] != next_section_number) { - String it = lines[i]; - item.string = lines[i]; - time_strings.add(item); - i += 1; - } - } - - IterRemove(time_strings) { - IterRemovePrepare(time_strings); - if (i > 0 && AreEqual(time_strings[i - 1].string, time_strings[i].string, true)) { - remove_item = true; - } - } - - return time_strings; -} - -struct TimeFile { - Array time_strings; - String file; -}; - struct ParseThreadIO { Array input_files; }; -struct XToTimeString { - String string; // String inside transcript arena - uint16_t hour; - uint16_t minute; - uint16_t second; - String filepath; +enum SourceKind { + SourceKind_Invalid, + SourceKind_SRT, + SourceKind_PDF, + SourceKind_TXT, +}; + +struct XToSource { + SourceKind kind; + String string; // String inside x arena + String filepath; + union { + struct { + uint16_t hour; + uint16_t minute; + uint16_t second; + } srt; + struct { + int page; + } pdf; + struct { + int64_t row; + } txt; + }; }; struct FileLoadResult { @@ -72,7 +35,7 @@ struct FileLoadResult { Arena XArena; std::mutex XArenaAddMutex; -Array XToTime; +Array XToSourceArray; Array XFileLoadResults; int64_t XLoadThreadComplete; @@ -80,24 +43,49 @@ void XInitLoading() { InitArena(&XArena); XArena.align = 0; - XToTime.reserve(1000000); + XToSourceArray.reserve(1000000); XFileLoadResults.reserve(10000); } WORK_FUNCTION(ParseFilesWork) { ParseThreadIO *io = (ParseThreadIO *)data; ForItem(it_time_file, io->input_files) { - Scratch scratch; - Array time_strings = ParseSrtFile(scratch, it_time_file); + Scratch scratch; + if (EndsWith(it_time_file, ".srt", true)) { + Array time_strings = ParseSrtFile(scratch, it_time_file); - XArenaAddMutex.lock(); - For(time_strings) { - String s = Copy(XArena, it.string); - s.data[s.len] = ' '; - XToTime.add({s, it.hour, it.minute, it.second, it_time_file}); + XArenaAddMutex.lock(); + For(time_strings) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + + XToSource t = {SourceKind_SRT, s, it_time_file}; + t.srt = {it.hour, it.minute, it.second}; + XToSourceArray.add(t); + } + XArenaAddMutex.unlock(); + XFileLoadResults.bounded_add({it_time_file}); + } else if (EndsWith(it_time_file, ".txt", true) || EndsWith(it_time_file, ".html", true)) { + String string = ReadFile(scratch, it_time_file); + if (string.data) { + XArenaAddMutex.lock(); + Array lines = Split(scratch, string, "\n"); + For(lines) { + String s = Copy(XArena, it); + s.data[s.len] = ' '; + + XToSource t = {SourceKind_TXT, s, it_time_file}; + t.txt = {lines.get_index(it)}; + XToSourceArray.add(t); + } + XArenaAddMutex.unlock(); + XFileLoadResults.bounded_add({it_time_file}); + } else { + XFileLoadResults.bounded_add({it_time_file, "failed to read the file"}); + } + } else { + XFileLoadResults.bounded_add({it_time_file, "internal error: extension is not supported but got propagated to parse stage"}); } - XArenaAddMutex.unlock(); - XFileLoadResults.bounded_add({it_time_file}); } AtomicIncrement(&XLoadThreadComplete); } @@ -118,31 +106,33 @@ void XUnlockFileResults() { void XAddFolder(String folder, Array *filenames) { Scratch scratch; - Array srt_files = {scratch}; + Array files_to_parse = {scratch}; for (FileIter iter = IterateFiles(scratch, folder); IsValid(iter); Advance(&iter)) { String file = Copy(Perm, iter.absolute_path); filenames->add(file); - if (EndsWith(iter.filename, ".srt")) { - srt_files.add(file); + if (EndsWith(iter.filename, ".srt", true)) { + files_to_parse.add(file); + } else if (EndsWith(iter.filename, ".txt", true) || EndsWith(iter.filename, ".html", true)) { + files_to_parse.add(file); } } - if (srt_files.len == 0) { + if (files_to_parse.len == 0) { XFileLoadResults.add({Copy(Perm, folder), "no files found"}); return; } int64_t thread_count = MainWorkQueue.thread_count; - int64_t files_per_thread = srt_files.len / thread_count; - int64_t remainder = srt_files.len % thread_count; + int64_t files_per_thread = files_to_parse.len / thread_count; + int64_t remainder = files_to_parse.len % thread_count; int64_t fi = 0; Array io = {Perm}; io.reserve(thread_count); for (int ti = 0; ti < thread_count; ti += 1) { Array files = {Perm}; - for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { - files.add(srt_files[fi]); + for (int i = 0; fi < files_to_parse.len && i < files_per_thread + remainder; fi += 1, i += 1) { + files.add(files_to_parse[fi]); } if (remainder) remainder = 0; @@ -152,11 +142,11 @@ void XAddFolder(String folder, Array *filenames) { } } -XToTimeString *XFindItem(String string) { - XToTimeString *result = NULL; +XToSource *XFindSource(String string) { + XToSource *result = NULL; XArenaAddMutex.lock(); - For(XToTime) { + For(XToSourceArray) { uintptr_t begin = (uintptr_t)(it.string.data); uintptr_t end = (uintptr_t)(it.string.data + it.string.len); uintptr_t needle = (uintptr_t)string.data; diff --git a/src/transcript_browser/main.cpp b/src/transcript_browser/main.cpp index 5970155..76c475f 100644 --- a/src/transcript_browser/main.cpp +++ b/src/transcript_browser/main.cpp @@ -1,5 +1,9 @@ /* +@todo: txt files +@todo: read recursive +@todo: add option for user to control what files are read @todo: pdf files +@todo: plumbing for custom commands - pdf: {tools_path}/vlc {filename} --start-at {hour}:{second}:{minute} @todo: config with preload_files, initial_query, style @todo: help menu and config menu @@ -21,70 +25,76 @@ #include #include "glad.h" +#include +#include "read_pdf.cpp" +#include "read_srt.cpp" + Arena Perm; WorkQueue MainWorkQueue; #include "loading_thread.cpp" #include "searching_thread.cpp" -#include +char SRTCommand[512] = "\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" --start-time {time_in_seconds} {video}"; +char PDFCommand[512]; +char TXTCommand[512] = "subl.exe {file}"; -#if 0 -extern "C" void OutputDebugStringA(const char *); -void Printf(const char *string, ...) { - Scratch scratch; - STRING_FORMAT(scratch, string, result); - OutputDebugStringA(result.data); -} -#else - #define Printf(...) (0) -#endif +struct ReplaceVar { + String replace; + String with; +}; -void TestPdfio() { - const char *filename = "C:/Users/Karol/Desktop/Hegels-Logic.pdf"; - pdfio_file_t *file = pdfioFileOpen(filename, NULL, NULL, NULL, NULL); - Assert(file); - defer { pdfioFileClose(file); }; +String ReplaceVars(Allocator allocator, Array vars_to_replace, String string) { + Array sb = {allocator}; + for (int64_t i = 0; i < string.len; i += 1) { + if (string[i] == '{') { - char buffer[1024]; - size_t page_count = pdfioFileGetNumPages(file); - Printf("%s: %u pages\n", filename, (unsigned)page_count); - for (size_t page_i = 0; page_i < page_count; page_i += 1) { - pdfio_obj_t *obj = pdfioFileGetPage(file, page_i); - if (obj == NULL) continue; + // extract the variable + i += 1; + String var = {string.data + i, 0}; + for (; i < string.len && string[i] != '}'; i += 1) + var.len += 1; - if (page_i > 110) __debugbreak(); - - size_t num_streams = pdfioPageGetNumStreams(obj); - Printf("%s: page%u=%p, num_streams=%u\n", filename, (unsigned)page_i, obj, (unsigned)num_streams); - for (size_t stream_i = 0; stream_i < num_streams; stream_i += 1) { - pdfio_stream_t *st = pdfioPageOpenStream(obj, stream_i, true); - if (st == NULL) continue; - Printf("%s: page%u st%u=%p\n", filename, (unsigned)page_i, (unsigned)stream_i, st); - defer { pdfioStreamClose(st); }; - - bool first = true; - while (pdfioStreamGetToken(st, buffer, sizeof(buffer))) { - if (buffer[0] == '(') { - if (first) { - first = false; - } else { - Printf(" "); - } - - Printf("%s", buffer + 1); - } else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") || !strcmp(buffer, "\'") || !strcmp(buffer, "\"")) { - Printf("\n"); - first = true; + // find the replacement + String found = ""; + For(vars_to_replace) { + if (it.replace == var) { + found = it.with; + break; } } - if (!first) { - Printf("\n"); - } - } + For(found) sb.add(it); + + } else sb.add(string[i]); + } + + sb.add('\0'); + String result = {sb.data, sb.len - 1}; + return result; +} + +void TestReplaceVars() { + Scratch scratch; + Array vars_to_replace = {scratch}; + String exe_folder = GetExePath(scratch); + vars_to_replace.add({"exe_folder", exe_folder}); + String data_folder = Format(scratch, "%.*s/data", FmtString(exe_folder)); + vars_to_replace.add({"data_folder", data_folder}); + + { + String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}"); + Assert(r == exe_folder); + } + + { + String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}{data_folder}"); + Assert(r == Format(scratch, "%.*s%.*s", FmtString(exe_folder), FmtString(data_folder))); + } + { + String r = ReplaceVars(scratch, vars_to_replace, "..{exe_folder}..{data_folder}..{exe_folder}asd"); + Assert(r == Format(scratch, "..%.*s..%.*s..%.*sasd", FmtString(exe_folder), FmtString(data_folder), FmtString(exe_folder))); } - __debugbreak(); } void UISearchResults(Array filenames) { @@ -123,22 +133,33 @@ void UISearchResults(Array filenames) { String string = Format(scratch, "%.*s**%.*s**%.*s", FmtString(left), FmtString(middle), FmtString(right)); - XToTimeString *item = XFindItem(middle); + XToSource *item = XFindSource(middle); if (ImGui::Button(string.data)) { - String base = ChopLastPeriod(item->filepath); // .srt - base = ChopLastPeriod(base); // .en + String exe_folder = GetExeDir(scratch); + exe_folder = Format(scratch, "\"%.*s\"", FmtString(exe_folder)); + Array replace_vars = {scratch}; + replace_vars.add({"exe_folder", GetExeDir(scratch)}); - For(filenames) { - if (StartsWith(it, base)) { - if (EndsWith(it, ".mkv") || EndsWith(it, ".webm") || EndsWith(it, ".mp4")) { - int seconds = item->hour * 60 * 60 + item->minute * 60 + item->second; - String copy = Copy(scratch, it); - for (int i = 0; i < copy.len; i += 1) - if (copy.data[i] == '/') copy.data[i] = '\\'; - String args = Format(scratch, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); - RunEx(args); - } + if (item->kind == SourceKind_SRT) { + String it = FindVideoForSRT(filenames, item->filepath); + if (it.len) { + int seconds = item->srt.hour * 60 * 60 + item->srt.minute * 60 + item->srt.second; + String copy = Copy(scratch, it); + for (int i = 0; i < copy.len; i += 1) + if (copy.data[i] == '/') copy.data[i] = '\\'; + + replace_vars.add({"video", Format(scratch, "\"%.*s\"", FmtString(copy))}); + replace_vars.add({"time_in_seconds", Format(scratch, "%d", seconds)}); + String args = ReplaceVars(scratch, replace_vars, SRTCommand); + Process process = RunEx(args); + if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); } + } else if (item->kind == SourceKind_TXT) { + replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))}); + replace_vars.add({"line", Format(scratch, "%d", item->txt.row + 1)}); + String args = ReplaceVars(scratch, replace_vars, TXTCommand); + Process process = RunEx(args); + if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); } } ImGui::SameLine(); @@ -175,8 +196,21 @@ int main(int, char **) { InitScratch(); InitArena(&Perm); - // TestPdfio(); - // return 0; + // { + // Scratch scratch; + // for (FileIter it = IterateFiles(scratch, "D:/pdfs"); IsValid(it); Advance(&it)) { + // if (it.is_directory || !EndsWith(it.absolute_path, ".pdf", true)) continue; + // PDF pdf = pdfioReadPDF(scratch, it.absolute_path); + // Array strings = {scratch}; + // For(pdf.pages) { + // strings.add(it.string); + // } + // String s = Merge(scratch, strings, "\n"); + // WriteFile(Format(scratch, "%.*s.txt", FmtString(it.absolute_path)), s); + + // String pdf_content = ReadFile(scratch, it.absolute_path); + // } + // } XInitLoading(); InitSearch(); @@ -223,8 +257,8 @@ int main(int, char **) { ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Setup Dear ImGui style // ImGui::StyleColorsDark(); @@ -235,6 +269,7 @@ int main(int, char **) { ImGui_ImplOpenGL3_Init(glsl_version); io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); + bool command_menu_open = false; bool show_loaded_files = false; bool set_focus_to_input = true; int64_t frame = -1; @@ -250,6 +285,7 @@ int main(int, char **) { bool tab_press = false; bool enter_press = false; bool f1_press = false; + bool f2_press = false; SDL_Event event; #if 1 while (SDL_PollEvent(&event)) { @@ -268,6 +304,7 @@ int main(int, char **) { case SDLK_BACKSPACE: case SDLK_DELETE: set_focus_to_input = true; + break; case SDLK_RETURN: enter_press = true; break; @@ -277,6 +314,9 @@ int main(int, char **) { case SDLK_F1: f1_press = true; break; + case SDLK_F2: + f2_press = true; + break; } } else if (event.type == SDL_TEXTINPUT) { set_focus_to_input = true; @@ -304,17 +344,28 @@ int main(int, char **) { ImGui::Begin("input", NULL, window_flags); if (show_loaded_files) { - if (ImGui::Button("Show search results (F1)") || f1_press) { + if (ImGui::Button("Show files (F1)") || f1_press) { show_loaded_files = !show_loaded_files; } } else { - if (ImGui::Button("Show loaded files (F1)") || f1_press) { + if (ImGui::Button("Hide files (F1)") || f1_press) { show_loaded_files = !show_loaded_files; } } + ImGui::SameLine(); + + if (command_menu_open) { + if (ImGui::Button("Hide config (F2)") || f2_press) { + command_menu_open = !command_menu_open; + } + } else { + if (ImGui::Button("Show config (F2)") || f2_press) { + command_menu_open = !command_menu_open; + } + } ImGui::SameLine(); - if (set_focus_to_input) ImGui::SetKeyboardFocusHere(0); + if (!command_menu_open && set_focus_to_input) ImGui::SetKeyboardFocusHere(0); if (tab_press && ImGui::IsWindowFocused()) set_focus_to_list = true; if (ImGui::InputText("Input your query", Prompt, sizeof(Prompt))) { StartSearchingForMatches(); @@ -339,11 +390,19 @@ int main(int, char **) { ImGui::Begin("result", NULL, window_flags); if (!ImGui::IsWindowFocused() && set_focus_to_list) ImGui::SetKeyboardFocusHere(0); - if (show_loaded_files) { - UILoadedFiles(); + + if (command_menu_open) { + ImGui::InputText(".srt", SRTCommand, sizeof(SRTCommand)); + ImGui::InputText(".pdf", PDFCommand, sizeof(PDFCommand)); + ImGui::InputText(".txt", TXTCommand, sizeof(TXTCommand)); } else { - UISearchResults(filenames); + if (show_loaded_files) { + UILoadedFiles(); + } else { + UISearchResults(filenames); + } } + ImGui::End(); } #endif