basic video fetching
This commit is contained in:
parent
48642bdd96
commit
cca6660c95
4 changed files with 177 additions and 0 deletions
2
main.go
2
main.go
|
@ -111,6 +111,8 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
handleTextSearch(w, query, safe, lang)
|
handleTextSearch(w, query, safe, lang)
|
||||||
case "image":
|
case "image":
|
||||||
handleImageSearch(w, query, safe, lang, page)
|
handleImageSearch(w, query, safe, lang, page)
|
||||||
|
case "video":
|
||||||
|
videoSearchEndpointHandler(w, r)
|
||||||
default:
|
default:
|
||||||
http.ServeFile(w, r, "static/search.html")
|
http.ServeFile(w, r, "static/search.html")
|
||||||
}
|
}
|
||||||
|
|
3
run.sh
Executable file
3
run.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
go run main.go text.go images.go imageproxy.go video.go
|
29
templates/videos.html
Normal file
29
templates/videos.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Video Results</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h1>Video Results</h1>
|
||||||
|
{{ if .Results }}
|
||||||
|
{{ range .Results }}
|
||||||
|
<div class="video-result">
|
||||||
|
<h2>{{ .Title }}</h2>
|
||||||
|
<a href="{{ .Href }}" target="_blank">
|
||||||
|
<img src="{{ .Image }}" alt="{{ .Title }}" style="width:100%;max-width:600px;">
|
||||||
|
</a>
|
||||||
|
<p>{{ .Views }} views | {{ .Duration }}</p>
|
||||||
|
<p>By {{ .Creator }}</p>
|
||||||
|
</div>
|
||||||
|
<!-- Removed the break as it's not directly supported in Go templates. If you want only the first result, adjust in Go code instead -->
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<p>No results found. Try adjusting your search term.</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
143
video.go
Normal file
143
video.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PIPED_INSTANCE = "pipedapi.kavin.rocks"
|
||||||
|
|
||||||
|
// VideoResult reflects the structured data for a video result
|
||||||
|
type VideoResult struct {
|
||||||
|
Href string
|
||||||
|
Title string
|
||||||
|
Date string
|
||||||
|
Views string
|
||||||
|
Creator string
|
||||||
|
Publisher string
|
||||||
|
Image string
|
||||||
|
Duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoAPIResponse matches the structure of the JSON response from the Piped API
|
||||||
|
type VideoAPIResponse struct {
|
||||||
|
Items []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
UploaderName string `json:"uploaderName"`
|
||||||
|
Views int `json:"views"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
UploadedDate string `json:"uploadedDate"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to format views similarly to the Python code
|
||||||
|
func formatViews(views int) string {
|
||||||
|
switch {
|
||||||
|
case views >= 1_000_000_000:
|
||||||
|
return fmt.Sprintf("%.1fB views", float64(views)/1_000_000_000)
|
||||||
|
case views >= 1_000_000:
|
||||||
|
return fmt.Sprintf("%dM views", views/1_000_000)
|
||||||
|
case views >= 10_000:
|
||||||
|
return fmt.Sprintf("%.1fK views", float64(views)/1_000)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d views", views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatDuration formats video duration as done in the Python code
|
||||||
|
func formatDuration(seconds int) string {
|
||||||
|
hours := seconds / 3600
|
||||||
|
minutes := (seconds % 3600) / 60
|
||||||
|
seconds = seconds % 60
|
||||||
|
if hours > 0 {
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02d:%02d", minutes, seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeHTMLRequest fetches search results from the Piped API, similarly to the Python `makeHTMLRequest`
|
||||||
|
func makeHTMLRequest(query string) (*VideoAPIResponse, error) {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("https://%s/search?q=%s&filter=all", PIPED_INSTANCE, url.QueryEscape(query)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error making request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResp VideoAPIResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func videoSearchEndpointHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
query := r.URL.Query().Get("q")
|
||||||
|
safe := r.URL.Query().Get("safe") // Assuming "safe" is a query parameter
|
||||||
|
lang := r.URL.Query().Get("lang") // Assuming "lang" is a query parameter
|
||||||
|
pageStr := r.URL.Query().Get("page")
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil {
|
||||||
|
// Handle error, perhaps set page to a default value if it's not critical
|
||||||
|
page = 1 // Default page
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVideoSearch(w, query, safe, lang, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleVideoSearch adapted from the Python `videoResults`, handles video search requests
|
||||||
|
func handleVideoSearch(w http.ResponseWriter, query, safe, lang string, page int) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Modify `makeHTMLRequest` to also accept `safe`, `lang`, and `page` parameters if necessary
|
||||||
|
apiResp, err := makeHTMLRequest(query)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error fetching video results: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []VideoResult
|
||||||
|
for _, item := range apiResp.Items {
|
||||||
|
if item.Type == "channel" || item.Type == "playlist" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
results = append(results, VideoResult{
|
||||||
|
Href: fmt.Sprintf("https://%s%s", PIPED_INSTANCE, item.URL),
|
||||||
|
Title: item.Title,
|
||||||
|
Date: item.UploadedDate,
|
||||||
|
Views: formatViews(item.Views),
|
||||||
|
Creator: item.UploaderName,
|
||||||
|
Publisher: "Piped",
|
||||||
|
Image: fmt.Sprintf("/img_proxy?url=%s", url.QueryEscape(item.Thumbnail)),
|
||||||
|
Duration: formatDuration(item.Duration),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
tmpl, err := template.ParseFiles("templates/videos.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing template: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl.Execute(w, map[string]interface{}{
|
||||||
|
"Results": results,
|
||||||
|
"Query": query,
|
||||||
|
"Fetched": fmt.Sprintf("%.2f seconds", elapsed.Seconds()),
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue