Skip to content

Commit

Permalink
release v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
PrincessMortix committed Jan 23, 2024
1 parent 3bd43dc commit 2c7ae60
Showing 1 changed file with 96 additions and 149 deletions.
245 changes: 96 additions & 149 deletions gobalt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,160 +8,102 @@ import (
"net/http"
"net/url"
"regexp"
"slices"
"strings"
"time"
)

var (
CobaltApi = "https://co.wuk.sh" //Override this value to use your own cobalt instance. See https://instances.hyper.lol/ for alternatives from the main instance.
useragent = "Gobalt/1.0"
CobaltApi = "https://co.wuk.sh" //Override this value to use your own cobalt instance. See https://instances.hyper.lol/ for alternatives from the main instance.
useragent = "Gobalt/1.0"
UserLanguage = "en-US"
)

type settings struct {
url string
videoCodec string
videoQuality string
audioCodec string
filenamePattern string
audioOnly bool
removeTikTokWatermark bool
fullTikTokAudio bool
removeAudio bool
dubbedYoutubeAudio bool
disableVideoMetadata bool
}

type serverInfo struct {
Version string `json:"version"`
Commit string `json:"commit"`
Branch string `json:"branch"`
Name string `json:"name"`
URL string `json:"url"`
Cors int `json:"cors"`
StartTime string `json:"startTime"`
Version string `json:"version"` //cobalt version
Commit string `json:"commit"` //git commit
Branch string `json:"branch"` //git branch
Name string `json:"name"` //name of the server
URL string `json:"url"` //full url of the api
Cors int `json:"cors"` //cors status, either 0 or 1.
StartTime string `json:"startTime"` //server start time in linux epoch
}

type cobaltResponse struct {
Status string `json:"status"`
URL string `json:"url"`
Text string `json:"text"`
}

// Define Option type as a function that modifies settings
type Option func(*settings)

/*
Video Codecs
h264, av1 or vp9. default is h264. applies only to youtube downloads. `h264` is recommended for phones.
*/
func VideoCodec(codec string) Option {
//Expecting H264, AV1 or VP9
supportedCodecsAudio := []string{"h264", "av1", "vp9"}
if !slices.Contains(supportedCodecsAudio, strings.ToLower(codec)) { //Unknown codec, will use h264
return func(s *settings) { s.videoCodec = "h264" }
}
return func(s *settings) { s.videoCodec = codec }
}

/*
Audio Codecs
MP3, Opus, ogg, wav or best, defaults to `best`.
*/
func AudioCodec(codec string) Option {
//Expecting MP3, Opus, ogg, wav or best
supportedCodecsAudio := []string{"mp3", "ogg", "opus", "wav", "best"}
if !slices.Contains(supportedCodecsAudio, strings.ToLower(codec)) { //Unknown codec, will try the best one
return func(s *settings) { s.audioCodec = "best" }
}
return func(s *settings) { s.audioCodec = codec }
}

// End of audio codecs

func VideoQuality(quality string) Option {
//Expecting any value between 144p and 4K, or best
supportedQuality := []string{"144", "240", "360", "480", "720", "1080", "1440", "2160", "best"}
if !slices.Contains(supportedQuality, quality) { //Unsupported quality, will return best
return func(s *settings) { s.videoQuality = "best" }
}
return func(s *settings) { s.videoQuality = quality }
}

// FilenamePattern types
func FilenamePattern(pattern string) Option {
//Expected: classic, pretty, nerdy or basic
supportedPatterns := []string{"classic", "pretty", "nerdy", "basic"}
/* Pattern examples:
* Classic: youtube_yPYZpwSpKmA_1920x1080_h264.mp4 | audio: youtube_yPYZpwSpKmA_audio.mp3
* Basic: Video Title (1080p, h264).mp4 | audio: Audio Title - Audio Author.mp3
* Pretty: Video Title (1080p, h264, youtube).mp4 | audio: Audio Title - Audio Author (soundcloud).mp3
* Nerdy: Video Title (1080p, h264, youtube, yPYZpwSpKmA).mp4 | audio: Audio Title - Audio Author (soundcloud, 1242868615).mp3
*/
if !slices.Contains(supportedPatterns, pattern) { //Unknown pattern will return basic
return func(s *settings) { s.filenamePattern = "basic" }
}
return func(s *settings) { s.filenamePattern = pattern }
}

func AudioOnly(value bool) Option {
return func(s *settings) { s.audioOnly = value }
}

func RemoveTikTokWatermark(value bool) Option {
return func(s *settings) { s.removeTikTokWatermark = value }
}

func FullTikTokAudio(value bool) Option {
return func(s *settings) { s.fullTikTokAudio = value }
}

func RemoveAudio(value bool) Option {
return func(s *settings) { s.removeAudio = value }
}

func DubbedYoutubeAudio(value bool) Option {
return func(s *settings) { s.dubbedYoutubeAudio = value }
}
Status string `json:"status"` //Will be error / redirect / stream / success / rate-limit / picker.
Picker []struct { //array of picker items
Type string `json:"type"`
URL string `json:"url"`
Thumb string `json:"thumb"`
} `json:"picker"`
URL string `json:"url"` //Returns the download link. If the status is picker this field will be empty. Direct link to a file or a link to cobalt's live render.
Text string `json:"text"` //Various text, mostly used for errors.
URLs []string //If the status is picker all the urls will go here.
}

type Settings struct {
Url string //Any URL from bilibili.com, instagram, pinterest, reddit, rutube, soundcloud, streamable, tiktok, tumblr, twitch clips, twitter/x, vimeo, vine archive, vk or youtube. Will be url encoded later.
VideoCodec codecs //H264, AV1 or VP9, defaults to H264.
VideoQuality int //144p to 2160p (4K), if not specified will default to 1080p.
AudioCodec audioCodec //MP3, Opus, Ogg or Wav. If not specified will default to best.
FilenamePattern pattern //Classic, Basic, Pretty or Nerdy. Defaults to Pretty
AudioOnly bool //Removes the video, downloads audio only. Default: false
RemoveTikTokWatermark bool //Removes TikTok watermark from TikTok videos. Default: false
FullTikTokAudio bool //Enables download of original sound used in a tiktok video. Default: false
VideoOnly bool //Downloads only the video, audio is muted/removed. Default: false
DubbedYoutubeAudio bool //Pass the User-Language HTTP header to use the dubbed audio of the respective language, must change according to user's preference, default is English (US). Uses ISO 639-1 standard.
DisableVideoMetadata bool //Removes file metadata. Default: false
}

type codecs string

const (
H264 codecs = "h264" //Default codec that is supported everywhere. Recommended for social media/phones, but tops up at 1080p.
AV1 codecs = "av1" //Recent codec, supports 8K/HDR. Generally less supported by media players, social media, etc.
VP9 codecs = "vp9" //Best quality codec with higher bitrate (preserve most detail), goes up to 4K and supports HDR.
)

func DisableVideoMetadata(value bool) Option {
return func(s *settings) { s.disableVideoMetadata = value }
}
type audioCodec string

// New initializes a settings object with default values and applies any provided options.
func New(url string, options ...Option) *settings {
if url == "" {
panic("url cannot be empty")
}
defaults := &settings{
url: url,
videoCodec: "h264",
audioCodec: "mp3",
videoQuality: "best",
filenamePattern: "basic",
audioOnly: false,
removeTikTokWatermark: false,
fullTikTokAudio: false,
removeAudio: false,
dubbedYoutubeAudio: false,
disableVideoMetadata: false,
}
const (
Best audioCodec = "best" //When "best" format is selected, you get audio the way it is on service's side. it's not re-encoded.
Opus audioCodec = "opus" //Re-encodes the audio using Opus codec. It's a lossy codec with low complexity. Works in Android 10+, Windows 10 1809+, MacOS High Sierra/iOS 17+.
Ogg audioCodec = "ogg" //Re-encodes to ogg, an older lossy audio codec. Should work everywhere.
Wav audioCodec = "wav" //Re-encodes to wav, an even older format. Good compatibility for older systems, like Windows 98. Tops up at 4GiB.
MP3 audioCodec = "mp3" //Re-encodes to mp3, the format used basically everywhere. Lossy audio, but generally good player/social media support. Can degrade quality as time passes.
)

for _, option := range options {
option(defaults)
}
type pattern string

return defaults
}
const (
Classic pattern = "classic" //Looks like this: youtube_yPYZpwSpKmA_1920x1080_h264.mp4 | audio: youtube_yPYZpwSpKmA_audio.mp3
Basic pattern = "basic" //Looks like: Video Title (1080p, h264).mp4 | audio: Audio Title - Audio Author.mp3
Nerdy pattern = "nerdy" //Looks like this: Video Title (1080p, h264, youtube).mp4 | audio: Audio Title - Audio Author (soundcloud).mp3
Pretty pattern = "pretty" //Looks like: Video Title (1080p, h264, youtube, yPYZpwSpKmA).mp4 | audio: Audio Title - Audio Author (soundcloud, 1242868615).mp3
)

func Run(opts *settings) (*cobaltResponse, error) {
//I was planning to handle other cobalt instances, but i think this should be left to the user.
validUrl, _ := regexp.MatchString(`[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?`, opts.url)
if !validUrl {
func New() *Settings { //Function New() creates the settings struct with default values.
options := &Settings{
Url: "",
VideoCodec: H264,
VideoQuality: 1080,
AudioCodec: Best,
FilenamePattern: Pretty,
AudioOnly: false,
RemoveTikTokWatermark: false,
FullTikTokAudio: false,
VideoOnly: false,
DubbedYoutubeAudio: false,
DisableVideoMetadata: false,
}
return options
}

func Run(opts *Settings) (*cobaltResponse, error) {
validUrl, _ := regexp.MatchString(`[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?`, opts.Url)
if opts.Url == "" || !validUrl {
return nil, errors.New("invalid url provided")
}

_, err := CobaltServerInfo(CobaltApi)
if err != nil {
return nil, fmt.Errorf("could not contact the cobalt server at url %v due of the following error %v", CobaltApi, err)
Expand All @@ -171,26 +113,26 @@ func Run(opts *settings) (*cobaltResponse, error) {
Timeout: 15 * time.Second,
}

optionsPayload := map[string]string{"url": url.QueryEscape(opts.url),
"vCodec": opts.videoCodec,
"vQuality": opts.videoQuality,
"aFormat": opts.audioCodec,
"filenamePattern": opts.filenamePattern,
"isAudioOnly": fmt.Sprint(opts.audioOnly),
"isNoTTWatermark": fmt.Sprint(opts.removeTikTokWatermark),
"isTTFullAudio": fmt.Sprint(opts.fullTikTokAudio),
"isAudioMuted": fmt.Sprint(opts.removeAudio),
"dubLang": fmt.Sprint(opts.dubbedYoutubeAudio),
"disableMetadata": fmt.Sprint(opts.disableVideoMetadata),
optionsPayload := map[string]string{"url": url.QueryEscape(opts.Url),
"vCodec": string(opts.VideoCodec),
"vQuality": fmt.Sprint(opts.VideoQuality),
"aFormat": string(opts.AudioCodec),
"filenamePattern": string(opts.FilenamePattern),
"isAudioOnly": fmt.Sprint(opts.AudioOnly),
"isNoTTWatermark": fmt.Sprint(opts.RemoveTikTokWatermark),
"isTTFullAudio": fmt.Sprint(opts.FullTikTokAudio),
"isAudioMuted": fmt.Sprint(opts.VideoOnly),
"dubLang": fmt.Sprint(opts.DubbedYoutubeAudio),
"disableMetadata": fmt.Sprint(opts.DisableVideoMetadata),
}

payload, _ := json.Marshal(optionsPayload)
//fmt.Println(string(payload))

req, err := http.NewRequest("POST", CobaltApi+"/api/json", strings.NewReader(string(payload)))
req.Header.Add("User-Agent", useragent)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Language", UserLanguage)
if err != nil {
return nil, err
}
Expand All @@ -217,13 +159,18 @@ func Run(opts *settings) (*cobaltResponse, error) {
}

if media.Status == "picker" {
return nil, errors.New("not implemented on gobalt yet, please open an issue")
for _, p := range media.Picker {
media.URLs = append(media.URLs, p.URL)
}
} else if media.Status == "stream" {
media.URLs = append(media.URLs, media.URL)
}

return &cobaltResponse{
Status: media.Status,
URL: media.URL,
Text: "ok", //Cobalt doesn't return any text if its ok
URLs: media.URLs,
}, nil
}

Expand Down

0 comments on commit 2c7ae60

Please sign in to comment.