func main() { root := "." args := os.Args if len(os.Args) > 1 { root = os.Args[1] args = args[1:] } var err error root, err = filepath.Abs(root) if err != nil { log.Fatalf("invalid path %v", err) } var newOnly bool var random bool for _, arg := range args { switch arg { case "new": newOnly = true case "random": random = true } } var images []string filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Printf("%s %v\n", path, err) return nil } if info.IsDir() { return nil } what := mime.TypeByExtension(filepath.Ext(path)) if !strings.HasPrefix(what, "image/") { return nil } images = append(images, path) return nil }) if len(images) == 0 { p("no image.\n") return } sort.Sort(sort.Reverse(sort.StringSlice(images))) if random { for i := len(images) - 1; i >= 1; i-- { j := rand.Intn(i + 1) images[i], images[j] = images[j], images[i] } } data := &struct { Count map[string]int }{ Count: make(map[string]int), } dbPath := filepath.Join(root, ".picture_viewer.json") file, err := jsonfile.New(&data, dbPath, 51294) if err != nil { log.Fatalf("open data file %v", err) } defer file.Save() var filtered []string for _, img := range images { if newOnly { if data.Count[img] > 0 { continue } } filtered = append(filtered, img) } images = filtered keys := make(chan rune) var nextImage func() g, err := lgtk.New(` GdkPixbuf = lgi.GdkPixbuf win = Gtk.Window{ Gtk.Grid{ expand = true, orientation = 'VERTICAL', Gtk.Label{ id = 'filename', }, Gtk.Button{ label = 'Next', on_clicked = function() next_image() end, }, Gtk.ScrolledWindow{ id = 'scroll', Gtk.Image{ id = 'img', expand = true, }, expand = true, }, Gtk.Button{ label = 'Next', on_clicked = function() next_image() end, }, }, } win.on_destroy:connect(Gtk.main_quit) win.on_key_press_event:connect(function(_, ev) key_press(ev.keyval) return true end) win:show_all() `, "key_press", func(k rune) { select { case keys <- k: default: } }, "next_image", func() { nextImage() }) if err != nil { log.Fatal(err) } defer g.Close() index := 0 showImage := func() { g.ExecEval(` print(F) buf, err = GdkPixbuf.Pixbuf.new_from_file(F) win.child.img:set_from_pixbuf(buf) win.child.scroll.vadjustment:set_value(0) win.child.filename:set_label(F) `, "F", images[index]) } showImage() nextImage = func() { if index == len(images)-1 { return } data.Count[images[index]]++ index++ showImage() } loop: for key := range keys { switch key { case 'q': break loop case ' ': nextImage() time.Sleep(time.Millisecond * 500) case 'z': if index == 0 { continue loop } index-- showImage() } } }
func (db *Db) lgi_gst(infos []*PathInfo) { index := 0 keys := make(chan rune) inputChan := make(chan string) g, err := lgtk.New(` Gst = lgi.require('Gst', '1.0') GstVideo = lgi.require('GstVideo', '1.0') GdkX11 = lgi.GdkX11 win = Gtk.Window{ Gtk.Grid{ expand = true, orientation = 'VERTICAL', Gtk.DrawingArea{ id = 'output', expand = true, can_focus = true, }, Gtk.Label{ id = 'uri', }, Gtk.Entry{ id = 'input', visible = false, }, }, } function win.on_destroy() Exit() end function win.on_realize() win.child.input:hide() end function win:on_key_press_event(event) Key(event.keyval) return true end pipeline = Gst.ElementFactory.make('playbin', 'bin') sink = Gst.ElementFactory.make('ximagesink', 'sink') pipeline.video_sink = sink xid = true function reload_video() pipeline.state = 'NULL' sink:set_window_handle(xid) path = GetPath() print('playing', path) pipeline.uri = 'file://' .. path pipeline.state = 'PLAYING' win.child.uri.label = path end function win.child.output:on_realize() xid = self.window:get_xid() reload_video() end function seek(position, duration, flag) if position > duration then return end if position < 0 then position = 0 end pipeline:seek_simple(Gst.Format.TIME, {'FLUSH', 'KEY_UNIT', flag}, position) end function seek_abs(position) pipeline:seek_simple(Gst.Format.TIME, {'FLUSH', 'KEY_UNIT', 'SNAP_BEFORE'}, position) end function seek_time(n) position = pipeline:query_position('TIME') if position == nil then return end position = position + n duration = pipeline:query_duration('TIME') flag = 'SNAP_AFTER' if n < 0 then flag = 'SNAP_BEFORE' end seek(position, duration, flag) end function seek_percent(n) position = pipeline:query_position('TIME') if position == nil then return end duration = pipeline:query_duration('TIME') position = position + duration / 100 * n flag = 'SNAP_AFTER' if n < 0 then flag = 'SNAP_BEFORE' end seek(position, duration, flag) end paused = false function toggle_pause() if paused then pipeline.state = 'PLAYING' paused = false else pipeline.state = 'PAUSED' paused = true end end pipeline.bus:add_watch(GLib.PRIORITY_DEFAULT, function(bus, message) if message.type.ERROR then print('Error:', message:parse_error().message, GetPath()) end if message.type.EOS then print('end of stream') seek_abs(0) pipeline.state = 'PLAYING' end return true end) input = win.child.input input.on_activate:connect(function() input:hide() win.child.output:grab_focus() ProvideInput(input:get_text()) pipeline.state = 'PLAYING' return true end) win:show_all() `, "GetPath", func() string { return infos[index].path }, "Key", func(val rune) { select { case keys <- val: default: } }, "ProvideInput", func(s string) { inputChan <- s }, ) if err != nil { panic(err) } // helper functions getPos := func() int64 { var ret int64 = -1 g.WaitExec(func() { pos, ok := g.Eval(`return pipeline:query_position('TIME')`)[0].(float64) if ok { ret = int64(pos) } }) return ret } run := func(code string) { g.Exec(func() { g.Eval(code) }) } /* // watch count resetTimer := make(chan bool) go func() { minWatchTime := time.Second * 30 t := time.NewTimer(minWatchTime) for { select { case <-t.C: // watched infos[index].file.WatchCount++ t.Stop() p("watched %s\n", infos[index].path) db.Save() case <-resetTimer: t.Reset(minWatchTime) } } }() */ reload := func() { run("reload_video()") //resetTimer <- true } for { key := <-keys switch key { case 'q': os.Exit(0) case ' ': // toggle pause run("toggle_pause()") case 'j', 'r': // next video infos[index].file.WatchCount++ p("watched %s\n", infos[index].path) db.Save() index += 1 if index >= len(infos) { index = 0 } reload() case 'k', 'z': // prev video index -= 1 if index < 0 { index = len(infos) - 1 } reload() case 'd': // seek forward run("seek_time(3000000000)") case 'a': // seek backward run("seek_time(-3000000000)") case 's': // seek forward long run("seek_time(10000000000)") case 'w': // seek backward long run("seek_time(-10000000000)") case 'D': // seek percent forward run("seek_percent(3)") case 'A': // seek percent backward run("seek_percent(-3)") case 'S': // seek percent forward long run("seek_percent(10)") case 'W': // seek percent backward long run("seek_percent(-10)") case 'e': // tag pos := getPos() if pos < 0 { continue } run(` pipeline.state = 'PAUSED' input:show() input:grab_focus() `) desc := <-inputChan if desc != "" { p("add tag %d %s\n", pos, desc) infos[index].file.AddTag(pos, desc) db.Save() } case 'f': // next tag pos := getPos() if pos < 0 { continue } var next int64 for _, tag := range infos[index].file.Tags { if tag.Position > pos { if tag.Position < next || next == 0 { next = tag.Position } } } if next > 0 { p("jump to tag %d\n", next) run(s("seek_abs(%d)", next)) } case 'c': // prev tag pos := getPos() if pos < 0 { continue } var prev int64 for _, tag := range infos[index].file.Tags { if tag.Position < pos { if tag.Position > prev || prev == 0 { prev = tag.Position } } } if prev > 0 { p("jump to tag %d\n", prev) run(s("seek_abs(%d)", prev)) } case 'x': // to first tag if len(infos[index].file.Tags) > 0 { run(s("seek_abs(%d)", infos[index].file.Tags[0].Position)) } } } }
func ui_gtk(connects []*Connect, mem *Memory) { // ui keys := make(chan rune) g, err := lgtk.New(` Gdk = lgi.Gdk css = Gtk.CssProvider.get_default() css:load_from_data([[ GtkWindow { background-color: black; color: white; } #hint { font-size: 16px; } #text { font-size: 48px; color: #0099CC; } #level { color: grey; } ]]) Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css, 999) win = Gtk.Window{ Gtk.Grid{ orientation = 'VERTICAL', Gtk.Label{ expand = true, }, Gtk.Label{ id = 'hint', name = 'hint', }, Gtk.Label{ id = 'text', name = 'text', }, Gtk.Label{ expand = true, }, Gtk.Label{ id = 'level', name = 'level', }, }, } function win:on_key_press_event(ev) Key(ev.keyval) return true end function win.on_destroy() Exit(0) end win:show_all() `, "Key", func(val rune) { select { case keys <- val: default: } }, ) if err != nil { log.Fatal(err) } setHint := func(s string) { g.ExecEval(`win.child.hint:set_label(T)`, "T", s) } setText := func(s string) { g.ExecEval(`win.child.text:set_label(T)`, "T", s) } setLevel := func(s string) { g.ExecEval(`win.child.level:set_label(T)`, "T", s) } setHint("press f to start") for { key := <-keys if key == 'f' { break } } setHint("") wg := new(sync.WaitGroup) save := func() { wg.Add(1) go func() { mem.Save() wg.Done() }() } // train loop: for _, connect := range connects { setHint("") setText("") from := mem.Concepts[connect.From] to := mem.Concepts[connect.To] lastHistory := connect.Histories[len(connect.Histories)-1] setLevel(strconv.Itoa(lastHistory.Level)) switch from.What { case AUDIO: // play audio setHint("playing...") from.Play() if to.What == WORD { setHint("press any key to show answer") <-keys setText(to.Text) } repeat: setHint("press G to levelup, T to reset level, Space to repeat") read_key: key := <-keys switch key { case 'g': connect.Histories = append(connect.Histories, History{Level: lastHistory.Level + 1, Time: time.Now()}) save() case 't': connect.Histories = append(connect.Histories, History{Level: 0, Time: time.Now()}) save() case ' ': setHint("playing...") from.Play() setHint("") goto repeat case 'q': setText("") setHint("exit...") break loop default: goto read_key } case WORD: // show text setText(from.Text) setHint("press any key to play audio") <-keys repeat2: setHint("playing...") to.Play() setHint("press G to levelup, T to reset level, Space to repeat") read_key2: key := <-keys switch key { case 'g': connect.Histories = append(connect.Histories, History{Level: lastHistory.Level + 1, Time: time.Now()}) save() case 't': connect.Histories = append(connect.Histories, History{Level: 0, Time: time.Now()}) save() case ' ': goto repeat2 case 'q': setText("") setHint("exit...") break loop default: goto read_key2 } default: panic("impossible") } } wg.Wait() }