From cf0f0f3e8ab43fba9e02ac6588e8c5955e2fbc40 Mon Sep 17 00:00:00 2001 From: blackteay Date: Tue, 7 May 2024 13:53:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 6148 bytes config/userconfig.yaml | 6 +- go.mod | 1 + go.sum | 30 ++- main.go | 504 ++++++++++++++++++++++++++++++++--------- 5 files changed, 431 insertions(+), 110 deletions(-) diff --git a/.DS_Store b/.DS_Store index b74b2311524a5922155c4a099c62ca98a0ee5b8f..3373b126ab5cff74ad30384510986f69c5d2fa31 100644 GIT binary patch delta 131 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jHAjU^g=(|7IQmGe%xEhGd3( zhCGHehD?U^$>)USH#-T=W}K`pJYn*0k-Ww192|noK+QlPzzrl^LFQ~M{LVa?U&a$; SDgzV529Tu;o8x)rFarQKz!z!& delta 443 zcmZoMXmOBWU|?W$DortDU;r^WfEYvza8E20o2aMAD7rCVH}hr%jz7$c**Q2SHn1>? zZsuVzW89p_I-5~jlp&3wk|B>Fl_8!X7f6;cBr>D`F^G<5$OOt{0@->D=?wXkU$Lzh z=Vr)Y$YCgE&}B$sCP}=RU?>3Um#-(FCMVr6 zI5|JJ0O$b*Ccd~TAVok&ZoZ34QcivnP$kFXMAjX4UyeHB*5wOxOm2P(3EE(~3NnzK zCnzv^Dtpn!hIvea%s?A~K!O`cxProdvmnQJ=E?jbo|FA~I5-#~(ZVn}o@eUh+3YzR I8)h>B0BC7mTmS$7 diff --git a/config/userconfig.yaml b/config/userconfig.yaml index 60a41d3..e6e5f65 100644 --- a/config/userconfig.yaml +++ b/config/userconfig.yaml @@ -1,6 +1,6 @@ -accesskey: "TOKEN_f7c84db0-1671-4a70-953e-558cb25a4d74" -secretkey: "7de3463e-0ad8-4f28-ba26-b5012f78f2dc" -endpoint: "ufile.cn-bj.ucloud.cn" +accesskey: "TOKEN_6096c736-12b7-4c20-bfa5-e85e0e9c9b65" +secretkey: "cc8a5965-3325-4231-9f64-a6626f624049" +endpoint: "cn-bj.ufileos.com" encrypt: "false" enablessl: "true" language: "ZH" \ No newline at end of file diff --git a/go.mod b/go.mod index 593e4f9..dbb41c9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module hls-builder go 1.22.0 require ( + fyne.io/fyne v1.4.3 fyne.io/fyne/v2 v2.4.5 github.com/google/uuid v1.1.2 ) diff --git a/go.sum b/go.sum index 50798f9..8f22508 100644 --- a/go.sum +++ b/go.sum @@ -37,12 +37,16 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I= +fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8= fyne.io/fyne/v2 v2.4.5 h1:W6jpAEmLoBbKyBB+EXqI7GMJ7kLgHQWCa0wZHUV2VfQ= fyne.io/fyne/v2 v2.4.5/go.mod h1:SlOgbca0y80cRObu/JOhxIJdIgtoW7aCyqUVlTMgs0Y= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -82,12 +86,15 @@ github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFN github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= +github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb h1:S9I8pIVT5JHKDvmI1vQ0qs5fqxzUfhcZm/YbUC/8k1k= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -97,10 +104,13 @@ github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKw github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3 h1:levTnuLLUmpavLGbJYLJA7fQnKeS7P1eCdAlM+vReXk= github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -194,6 +204,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= +github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -203,11 +215,11 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -225,9 +237,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -244,14 +261,18 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -313,6 +334,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -436,6 +458,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -492,6 +515,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -513,6 +537,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -641,8 +666,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index e8dd7a8..cb1ba29 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ /* * @Author: BlackTeay * @Date: 2024-04-30 09:37:39 - * @LastEditTime: 2024-05-06 17:33:58 + * @LastEditTime: 2024-05-07 13:53:30 * @LastEditors: BlackTeay * @Description: * @FilePath: /hls_builder/main.go @@ -42,24 +42,56 @@ var ttfBytes []byte //go:embed bin/* var ffmpegFS embed.FS +//go:embed config/userconfig.yaml +var configFS embed.FS + +// MyTheme 定义了一个自定义的主题结构体。 type MyTheme struct{} +// Color 根据给定的主题颜色名称和变种返回相应的颜色。 +// 参数 c 为主题颜色的名称。 +// 参数 v 为颜色的变种。 +// 返回值为符合 color.Color 接口的颜色值。 func (MyTheme) Color(c fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { return theme.DefaultTheme().Color(c, v) } +// Font 根据给定的文本样式返回相应的字体资源。 +// 参数 s 为文本的样式,决定了使用的字体。 +// 返回值为一个 fyne.Resource 类型的字体资源。 func (MyTheme) Font(s fyne.TextStyle) fyne.Resource { return fyne.NewStaticResource("./font/NotoSansSC-Regular.ttf", ttfBytes) } +// Icon 根据给定的图标名称返回相应的图标资源。 +// 参数 n 为图标的名称。 +// 返回值为一个 fyne.Resource 类型的图标资源。 func (MyTheme) Icon(n fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(n) } +// Size 根据给定的主题尺寸名称返回相应的尺寸值。 +// 参数 s 为主题尺寸的名称。 +// 返回值为相应的尺寸值,单位为 float32。 func (MyTheme) Size(s fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(s) } -func getFFmpegPath() string { + +type UploadStats struct { + Successed int + Failed int + TotalSize string + AverageSpeed string + Elapsed string +} + +// getFFmpegPath 获取FFmpeg可执行文件的路径。 +// 该函数根据运行时操作系统选择对应的FFmpeg文件,将该文件从嵌入的文件系统读出, +// 并写入到一个临时目录中,然后返回这个临时文件的路径。 +// 返回值: +// +// string - FFmpeg可执行文件的临时路径。 +func getFFmpegPath() (string, func(), error) { var ffmpegFileName string switch runtime.GOOS { case "windows": @@ -68,13 +100,13 @@ func getFFmpegPath() string { ffmpegFileName = "ffmpeg_darwin" } - // 读取文件内容 + // 根据操作系统选择的FFmpeg文件名,从嵌入的文件系统中读取文件内容 data, err := ffmpegFS.ReadFile("bin/" + ffmpegFileName) if err != nil { log.Fatal(err) } - // 写入到临时文件中 + // 创建临时目录并将FFmpeg文件内容写入该目录中的文件 tmpDir, err := os.MkdirTemp("", "ffmpeg") if err != nil { log.Fatal(err) @@ -83,10 +115,19 @@ func getFFmpegPath() string { if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { log.Fatal(err) } + cleanup := func() { + os.RemoveAll(tmpDir) // 删除整个临时目录 + } - return tmpFilePath + return tmpFilePath, cleanup, nil } -func getUs3cliPath() string { + +// getUs3cliPath 获取us3cli二进制文件的临时路径。 +// 该函数根据运行的操作系统选择对应的us3cli文件,并将其从嵌入的文件系统中读出, +// 然后写入到一个临时文件中,最后返回这个临时文件的路径。 +// 返回值: +// - string: us3cli临时文件的路径。 +func getUs3cliPath() (string, func(), error) { var us3cliFileName string switch runtime.GOOS { case "windows": @@ -95,13 +136,13 @@ func getUs3cliPath() string { us3cliFileName = "us3cli-mac" } - // 读取文件内容 + // 根据操作系统选择的us3cli文件名,从嵌入的文件系统中读取文件内容 data, err := ffmpegFS.ReadFile("bin/" + us3cliFileName) if err != nil { log.Fatal(err) } - // 写入到临时文件中 + // 创建临时目录并将us3cli文件内容写入其中 tmpDir, err := os.MkdirTemp("", "us3cli") if err != nil { log.Fatal(err) @@ -110,22 +151,57 @@ func getUs3cliPath() string { if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { log.Fatal(err) } + cleanup := func() { + os.RemoveAll(tmpDir) // 删除整个临时目录 + } - return tmpFilePath + return tmpFilePath, cleanup, nil } + +func getUs3Config() (string, func(), error) { + // 读取嵌入的配置文件 + data, err := configFS.ReadFile("config/userconfig.yaml") + if err != nil { + log.Fatal(err) + } + + // 创建临时目录并将us3cli文件内容写入其中 + tmpDir, err := os.MkdirTemp("", "config") + if err != nil { + log.Fatal(err) + } + tmpFilePath := filepath.Join(tmpDir, "userconfig.yaml") + if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { + log.Fatal(err) + } + cleanup := func() { + os.RemoveAll(tmpDir) // 删除整个临时目录 + } + + return tmpFilePath, cleanup, nil +} + +// main 是应用程序的入口点。 func main() { - //打印程序当前路径 + // 打印当前工作目录 log.Println("Current path:", os.Getenv("PWD")) + + // 初始化应用并设置主题 myApp := app.NewWithID("hls_builder") myApp.Settings().SetTheme(MyTheme{}) - myWindow := myApp.NewWindow("HLS-builder 视频切片工具") - // 设置窗口大小 - myWindow.Resize(fyne.NewSize(800, 600)) // 设置窗口宽为800像素,高为600像素 + + // 创建主窗口并设置大小 + myWindow := myApp.NewWindow("HLS-builder 视频切片上传工具") + myWindow.Resize(fyne.NewSize(800, 600)) + + // 初始化UI组件:文件选择提示、输出路径选择提示、进度条 inputFile := widget.NewLabel("请选择mp4文件!") outputDir := widget.NewLabel("请选择输出路径!") - progressBar := widget.NewProgressBar() - + sliceProgressBar := widget.NewProgressBar() + uploadProgressBar := widget.NewProgressBar() + // 创建选择视频文件按钮并设置点击事件 btnSelectFile := widget.NewButton("选择mp4文件", func() { + // 设置文件类型过滤器 filter := storage.NewExtensionFileFilter([]string{".mp4"}) fileDialog := dialog.NewFileOpen(func(file fyne.URIReadCloser, err error) { if err != nil { @@ -137,10 +213,11 @@ func main() { inputFile.SetText(file.URI().Path()) } }, myWindow) - fileDialog.SetFilter(filter) // 设置文件过滤器 + fileDialog.SetFilter(filter) fileDialog.Show() }) + // 创建选择输出路径按钮并设置点击事件 btnSelectOutput := widget.NewButton("选择输出路径", func() { dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) { if err == nil && uri != nil { @@ -149,235 +226,324 @@ func main() { } }, myWindow) }) - var btnSlice *widget.Button // 预先声明btnSlice变量 var randDir string var hlsDir string + + // 初始化上传按钮 + uploadLabel := widget.NewLabel("上传切片到Ucloud:") + var btnUpload *widget.Button + btnUpload = widget.NewButton("上传", func() { + btnUpload.Text = "正在上传..." + btnUpload.Disable() + + // 调用上传函数,并在上传完成后启用按钮 + go upload(hlsDir, randDir, uploadProgressBar, myWindow, func() { + + btnUpload.Text = "上传" + btnUpload.Enable() + }) + }) + // 初始化切片按钮及相关变量 + var btnSlice *widget.Button btnSlice = widget.NewButton("开始切片", func() { - if inputFile.Text == "没有选择视频文件" || outputDir.Text == "没有选择输出路径" { + fmt.Println(inputFile.Text) + fmt.Println(outputDir.Text) + // 验证用户选择 + if inputFile.Text == "请选择mp4文件!" || outputDir.Text == "请选择输出路径!" { dialog.ShowError(errors.New("请选择视频文件和输出路径"), myWindow) return } - // 禁用按钮 + // 禁用UI元素以防止用户干预 btnSelectFile.Disable() btnSelectOutput.Disable() btnSlice.Disable() btnSlice.SetText("切片中...") + // 生成随机目录名并在输出路径下创建该目录 randDir = uuid.New().String() - //在outputDir中创建一个文件夹,名为一个UUID字符串 hlsDir = outputDir.Text + "/" + randDir - //新建这个目录 err := os.Mkdir(hlsDir, 0755) if err != nil { dialog.ShowError(errors.New("建立随机目录出错!"), myWindow) } - go sliceVideo(inputFile.Text, hlsDir, progressBar, func() { - // UI updates directly in the callback + // 调用切片函数,并在切片完成后更新UI + go sliceVideo(inputFile.Text, hlsDir, sliceProgressBar, func() { btnSelectFile.Enable() btnSelectOutput.Enable() btnSlice.Enable() btnSlice.SetText("开始切片") - // You may also want to display a dialog or notification - // dialog.ShowInformation("完成", "Video slicing completed successfully.", myWindow) - }, btnSelectFile, btnSelectOutput, btnSlice, myWindow) + }, btnSelectFile, btnSelectOutput, btnSlice, btnUpload, myWindow) + }) - }) - uploadLabel := widget.NewLabel("上传切片到Ucloud") - var btnUpload *widget.Button - btnUpload = widget.NewButton("上传到服务器", func() { - go upload(hlsDir, randDir, func() { - btnUpload.Enable() - }) - }) - // btnUpload.Disable() + btnUpload.Disable() + // 组合UI元素 content := container.NewVBox( inputFile, btnSelectFile, outputDir, btnSelectOutput, - progressBar, + sliceProgressBar, btnSlice, uploadLabel, btnUpload, + uploadProgressBar, ) + // 设置窗口内容并显示 myWindow.SetContent(content) myWindow.ShowAndRun() } -func sliceVideo(inputFile, outputDir string, progressBar *widget.ProgressBar, onComplete func(), btnSelectFile, btnSelectOutput, btnSlice *widget.Button, myWindow fyne.Window) { - ffmpegPath := getFFmpegPath() - startTime := time.Now() // 记录开始时间 +// sliceVideo 切割视频文件为HLS格式的多个段。 +// +// 参数: +// inputFile - 输入视频文件的路径。 +// outputDir - 输出目录的路径,切片和播放列表将保存于此目录。 +// progressBar - 用于显示切割过程进度的进度条。 +// onComplete - 切割完成后调用的回调函数。 +// btnSelectFile, btnSelectOutput, btnSlice - 用于用户界面交互的按钮。 +// myWindow - 所属的FYNE窗口,用于显示对话框和其他UI元素。 +// +// 此函数使用FFmpeg工具切割视频,将视频文件分解为多个时长为5秒的HLS段,并创建一个播放列表。 +// 过程中会更新进度条,并在完成时显示一个通知对话框。 +func sliceVideo(inputFile, outputDir string, progressBar *widget.ProgressBar, onComplete func(), btnSelectFile, btnSelectOutput, btnSlice *widget.Button, btnUpload *widget.Button, myWindow fyne.Window) { + // 获取FFmpeg可执行文件的路径 + ffmpegPath, cleanup, err := getFFmpegPath() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 + startTime := time.Now() // 记录开始时间以便计算总耗时 log.Println("开始切割视频") - // 调整 FFmpeg 命令以输出调试信息,有助于解析 + + // 构建FFmpeg命令行,配置输出为HLS格式,每5秒切一片 cmd := exec.Command( ffmpegPath, "-i", inputFile, // 输入文件 - "-c", "copy", // 复制编解码器设置 - "-start_number", "0", // HLS 段的起始编号 - "-hls_time", "5", // 每个 HLS 段的最大时长(秒) + "-c", "copy", // 复制编解码器设置,避免重新编码 + "-start_number", "0", // HLS段的起始编号 + "-hls_time", "5", // 每个HLS段的最大时长(秒) "-hls_list_size", "0", // 播放列表中的最大段数,0表示无限制 - "-f", "hls", // 输出格式为 HLS + "-f", "hls", // 输出格式为HLS "-hls_segment_filename", outputDir+"/%08d.ts", // 段文件的命名方式 - outputDir+"/playlist.m3u8", // 输出的 m3u8 文件位置 + outputDir+"/playlist.m3u8", // 输出的m3u8文件位置 ) - // cmd.Stderr = os.Stderr // 将 stderr 直接定向到系统的 stderr,以便直接调试 FFmpeg 的输出 - cmdReader, err := cmd.StderrPipe() // 现在使用 stderr 管道而不是 stdout + // 设置管道以读取FFmpeg的stderr,用于解析进度信息 + cmdReader, err := cmd.StderrPipe() if err != nil { - log.Fatal("创建 stderr 管道失败:", err) + log.Fatal("创建stderr管道失败:", err) } if err := cmd.Start(); err != nil { - log.Fatalf("cmd.Start() 失败: %s\n", err) + log.Fatalf("cmd.Start()失败: %s\n", err) } scanner := bufio.NewScanner(cmdReader) - totalDuration := getTotalDuration(inputFile) // 确保此函数正确获取总持续时间 + totalDuration := getTotalDuration(inputFile) // 获取视频的总时长 log.Printf("视频总时长%f\n", totalDuration) - segment := math.Ceil(totalDuration / 5) + segment := math.Ceil(totalDuration / 5) // 计算所需的段数 log.Println(segment) - // currentDuration := 0.0 + // 在后台goroutine中监控FFmpeg的输出,更新进度条并处理完成状态 go func() { var completed bool for scanner.Scan() { line := scanner.Text() - log.Println("FFmpeg 输出:", line) + log.Println("FFmpeg输出:", line) + // 解析FFmpeg的输出,更新进度条 if segmentCount := parseFFmpegProgressOutput(line); segmentCount > 0 { log.Println(segmentCount / segment) progressBar.SetValue(segmentCount / segment) } - // 检查是否输出了完成的标志 + // 检查是否已完成切割 if checkFFmpegCompletion(line) { completed = true endTime := time.Now() // 记录完成时间 - duration := endTime.Sub(startTime) // 计算耗时 - formattedDuration := formatDuration(duration) // 格式化显示耗时 - progressBar.SetValue(1.0) // 设置进度条到最大值 - fyne.CurrentApp().SendNotification(&fyne.Notification{ - Title: "HLS-builder 视频切片工具", - Content: fmt.Sprintf("✅ 视频切片完成! 总用时: %s\n存储位置:%s", formattedDuration, outputDir), - }) - - dialog.ShowInformation("完成", fmt.Sprintf("✅ 视频切片完成! 总用时: %s\n存储位置:%s", formattedDuration, outputDir), myWindow) - + duration := endTime.Sub(startTime) // 计算总耗时 + formattedDuration := formatDuration(duration) // 格式化耗时显示 + progressBar.SetValue(1.0) // 设置进度条为完成状态 + // 显示完成信息的对话框 + dialog.ShowInformation("完成", fmt.Sprintf("✅ 视频切片完成!可以将切片直接上传云存储。\n总用时: %s\n存储位置:%s", formattedDuration, outputDir), myWindow) log.Println("视频切片完成") + btnUpload.Enable() break } } if err := cmd.Wait(); err != nil { - log.Fatalf("cmd.Wait() 失败: %s\n", err) + log.Fatalf("cmd.Wait()失败: %s\n", err) } if !completed { log.Println("❌ 视频切片可能未正确完成") } - onComplete() + onComplete() // 调用完成回调 }() } -// getTotalDuration extracts total duration of the video file using FFmpeg +// getTotalDuration 使用 FFmpeg 提取视频文件的总时长 +// 参数: +// +// file string - 视频文件的路径 +// +// 返回值: +// +// float64 - 视频的总时长(单位:秒) func getTotalDuration(file string) float64 { - ffmpegPath := getFFmpegPath() - // 构建和启动 ffmpeg 命令 + // 获取 FFmpeg 可执行文件的路径 + ffmpegPath, cleanup, err := getFFmpegPath() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 + // 构建并启动 FFmpeg 命令,用于获取视频时长 cmd := exec.Command(ffmpegPath, "-i", file) - // 由于 ffmpeg 在只有 '-i' 时输出到 stderr,因此需要捕获 stderr + + // 由于 FFmpeg 在只有 "-i" 参数时将输出发送到 stderr,因此我们需要捕获 stderr stderr, err := cmd.StderrPipe() if err != nil { log.Fatal("Failed to get stderr pipe:", err) } + // 启动 FFmpeg 命令 if err := cmd.Start(); err != nil { log.Println("Failed to start ffmpeg:", err) - return 0.0 // 这里选择不是致命错误,只是返回0.0表示无法获取时长 + return 0.0 // 无法获取时长时返回 0.0 } - // 使用 Scanner 来读取 stderr + // 使用 Scanner 从 stderr 中读取输出 scanner := bufio.NewScanner(stderr) + // 正则表达式用于匹配 FFmpeg 输出的时长信息 durationRegex := regexp.MustCompile(`Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})`) var duration float64 = 0.0 for scanner.Scan() { + // 寻找匹配的时长字符串,并解析为秒 matches := durationRegex.FindStringSubmatch(scanner.Text()) if matches != nil { hours, _ := strconv.Atoi(matches[1]) minutes, _ := strconv.Atoi(matches[2]) seconds, _ := strconv.ParseFloat(matches[3], 64) duration = float64(hours)*3600 + float64(minutes)*60 + seconds - break + break // 找到时长后即刻跳出循环 } } - // 等待命令完成,忽略错误 - cmd.Wait() // 注意这里我们不处理错误,因为 FFmpeg 总是返回错误状态 + // 等待 FFmpeg 命令执行完成,不处理命令结束状态 + cmd.Wait() return duration } +// parseFFmpegProgressOutput 函数解析 FFmpeg 进度输出行,返回匹配到的数字部分。 +// 参数: +// +// line string - FFmpeg 进度输出的一行文本。 +// +// 返回值: +// +// float64 - 匹配到的数字转换为浮点数后返回,如果没有匹配到则返回 0。 func parseFFmpegProgressOutput(line string) float64 { - re := regexp.MustCompile(`(\d+)\.ts`) // 正则表达式匹配数字后跟.ts + re := regexp.MustCompile(`(\d+)\.ts`) // 使用正则表达式编译模式以匹配数字后跟.ts的字符串 matches := re.FindStringSubmatch(line) if len(matches) > 1 { - number, err := strconv.Atoi(matches[1]) // 将提取的数字转换为int + number, err := strconv.Atoi(matches[1]) // 将提取的数字字符串转换为int类型 if err != nil { - log.Println("Error converting string to integer:", err) + log.Println("Error converting string to integer:", err) // 记录转换错误日志 return 0 } - return float64(number) + return float64(number) // 返回匹配到的数字转换后的浮点数 } - return 0 // 如果没有找到匹配,返回0 + return 0 // 如果没有找到匹配的模式,返回0 } + +// checkFFmpegCompletion 函数用于检查 FFmpeg 处理进程是否完成。 +// 它通过分析 FFmpeg 输出的行中是否包含特定的字符串来判断处理是否完成。 +// +// 参数: +// line string - FFmpeg 输出的一行文本。 +// +// 返回值: +// bool - 如果行中包含指定的字符串,则返回 true,表示处理已完成;否则返回 false。 func checkFFmpegCompletion(line string) bool { // 记录接收到的行,以便于调试 log.Println("line:", line) + // 正则表达式匹配含有 "out#0/hls" 的行,这通常在 FFmpeg 输出最后阶段显示 re := regexp.MustCompile(`out#0/hls`) + // 检查这一行是否匹配正则表达式 matchFound := re.MatchString(line) log.Println("re", matchFound) + return matchFound } + +// formatDuration 将时间Duration格式化为易读的字符串 +// 参数: +// +// d time.Duration - 需要格式化的时间长度 +// +// 返回值: +// +// string - 格式化后的时间字符串,格式为 "X小时X分X秒X毫秒",根据实际时间长度包含其中的项 func formatDuration(d time.Duration) string { + // 将时间转换为总秒数 totalSeconds := int(d.Seconds()) + // 计算小时、分钟和秒 hours := totalSeconds / 3600 minutes := (totalSeconds % 3600) / 60 seconds := totalSeconds % 60 - milliseconds := int(d.Milliseconds()) % 1000 // 获取剩余的毫秒数 + // 获取毫秒数 + milliseconds := int(d.Milliseconds()) % 1000 result := "" + // 如果有小时,则添加到结果中 if hours > 0 { result += fmt.Sprintf("%d小时", hours) } - if minutes > 0 || hours > 0 { // 显示分钟,如果有小时无论如何都显示 + // 如果有小时或者分钟不为0,则添加分钟到结果中 + if minutes > 0 || hours > 0 { result += fmt.Sprintf("%d分", minutes) } - // 只在秒大于0或小时和分钟都为0时显示秒 + // 如果秒不为0,或者同时小时和分钟都不为0,则添加秒到结果中 if seconds > 0 || (hours != 0 && minutes != 0) { result += fmt.Sprintf("%d秒", seconds) } + // 总是添加毫秒到结果中 result += fmt.Sprintf("%d毫秒", milliseconds) return result } // 上传到Ucloud -func upload(localPath string, keyPath string, onComplete func()) { +func upload(localPath string, keyPath string, progressBar *widget.ProgressBar, myWindow fyne.Window, onComplete func()) { fmt.Println("上传到Ucloud") fmt.Println("localPath:", localPath) fmt.Println("keyPath:", keyPath) - // 统计 .ts 文件数量 - tsCount, err := countTSFiles(localPath) + // 统计 .ts .m3u8 文件数量 + tsFiles, m3u8Files, err := countMediaFiles(localPath) if err != nil { - log.Fatalf("Failed to count .ts files: %s", err) - } else { - fmt.Printf("Found %d .ts files in %s\n", tsCount, localPath) + log.Fatalf("Error counting media files: %s", err) } - - us3cliPath := getUs3cliPath() + fmt.Printf("Total .ts files: %d\n", tsFiles) + fmt.Printf("Total .m3u8 files: %d\n", m3u8Files) + filesTotal := float64(tsFiles + m3u8Files) + fmt.Printf("Total files: %d\n", int(filesTotal)) + us3cliPath, cleanup, err := getUs3cliPath() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 bucket := "us3://jlntv-live/replay/" + keyPath - - cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20") + us3ConfigPath, cleanup, err := getUs3Config() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 + cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--config", us3ConfigPath) + //输出cmd + fmt.Println(cmd.String()) stdout, err := cmd.StdoutPipe() if err != nil { @@ -387,13 +553,64 @@ func upload(localPath string, keyPath string, onComplete func()) { if err := cmd.Start(); err != nil { log.Fatalf("Failed to start command: %s", err) } - + progress := 0.0 go func() { buffer := make([]byte, 4096) // 创建一个足够大的buffer for { n, err := stdout.Read(buffer) if n > 0 { fmt.Print("STDOUT:", string(buffer[:n])) // 打印实时输出 + filesUploadedTotal, err := extractTotalValue(string(buffer[:n])) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Println("Total:", filesUploadedTotal) + } + stats, err := parseUploadStats(string(buffer[:n])) + if err != nil { + fmt.Println("Error parsing upload stats:", err) + } else { + fmt.Printf("Uploaded: %d, Failed: %d, Total Size: %s, Speed: %s\n", + stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed) + if float64(stats.Successed) == filesTotal { + progressBar.SetValue(1) + showCustomDialog := func() { + replayURL := fmt.Sprintf("https://video.jlntv.cn/replay/%s/playlist.m3u8", keyPath) + copyBtn := widget.NewButton("复制地址", func() { + fmt.Println("copyed:", replayURL) + // dialog.ShowInformation("Submitted", "You submitted: "+input.Text, myWindow) + clipboard := myWindow.Clipboard() + clipboard.SetContent(replayURL) + // showToast(myWindow, "地址已复制到剪贴板", 3*time.Second) + dialog.ShowInformation("提示", "地址已复制到剪贴板!", myWindow) + }) + // 创建自定义内容的容器 + content := container.NewVBox( + widget.NewLabel("Upload Stats:"), + widget.NewLabel(fmt.Sprintf("用时:%s秒", stats.Elapsed)), + widget.NewLabel(fmt.Sprintf("成功:%d", stats.Successed)), + widget.NewLabel(fmt.Sprintf("失败:%d", stats.Failed)), + widget.NewLabel(fmt.Sprintf("平均速度:%s", stats.AverageSpeed)), + widget.NewLabel(fmt.Sprintf("总大小:%s", stats.TotalSize)), + widget.NewLabel(fmt.Sprintf("回看地址: %s", replayURL)), + copyBtn, + ) + // 设置内容容器的最小尺寸 + content.Resize(fyne.NewSize(500, 600)) // 你可以根据需要调整这个尺寸 + // 创建并显示自定义对话框 + customDialog := dialog.NewCustom("上传成功", "关闭", content, myWindow) + customDialog.Show() + } + showCustomDialog() + // dialog.ShowInformation("完成", fmt.Sprintf("✅ HLS上传完毕\n总用时:%s秒\n成功:%d 失败:%d 总大小:%s 平均速度:%s", stats.Elapsed, stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed), myWindow) + fmt.Println("Upload completed!") + } else { + progress = filesUploadedTotal / filesTotal + fmt.Println("Progress:", progress) + progressBar.SetValue(progress) + } + } + } if err != nil { break @@ -410,24 +627,101 @@ func upload(localPath string, keyPath string, onComplete func()) { } } -// countTSFiles 统计指定目录下所有 .ts 文件的数量。 +func extractTotalValue(text string) (float64, error) { + re := regexp.MustCompile(`Total:(\d+)`) + matches := re.FindStringSubmatch(text) + if len(matches) > 1 { + total, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, err // 在转换过程中发生错误 + } + return float64(total), nil + } + return 200, fmt.Errorf("no total found in the text") +} +func parseUploadStats(line string) (*UploadStats, error) { + stats := &UploadStats{} + var err error + + successedRegex := regexp.MustCompile(`(\d+) Successed`) + failedRegex := regexp.MustCompile(`(\d+) Failed`) + sizeRegex := regexp.MustCompile(`Size: ([\d.]+ MB)`) + speedRegex := regexp.MustCompile(`Average speed ([\d.]+ MB/s)`) + elapsedRegex := regexp.MustCompile(`Elapsed\s*:\s*([\d.]+)s`) + + // Extracting number of files successed + successedMatches := successedRegex.FindStringSubmatch(line) + if len(successedMatches) > 1 { + stats.Successed, err = strconv.Atoi(successedMatches[1]) + if err != nil { + return nil, fmt.Errorf("error parsing successed number: %v", err) + } + } + + // Extracting number of files failed + failedMatches := failedRegex.FindStringSubmatch(line) + if len(failedMatches) > 1 { + stats.Failed, err = strconv.Atoi(failedMatches[1]) + if err != nil { + return nil, fmt.Errorf("error parsing failed number: %v", err) + } + } + + // Extracting total size + sizeMatches := sizeRegex.FindStringSubmatch(line) + if len(sizeMatches) > 1 { + stats.TotalSize = sizeMatches[1] + } + + // Extracting average speed + speedMatches := speedRegex.FindStringSubmatch(line) + if len(speedMatches) > 1 { + stats.AverageSpeed = speedMatches[1] + } + + // Extracting Elapsed + elapsedMatches := elapsedRegex.FindStringSubmatch(line) + if len(elapsedMatches) > 1 { + stats.Elapsed = elapsedMatches[1] + } + + return stats, nil +} + +// countMediaFiles 统计给定目录中 .ts 和 .m3u8 文件的数量。 // // 参数: -// directory - 需要统计的目录路径。 +// +// directory string - 需要遍历统计文件的目录路径。 // // 返回值: -// 返回 .ts 文件的数量以及可能出现的错误。 -func countTSFiles(directory string) (int, error) { - var count int // 用于记录 .ts 文件数量 +// +// int - .ts 文件的数量。 +// int - .m3u8 文件的数量。 +// error - 遍历目录过程中遇到的任何错误。 +func countMediaFiles(directory string) (int, int, error) { + var tsCount, m3u8Count int // 分别用于记录 .ts 和 .m3u8 文件的数量 + + // 使用 filepath.Walk 遍历指定目录及其子目录中的所有文件 err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + // 检查遍历过程是否遇到错误 if err != nil { - return err // 如果遍历过程中出现错误,则返回该错误 + return err // 如果有错误,则终止遍历并返回该错误 } - // 检查当前路径项是否为文件且扩展名为 ".ts" - if !info.IsDir() && filepath.Ext(info.Name()) == ".ts" { - count++ // 如果满足条件,则计数加一 + + // 检查当前路径项是否为文件,不遍历目录 + if !info.IsDir() { + // 根据文件扩展名统计 .ts 和 .m3u8 文件数量 + switch filepath.Ext(info.Name()) { + case ".ts": + tsCount++ // .ts 文件计数 + case ".m3u8": + m3u8Count++ // .m3u8 文件计数 + } } - return nil + return nil // 继续遍历下一个文件或目录 }) - return count, err // 返回计数结果和可能出现的错误 + + // 返回 .ts 和 .m3u8 文件的计数结果,以及遍历过程中可能遇到的错误 + return tsCount, m3u8Count, err }