From fc15e7376f4e24b60f812f1ef31f058253e32d9c Mon Sep 17 00:00:00 2001 From: Nick White Date: Tue, 26 Oct 2021 16:59:22 +0100 Subject: rescribe: Separate gui code, and organise it better (should be no functional change) --- cmd/rescribe/gui.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/rescribe/main.go | 116 +++--------------------------------------- 2 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 cmd/rescribe/gui.go diff --git a/cmd/rescribe/gui.go b/cmd/rescribe/gui.go new file mode 100644 index 0000000..4944e42 --- /dev/null +++ b/cmd/rescribe/gui.go @@ -0,0 +1,140 @@ +// Copyright 2021 Nick White. +// Use of this source code is governed by the GPLv3 +// license that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// copyStdoutToChan creates a pipe to copy anything written +// to stdout instead to a rune channel +func copyStdoutToChan() (chan rune, error) { + c := make(chan rune) + + origStdout := os.Stdout + r, w, err := os.Pipe() + if err != nil { + return c, fmt.Errorf("Error creating pipe for stdout redirection: %v", err) + } + os.Stdout = w + + bufReader := bufio.NewReader(r) + + go func() { + defer func() { + close(c) + w.Close() + os.Stdout = origStdout + }() + for { + r, _, err := bufReader.ReadRune() + if err != nil && err != io.EOF { + return + } + c <- r + if err == io.EOF { + return + } + } + }() + + return c, nil +} + +// startGui starts the gui process +func startGui(log log.Logger, cmd string, training string, systess bool, tessdir string) error { + myApp := app.New() + myWindow := myApp.NewWindow("Rescribe OCR") + + var gobtn *widget.Button + + dir := widget.NewEntry() + dir.SetPlaceHolder("Folder to process") + dir.OnChanged = func(s string) { + // TODO: also check if string is a directory, and only enable if so + if dir.Text != "" { + gobtn.Enable() + } else { + gobtn.Disable() + } + } + + openbtn := widget.NewButtonWithIcon("Choose folder", theme.FolderOpenIcon(), func() { + dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) { + if err == nil && uri != nil { + dir.SetText(uri.Path()) + } + }, myWindow)}) + + progressBar := widget.NewProgressBar() + + logarea := widget.NewMultiLineEntry() + logarea.Disable() + + + // TODO: have the button be pressed if enter is pressed + gobtn = widget.NewButtonWithIcon("Process OCR", theme.UploadIcon(), func() { + if dir.Text == "" { + return + } + + gobtn.Disable() + gobtn.SetText("Processing...") + + progressBar.SetValue(0.5) + + stdout, err := copyStdoutToChan() + if err != nil { + fmt.Fprintf(os.Stderr, "Error copying stdout to chan: %v\n", err) + return + } + + // update log area with output from outC in a concurrent goroutine + go func() { + for r := range stdout { + logarea.SetText(logarea.Text + string(r)) + logarea.CursorRow = strings.Count(logarea.Text, "\n") + // TODO: set text on progress bar, or a label below it, to latest line printed, rather than just using a whole multiline entry like this + // TODO: parse the stdout and set progressBar based on that + } + }() + + err = startProcess(log, cmd, dir.Text, filepath.Base(dir.Text), training, systess, dir.Text, tessdir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error executing process: %v\n", err) + return + } + + progressBar.SetValue(1.0) + gobtn.SetText("Process OCR") + gobtn.Enable() + }) + gobtn.Disable() + + diropener := container.New(layout.NewGridLayout(2), dir, openbtn) + + content := container.NewVBox(diropener, gobtn, progressBar, logarea) + + myWindow.SetContent(content) + + myWindow.Show() + myApp.Run() + + return nil +} diff --git a/cmd/rescribe/main.go b/cmd/rescribe/main.go index 6ff99e7..3944ace 100644 --- a/cmd/rescribe/main.go +++ b/cmd/rescribe/main.go @@ -11,7 +11,6 @@ package main import ( "archive/zip" - "bufio" "bytes" _ "embed" "errors" @@ -28,21 +27,13 @@ import ( "strings" "time" - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" - "rescribe.xyz/bookpipeline" "rescribe.xyz/utils/pkg/hocr" "rescribe.xyz/bookpipeline/internal/pipeline" ) -const usage = `Usage: rescribe [-v] [-t training] bookdir [savedir] +const usage = `Usage: rescribe [-v] [-gui] [-systess] [-tesscmd] [-t training] bookdir [savedir] Process and OCR a book using the Rescribe pipeline on a local machine. @@ -151,6 +142,7 @@ func main() { } verbose := flag.Bool("v", false, "verbose") + usegui := flag.Bool("gui", false, "Use graphical user interface") systess := flag.Bool("systess", false, "Use the system installed Tesseract, rather than the copy embedded in rescribe.") training := flag.String("t", "rescribev8_fast.traineddata", `Path to the tesseract training file to use. These training files are included in rescribe, and are always available: @@ -243,107 +235,11 @@ These training files are included in rescribe, and are always available: log.Fatalln("Error setting TESSDATA_PREFIX:", err) } - if flag.NArg() < 1 { - myApp := app.New() - myWindow := myApp.NewWindow("Rescribe OCR") - - var gobtn *widget.Button - - dir := widget.NewEntry() - dir.SetPlaceHolder("Folder to process") - dir.OnChanged = func(s string) { - // TODO: also check if string is a directory, and only enable if so - if dir.Text != "" { - gobtn.Enable() - } else { - gobtn.Disable() - } + if flag.NArg() < 1 || *usegui { + err := startGui(*verboselog, tessCommand, trainingName, *systess, tessdir) + if err != nil { + log.Fatalln("Error in gui:", err) } - - openbtn := widget.NewButtonWithIcon("Choose folder", theme.FolderOpenIcon(), func() { - dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) { - if err == nil && uri != nil { - dir.SetText(uri.Path()) - } - }, myWindow)}) - - progressBar := widget.NewProgressBar() - - logarea := widget.NewMultiLineEntry() - logarea.Disable() - - - // TODO: have the button be pressed if enter is pressed - gobtn = widget.NewButtonWithIcon("Process OCR", theme.UploadIcon(), func() { - if dir.Text == "" { - return - } - - gobtn.Disable() - gobtn.SetText("Processing...") - - progressBar.SetValue(0.5) - - - // https://stackoverflow.com/questions/10473800/in-go-how-do-i-capture-stdout-of-a-function-into-a-string - // https://eli.thegreenplace.net/2020/faking-stdin-and-stdout-in-go/ - origStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - log.Fatalln("Error creating pipe for stdout redirection: ", err) - } - os.Stdout = w - defer func() { - w.Close() - os.Stdout = origStdout - }() - - bufReader := bufio.NewReader(r) - outC := make(chan rune) - go func() { - for { - r, _, err := bufReader.ReadRune() - if err != nil && err != io.EOF { - log.Fatalf("Error reading stdout: %v", err) - return - } - outC <- r - if err == io.EOF { - close(outC) - return - } - } - }() - - // update log area with output from outC in a concurrent goroutine - go func() { - for r := range outC { - logarea.SetText(logarea.Text + string(r)) - logarea.CursorRow = strings.Count(logarea.Text, "\n") - // TODO: set text on progress bar, or a label below it, to latest line printed, rather than just using a whole multiline entry like this - // TODO: parse the stdout and set progressBar based on that - } - }() - - err = startProcess(*verboselog, tessCommand, dir.Text, filepath.Base(dir.Text), trainingName, *systess, dir.Text, tessdir) - if err != nil { - log.Fatalln(err) - } - - progressBar.SetValue(1.0) - gobtn.SetText("Process OCR") - gobtn.Enable() - }) - gobtn.Disable() - - diropener := container.New(layout.NewGridLayout(2), dir, openbtn) - - content := container.NewVBox(diropener, gobtn, progressBar, logarea) - - myWindow.SetContent(content) - - myWindow.Show() - myApp.Run() return } -- cgit v1.2.1-24-ge1ad