diff --git a/assets/Russian.json b/assets/Russian.json index 694b12a..a33af6c 100644 --- a/assets/Russian.json +++ b/assets/Russian.json @@ -322,6 +322,10 @@ "Compress": "сжатый", "LIFO": "Сначало новые", "Up": "Наверх", + "Slide": "Размер фото", + "Origin": "как есть", + "Scale": "как экран", + "VideoWall":"Видео-обои", "About":"- ПО для просмотра контента пользователя и разработки пользовательских плагинов.{br}Данное ПО не предоставляет никакого видео/аудио контента само по себе!" } } \ No newline at end of file diff --git a/assets/Ukrainian.json b/assets/Ukrainian.json index 549b837..921ef9f 100644 --- a/assets/Ukrainian.json +++ b/assets/Ukrainian.json @@ -321,6 +321,10 @@ "HasUpdate":"Є оновлення", "LIFO": "Спочатку нові", "Up": "Нагору", + "Slide": "Розмір фото", + "Origin": "як є", + "Scale": "як екран", + "VideoWall":"Відео-шпалери", "About":"- ПЗ для перегляду контенту користувача та розробки плагінів користувача.{br}Дане ПЗ не надає ніякого відео/аудіо контенту саме по собі!" } } \ No newline at end of file diff --git a/assets/index.html b/assets/index.html index bb879d8..05fa826 100644 --- a/assets/index.html +++ b/assets/index.html @@ -111,7 +111,7 @@ else if(confirm($() ? ("Есть обновление:\nServerMSX версия " + d.tag + "\nОбновить?") : ("There is an update:\nServeMSX version " + d.tag + "\nUpdate?") - )) update("/update?link=" + d.link, document.location.reload()); + )) $("/update?link=" + d.link, () => {setTimeout(() => {document.location.reload()}, (d.wait || 1)*1000)}); }) } function start(){ diff --git a/assets/index.html.gz b/assets/index.html.gz index f79b62d..a04af25 100644 Binary files a/assets/index.html.gz and b/assets/index.html.gz differ diff --git a/dic.go b/dic.go index 60e4cf2..29d208d 100644 --- a/dic.go +++ b/dic.go @@ -43,20 +43,23 @@ func init() { } svcAnswer(w, act, dat) } else { - ds := []plistObj{{"label": "default", "data": ""}} - i, e := gitRelease("", Vers) - check(e) - for _, a := range i.Assets { - if strings.HasSuffix(a.Name, ".json.gz") { - ds = append(ds, plistObj{"label": strings.TrimSuffix(a.Name, ".json.gz"), "data": a.Browser_download_url}) - } - } - (&plist{ + l := &plist{ Type: "list", Head: "{dic:label:language|Language}:", Template: plistObj{"type": "button", "layout": "0,0,8,1", "action": "execute:http://" + r.Host + r.URL.Path}, - Items: ds, - }).write(w) + Items: []plistObj{{"label": "default", "data": ""}}, + } + i, e := gitRelease("", Vers) + if e == nil { + for _, a := range i.Assets { + if strings.HasSuffix(a.Name, ".json.gz") { + l.Items = append(l.Items, plistObj{"label": strings.TrimSuffix(a.Name, ".json.gz"), "data": a.Browser_download_url}) + } + } + } else { + l.Ready = plistObj{"action": "error:" + e.Error()} + } + l.write(w) } }) } diff --git a/ffmpeg.go b/ffmpeg.go index 70b098d..8822930 100644 --- a/ffmpeg.go +++ b/ffmpeg.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "time" ) @@ -27,8 +28,12 @@ func init() { log.Println(e) } } else if d = strings.ToLower(filepath.Ext(n)); strings.Contains(extPic, d) { - d = filepath.Join(tempDir, n) + ".jpeg" - if c := ffmpegCmd(false, "-hide_banner", "-i", p, "-y", "-vf", "scale=408:-1", d); c != nil && c.Run() == nil { + h, _ := strconv.Atoi(r.FormValue("height")) + if h < 1 { + h = 264 + } + d = filepath.Join(tempDir, n) + ".png" + if c := ffmpegCmd(false, "-hide_banner", "-i", p, "-y", "-vf", "scale=-1:'min("+strconv.Itoa(h)+",ih)'", d); c != nil && c.Run() == nil { http.ServeFile(w, r, d) if e := os.Remove(d); e != nil { log.Println(e) @@ -38,7 +43,7 @@ func init() { http.ServeFile(w, r, p) } else if strings.Contains(extAud, d) { var a []string - n += ".jpeg" + n += ".png" d = filepath.Join(tempDir, n) if c := ffmpegCmd(false, "-hide_banner", "-i", p, "-y", "-an", "-vf", "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2", d); c != nil && c.Run() == nil { a = append(a, "player:background:http://"+r.Host+pthFFmpeg+url.PathEscape(n)) diff --git a/files.go b/files.go index 80b245b..b1e3698 100644 --- a/files.go +++ b/files.go @@ -36,6 +36,7 @@ func files(w http.ResponseWriter, r *http.Request) { z = "label" ts, ms []plistObj t byte + hl, hs int ) id := r.FormValue("id") if strings.IndexRune(p, filepath.Separator) > 0 { @@ -50,12 +51,20 @@ func files(w http.ResponseWriter, r *http.Request) { z, l = "title", &plist{ Type: "list", Head: hdr, Ext: "{ico:msx-white:photo-library}", Template: plistObj{"type": "separate", "imageFiller": "smart", "layout": "0,0,4,4"}, Compress: true, - Options: options(opt, plistObj{"key": "yellow", "label": "{dic:Up|up}", "action": "focus:index:0"}), + Options: options("", opt, plistObj{"key": "yellow", "label": "{dic:Up|up}", "action": "focus:index:0"}), } } else { l = mediaList(r, hdr, "{ico:msx-white:photo-library}", "image", []plistObj{opt}, false, false, false) } l.Flag = "photo" + if hh := r.FormValue("height"); hh != "" && stg.FFmpeg != "" { + if hs, _ = strconv.Atoi(hh); hs > 0 { + if stg.Clients[id]&cPhotoScale != 0 { + hl = hs + } + hs /= 4 + } + } case 'm': l, ext = mediaList(r, hdr, "{ico:msx-white:library-music}", "audiotrack", nil, false, true, true), extAud default: @@ -66,7 +75,11 @@ func files(w http.ResponseWriter, r *http.Request) { x, u := strings.ToLower(filepath.Ext(n)), "http://"+r.Host+r.URL.EscapedPath()+url.PathEscape(n) switch { case f.IsDir(): - l.Items = append(l.Items, plistObj{"icon": "msx-yellow:folder", "label": n, "action": "content:" + u + "/?id={ID}"}) + q := "/?id={ID}" + if t == 'p' { + q += "&height={HEIGHT}" + } + l.Items = append(l.Items, plistObj{"icon": "msx-yellow:folder", "label": n, "action": "content:" + u + q}) case x == ".torrent": if t != 'p' && stg.TorrServer != "" { ts = append(ts, plistObj{"icon": "msx-yellow:offline-bolt", "label": n, "action": "content:http://" + r.Host + "/msx/torr?id={ID}&link=" + url.QueryEscape(u)}) @@ -77,9 +90,18 @@ func files(w http.ResponseWriter, r *http.Request) { i := plistObj{z: n, "playerLabel": n, "extensionLabel": sizeFormat(f.Size())} switch t { case 'p': - i["action"] = "image:" + u + uu := strings.Replace(u, "/msx/", pthFFmpeg, 1) + "?height=" + if hl > 0 { + i["action"] = "image:" + uu + strconv.Itoa(hl) + } else { + i["action"] = "image:" + u + } if stg.Clients[id]&cPhoto != 0 { - i["image"] = strings.Replace(u, "/msx/", pthFFmpeg, 1) + if hs > 0 { + i["image"] = uu + strconv.Itoa(hs) + } else { + i["image"] = u + } } case 'm': i["cover"] = strings.Replace(u, "/msx/", pthFFmpeg, 1) diff --git a/maintenance.go b/maintenance.go index 7cfd362..ea326c2 100644 --- a/maintenance.go +++ b/maintenance.go @@ -62,7 +62,7 @@ func init() { } if as[0] != "" { if r.FormValue("v") == "" { - w.Write([]byte(`{"link":"` + strings.Join(as, "&dic=") + `","tag":"` + i.Tag_name + `"}`)) + w.Write([]byte(`{"link":"` + strings.Join(as, "&dic=") + `","tag":"` + i.Tag_name + `","wait":` + strconv.Itoa(performSecs) + `}`)) } else { svcAnswer(w, "execute:http://"+r.Host+"/msx/dialog", dialogStg{ "execute:fetch:http://" + r.Host + "/update?link=" + strings.Join(as, "&dic="), diff --git a/menu.go b/menu.go index f0ef76b..1146d93 100644 --- a/menu.go +++ b/menu.go @@ -1,13 +1,17 @@ package main import ( + "encoding/json" "log" "net/http" "os" "runtime" + "strconv" "strings" ) +const pthVideoWall = "/msx/videowall" + var startFocus string func init() { @@ -28,17 +32,14 @@ func init() { http.HandleFunc("/msx/menu", func(w http.ResponseWriter, r *http.Request) { pls, id, u := strings.SplitN(r.FormValue("player"), "/", 2)[0], r.FormValue("id"), "http://"+r.Host clients[id] = client{r.RemoteAddr, r.FormValue("platform"), pls, r.FormValue("v")} - if stg.Clients[id]&cHTML5X == 0 { - pls = "{txt:msx-white:" + pls + "} {ico:msx-white:toggle-off} html5x" - } else { - pls += " {col:msx-white}{ico:toggle-on} html5x" - } - l := &plist{Logo: u + "/logotype.png", Menu: []plistObj{}} + l := &plist{Logo: u + "/logotype.png", Flag: Name, Ready: plistObj{"action": "execute:service:video:data:http://" + r.Host + pthVideoWall}, Menu: []plistObj{}} for i, f := range [4][2]string{{pthMarks, "bookmarks"}, {pthVideo, "video-library"}, {pthMusic, "library-music"}, {pthPhoto, "photo-library"}} { if _, e := os.Stat(f[0]); !os.IsNotExist(e) { c, s := "folder-open", "/?id={ID}" if i == 0 { c, s = "cloud-queue", "?id={ID}" + } else if i == 3 { + s += "&height={HEIGHT}" } l.Menu = append(l.Menu, plistObj{"icon": f[1], "extensionIcon": c, "label": "{dix:" + f[0] + "}My " + f[0], "data": u + "/msx/" + f[0] + s}) } @@ -82,31 +83,73 @@ func init() { } } ts := started.UnixMilli() - lst := "{txt:msx-white:dic:Default|default} {ico:msx-white:toggle-off} {dic:Compress|compressed}" - if stg.Clients[id]&cCompressed != 0 { - lst = "{dic:Default|default} {col:msx-white}{ico:toggle-on} {dic:Compress|compressed}" - } hard := runtime.GOOS + "/" + runtime.GOARCH if stg.FFmpeg != "" { hard += " {txt:msx-yellow:+ ffmpeg}" } + walls := []plistObj{{"offset": "0,0,5,-1", "label": "{dic:label:no|No}", "data": 100, "enumerate": false}, {"type": "space"}} + for i := 1; i < 7; i++ { + ii := strconv.Itoa(i) + walls = append(walls, plistObj{"image": "http://msx.benzac.de/media/thumbs/atmos" + ii + ".jpg", "label": ii, "data": 100 + i}) + } + wall := "{dic:label:noту|None}" + if stg.VideoWall > 0 && stg.VideoWall < 7 { + wall = "{col:msx-white}" + strconv.Itoa(stg.VideoWall) + } + sa := "execute:" + u + "/settings?id={ID}" l.Menu = append(l.Menu, plistObj{"id": "stg", "icon": "settings", "label": "{dic:label:settings|Settings}", - "data": plistObj{"extension": "{ico:msx-white:settings}", + "data": plistObj{"extension": "{ico:msx-white:settings}", "compress": true, "pages": []map[string][]plistObj{{"items": { - {"type": "space", "layout": "0,0,12,2", "image": u + "/logotype.png", "imageFiller": "height", "imageWidth": 7, "imagePreload": true, + {"type": "space", "layout": "0,0,16,3", "image": u + "/logotype.png", "imageFiller": "width-top", "imageWidth": 10, "imagePreload": true, "headline": "{txt:msx-white-soft:dic:label:version|Version} " + Vers, "titleHeader": "", "titleFooter": "{ico:http}{tb}{col:msx-white}" + r.Host + "{br}{ico:msx-white-soft:hardware}{tb}" + hard + - "{br}{ico:msx-white-soft:web}{tb}https://github.com/" + gitRepo, + "{br}{br}{ico:msx-white-soft:web}{tb}https://github.com/" + gitRepo, "live": plistObj{"type": "schedule", "from": ts, "to": ts, "titleHeader": "{ico:timer}{tb}{txt:msx-white:overflow:text:dhms}"}}, - {"type": "space", "layout": "0,2,12,1", "text": "{txt:msx-white:" + Name + "} {dix:About}is a software for playing user's content and developing user's plugins.{br}It does not provide any video/audio content by itself!", "alignment": "center"}, - {"id": "update", "type": "control", "layout": "0,3,6,1", "label": "{dic:CheckUp|Check updates}", "icon": "system-update-alt", "action": "execute:fetch:" + u + "/update"}, - {"type": "control", "layout": "0,4,6,1", "icon": "smart-display", "label": "{dic:label:player|Player}:", "extensionLabel": pls, "action": "execute:" + u + "/settings?id={ID}", "data": cHTML5X}, - {"type": "control", "layout": "0,5,6,1", "label": "{dic:Files|List of files}:", "icon": "format-list-bulleted", "extensionLabel": lst, "action": "execute:" + u + "/settings?id={ID}", "data": cCompressed}, - {"id": "dic", "type": "control", "layout": "6,3,6,1", "icon": "language", "label": "{dic:Language|Language}:", "extensionLabel": "default", "action": "panel:" + u + "/msx/dictionary", "live": map[string]string{"type": "setup", "action": "execute:service:info:dictionary:" + u + "/msx/dictionary"}}, - {"type": "control", "layout": "6,4,6,1", "icon": "bolt", "label": "TorrServer:", "extensionLabel": ta, "action": "execute:" + u + "/settings", "data": nil}, - {"type": "control", "layout": "6,5,6,1", "label": "{dic:label:application|Application}", "icon": "monitor", "extensionIcon": "menu-open", "action": "dialog:application"}, + {"type": "space", "layout": "0,3,16,1", "text": "{txt:msx-white:" + Name + "} {dix:About}is a software for playing user's content and developing user's plugins.{br}It does not provide any video/audio content by itself!", "alignment": "center"}, + + {"type": "control", "layout": "0,4,8,1", "label": "{dic:CheckUp|Check updates}", "icon": "system-update-alt", "action": "execute:fetch:" + u + "/update"}, + {"type": "control", "layout": "0,5,8,1", "icon": "smart-display", "label": "{dic:label:player|Player}:", "extensionLabel": stg.switcher(r, cHTML5X, pls, "html5x"), "action": sa, "data": cHTML5X}, + {"type": "control", "layout": "0,6,8,1", "label": "{dic:Files|List of files}:", "icon": "format-list-bulleted", "extensionLabel": stg.switcher(r, cCompressed, "{dic:Default|default}", "{dic:Compress|compressed}"), "action": sa, "data": cCompressed}, + {"type": "control", "layout": "0,7,8,1", "icon": "photo-library", "label": "{dic:Slide|Photo size}:", "extensionLabel": stg.switcher(r, cPhotoScale, "{dic:Origin|as is}", "{dic:Scale|as screen}"), "action": sa, "data": cPhotoScale, "enable": stg.FFmpeg != ""}, + {"id": "dic", "type": "control", "layout": "8,4,8,1", "icon": "language", "label": "{dic:Language|Language}:", "extensionLabel": "default", "action": "panel:" + u + "/msx/dictionary", "live": map[string]string{"type": "setup", "action": "execute:service:info:dictionary:" + u + "/msx/dictionary"}}, + {"type": "control", "layout": "8,5,8,1", "icon": "bolt", "label": "TorrServer:", "extensionLabel": ta, "action": sa, "data": nil}, + {"type": "control", "layout": "8,6,8,1", "label": "{dic:VideoWall|Video wallpaper}:", "icon": "wallpaper", "extensionLabel": wall, "action": "panel:data", "data": plistObj{ + "type": "list", "headline": "{dic:VideoWall|Video wallpaper}:", "extension": "© Benjamin Zachey", "compress": true, "template": plistObj{"layout": "0,0,5,2", "imageFiller": "cover", "action": "[quiet|" + sa + "]"}, "items": walls, + }}, + {"type": "control", "layout": "8,7,8,1", "label": "{dic:label:application|Application}", "icon": "monitor", "extensionIcon": "menu-open", "action": "dialog:application"}, }}}}}) l.write(w) }) + http.HandleFunc(pthVideoWall, func(w http.ResponseWriter, r *http.Request) { + switch { + case stg.VideoWall < 1 || stg.VideoWall > 6: + svcAnswer(w, "[]", nil) + case r.Method == "POST": + var i struct { + Video struct{ Data struct{ State int } } + } + if e := json.NewDecoder(r.Body).Decode(&i); e != nil { + panic(e) + } else if i.Video.Data.State > 1 { + svcAnswer(w, "[]", nil) + return + } + fallthrough + default: + i := strconv.Itoa(stg.VideoWall) + svcAnswer(w, + "video:auto:plugin:http://msx.benzac.de/plugins/background.html?url=http%3A%2F%2Fmsx.benzac.de%2Fmedia%2Fatmos"+i+".mp4", + plistObj{ + "playerLabel": "Background Video " + i, + "properties": plistObj{ + "control:type": "extended", + "control:reuse": "play", + "control:transparent": true, + "label:duration": "{VALUE} {ico:repeat}", + }, + }, + ) + } + }) } diff --git a/plist.go b/plist.go index 56ccaa2..afc3035 100644 --- a/plist.go +++ b/plist.go @@ -9,23 +9,26 @@ import ( type plistObj map[string]interface{} type plist struct { - Type string `json:"type,omitempty"` - Reuse bool `json:"reuse"` - Cache bool `json:"cache"` - Restore bool `json:"restore"` - Compress bool `json:"compress,omitempty"` - Background string `json:"background,omitempty"` - Action string `json:"action,omitempty"` - Flag string `json:"flag,omitempty"` - Head string `json:"headline,omitempty"` - Ext string `json:"extension,omitempty"` - Logo string `json:"logo,omitempty"` - Header plistObj `json:"header,omitempty"` - Options plistObj `json:"options,omitempty"` - Template plistObj `json:"template,omitempty"` - Items []plistObj `json:"items,omitempty"` - Pages []map[string][]plistObj `json:"pages,omitempty"` - Menu []plistObj `json:"menu,omitempty"` + Type string `json:"type,omitempty"` + Reuse bool `json:"reuse"` + Cache bool `json:"cache"` + Restore bool `json:"restore"` + Compress bool `json:"compress,omitempty"` + Transparent int `json:"transparent,omitempty"` + Background string `json:"background,omitempty"` + Action string `json:"action,omitempty"` + Flag string `json:"flag,omitempty"` + Head string `json:"headline,omitempty"` + Ext string `json:"extension,omitempty"` + Logo string `json:"logo,omitempty"` + Style string `json:"style,omitempty"` + Ready plistObj `json:"ready,omitempty"` + Header plistObj `json:"header,omitempty"` + Options plistObj `json:"options,omitempty"` + Template plistObj `json:"template,omitempty"` + Items []plistObj `json:"items,omitempty"` + Pages []map[string][]plistObj `json:"pages,omitempty"` + Menu []plistObj `json:"menu,omitempty"` } func (p *plist) write(w io.Writer) error { @@ -56,9 +59,9 @@ func mediaList(r *http.Request, hdr, ext, ico string, opt []plistObj, optEach, c Template: plistObj{"icon": ico, "type": "control", "layout": lay, "progress": -1, "live": liv, "properties": ps}, } if optEach { - rtn.Template["options"] = options(opt...) + rtn.Template["options"] = options("", opt...) } else { - rtn.Options = options(opt...) + rtn.Options = options("", opt...) } return rtn } @@ -94,6 +97,7 @@ func playerProp(host, id string, live, ext bool) (ps map[string]string) { if ext { ps["control:type"] = "extended" } + ps["trigger:stop"] = "execute:service:fetch:http://" + host + pthVideoWall switch { case stg.Clients[id]&cHTML5X != 0: ps["button:content:action"] = "panel:request:player:options" @@ -106,8 +110,11 @@ func playerProp(host, id string, live, ext bool) (ps map[string]string) { } return } -func options(opts ...plistObj) plistObj { - cap := "{dic:caption:options|Options}:" +func options(cap string, opts ...plistObj) plistObj { + if cap == "" { + cap = "{dic:caption:options|Options}" + } + cap += ":" for i := 0; i < len(opts); i++ { if opts[i] == nil { opts[i] = plistObj{"key": "yellow", "label": "{dic:Up|Up}", "action": "[cleanup|focus:index:0]"} diff --git a/settings.go b/settings.go index a0d1320..10e9dcd 100644 --- a/settings.go +++ b/settings.go @@ -11,16 +11,17 @@ import ( "time" ) -const pthSettings, cHTML5X, cCompressed, cPhoto, cMarksLIFO = "settings.json", 1, 2, 4, 8 +const pthSettings, cHTML5X, cCompressed, cPhoto, cMarksLIFO, cPhotoScale = "settings.json", 1, 2, 4, 8, 16 type settings struct { TorrServer, FFmpeg, FFprobe, FFstream, Background string + VideoWall int Clients map[string]int } type client struct{ Addr, Platform, Player, Vers string } var ( - stg = &settings{"", "ffmpeg", "ffprobe", "8009", "background.jpg", make(map[string]int)} + stg = &settings{"", "ffmpeg", "ffprobe", "8009", "background.jpg", 0, make(map[string]int)} clients = make(map[string]client) ) @@ -56,6 +57,13 @@ func (s *settings) load() (e error) { } return } +func (s *settings) switcher(r *http.Request, sv int, fv, tv string) string { + if s.Clients[r.FormValue("id")]&sv == 0 { + return "{col:msx-white}" + fv + " {ico:toggle-off}{col:msx-white-soft} " + tv + } else { + return "{col:msx-white-soft}" + fv + " {col:msx-white}{ico:toggle-on} " + tv + } +} func (s *settings) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { var ( @@ -69,9 +77,13 @@ func (s *settings) ServeHTTP(w http.ResponseWriter, r *http.Request) { a, d = s.setTorr(v, !r.URL.Query().Has("v")) check(s.save()) case float64: - s.Clients[r.URL.Query().Get("id")] ^= int(v) + if k := int(v); k < 100 { + s.Clients[r.URL.Query().Get("id")] ^= k + } else { + s.VideoWall = k - 100 + } check(s.save()) - a = "reload:menu" + a = "[cleanup|reload:menu]" case map[string]interface{}: for k, vv := range v { if ss, o := vv.(string); o { diff --git a/torrserver.go b/torrserver.go index ff28a9e..0559470 100644 --- a/torrserver.go +++ b/torrserver.go @@ -39,7 +39,7 @@ func torrMain(w http.ResponseWriter, r *http.Request) { sort.Slice(d, func(i, j int) bool { return d[i].Stat < d[j].Stat }) l := &plist{Type: "list", Ext: "{ico:msx-white:bolt} TorrServer: " + stg.TorrServer, Compress: cm, Template: plistObj{ "imageWidth": 1.25, "layout": ly, "imageFiller": "height-left", "icon": "msx-glass:bolt", - "options": options( + "options": options("", plistObj{"key": "red", "label": "{dic:Remove|Remove}", "action": "execute:fetch:http://" + r.Host + "/msx/torr?del={context:id}"}, plistObj{"key": "green", "label": "{dic:Drop|Drop}", "action": "execute:fetch:http://" + r.Host + "/msx/torr?drop={context:id}"}, nil, diff --git a/version.go b/version.go index 7a15bf7..56425d9 100644 --- a/version.go +++ b/version.go @@ -1,2 +1,2 @@ package main -const Name, Vers = "ServeMSX", "0.03.203" +const Name, Vers = "ServeMSX", "0.04.203"