Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b433304f6 | |||
| 3964c6fa72 | |||
| 1e770e5c72 | |||
| 4069109509 | |||
| c88a801e97 | |||
| 3bd3d30701 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.5.2] - 2021-10-26
|
||||
|
||||
- Provide link to re-display the popup window from the index
|
||||
- Visual improvements
|
||||
|
||||
## [v0.5.1] - 2021-10-25
|
||||
|
||||
- Add note about adblockers potentially blocking the popup
|
||||
- Make it possible to refresh the popup window without initating a new download
|
||||
|
||||
## [v0.5.0] - 2021-10-01
|
||||
|
||||
- No more command line options, all configuration is now app-managed
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
type Download struct {
|
||||
Id int `json:"id"`
|
||||
Url string `json:"url"`
|
||||
PopupUrl string `json:"popup_url"`
|
||||
Pid int `json:"pid"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
State string `json:"state"`
|
||||
@@ -119,7 +120,7 @@ func (dl *Download) updateDownload(r io.Reader) {
|
||||
func (dl *Download) updateMetadata(s string) {
|
||||
|
||||
// [download] 49.7% of ~15.72MiB at 5.83MiB/s ETA 00:07
|
||||
etaRE := regexp.MustCompile(`download.+ETA +(\d\d:\d\d)`)
|
||||
etaRE := regexp.MustCompile(`download.+ETA +(\d\d:\d\d(?::\d\d)?)$`)
|
||||
matches := etaRE.FindStringSubmatch(s)
|
||||
if len(matches) == 2 {
|
||||
dl.Eta = matches[1]
|
||||
|
||||
62
download/download_test.go
Normal file
62
download/download_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package download
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUpdateMetadata(t *testing.T) {
|
||||
newD := Download{}
|
||||
|
||||
// first time we spot a filename
|
||||
newD.updateMetadata("[download] Destination: Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f401.mp4")
|
||||
if len(newD.Files) != 1 || newD.Files[0] != "Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f401.mp4" {
|
||||
t.Fatalf("incorrect Files:%v", newD.Files)
|
||||
}
|
||||
|
||||
// eta's might be xx:xx:xx or xx:xx
|
||||
newD.updateMetadata("[download] 0.0% of 504.09MiB at 135.71KiB/s ETA 01:03:36")
|
||||
if newD.Eta != "01:03:36" {
|
||||
t.Fatalf("bad long eta in dl\n%v", newD)
|
||||
}
|
||||
newD.updateMetadata("[download] 0.0% of 504.09MiB at 397.98KiB/s ETA 21:38")
|
||||
if newD.Eta != "21:38" {
|
||||
t.Fatalf("bad short eta in dl\n%v", newD)
|
||||
}
|
||||
|
||||
// added a new file, now we are tracking two
|
||||
newD.updateMetadata("[download] Destination: Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f140.m4a")
|
||||
if len(newD.Files) != 2 || newD.Files[1] != "Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f140.m4a" {
|
||||
t.Fatalf("incorrect Files:%v", newD.Files)
|
||||
}
|
||||
|
||||
// merging
|
||||
newD.updateMetadata("[ffmpeg] Merging formats into \"Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.mp4\"")
|
||||
if len(newD.Files) != 3 || newD.Files[2] != "Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.mp4" {
|
||||
t.Fatalf("did not find merged filename")
|
||||
t.Fatalf("%v", newD.Files)
|
||||
}
|
||||
|
||||
// deletes
|
||||
// TODO. Not sure why I don't always see the "Deleting original file" messages after merge -
|
||||
// maybe a youtube-dl fork thing?
|
||||
|
||||
}
|
||||
|
||||
// [youtube] wi7Agv1M6PY: Downloading webpage
|
||||
// [info] Writing video description metadata as JSON to: Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.info.json
|
||||
// [download] Destination: Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f401.mp4
|
||||
// [download] 0.0% of 504.09MiB at 135.71KiB/s ETA 01:03:36
|
||||
// [download] 0.0% of 504.09MiB at 397.98KiB/s ETA 21:38
|
||||
// [download] 0.0% of 504.09MiB at 918.97KiB/s ETA 09:22
|
||||
// [download] 0.0% of 504.09MiB at 1.90MiB/s ETA 04:25
|
||||
// ..
|
||||
// [download] 99.6% of 504.09MiB at 8.91MiB/s ETA 00:00
|
||||
// [download] 100.0% of 504.09MiB at 9.54MiB/s ETA 00:00
|
||||
// [download] 100% of 504.09MiB in 01:00
|
||||
// [download] Destination: Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.f140.m4a
|
||||
// [download] 0.0% of 4.64MiB at 155.26KiB/s ETA 00:30
|
||||
// [download] 0.1% of 4.64MiB at 457.64KiB/s ETA 00:10
|
||||
// [download] 0.1% of 4.64MiB at 1.03MiB/s ETA 00:04
|
||||
// ..
|
||||
// [download] 86.2% of 4.64MiB at 10.09MiB/s ETA 00:00
|
||||
// [download] 100.0% of 4.64MiB at 10.12MiB/s ETA 00:00
|
||||
// [download] 100% of 4.64MiB in 00:00
|
||||
// [ffmpeg] Merging formats into "Halo Infinite Flight 4K Gameplay-wi7Agv1M6PY.mp4"
|
||||
32
main.go
32
main.go
@@ -23,7 +23,7 @@ var downloads []*download.Download
|
||||
var downloadId = 0
|
||||
var conf *config.Config
|
||||
|
||||
var versionInfo = version.Info{CurrentVersion: "v0.5.0"}
|
||||
var versionInfo = version.Info{CurrentVersion: "v0.5.2"}
|
||||
|
||||
//go:embed web
|
||||
var webFS embed.FS
|
||||
@@ -55,6 +55,7 @@ func main() {
|
||||
r.HandleFunc("/", homeHandler)
|
||||
r.HandleFunc("/config", configHandler)
|
||||
r.HandleFunc("/fetch", fetchHandler)
|
||||
r.HandleFunc("/fetch/{id}", fetchHandler)
|
||||
|
||||
// info for the list
|
||||
r.HandleFunc("/rest/fetch", fetchInfoRESTHandler)
|
||||
@@ -110,11 +111,13 @@ func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
type Info struct {
|
||||
Downloads []*download.Download
|
||||
BookmarkletURL template.URL
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
info := Info{
|
||||
Downloads: downloads,
|
||||
BookmarkletURL: template.URL(bookmarkletURL),
|
||||
Config: conf,
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(w, "layout", info)
|
||||
@@ -241,6 +244,32 @@ func fetchInfoRESTHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func fetchHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// if they refreshed the popup, just load the existing object, don't
|
||||
// create a new one
|
||||
vars := mux.Vars(r)
|
||||
idString := vars["id"]
|
||||
|
||||
idInt, err := strconv.ParseInt(idString, 10, 32)
|
||||
if err == nil && idInt > 0 {
|
||||
for _, dl := range downloads {
|
||||
if dl.Id == int(idInt) {
|
||||
t, err := template.ParseFS(webFS, "web/layout.tmpl", "web/popup.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
templateData := map[string]interface{}{"dl": dl, "config": conf}
|
||||
|
||||
err = t.ExecuteTemplate(w, "layout", templateData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
url, present := query["url"]
|
||||
|
||||
@@ -265,6 +294,7 @@ func fetchHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Id: downloadId,
|
||||
Url: url[0],
|
||||
PopupUrl: fmt.Sprintf("/fetch/%d", downloadId),
|
||||
State: "choose profile",
|
||||
Finished: false,
|
||||
Eta: "?",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
{{ template "menu.tmpl" . }}
|
||||
|
||||
|
||||
<div x-data="index()" x-init="fetch_data(); fetch_version()">
|
||||
|
||||
<p x-cloak x-show="version && version.upgrade_available">
|
||||
@@ -17,12 +16,15 @@
|
||||
Drag this bookmarklet: <a href="{{ .BookmarkletURL }}">Gropple</a> to your bookmark bar, and click it
|
||||
on any page you want to grab the video from.
|
||||
</p>
|
||||
<p>
|
||||
Please note that some adblockers may prevent the bookmarklet from opening the popup window.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<table class="pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th><th>filename</th><th>url</th><th>state</th><th>percent</th><th>eta</th><th>finished</th>
|
||||
<th>id</th><th>filename</th><th>url</th><th>show</th><th>state</th><th>percent</th><th>eta</th><th>finished</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -30,11 +32,12 @@
|
||||
<tr>
|
||||
<td x-text="item.id"></td>
|
||||
<td x-text="item.files"></td>
|
||||
<td><a x-bind:href="item.url">link</a></td>
|
||||
<td x-text="item.state"></td>
|
||||
<td><a class="int-link" x-bind:href="item.url">↗</a></td>
|
||||
<td><a class="int-link" @click="show_popup(item)" href="#">📄</a></td>
|
||||
<td :class="'state-'+item.state" x-text="item.state"></td>
|
||||
<td x-text="item.percent"></td>
|
||||
<td x-text="item.eta"></td>
|
||||
<td x-text="item.finished"></td>
|
||||
<td x-text="item.finished ? '✔' : '-'"></td>
|
||||
</tr>
|
||||
|
||||
</template>
|
||||
@@ -75,7 +78,10 @@
|
||||
setTimeout(() => { this.fetch_data() }, 1000);
|
||||
})
|
||||
},
|
||||
show_popup(item) {
|
||||
window.open(item.popup_url, item.id, "width={{ .Config.UI.PopupWidth }},height={{ .Config.UI.PopupHeight }}");
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -14,36 +14,48 @@
|
||||
overflow:auto;
|
||||
}
|
||||
footer {
|
||||
padding-top: 50px;
|
||||
font-size: 30%;
|
||||
padding-top: 50px;
|
||||
font-size: 30%;
|
||||
}
|
||||
.int-link {
|
||||
text-decoration: none;
|
||||
hover { color: red; }
|
||||
}
|
||||
.state-failed {
|
||||
color: red;
|
||||
}
|
||||
.state-downloading {
|
||||
color: blue;
|
||||
}
|
||||
.state-complete {
|
||||
color: green;
|
||||
}
|
||||
.gropple-config {
|
||||
font-size: 80%;
|
||||
font-size: 80%;
|
||||
}
|
||||
.gropple-config input.input-long {
|
||||
width: 27em;
|
||||
width: 27em;
|
||||
}
|
||||
.gropple-config button {
|
||||
border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.gropple-config button.button-del {
|
||||
background: rgb(202, 60, 60);
|
||||
background: rgb(202, 60, 60);
|
||||
}
|
||||
.gropple-config button.button-add {
|
||||
background: rgb(60, 200, 60);
|
||||
background: rgb(60, 200, 60);
|
||||
}
|
||||
.gropple-config .pure-form-message {
|
||||
padding-top: .5em;
|
||||
padding-bottom: 1.5em;
|
||||
padding-top: .5em;
|
||||
padding-bottom: 1.5em;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
.success {
|
||||
color: green;
|
||||
color: green;
|
||||
}
|
||||
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
</style>
|
||||
@@ -57,4 +69,4 @@
|
||||
</body>
|
||||
{{ template "js" . }}
|
||||
</html>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
{{ define "js" }}
|
||||
<script>
|
||||
function popup() {
|
||||
history.replaceState(null, '', ['/fetch/{{ .dl.Id }}'])
|
||||
return {
|
||||
eta: '', percent: 0.0, state: '??', filename: '', finished: false, log :'',
|
||||
profile_chosen: null,
|
||||
watch_profile() {
|
||||
console.log('will wtch profile');
|
||||
this.$watch('profile_chosen', value => this.profile_chosen(value))
|
||||
},
|
||||
update_profile(name) {
|
||||
@@ -57,6 +57,9 @@
|
||||
this.eta = info.eta;
|
||||
this.percent = info.percent + "%";
|
||||
this.state = info.state;
|
||||
if (this.state != 'choose profile') {
|
||||
this.profile_chosen = true;
|
||||
}
|
||||
this.finished = info.finished;
|
||||
if (info.files && info.files.length > 0) {
|
||||
this.filename = info.files[info.files.length - 1];
|
||||
|
||||
Reference in New Issue
Block a user