上传
This commit is contained in:
parent
7938f80b8e
commit
cf0f0f3e8a
@ -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"
|
1
go.mod
1
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
|
||||
)
|
||||
|
30
go.sum
30
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=
|
||||
|
504
main.go
504
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user