Skip to content

Commit acb600b

Browse files
authored
Merge pull request #1 from Hidayathamir/feat/download-all-episodes
2 parents 218acb2 + 8c898f5 commit acb600b

File tree

6 files changed

+136
-2
lines changed

6 files changed

+136
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919

2020
# Go workspace file
2121
go.work
22+
23+
.vscode/

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ Why not.
1212
- List all available episodes
1313
- Stream episodes directly with minimal user interaction
1414
- Customize the video player used for streaming
15+
- Download all episodes asynchronously
1516

1617
## Usage
1718

1819
Running spongebob-cli without any flags will prompt the user to select the episode number.
1920

2021
```
2122
Usage of spongebob-cli:
23+
-d int
24+
download all episodes asynchronously but max [d] episode at a time (default -1)
2225
-l list episodes and quit
2326
-p int
2427
play the wanted episode without any user interaction (default -1)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"sync"
13+
"time"
14+
15+
"golang.org/x/sync/semaphore"
16+
)
17+
18+
func downloadAllEpisodes(maxConcurrent int) error {
19+
dir, err := mkdirSpongebob()
20+
if err != nil {
21+
return err
22+
}
23+
24+
episodesUrls, _ := getEpisodes()
25+
26+
// asynchronously download all episodes but max {maxConcurrent} episode at a time.
27+
// source: https://gist.github.com/AntoineAugusti/80e99edfe205baf7a094?permalink_comment_id=4088548#gistcomment-4088548
28+
sem := semaphore.NewWeighted(int64(maxConcurrent))
29+
ctx := context.TODO()
30+
var wg sync.WaitGroup
31+
32+
start := time.Now()
33+
34+
for i := 0; i < len(episodesUrls); i++ {
35+
wg.Add(1)
36+
37+
go func(i int) {
38+
_ = sem.Acquire(ctx, 1)
39+
defer sem.Release(1)
40+
defer wg.Done()
41+
42+
episodeUrl := episodesUrls[i]
43+
44+
videoSource := extractVideo(episodeUrl)
45+
46+
newFileName, err := getNewFileNameWithDir(dir, i, videoSource)
47+
if err != nil {
48+
fmt.Printf("Error while get new file name with dir: %v\n", err)
49+
return
50+
}
51+
52+
fmt.Printf("downloading\t%d %s\n", i, videoSource)
53+
54+
if err := downloadFile(newFileName, videoSource); err != nil {
55+
fmt.Printf("failed\t%d %s: %v\n", i, videoSource, err)
56+
return
57+
}
58+
59+
fmt.Printf("success\t\t%d %s\n", i, videoSource)
60+
}(i)
61+
}
62+
63+
wg.Wait()
64+
65+
fmt.Printf("total time %v\n", time.Since(start))
66+
67+
return nil
68+
}
69+
70+
func mkdirSpongebob() (string, error) {
71+
spongebobDir := filepath.Join(".", "spongebob")
72+
if err := os.MkdirAll(spongebobDir, os.ModePerm); err != nil {
73+
return "", err
74+
}
75+
76+
return spongebobDir, nil
77+
}
78+
79+
func getNewFileNameWithDir(dir string, index int, videoSource string) (string, error) {
80+
newFileName := fmt.Sprintf("%d-", index)
81+
newFileName = filepath.Join(dir, newFileName)
82+
83+
videoSourceSplitted := strings.Split(videoSource, "/")
84+
if len(videoSourceSplitted) == 0 {
85+
return "", errors.New("error split video source")
86+
}
87+
88+
videoSourceFileName := videoSourceSplitted[len(videoSourceSplitted)-1]
89+
newFileName += videoSourceFileName
90+
return newFileName, nil
91+
}
92+
93+
func downloadFile(filepath string, videoUrl string) error {
94+
out, err := os.Create(filepath)
95+
if err != nil {
96+
return err
97+
}
98+
defer out.Close()
99+
100+
resp, err := http.Get(videoUrl)
101+
if err != nil {
102+
return err
103+
}
104+
defer resp.Body.Close()
105+
106+
if resp.StatusCode != http.StatusOK {
107+
return err
108+
}
109+
110+
_, err = io.Copy(out, resp.Body)
111+
if err != nil {
112+
return err
113+
}
114+
115+
return nil
116+
}

spongebob-cli/go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ module spongebob-cli
33
go 1.21.5
44

55
require (
6-
github.com/PuerkitoBio/goquery v1.8.1 // indirect
6+
github.com/PuerkitoBio/goquery v1.8.1
7+
github.com/olekukonko/tablewriter v0.0.5
8+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
9+
)
10+
11+
require (
712
github.com/andybalholm/cascadia v1.3.1 // indirect
813
github.com/mattn/go-runewidth v0.0.9 // indirect
9-
github.com/olekukonko/tablewriter v0.0.5 // indirect
1014
golang.org/x/net v0.7.0 // indirect
1115
)

spongebob-cli/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
1717
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
1818
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
1919
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
20+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
2021
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2122
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
2223
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

spongebob-cli/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var (
1717
play = flag.Int("p", -1, "play the wanted episode without any user interaction")
1818
list = flag.Bool("l", false, "list episodes and quit")
1919
videoPlayer = flag.String("vp", "mpv", "use another video player [default=mpv]")
20+
download = flag.Int("d", -1, "download all episodes asynchronously but max [d] episode at a time")
2021
)
2122

2223
func getEpisodes() ([]string, []string) {
@@ -125,6 +126,13 @@ func main() {
125126
fmt.Printf("Playing '%s'...\n", episodesTitles[user-1])
126127
playVideo(video, *videoPlayer)
127128
} else {
129+
if *download > 0 {
130+
if err := downloadAllEpisodes(*download); err != nil {
131+
fmt.Printf("Error while download all episodes: %v\n", err)
132+
}
133+
return
134+
}
135+
128136
if *play >= 1 {
129137
video := extractVideo(episodesUrls[*play-1])
130138
fmt.Printf("Playing '%s'...\n", episodesTitles[*play])

0 commit comments

Comments
 (0)