updated with session
This commit is contained in:
parent
e9126e34f0
commit
fcc853de47
149
main.go
149
main.go
@ -23,23 +23,67 @@ var roomDossierRegexp = regexp.MustCompile(`window\.initialRoomDossier = "(.*?)"
|
|||||||
// --- main ---
|
// --- main ---
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("Verwendung: recorder.exe <Chaturbate-URL oder Benutzername> [-o <Pfad/zieldatei.ts>]")
|
fmt.Println("Verwendung: recorder.exe [--http-cookie \"<Cookie>\"] <Chaturbate-URL oder Benutzername> [best] [-o <Pfad/zieldatei.ts>] [-f]")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
arg := os.Args[1]
|
var (
|
||||||
username := extractUsername(arg)
|
httpCookie string
|
||||||
|
outputPath string
|
||||||
|
urlArg string
|
||||||
|
)
|
||||||
|
|
||||||
outputPath := fmt.Sprintf("%s_%s.ts", username, time.Now().Format("20060102_150405"))
|
args := os.Args[1:]
|
||||||
for i := 2; i < len(os.Args); i++ {
|
|
||||||
if os.Args[i] == "-o" && i+1 < len(os.Args) {
|
for i := 0; i < len(args); i++ {
|
||||||
outputPath = os.Args[i+1]
|
a := args[i]
|
||||||
break
|
|
||||||
|
switch a {
|
||||||
|
case "--http-cookie":
|
||||||
|
if i+1 >= len(args) {
|
||||||
|
fmt.Println("Fehlender Wert nach --http-cookie")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
httpCookie = args[i+1]
|
||||||
|
i++
|
||||||
|
|
||||||
|
case "-o":
|
||||||
|
if i+1 >= len(args) {
|
||||||
|
fmt.Println("Fehlender Wert nach -o")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
outputPath = args[i+1]
|
||||||
|
i++
|
||||||
|
|
||||||
|
case "-f":
|
||||||
|
// nur für Kompatibilität, wird ignoriert
|
||||||
|
case "best":
|
||||||
|
// wird ignoriert, wir wählen sowieso beste Qualität
|
||||||
|
|
||||||
|
default:
|
||||||
|
// erste „normale“ Angabe ist die URL / der Benutzername
|
||||||
|
if urlArg == "" {
|
||||||
|
urlArg = a
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if urlArg == "" {
|
||||||
|
fmt.Println("Keine URL / kein Benutzername angegeben.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
username := extractUsername(urlArg)
|
||||||
|
|
||||||
|
// Default-Ausgabedatei, falls kein -o
|
||||||
|
if outputPath == "" {
|
||||||
|
outputPath = fmt.Sprintf("%s_%s.ts", username, time.Now().Format("20060102_150405"))
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
body, err := fetchPage("https://chaturbate.com/" + username)
|
|
||||||
|
// Seite laden (inkl. Cookie, falls gesetzt)
|
||||||
|
body, err := fetchPage("https://chaturbate.com/"+username, httpCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("❌ Fehler beim Laden der Seite:", err)
|
fmt.Println("❌ Fehler beim Laden der Seite:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -51,7 +95,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist, err := FetchPlaylist(ctx, hlsURL)
|
playlist, err := FetchPlaylist(ctx, hlsURL, httpCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("❌ Fehler beim Abrufen der Playlist:", err)
|
fmt.Println("❌ Fehler beim Abrufen der Playlist:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -66,7 +110,7 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("📡 Aufnahme gestartet:", outputPath)
|
fmt.Println("📡 Aufnahme gestartet:", outputPath)
|
||||||
|
|
||||||
err = playlist.WatchSegments(ctx, func(b []byte, _ float64) error {
|
err = playlist.WatchSegments(ctx, httpCookie, func(b []byte, _ float64) error {
|
||||||
_, err := file.Write(b)
|
_, err := file.Write(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("❌ Fehler beim Schreiben in Datei:", err)
|
fmt.Println("❌ Fehler beim Schreiben in Datei:", err)
|
||||||
@ -78,10 +122,9 @@ func main() {
|
|||||||
fmt.Println("❌ Aufnahmefehler:", err)
|
fmt.Println("❌ Aufnahmefehler:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TS -> MP4 remuxen (wie gehabt)
|
||||||
mp4Out := outputPath
|
mp4Out := outputPath
|
||||||
ext := filepath.Ext(outputPath)
|
ext := filepath.Ext(outputPath)
|
||||||
|
|
||||||
// Falls nicht bereits .mp4 als Endung existiert
|
|
||||||
if ext != ".mp4" {
|
if ext != ".mp4" {
|
||||||
mp4Out = strings.TrimSuffix(outputPath, ext) + ".mp4"
|
mp4Out = strings.TrimSuffix(outputPath, ext) + ".mp4"
|
||||||
}
|
}
|
||||||
@ -100,7 +143,6 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("✅ Umwandlung abgeschlossen (web-optimiert):", mp4Out)
|
fmt.Println("✅ Umwandlung abgeschlossen (web-optimiert):", mp4Out)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- helper ---
|
// --- helper ---
|
||||||
@ -114,13 +156,40 @@ func extractUsername(input string) string {
|
|||||||
return strings.TrimSpace(input)
|
return strings.TrimSpace(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPage(url string) (string, error) {
|
func addCookiesFromString(req *http.Request, cookieStr string) {
|
||||||
|
if cookieStr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pairs := strings.Split(cookieStr, ";")
|
||||||
|
for _, pair := range pairs {
|
||||||
|
parts := strings.SplitN(strings.TrimSpace(pair), "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func fetchPage(url, httpCookie string) (string, error) {
|
||||||
client := http.Client{Timeout: 10 * time.Second}
|
client := http.Client{Timeout: 10 * time.Second}
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||||
|
if httpCookie != "" {
|
||||||
|
// kompletter Inhalt von --http-cookie, z.B. "session=XYZ; foo=bar"
|
||||||
|
addCookiesFromString(req, httpCookie)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -140,6 +209,7 @@ func fetchPage(url string) (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func ParseStream(html string) (string, error) {
|
func ParseStream(html string) (string, error) {
|
||||||
matches := roomDossierRegexp.FindStringSubmatch(html)
|
matches := roomDossierRegexp.FindStringSubmatch(html)
|
||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
@ -176,12 +246,22 @@ type Resolution struct {
|
|||||||
Width int
|
Width int
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) {
|
func FetchPlaylist(ctx context.Context, hlsSource, httpCookie string) (*Playlist, error) {
|
||||||
if hlsSource == "" {
|
if hlsSource == "" {
|
||||||
return nil, errors.New("HLS-URL leer")
|
return nil, errors.New("HLS-URL leer")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(hlsSource)
|
client := http.Client{Timeout: 10 * time.Second}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", hlsSource, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Fehler beim Erstellen der Playlist-Request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||||
|
if httpCookie != "" {
|
||||||
|
req.Header.Set("Cookie", httpCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Fehler beim Laden der Playlist: %w", err)
|
return nil, fmt.Errorf("Fehler beim Laden der Playlist: %w", err)
|
||||||
}
|
}
|
||||||
@ -217,7 +297,6 @@ func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) {
|
|||||||
fr = 60
|
fr = 60
|
||||||
}
|
}
|
||||||
|
|
||||||
// Besser, wenn größere Auflösung oder gleiche Auflösung mit höherem Framerate
|
|
||||||
if width > bestWidth || (width == bestWidth && fr > bestFramerate) {
|
if width > bestWidth || (width == bestWidth && fr > bestFramerate) {
|
||||||
bestWidth = width
|
bestWidth = width
|
||||||
bestFramerate = fr
|
bestFramerate = fr
|
||||||
@ -238,12 +317,16 @@ func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float64) error) error {
|
func (p *Playlist) WatchSegments(
|
||||||
|
ctx context.Context,
|
||||||
|
httpCookie string,
|
||||||
|
handler func([]byte, float64) error,
|
||||||
|
) error {
|
||||||
var lastSeq int64 = -1
|
var lastSeq int64 = -1
|
||||||
client := http.Client{Timeout: 10 * time.Second}
|
client := http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
emptyRounds := 0
|
emptyRounds := 0
|
||||||
const maxEmptyRounds = 5 // z. B. nach 5 leeren Runden abbrechen
|
const maxEmptyRounds = 5
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -252,7 +335,17 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Get(p.PlaylistURL)
|
// Playlist holen
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", p.PlaylistURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Fehler beim Erstellen der Playlist-Request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||||
|
if httpCookie != "" {
|
||||||
|
req.Header.Set("Cookie", httpCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
emptyRounds++
|
emptyRounds++
|
||||||
if emptyRounds >= maxEmptyRounds {
|
if emptyRounds >= maxEmptyRounds {
|
||||||
@ -261,6 +354,7 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float
|
|||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist, listType, err := m3u8.DecodeFrom(resp.Body, true)
|
playlist, listType, err := m3u8.DecodeFrom(resp.Body, true)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
@ -288,7 +382,17 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float
|
|||||||
newSegment = true
|
newSegment = true
|
||||||
|
|
||||||
segmentURL := p.RootURL + segment.URI
|
segmentURL := p.RootURL + segment.URI
|
||||||
segResp, err := client.Get(segmentURL)
|
|
||||||
|
segReq, err := http.NewRequestWithContext(ctx, "GET", segmentURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
segReq.Header.Set("User-Agent", "Mozilla/5.0")
|
||||||
|
if httpCookie != "" {
|
||||||
|
segReq.Header.Set("Cookie", httpCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
segResp, err := client.Do(segReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -316,3 +420,4 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float
|
|||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
recorder.exe
BIN
recorder.exe
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user