VideoTools/cmd/gtkplayer/main.go

230 lines
5.4 KiB
Go

package main
import (
"fmt"
"log"
"path/filepath"
"time"
"git.leaktechnologies.dev/stu/VT_Player/player/mpvembed"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
)
type pane struct {
area *gtk.DrawingArea
mpv *mpvembed.Client
path string
}
func main() {
gtk.Init(nil)
win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
log.Fatalf("window: %v", err)
}
win.SetTitle("VT Player (GTK/mpv)")
win.SetDefaultSize(1400, 800)
grid, _ := gtk.GridNew()
grid.SetColumnHomogeneous(true)
grid.SetRowHomogeneous(false)
win.Add(grid)
// Two panes for compare; left/right
left := newPane()
right := newPane()
controls := buildControls(win, left, right)
grid.Attach(controls, 0, 0, 2, 1)
grid.Attach(left.area, 0, 1, 1, 1)
grid.Attach(right.area, 1, 1, 1, 1)
win.Connect("destroy", func() {
if left.mpv != nil {
left.mpv.Destroy()
}
if right.mpv != nil {
right.mpv.Destroy()
}
gtk.MainQuit()
})
win.ShowAll()
gtk.Main()
}
func newPane() *pane {
da, _ := gtk.DrawingAreaNew()
da.SetHExpand(true)
da.SetVExpand(true)
p := &pane{area: da}
da.Connect("realize", func() {
if p.mpv != nil {
return
}
mpv, err := mpvembed.New()
if err != nil {
log.Printf("mpv create: %v", err)
return
}
p.mpv = mpv
if w, ok := da.GetWindow(); ok && w != nil {
if xid := getWindowID(w); xid != 0 {
_ = mpv.SetWID(xid)
}
}
_ = mpv.SetOptionString("pause", "yes")
if err := mpv.Initialize(); err != nil {
log.Printf("mpv init: %v", err)
}
})
return p
}
func buildControls(win *gtk.Window, left, right *pane) *gtk.Box {
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 6)
row1, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
row2, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
row3, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
openL, _ := gtk.ButtonNewWithLabel("Open Left")
openR, _ := gtk.ButtonNewWithLabel("Open Right")
play, _ := gtk.ButtonNewWithLabel("Play Both")
pause, _ := gtk.ButtonNewWithLabel("Pause Both")
seek0, _ := gtk.ButtonNewWithLabel("Seek 0")
stepF, _ := gtk.ButtonNewWithLabel("Step +1f")
stepB, _ := gtk.ButtonNewWithLabel("Step -1f")
info, _ := gtk.LabelNew("Meta: -")
openL.Connect("clicked", func() { chooseAndLoad(win, left) })
openR.Connect("clicked", func() { chooseAndLoad(win, right) })
play.Connect("clicked", func() {
if left.mpv != nil {
_ = left.mpv.SetPropertyBool("pause", false)
}
if right.mpv != nil {
_ = right.mpv.SetPropertyBool("pause", false)
}
})
pause.Connect("clicked", func() {
if left.mpv != nil {
_ = left.mpv.SetPropertyBool("pause", true)
}
if right.mpv != nil {
_ = right.mpv.SetPropertyBool("pause", true)
}
})
seek0.Connect("clicked", func() {
if left.mpv != nil {
_ = left.mpv.Command("seek", "0", "absolute", "exact")
}
if right.mpv != nil {
_ = right.mpv.Command("seek", "0", "absolute", "exact")
}
})
stepF.Connect("clicked", func() {
if left.mpv != nil {
_ = left.mpv.Command("frame-step")
}
if right.mpv != nil {
_ = right.mpv.Command("frame-step")
}
})
stepB.Connect("clicked", func() {
if left.mpv != nil {
_ = left.mpv.Command("frame-back-step")
}
if right.mpv != nil {
_ = right.mpv.Command("frame-back-step")
}
})
// Poll meta/progress
go func() {
t := time.NewTicker(500 * time.Millisecond)
defer t.Stop()
for range t.C {
text := metaSummary(left, right)
glib.IdleAdd(func() { info.SetText(text) })
}
}()
row1.PackStart(openL, false, false, 0)
row1.PackStart(openR, false, false, 0)
row1.PackStart(play, false, false, 0)
row1.PackStart(pause, false, false, 0)
row1.PackStart(seek0, false, false, 0)
row1.PackStart(stepB, false, false, 0)
row1.PackStart(stepF, false, false, 0)
row2.PackStart(info, false, false, 0)
box.PackStart(row1, false, false, 0)
box.PackStart(row2, false, false, 0)
box.PackStart(row3, false, false, 0)
return box
}
func chooseAndLoad(win *gtk.Window, p *pane) {
dlg, _ := gtk.FileChooserDialogNewWith1Button("Open Video", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT)
if resp := dlg.Run(); resp == gtk.RESPONSE_ACCEPT {
filename := dlg.GetFilename()
if filename != "" {
loadIntoPane(p, filename)
}
}
dlg.Destroy()
}
func loadIntoPane(p *pane, filename string) {
if p.mpv == nil {
return
}
p.path = filename
if err := p.mpv.Command("loadfile", filename, "replace"); err != nil {
log.Printf("loadfile %s: %v", filename, err)
}
_ = p.mpv.SetPropertyBool("pause", false)
}
func metaSummary(a, b *pane) string {
parts := func(p *pane) string {
if p == nil || p.mpv == nil || p.path == "" {
return "[empty]"
}
dur, _ := p.mpv.GetPropertyDouble("duration")
pos, _ := p.mpv.GetPropertyDouble("time-pos")
w, _ := p.mpv.GetPropertyInt64("width")
h, _ := p.mpv.GetPropertyInt64("height")
return fmt.Sprintf("%s | %dx%d | %.1f/%.1fs", filepath.Base(p.path), w, h, pos, dur)
}
return fmt.Sprintf("L: %s | R: %s", parts(a), parts(b))
}
// getWindowID returns the native window handle (XID on X11, HWND on Windows).
func getWindowID(w *gdk.Window) uint64 {
// X11
if xid := gdkWindowGetXID(w); xid != 0 {
return uint64(xid)
}
// TODO: add Windows handle if needed.
return 0
}
// gdkWindowGetXID extracts the XID from a GDK window when running on X11.
func gdkWindowGetXID(w *gdk.Window) uint {
type xidGetter interface {
GetXID() uint
}
if xw, ok := w.(xidGetter); ok {
return xw.GetXID()
}
return 0
}