mirror of
https://github.com/teacat/chaturbate-dvr.git
synced 2025-10-29 16:59:59 +00:00
This commit is contained in:
parent
b72d565cfa
commit
cf60326fd8
1
.old/.gitignore
vendored
1
.old/.gitignore
vendored
@ -1 +0,0 @@
|
||||
./video
|
||||
@ -1,7 +0,0 @@
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr
|
||||
|
||||
GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr
|
||||
@ -1,11 +0,0 @@
|
||||
FROM golang:latest
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download && go mod verify
|
||||
|
||||
COPY . .
|
||||
RUN go build
|
||||
|
||||
CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME" ]
|
||||
21
.old/LICENSE
21
.old/LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 TeaCat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,90 +0,0 @@
|
||||
# Chaturbate 重新錄播 (Alpha)
|
||||
|
||||
這個程式能夠監聽指定的 Chaturbate 頻道,並且在該頻道開始直播時自動儲存影片至本機。這樣你就不會錯過任何精彩的事情
|
||||
|
||||
**警告**:在 Chaturbate 上的直播內容都有版權,你不應該複製、分享、散播這些內容。(想閱讀更多,請參閱 [DMCA](https://www.dmca.com/))
|
||||
|
||||
**免責聲明**:因為這還在早期開發階段,錄播內容可能會有幀數遺失(3 小時直播遺失 20 秒內容),這仍然需要測試。
|
||||
|
||||
## 使用方式
|
||||
|
||||
這個程式能夠在 64 位元的 macOS、Linux、Windows(懶得編譯 32 位元的版本)上正常運作。你只需要進入 `/bin` 資料夾找到對應你的系統,然後在終端機執行該檔案即可。
|
||||
|
||||
```bash
|
||||
$ chaturbate-dvr -u 好棒棒頻道名稱
|
||||
|
||||
.o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b
|
||||
d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88'
|
||||
8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo
|
||||
8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~
|
||||
Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88.
|
||||
`Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P
|
||||
d8888b. db db d8888b.
|
||||
88 `8D 88 88 88 `8D
|
||||
88 88 Y8 8P 88oobY'
|
||||
88 88 `8b d8' 88`8b
|
||||
88 .8D `8bd8' 88 `88.
|
||||
Y8888D' YP 88 YD
|
||||
---
|
||||
2020/02/13 18:05:22 好棒棒頻道名稱 is online! fetching...
|
||||
2020/02/13 18:05:24 the video will be saved as "2020-02-13_22-16-27.ts".
|
||||
2020/02/13 18:05:28 fetching media_w402018999_b5128000_t64RlBTOjI5Ljk3_9134.ts (size: 936428)
|
||||
2020/02/13 19:07:06 failed to fetch the video segments, will try again. (1/2)
|
||||
2020/02/13 19:07:06 failed to fetch the video segments, will try again. (2/2)
|
||||
2020/02/13 19:07:11 failed to fetch the video segments after retried, 好棒棒頻道名稱 might went offline.
|
||||
2020/02/13 19:07:11 好棒棒頻道名稱 is not online, check again after 3 minute(s)...
|
||||
```
|
||||
|
||||
## 說明
|
||||
|
||||
```bash
|
||||
NAME:
|
||||
chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file
|
||||
|
||||
USAGE:
|
||||
main [global options] command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--username value, -u value channel username to watching
|
||||
--interval value, -i value minutes to check if a channel goes online or not (default: 1)
|
||||
--strip value, -s value MB sizes to split the video into chunks (default: 0)
|
||||
--resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080")
|
||||
--resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down")
|
||||
--fps value, -f value Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS (default: "60")
|
||||
--help, -h show help (default: false)
|
||||
--version, -v print the version (default: false)
|
||||
```
|
||||
|
||||
## 中文對應
|
||||
|
||||
```
|
||||
XXX is online! fetching...
|
||||
XXX 正在線上!開始撈取實況內容…
|
||||
|
||||
the video will be saved as "XXX".
|
||||
影片將會被保存為「XXX」。
|
||||
|
||||
fetching XXX.ts (size: XXX)
|
||||
正在擷取 XXX.ts 片段(大小:XXX)
|
||||
|
||||
failed to fetch the video segments, will try again. (1/2)
|
||||
無法取得影片段落,稍後會重新嘗試。(1/2)
|
||||
|
||||
failed to fetch the video segments after retried, XXX might went offline.
|
||||
無法取得影片段落,XXX 可能已經結束直播了。
|
||||
|
||||
cannot find segment XXX, will try again. (1/5)
|
||||
無法找到影片段落,燒後會重新嘗試。(1/5)
|
||||
|
||||
inserting XXX segment to the master file. (total: XXX)
|
||||
正在插入片段 XXX 至主要影片檔案。(總共:XXX)
|
||||
|
||||
skipped XXX due to the empty body!
|
||||
跳過 XXX 片段因為其為空白內容!
|
||||
|
||||
exceeded the specified stripping limit, creating new video file. (file: XXX)
|
||||
達到影片分割上限,建立新的影片檔案(檔名:XXX)
|
||||
```
|
||||
@ -1,61 +0,0 @@
|
||||
# Chaturbate DVR (Alpha)
|
||||
|
||||
[[正體中文翻譯點此]](README-tw.md)
|
||||
|
||||
The program watches a specified Chaturbate channel and save the stream in real-time when the channel goes online, so you won't miss anything.
|
||||
|
||||
**Warning**: The streaming content on Chaturbate is copyrighted, you should not copy, share, distribute the content. (for more information, check [DMCA](https://www.dmca.com/))
|
||||
|
||||
**Disclaimer**: Due to early development, might have frames dropped (20s gone in a 3hrs long stream), still requires more tests.
|
||||
|
||||
## Usage
|
||||
|
||||
The program works for 64-bit macOS, Linux, Windows (too lazy to compile for 32-bit). Just get in the `/bin` folder and find your operating system then execute the program in terminal.
|
||||
|
||||
```bash
|
||||
$ chaturbate-dvr -u my_lovely_channel_name
|
||||
|
||||
.o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b
|
||||
d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88'
|
||||
8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo
|
||||
8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~
|
||||
Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88.
|
||||
`Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P
|
||||
d8888b. db db d8888b.
|
||||
88 `8D 88 88 88 `8D
|
||||
88 88 Y8 8P 88oobY'
|
||||
88 88 `8b d8' 88`8b
|
||||
88 .8D `8bd8' 88 `88.
|
||||
Y8888D' YP 88 YD
|
||||
---
|
||||
2020/02/13 18:05:22 my_lovely_channel_name is online! fetching...
|
||||
2020/02/13 18:05:24 the video will be saved as "2020-02-13_22-16-27.ts".
|
||||
2020/02/13 18:05:28 fetching media_w402018999_b5128000_t64RlBTOjI5Ljk3_9134.ts (size: 936428)
|
||||
2020/02/13 19:07:06 failed to fetch the video segments, will try again. (1/2)
|
||||
2020/02/13 19:07:06 failed to fetch the video segments, will try again. (2/2)
|
||||
2020/02/13 19:07:11 failed to fetch the video segments after retried, my_lovely_channel_name might went offline.
|
||||
2020/02/13 19:07:11 my_lovely_channel_name is not online, check again after 3 minute(s)...
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
```bash
|
||||
NAME:
|
||||
chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file
|
||||
|
||||
USAGE:
|
||||
main [global options] command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--username value, -u value channel username to watching
|
||||
--interval value, -i value minutes to check if a channel goes online or not (default: 1)
|
||||
--strip value, -s value MB sizes to split the video into chunks (default: 0)
|
||||
--resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080")
|
||||
--resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down")
|
||||
--fps value, -f value Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS (default: "60")
|
||||
--help, -h show help (default: false)
|
||||
--version, -v print the version (default: false)
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,9 +0,0 @@
|
||||
version: "3.0"
|
||||
|
||||
services:
|
||||
chaturbate-dvr:
|
||||
build: .
|
||||
environment:
|
||||
- USERNAME=my_lovely_channel_name
|
||||
volumes:
|
||||
- ./video/my_lovely_channel_name:/usr/src/app/video
|
||||
23
.old/go.mod
23
.old/go.mod
@ -1,23 +0,0 @@
|
||||
module github.com/YamiOdymel/chaturbate-dvr
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/TwiN/go-color v1.1.0
|
||||
github.com/grafov/m3u8 v0.11.1
|
||||
github.com/parnurzeal/gorequest v0.2.16
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
46
.old/go.sum
46
.old/go.sum
@ -1,46 +0,0 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/TwiN/go-color v1.1.0 h1:yhLAHgjp2iAxmNjDiVb6Z073NE65yoaPlcki1Q22yyQ=
|
||||
github.com/TwiN/go-color v1.1.0/go.mod h1:aKVf4e1mD4ai2FtPifkDPP5iyoCwiK08YGzGwerjKo0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 h1:lS3P5Nw3oPO05Lk2gFiYUOL3QPaH+fRoI1wFOc4G1UY=
|
||||
github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA=
|
||||
github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ=
|
||||
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c=
|
||||
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
|
||||
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
|
||||
510
.old/main.go
510
.old/main.go
@ -1,510 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/TwiN/go-color"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/grafov/m3u8"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// chaturbateURL is the base url of the website.
|
||||
const chaturbateURL = "https://chaturbate.com/"
|
||||
|
||||
// retriesAfterOnlined tells the retries for stream when disconnected but not really offlined.
|
||||
var retriesAfterOnlined = 0
|
||||
|
||||
// temp stores the used segment to prevent fetched the duplicates.
|
||||
var temp []string
|
||||
|
||||
// segmentIndex is current stored segment index.
|
||||
var segmentIndex int
|
||||
|
||||
// segmentMap is the map stores temporary video segments, it will be merged into master video file then got deleted.
|
||||
var segmentMap map[string][]byte = make(map[string][]byte)
|
||||
|
||||
var segmentMapLock sync.Mutex
|
||||
|
||||
// stripLimit reprsents the maximum Bytes sizes to split the video into chunks.
|
||||
var stripLimit int
|
||||
|
||||
// stripQuota represents how many Bytes left til the next video chunk stripping.
|
||||
var stripQuota int
|
||||
|
||||
// preferredFPS represents the preferred framerate.
|
||||
var preferredFPS string
|
||||
|
||||
// preferredResolution represents the preferred resolution, e.g. `240`, `480`, `540`, `720`, `1080`.
|
||||
var preferredResolution string
|
||||
|
||||
// preferredResolutionFallback represents the preferred resolution fallback, `up`, `down` or `no`.
|
||||
var preferredResolutionFallback string
|
||||
|
||||
// path save video
|
||||
const savePath = "video"
|
||||
|
||||
// error/message handler
|
||||
var (
|
||||
errInternal = errors.New("err")
|
||||
errNoUsername = errors.New("recording: channel username required `-u [USERNAME]` option")
|
||||
errSegRetFail = color.Colorize(color.Red, ("[FAILED] to fetch the video segments after retried, %s might went offline or is in ticket/privat show."))
|
||||
errSegRetFailOnline = color.Colorize(color.Red, ("[FAILED] to fetch the video segments, will try again. [%d/10]"))
|
||||
infoIsOnline = color.Colorize(color.Green, ("[RECORDING] %s is online! start fetching.."))
|
||||
infoBackOnline = color.Colorize(color.Green, ("[INFO] %s is back online!"))
|
||||
infoMergeSegment = color.Colorize(color.Green, ("[INFO] inserting %d segment to the master file. [total: %d]"))
|
||||
infoSkipped = color.Colorize(color.Blue, ("[INFO] skipped %s due to the empty body!\n"))
|
||||
infoNotOnline = color.Colorize(color.Gray, ("[INFO] %s is not online, check again in %d minute(s)"))
|
||||
warningSegment = color.Colorize(color.Yellow, ("[WARNING] cannot find segment %d, will try again. [%d/5]"))
|
||||
)
|
||||
|
||||
// roomDossier is the struct to parse the HLS source from the content body.
|
||||
type roomDossier struct {
|
||||
HLSSource string `json:"hls_source"`
|
||||
}
|
||||
|
||||
// unescapeUnicode escapes the unicode from the content body.
|
||||
func unescapeUnicode(raw string) string {
|
||||
str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(raw)), `\\u`, `\u`, -1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// getChannelURL returns the full channel url to the specified user.
|
||||
func getChannelURL(username string) string {
|
||||
return fmt.Sprintf("%s%s", chaturbateURL, username)
|
||||
}
|
||||
|
||||
// getBody gets the channel page content body.
|
||||
func getBody(username string) string {
|
||||
resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(getChannelURL(username)).End()
|
||||
if len(errs) > 0 {
|
||||
log.Println(color.Colorize(color.Red, errs[0].Error()))
|
||||
}
|
||||
if resp == nil || resp.StatusCode != 200 {
|
||||
return ""
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// getOnlineStatus check if the user is currently online by checking the playlist exists in the content body or not.
|
||||
func getOnlineStatus(username string) bool {
|
||||
return strings.Contains(getBody(username), "playlist.m3u8")
|
||||
}
|
||||
|
||||
// getHLSSource extracts the playlist url from the room detail page body.
|
||||
func getHLSSource(body string) (string, string) {
|
||||
// Get the room data from the page body.
|
||||
r := regexp.MustCompile(`window\.initialRoomDossier = "(.*?)"`)
|
||||
matches := r.FindAllStringSubmatch(body, -1)
|
||||
|
||||
// Extract the data and get the HLS source URL.
|
||||
var roomData roomDossier
|
||||
data := unescapeUnicode(matches[0][1])
|
||||
err := json.Unmarshal([]byte(data), &roomData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roomData.HLSSource, strings.TrimSuffix(roomData.HLSSource, "playlist.m3u8")
|
||||
}
|
||||
|
||||
// parseHLSSource parses the HLS table and return the maximum resolution m3u8 source.
|
||||
func parseHLSSource(url string, baseURL string) string {
|
||||
resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(url).End()
|
||||
if len(errs) > 0 {
|
||||
log.Println(color.Colorize(color.Red, errs[0].Error()))
|
||||
}
|
||||
if resp == nil || resp.StatusCode == 403 {
|
||||
return ""
|
||||
}
|
||||
p, _, _ := m3u8.DecodeFrom(strings.NewReader(body), true)
|
||||
master, ok := p.(*m3u8.MasterPlaylist)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
resolutions := make(map[string][]string)
|
||||
resolutionInts := []string{}
|
||||
|
||||
for _, v := range master.Variants {
|
||||
resStr := strings.Split(v.Resolution, "x")
|
||||
resolutionInts = append(resolutionInts, resStr[1])
|
||||
// If the resolution exists in local, it might be a higher framerate source, store it for later use
|
||||
if _, ok := resolutions[resStr[1]]; ok {
|
||||
resolutions[resStr[1]] = append(resolutions[resStr[1]], v.URI)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(v.Name, "FPS:60.0") {
|
||||
if _, ok := resolutions[resStr[1]]; !ok {
|
||||
resolutions[resStr[1]] = []string{"", v.URI} // The video has no 30 FPS, we fill it with an empty URI
|
||||
} else {
|
||||
resolutions[resStr[1]] = []string{v.URI}
|
||||
}
|
||||
} else {
|
||||
resolutions[resStr[1]] = []string{v.URI}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Found available resolutions: %s", strings.TrimPrefix(lo.Reduce(resolutionInts, func(prev string, cur string, _ int) string {
|
||||
return fmt.Sprintf("%s, %s", prev, cur)
|
||||
}, ""), ", "))
|
||||
|
||||
pickedResolution, ok := resolutions[preferredResolution]
|
||||
if !ok {
|
||||
var comparison []string
|
||||
if preferredResolutionFallback == "down" {
|
||||
comparison = lo.Reverse(lo.Map(resolutionInts, func(v string, _ int) string { return v }))
|
||||
} else {
|
||||
comparison = resolutionInts
|
||||
}
|
||||
fallbackResolution, ok := lo.Find(comparison, func(v string) bool {
|
||||
sizeInt, _ := strconv.Atoi(v)
|
||||
prefInt, _ := strconv.Atoi(preferredResolution)
|
||||
//
|
||||
if preferredResolutionFallback == "down" {
|
||||
return sizeInt < prefInt
|
||||
} else {
|
||||
return sizeInt > prefInt
|
||||
}
|
||||
})
|
||||
if ok {
|
||||
pickedResolution = resolutions[fallbackResolution]
|
||||
log.Printf("Preferred video resolution %sp not found, use %sp instead.", preferredResolution, fallbackResolution)
|
||||
} else {
|
||||
if preferredResolutionFallback == "down" {
|
||||
pickedResolution = resolutions[resolutionInts[0]]
|
||||
log.Printf("No fallback video resolution was found, use worse quality %sp instead.", resolutionInts[0])
|
||||
} else {
|
||||
pickedResolution = resolutions[resolutionInts[len(resolutionInts)-1]]
|
||||
log.Printf("No fallback video resolution was found, use best quality %sp instead.", resolutionInts[len(resolutionInts)-1])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("Fetching video resolution in %sp.", preferredResolution)
|
||||
}
|
||||
|
||||
var uri string
|
||||
|
||||
if preferredFPS == "60" && len(pickedResolution) > 1 {
|
||||
log.Printf("Fetching video in 60 FPS.")
|
||||
uri = pickedResolution[1]
|
||||
} else {
|
||||
log.Printf("Fetching video in 30 FPS.")
|
||||
uri = pickedResolution[0]
|
||||
|
||||
if uri == "" {
|
||||
log.Printf("The video has no 30 FPS, use 60 FPS instead.")
|
||||
uri = pickedResolution[1]
|
||||
}
|
||||
}1
|
||||
|
||||
return fmt.Sprintf("%s%s", baseURL, uri)
|
||||
}
|
||||
|
||||
// parseM3U8Source gets the current segment list, the channel might goes offline if 403 was returned.
|
||||
func parseM3U8Source(url string) (chunks []*m3u8.MediaSegment, wait float64, err error) {
|
||||
resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(url).End()
|
||||
if len(errs) > 0 {
|
||||
log.Println(color.Colorize(color.Red, errs[0].Error()))
|
||||
}
|
||||
// Retry after 3 seconds if the connection lost or status code returns 403 (the channel might went offline).
|
||||
if len(errs) > 0 || resp == nil || resp.StatusCode == http.StatusForbidden {
|
||||
return nil, 3, errInternal
|
||||
}
|
||||
|
||||
// Decode the segment table.
|
||||
p, _, err := m3u8.DecodeFrom(strings.NewReader(body), true)
|
||||
if err != nil {
|
||||
log.Println(color.Colorize(color.Red, err.Error()))
|
||||
}
|
||||
media, ok := p.(*m3u8.MediaPlaylist)
|
||||
if !ok {
|
||||
return nil, 3, errInternal
|
||||
}
|
||||
wait = media.TargetDuration / 1.5
|
||||
|
||||
// Ignore the empty segments.
|
||||
for _, v := range media.Segments {
|
||||
if v != nil {
|
||||
chunks = append(chunks, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// capture captures the specified channel streaming.
|
||||
func capture(username string) {
|
||||
// Define the video filename by current time //04.09.22 added username into filename mK33y.
|
||||
filename := username + "_" + time.Now().Format("2006-01-02_15-04-05")
|
||||
var m3u8Source, baseURL, hlsSource string
|
||||
var tried int
|
||||
for {
|
||||
tried++
|
||||
//
|
||||
if tried > 10 {
|
||||
panic(errors.New("cannot fetch the Playlist correctly after 10 tries"))
|
||||
}
|
||||
// Get the channel page content body.
|
||||
body := getBody(username)
|
||||
//
|
||||
if body == "" {
|
||||
continue
|
||||
}
|
||||
// Get the master playlist URL from extracting the channel body.
|
||||
hlsSource, baseURL = getHLSSource(body)
|
||||
// Get the best resolution m3u8 by parsing the HLS source table.
|
||||
m3u8Source = parseHLSSource(hlsSource, baseURL)
|
||||
//
|
||||
if m3u8Source != "" {
|
||||
break
|
||||
}
|
||||
<-time.After(time.Millisecond * 500)
|
||||
}
|
||||
// Create the master video file.
|
||||
masterFile, err := os.OpenFile("./"+savePath+"/"+filename+".ts", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
log.Println(color.Colorize(color.Red, err.Error()))
|
||||
}
|
||||
//
|
||||
log.Printf("the video will be saved as \"./"+savePath+"/%s\".", filename+".ts")
|
||||
|
||||
go combineSegment(masterFile, filename)
|
||||
watchStream(m3u8Source, username, masterFile, filename, baseURL)
|
||||
}
|
||||
|
||||
// watchStream watches the stream and ends if the channel went offline.
|
||||
func watchStream(m3u8Source string, username string, masterFile *os.File, filename string, baseURL string) {
|
||||
// Keep fetching the stream chunks until the playlist cannot be accessed after retried x times.
|
||||
for {
|
||||
// Get the chunks.
|
||||
chunks, wait, err := parseM3U8Source(m3u8Source)
|
||||
// Exit the fetching loop if the channel went offline.
|
||||
if err != nil {
|
||||
if retriesAfterOnlined > 10 {
|
||||
log.Printf(errSegRetFail, username)
|
||||
break
|
||||
} else {
|
||||
log.Printf(errSegRetFailOnline, retriesAfterOnlined)
|
||||
retriesAfterOnlined++
|
||||
// Wait to fetch the next playlist.
|
||||
<-time.After(time.Duration(wait*1000) * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if retriesAfterOnlined != 0 {
|
||||
log.Printf(infoBackOnline, username)
|
||||
retriesAfterOnlined = 0
|
||||
}
|
||||
for _, v := range chunks {
|
||||
// Ignore the duplicated chunks.
|
||||
if isDuplicateSegment(v.URI) {
|
||||
continue
|
||||
}
|
||||
segmentIndex++
|
||||
go fetchSegment(masterFile, v, baseURL, filename, segmentIndex)
|
||||
}
|
||||
<-time.After(time.Duration(wait*1000) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// isDuplicateSegment returns true if the segment is already been fetched.
|
||||
func isDuplicateSegment(URI string) bool {
|
||||
for _, v := range temp {
|
||||
if URI[len(URI)-10:] == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
temp = append(temp, URI[len(URI)-10:])
|
||||
return false
|
||||
}
|
||||
|
||||
// combineSegment combines the segments to the master video file in the background.
|
||||
// fixed segment problems mK33y.
|
||||
// still needs some attention here
|
||||
func combineSegment(master *os.File, filename string) {
|
||||
index := 1
|
||||
stripIndex := 1
|
||||
var retry int
|
||||
<-time.After(4 * time.Second)
|
||||
|
||||
for {
|
||||
<-time.After(300 * time.Millisecond)
|
||||
|
||||
if index >= segmentIndex {
|
||||
<-time.After(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)]; !ok {
|
||||
if retry >= 5 {
|
||||
index++
|
||||
retry = 0
|
||||
continue
|
||||
}
|
||||
if retry != 0 {
|
||||
log.Printf(warningSegment, index, retry)
|
||||
}
|
||||
retry++
|
||||
<-time.After(time.Duration(1*retry) * time.Second)
|
||||
continue
|
||||
}
|
||||
if retry != 0 {
|
||||
retry = 0
|
||||
}
|
||||
//
|
||||
b := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)]
|
||||
//
|
||||
var err error
|
||||
if stripLimit != 0 && stripQuota <= 0 {
|
||||
newMasterFilename := "./" + savePath + "/" + filename + "_" + strconv.Itoa(stripIndex) + ".ts"
|
||||
master, err = os.OpenFile(newMasterFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
log.Println(color.Colorize(color.Red, err.Error()))
|
||||
}
|
||||
log.Printf("exceeded the specified stripping limit, creating new video file. (file: %s)", newMasterFilename)
|
||||
stripQuota = stripLimit
|
||||
stripIndex++
|
||||
}
|
||||
master.Write(b)
|
||||
//
|
||||
log.Printf(infoMergeSegment, index, segmentIndex)
|
||||
segmentMapLock.Lock()
|
||||
delete(segmentMap, fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index))
|
||||
segmentMapLock.Unlock()
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// fetchSegment fetches the segment and append to the master file.
|
||||
func fetchSegment(master *os.File, segment *m3u8.MediaSegment, baseURL string, filename string, index int) {
|
||||
_, body, _ := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(fmt.Sprintf("%s%s", baseURL, segment.URI)).EndBytes()
|
||||
log.Printf("fetching %s (size: %d)\n", segment.URI, len(body))
|
||||
if len(body) == 0 {
|
||||
log.Printf(infoSkipped, segment.URI)
|
||||
return
|
||||
}
|
||||
stripQuota -= len(body)
|
||||
segmentMapLock.Lock()
|
||||
segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)] = body
|
||||
segmentMapLock.Unlock()
|
||||
}
|
||||
|
||||
// endpoint implements the application main function endpoint.
|
||||
func endpoint(c *cli.Context) error {
|
||||
if c.String("username") == "" {
|
||||
log.Fatal(errNoUsername)
|
||||
}
|
||||
// Converts `strip` from MiB to Bytes
|
||||
stripLimit = c.Int("strip") * 1024 * 1024
|
||||
stripQuota = c.Int("strip") * 1024 * 1024
|
||||
//
|
||||
preferredFPS = c.String("fps")
|
||||
preferredResolution = c.String("resolution")
|
||||
preferredResolutionFallback = c.String("resolution-fallback")
|
||||
//
|
||||
|
||||
fmt.Println(" .o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b")
|
||||
fmt.Println("d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88'")
|
||||
fmt.Println("8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo")
|
||||
fmt.Println("8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~")
|
||||
fmt.Println("Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88.")
|
||||
fmt.Println(" `Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P")
|
||||
fmt.Println("d8888b. db db d8888b.")
|
||||
fmt.Println("88 `8D 88 88 88 `8D")
|
||||
fmt.Println("88 88 Y8 8P 88oobY'")
|
||||
fmt.Println("88 88 `8b d8' 88`8b")
|
||||
fmt.Println("88 .8D `8bd8' 88 `88.")
|
||||
fmt.Println("Y8888D' YP 88 YD")
|
||||
fmt.Println("---")
|
||||
|
||||
// Mkdir video folder
|
||||
if _, err := os.Stat("./" + savePath); os.IsNotExist(err) {
|
||||
os.Mkdir("./"+savePath, 0777)
|
||||
}
|
||||
//
|
||||
if c.Int("strip") != 0 {
|
||||
log.Printf("specifying stripping limit as %d MiB(s)", c.Int("strip"))
|
||||
}
|
||||
|
||||
for {
|
||||
// Capture the stream if the user is currently online.
|
||||
if getOnlineStatus(c.String("username")) {
|
||||
log.Printf(infoIsOnline, c.String("username"))
|
||||
capture(c.String("username"))
|
||||
segmentIndex = 0
|
||||
temp = []string{}
|
||||
retriesAfterOnlined = 0
|
||||
continue
|
||||
}
|
||||
// Otherwise we keep checking the channel status until the user is online.
|
||||
log.Printf(infoNotOnline, c.String("username"), c.Int("interval"))
|
||||
<-time.After(time.Minute * time.Duration(c.Int("interval")))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Version: "0.94 Alpha",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Value: "",
|
||||
Usage: "channel username to watching",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "interval",
|
||||
Aliases: []string{"i"},
|
||||
Value: 1,
|
||||
Usage: "minutes to check if a channel goes online or not",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "strip",
|
||||
Aliases: []string{"s"},
|
||||
Value: 0,
|
||||
Usage: "MB sizes to split the video into chunks",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "resolution",
|
||||
Aliases: []string{"r"},
|
||||
Value: "1080",
|
||||
Usage: "Video resolution, could be `240`, `480`, `540`, `720`, `1080`",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "resolution-fallback",
|
||||
Aliases: []string{"rf"},
|
||||
Value: "down",
|
||||
Usage: "Looking for larger or smaller resolution (`up` for larger, `down` for smaller) if a specified resolution was not found",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "fps",
|
||||
Aliases: []string{"f"},
|
||||
Value: "60",
|
||||
Usage: "Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS",
|
||||
},
|
||||
},
|
||||
Name: "chaturbate-dvr",
|
||||
Usage: "watching a specified chaturbate channel and auto saves the stream as local file",
|
||||
Action: endpoint,
|
||||
}
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -8,4 +8,4 @@ RUN go mod download && go mod verify
|
||||
COPY . .
|
||||
RUN go build
|
||||
|
||||
CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME -ui no start" ]
|
||||
CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME start" ]
|
||||
106
README.md
106
README.md
@ -1,13 +1,54 @@
|
||||
# Chaturbate DVR
|
||||
|
||||
The program records Chaturbate stream, supports 32-bit/64-bit Windows, macOS, Linux (or ARM), comes with a Web UI.
|
||||
The program can records **multiple** Chaturbate streams, supports 32-bit/64-bit/ARM macOS, Windows, Linux, can be run on Docker.
|
||||
|
||||
For Chaturbate-**only**, private/ticket stream is **unsupported**.
|
||||
|
||||
※ **[DMCA WARNING](https://www.dmca.com/)**: Contents on Chaturbate are copyrighted, you should not copy, share, distribute the content.
|
||||
|
||||
## Hello
|
||||
|
||||
|
||||

|
||||

|
||||
## Usage
|
||||
|
||||
1. Download **`source code (zip)`** from **[Release](https://github.com/teacat/chaturbate-dvr/releases)** page.
|
||||
2. Unzip **`/bin`** folder and look up for executable that **fits your system**.
|
||||
|
||||
|
||||
|
||||
**🌐 Start the program with the Web UI**
|
||||
|
||||
Visit [`http://localhost:8080`](http://localhost:8080) to use the Web UI.
|
||||
|
||||
```yaml
|
||||
# Windows (or double-click `chaturbate-dvr.exe` to open)
|
||||
$ chaturbate-dvr.exe
|
||||
|
||||
# macOS or Linux
|
||||
$ chaturbate-dvr
|
||||
```
|
||||
|
||||
|
||||
|
||||
**💻 or... Run as a command-line tool**
|
||||
|
||||
Run the program with a channel name (`-u CHANNEL_USERNAME`) records the channel immediately, and the Web UI will be disabled.
|
||||
|
||||
```yaml
|
||||
# Windows
|
||||
$ chaturbate-dvr.exe -u CHANNEL_USERNAME
|
||||
|
||||
# macOS or Linux
|
||||
$ chaturbate-dvr -u CHANNEL_USERNAME
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||

|
||||
|
||||
**or... Command-line tool**
|
||||
|
||||
```
|
||||
$ ./chaturbate-dvr -u emillybrowm start
|
||||
@ -36,30 +77,6 @@ $ ./chaturbate-dvr -u emillybrowm start
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Start the program also enables the Web UI. Visit [`http://localhost:8080`](http://localhost:8080) to use the Web UI to manage channels.
|
||||
|
||||
```yaml
|
||||
# Windows
|
||||
$ chaturbate-dvr.exe start
|
||||
|
||||
# macOS or Linux
|
||||
$ chaturbate-dvr start
|
||||
```
|
||||
|
||||
|
||||
|
||||
**💻 or... Run as a command-line tool**
|
||||
|
||||
Run the program with a channel name (`-u CHANNEL_USERNAME`) records the channel immediately, and the Web UI will be disabled.
|
||||
|
||||
```yaml
|
||||
$ chaturbate-dvr -u CHANNEL_USERNAME start
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
```bash
|
||||
@ -75,7 +92,6 @@ VERSION:
|
||||
1.0.0
|
||||
|
||||
COMMANDS:
|
||||
start
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
@ -96,25 +112,25 @@ GLOBAL OPTIONS:
|
||||
|
||||
```yaml
|
||||
# Records in 720p/60fps
|
||||
$ chaturbate-dvr -u yamiodymel -f 60 -r 720 start
|
||||
$ chaturbate-dvr -u yamiodymel -r 720 -f 60
|
||||
|
||||
# Split the video every 30 minutes
|
||||
$ chaturbate-dvr -u yamiodymel -sd 30 start
|
||||
$ chaturbate-dvr -u yamiodymel -sd 30
|
||||
|
||||
# Split the video every 1024 MB
|
||||
$ chaturbate-dvr -u yamiodymel -sf 1024 start
|
||||
$ chaturbate-dvr -u yamiodymel -sf 1024
|
||||
|
||||
# Change output filename pattern
|
||||
$ chaturbate-dvr -u yamiodymel -fp video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}}-{{.Second}}_{{.Sequence}} start
|
||||
$ chaturbate-dvr -u yamiodymel -fp video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}}-{{.Second}}_{{.Sequence}}
|
||||
```
|
||||
|
||||
※ If the `-u CHANNEL_NAME` flag was not specified, the settings will be default settings for Web UI to create channels.
|
||||
※ When runs in Web UI mode, the settings will be default settings for Web UI to create channels.
|
||||
|
||||
|
||||
|
||||
## 📺 Framerate & Resolution / Fallback
|
||||
|
||||
Fallback indicates what to do when there's no expected target resolution, imagine the situation:
|
||||
Fallback indicates what to do when there's no expected target resolution, situation:
|
||||
|
||||
```
|
||||
Availables: 1080p, 720p, 240p
|
||||
@ -160,3 +176,23 @@ Pattern: video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}}
|
||||
```
|
||||
|
||||
※ The file will be saved as `.ts` format and it's not configurable.
|
||||
|
||||
|
||||
|
||||
## 💬 Verbose Log
|
||||
|
||||
Change `-log-level` to `DEBUG` to see more details in terminal, like Duration and Size.
|
||||
|
||||
```yaml
|
||||
# Availables: DEBUG, INFO, WARN, ERROR
|
||||
$ chaturbate-dvr -u hepbugbear -log-level DEBUG
|
||||
[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #0 written
|
||||
[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 0.00 MiB
|
||||
[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #1 written
|
||||
[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 1.36 MiB
|
||||
[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #2 written
|
||||
[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 2.72 MiB
|
||||
[2024-01-24 01:18:12] [DEBUG] [hepbugbear] segment #3 fetched
|
||||
[2024-01-24 01:18:13] [INFO] [hepbugbear] segment #3 written
|
||||
[2024-01-24 01:18:13] [DEBUG] [hepbugbear] duration: 00:00:10, size: 4.08 MiB
|
||||
```
|
||||
|
||||
26
README_DEV.md
Normal file
26
README_DEV.md
Normal file
@ -0,0 +1,26 @@
|
||||
Compile for 64-bit Windows, macOS, Linux:
|
||||
|
||||
```
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr
|
||||
```
|
||||
|
||||
Compile for arm64 Windows, macOS, Linux:
|
||||
|
||||
```
|
||||
GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr
|
||||
```
|
||||
|
||||
or Compile All at once:
|
||||
|
||||
```
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr &&
|
||||
GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe &&
|
||||
GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr &&
|
||||
GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 108 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
BIN
bin/arm64/darwin/chatubrate-dvr
Normal file
BIN
bin/arm64/darwin/chatubrate-dvr
Normal file
Binary file not shown.
BIN
bin/arm64/linux/chatubrate-dvr
Normal file
BIN
bin/arm64/linux/chatubrate-dvr
Normal file
Binary file not shown.
BIN
bin/arm64/windows/chatubrate-dvr.exe
Normal file
BIN
bin/arm64/windows/chatubrate-dvr.exe
Normal file
Binary file not shown.
BIN
bin/darwin/chatubrate-dvr
Normal file
BIN
bin/darwin/chatubrate-dvr
Normal file
Binary file not shown.
BIN
bin/linux/chatubrate-dvr
Normal file
BIN
bin/linux/chatubrate-dvr
Normal file
Binary file not shown.
BIN
bin/windows/chatubrate-dvr.exe
Normal file
BIN
bin/windows/chatubrate-dvr.exe
Normal file
Binary file not shown.
BIN
chaturbate-dvr
BIN
chaturbate-dvr
Binary file not shown.
@ -4,6 +4,6 @@ services:
|
||||
chaturbate-dvr:
|
||||
build: .
|
||||
environment:
|
||||
- USERNAME=my_lovely_channel_name
|
||||
- USERNAME=CHANNEL_USERNAME
|
||||
volumes:
|
||||
- ./video/my_lovely_channel_name:/usr/src/app/video
|
||||
- ./videos:/usr/src/app/videos
|
||||
|
||||
4
go.mod
4
go.mod
@ -16,7 +16,6 @@ require (
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/cors v1.5.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
@ -24,11 +23,13 @@ require (
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
@ -40,5 +41,6 @@ require (
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
45
go.sum
45
go.sum
@ -1,11 +1,8 @@
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
@ -13,13 +10,12 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
||||
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
@ -30,8 +26,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
@ -47,11 +41,17 @@ github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
@ -61,12 +61,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
@ -80,8 +82,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
@ -92,41 +93,31 @@ github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
8
main.go
8
main.go
@ -93,12 +93,6 @@ func main() {
|
||||
//},
|
||||
},
|
||||
Action: start,
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "start",
|
||||
Action: start,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
@ -156,5 +150,7 @@ func startWeb(c *cli.Context) error {
|
||||
r.POST("/api/get_settings", handler.NewGetSettingsHandler(c).Handle)
|
||||
r.POST("/api/terminate_program", handler.NewTerminateProgramHandler(c).Handle)
|
||||
|
||||
fmt.Printf("👋 Visit http://localhost:%s to use the Web UI\n", c.String("port"))
|
||||
|
||||
return r.Run(fmt.Sprintf(":%s", c.String("port")))
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user