diff --git a/src/transcript_browser/loading_thread.cpp b/src/transcript_browser/loading_thread.cpp new file mode 100644 index 0000000..9b21696 --- /dev/null +++ b/src/transcript_browser/loading_thread.cpp @@ -0,0 +1,141 @@ +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; + + // output + Arena *arena; + Array time_files; +}; + +void ParseThreadEntry(ParseThreadIO *io) { + io->arena = AllocArena(); + io->time_files.allocator = *io->arena; + For(io->input_files) { + Array time_strings = ParseSrtFile(io->arena, it); + io->time_files.add({time_strings, it}); + } +} + +struct XToTimeString { + String string; // String inside transcript arena + uint16_t hour; + uint16_t minute; + uint16_t second; + String filepath; +}; +Arena XArena; + +void AddFolder(String folder, Array *filenames, Array *x_to_time_string) { + Scratch scratch; + + Array srt_files = {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); + } + } + + int64_t thread_count = 16; + Array threads = {scratch}; + int64_t files_per_thread = srt_files.len / thread_count; + int64_t remainder = srt_files.len % thread_count; + int64_t fi = 0; + + Array io = {scratch}; + io.reserve(thread_count); + for (int ti = 0; ti < thread_count; ti += 1) { + Array files = {scratch}; + for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { + files.add(srt_files[fi]); + } + if (remainder) remainder = 0; + + ParseThreadIO *i = io.alloc(); + i->input_files = files; + threads.add(new std::thread(ParseThreadEntry, i)); + } + + For(threads) { + it->join(); + delete it; + } + + ForItem(it_io, io) { + ForItem(it_time_file, it_io.time_files) { + For(it_time_file.time_strings) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + x_to_time_string->add({s, it.hour, it.minute, it.second, it_time_file.file}); + } + } + Release(it_io.arena); + } +} + +XToTimeString *FindItem(Array &x_to_time_string, String string) { + For(x_to_time_string) { + 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; + if (needle >= begin && needle < end) { + return ⁢ + } + } + return NULL; +} diff --git a/src/transcript_browser/main.cpp b/src/transcript_browser/main.cpp index b820111..b1d0edd 100644 --- a/src/transcript_browser/main.cpp +++ b/src/transcript_browser/main.cpp @@ -15,208 +15,19 @@ Arena Perm; +#include "loading_thread.cpp" +#include "searching_thread.cpp" + /* TODO: - New threading model idea: I could just spin up a bunch of threads and then don't kill them. Just use them for searching! Each one would have it's own xarena and so on. -- Improve scrolling -- Highlight selected line -- Improve the looks of the application */ -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; - - // output - Arena *arena; - Array time_files; -}; - -void ParseThreadEntry(ParseThreadIO *io) { - io->arena = AllocArena(); - io->time_files.allocator = *io->arena; - For(io->input_files) { - Array time_strings = ParseSrtFile(io->arena, it); - io->time_files.add({time_strings, it}); - } -} - -struct XToTimeString { - String string; // String inside transcript arena - uint16_t hour; - uint16_t minute; - uint16_t second; - String filepath; -}; -Arena XArena; - -void AddFolder(String folder, Array *filenames, Array *x_to_time_string) { - Scratch scratch; - - Array srt_files = {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); - } - } - - int64_t thread_count = 16; - Array threads = {scratch}; - int64_t files_per_thread = srt_files.len / thread_count; - int64_t remainder = srt_files.len % thread_count; - int64_t fi = 0; - - Array io = {scratch}; - io.reserve(thread_count); - for (int ti = 0; ti < thread_count; ti += 1) { - Array files = {scratch}; - for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { - files.add(srt_files[fi]); - } - if (remainder) remainder = 0; - - ParseThreadIO *i = io.alloc(); - i->input_files = files; - threads.add(new std::thread(ParseThreadEntry, i)); - } - - For(threads) { - it->join(); - delete it; - } - - ForItem(it_io, io) { - ForItem(it_time_file, it_io.time_files) { - For(it_time_file.time_strings) { - String s = Copy(XArena, it.string); - s.data[s.len] = ' '; - x_to_time_string->add({s, it.hour, it.minute, it.second, it_time_file.file}); - } - } - Release(it_io.arena); - } -} - // // Searching thread // -Arena MatchesArena; -Array Matches = {MatchesArena}; -XToTimeString *ItemFound; -char Prompt[256]; - -std::mutex SearchThreadArrayMutex; -std::binary_semaphore SearchThreadSemaphore{0}; -bool SearchThreadStopSearching = false; -bool SearchThreadRunning = true; -void SearchThreadEntry() { - InitArena(&MatchesArena); - for (;;) { - SearchThreadSemaphore.acquire(); - if (!SearchThreadRunning) break; - SearchThreadStopSearching = false; - - if (Prompt[0]) { - SearchThreadArrayMutex.lock(); - { - Matches.clear(); - } - SearchThreadArrayMutex.unlock(); - - String buffer = {(char *)XArena.data, (int64_t)XArena.len}; - String find = Prompt; - int64_t index = 0; - while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { - String found = {buffer.data + index, find.len}; - SearchThreadArrayMutex.lock(); - { - Matches.add(found); - } - SearchThreadArrayMutex.unlock(); - - if (SearchThreadStopSearching) break; - buffer = buffer.skip(index + find.len); - } - } - } -} - -void SearchThreadClose(std::thread &thread) { - SearchThreadRunning = false; - SearchThreadSemaphore.release(); - thread.join(); -} - -XToTimeString *FindItem(Array &x_to_time_string, String string) { - For(x_to_time_string) { - 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; - if (needle >= begin && needle < end) { - return ⁢ - } - } - return NULL; -} - int main(int, char **) { InitOS(); InitScratch(); @@ -248,7 +59,7 @@ int main(int, char **) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window *window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + SDL_Window *window = SDL_CreateWindow("Transcript browser", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); return -1; @@ -280,28 +91,40 @@ int main(int, char **) { bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - bool enter_down = false; - int64_t frame = -1; + bool set_focus_to_input = true; + int64_t frame = -1; // Main loop bool done = false; while (!done) { Scratch frame_arena; frame += 1; + if (frame > 4) set_focus_to_input = false; - bool enter_press = false; + bool set_focus_to_list = false; + bool tab_press = false; + bool enter_press = false; SDL_Event event; +#if 1 while (SDL_PollEvent(&event)) { +#else + if (SDL_WaitEvent(&event)) { +#endif ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_KEYDOWN) { if (event.key.keysym.sym == SDLK_RETURN) { - if (enter_down == false) enter_press = true; - enter_down = true; + enter_press = true; + } else if (event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_DOWN) { + set_focus_to_list = true; + } else if (event.key.keysym.sym == SDLK_BACKSPACE) { + set_focus_to_input = true; + } else if (event.key.keysym.sym == SDLK_TAB) { + tab_press = true; } } else if (event.type == SDL_KEYUP) { - if (event.key.keysym.sym == SDLK_RETURN) { - enter_down = false; - } + + } else if (event.type == SDL_TEXTINPUT) { + set_focus_to_input = true; } if (event.type == SDL_QUIT) done = true; @@ -309,13 +132,10 @@ int main(int, char **) { done = true; } - // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); -#if 1 - { ImGuiWindowFlags window_flags = 0; @@ -330,7 +150,8 @@ int main(int, char **) { ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, 20), ImGuiCond_Always); ImGui::Begin("input", NULL, window_flags); - if (frame < 4) ImGui::SetKeyboardFocusHere(0); + if (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))) { match_search_offset = 0; SearchThreadStopSearching = true; @@ -359,19 +180,20 @@ int main(int, char **) { ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, main_viewport->Size.y - 30), ImGuiCond_Always); ImGui::Begin("result", NULL, window_flags); - + if (!ImGui::IsWindowFocused() && set_focus_to_list) ImGui::SetKeyboardFocusHere(0); SearchThreadArrayMutex.lock(); float font_size = ImGui::GetFontSize(); ImFont *font = ImGui::GetFont(); - int64_t chars_per_line = (int64_t)main_viewport->WorkSize.x / (int64_t)font->GetCharAdvance('_') - strlen(Prompt) * 2; + int64_t chars_per_line = (int64_t)(main_viewport->WorkSize.x / 2) / (int64_t)font->GetCharAdvance('_') - strlen(Prompt) * 2; // int64_t chars_per_line = 200; ImGuiListClipper clipper; clipper.Begin((int)Matches.len); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - // for (int64_t i = 0; i < Matches.len; i += 1) { + ImGui::PushID(i); + defer { ImGui::PopID(); }; auto &it = Matches[i]; uintptr_t begin_region = (uintptr_t)XArena.data; uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; @@ -388,50 +210,29 @@ int main(int, char **) { String right = {(char *)c, (int64_t)(d - c)}; String middle = it; - String string = Format(frame_arena, "%.*s**%.*s**%.*s##%d", - FmtString(left), FmtString(middle), FmtString(right), (int)i); + String string = Format(frame_arena, "%.*s**%.*s**%.*s", + FmtString(left), FmtString(middle), FmtString(right)); - if (ImGui::CollapsingHeader(string.data)) { - uintptr_t begin_region = (uintptr_t)XArena.data; - uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + XToTimeString *item = FindItem(x_to_time_string, middle); + if (ImGui::Button(string.data)) { + String base = ChopLastPeriod(item->filepath); // .srt + base = ChopLastPeriod(base); // .en - uintptr_t begin = (uintptr_t)(it.data - 1000); - uintptr_t end = (uintptr_t)(it.data + 1000); - - uint64_t a = Clamp(begin, begin_region, end_region); - uint64_t b = Clamp((uintptr_t)it.data, begin_region, end_region); - String left = {(char *)a, (int64_t)(b - a)}; - - uint64_t c = Clamp((uintptr_t)(it.data + it.len), begin_region, end_region); - uint64_t d = Clamp(end, begin_region, end_region); - String right = {(char *)c, (int64_t)(d - c)}; - - String middle = it; - String string = Format(frame_arena, "%.*s***************%.*s***************%.*s", - FmtString(left), FmtString(middle), FmtString(right)); - - XToTimeString *item = FindItem(x_to_time_string, middle); - if (ImGui::Button("Open")) { - String base = ChopLastPeriod(item->filepath); // .srt - base = ChopLastPeriod(base); // .en - - 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(*frame_arena, it); - for (int i = 0; i < copy.len; i += 1) - if (copy.data[i] == '/') copy.data[i] = '\\'; - String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); - RunEx(args); - } + 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(*frame_arena, it); + for (int i = 0; i < copy.len; i += 1) + if (copy.data[i] == '/') copy.data[i] = '\\'; + String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); + RunEx(args); } } } - ImGui::SameLine(); - ImGui::Text(item->filepath.data); - ImGui::TextWrapped(string.data); } + ImGui::SameLine(); + ImGui::Text(SkipToLastSlash(item->filepath).data); } } SearchThreadArrayMutex.unlock(); @@ -439,44 +240,6 @@ int main(int, char **) { ImGui::End(); } -#else - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) - ImGui::ShowDemoWindow(&show_demo_window); - - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. - { - static float f = 0.0f; - static int counter = 0; - - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); - - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float *)&clear_color); // Edit 3 floats representing a color - - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::End(); - } - - // 3. Show another simple window. - if (show_another_window) { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } -#endif - // Rendering ImGui::Render(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); @@ -484,6 +247,7 @@ int main(int, char **) { glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); + SDL_Delay(16); } SearchThreadClose(search_thread); diff --git a/src/transcript_browser/searching_thread.cpp b/src/transcript_browser/searching_thread.cpp new file mode 100644 index 0000000..2705467 --- /dev/null +++ b/src/transcript_browser/searching_thread.cpp @@ -0,0 +1,45 @@ +Arena MatchesArena; +Array Matches = {MatchesArena}; +char Prompt[256]; + +std::mutex SearchThreadArrayMutex; +std::binary_semaphore SearchThreadSemaphore{0}; +bool SearchThreadStopSearching = false; +bool SearchThreadRunning = true; +void SearchThreadEntry() { + InitArena(&MatchesArena); + for (;;) { + SearchThreadSemaphore.acquire(); + if (!SearchThreadRunning) break; + SearchThreadStopSearching = false; + + if (Prompt[0]) { + SearchThreadArrayMutex.lock(); + { + Matches.clear(); + } + SearchThreadArrayMutex.unlock(); + + String buffer = {(char *)XArena.data, (int64_t)XArena.len}; + String find = Prompt; + int64_t index = 0; + while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { + String found = {buffer.data + index, find.len}; + SearchThreadArrayMutex.lock(); + { + Matches.add(found); + } + SearchThreadArrayMutex.unlock(); + + if (SearchThreadStopSearching) break; + buffer = buffer.skip(index + find.len); + } + } + } +} + +void SearchThreadClose(std::thread &thread) { + SearchThreadRunning = false; + SearchThreadSemaphore.release(); + thread.join(); +}