PrivateGER
3 years ago
commit
a72404a977
6 changed files with 346 additions and 0 deletions
@ -0,0 +1,108 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"github.com/dlclark/regexp2" |
|||
"io/ioutil" |
|||
"log" |
|||
"path/filepath" |
|||
"strings" |
|||
"sync" |
|||
) |
|||
|
|||
type FileList struct { |
|||
files map[string]VideoFile |
|||
mu sync.Mutex |
|||
} |
|||
|
|||
type VideoFile struct { |
|||
Filename string |
|||
Extension string |
|||
Title string |
|||
Id string |
|||
} |
|||
|
|||
var FL FileList |
|||
|
|||
func ScanDirectory(group *sync.WaitGroup, path string) { |
|||
defer group.Done() |
|||
|
|||
FL.mu.Lock() |
|||
defer FL.mu.Unlock() |
|||
|
|||
fmt.Println("Scanning archive...") |
|||
|
|||
fileList, err := ioutil.ReadDir(path) |
|||
if err != nil { |
|||
log.Println(err) |
|||
return |
|||
} |
|||
|
|||
FL.files = make(map[string]VideoFile) |
|||
for _, video := range fileList { |
|||
extension := filepath.Ext(video.Name())[1:] |
|||
// check if extension is one of valid yt-dlp extensions, if not ignore file
|
|||
switch extension { |
|||
case "3gp", |
|||
"aac", |
|||
"flv", |
|||
"m4a", |
|||
"mp3", |
|||
"mp4", |
|||
"webm": |
|||
break |
|||
default: |
|||
continue |
|||
} |
|||
|
|||
id := filenameToID(video.Name()) |
|||
|
|||
FL.files[id] = VideoFile{ |
|||
Filename: video.Name(), |
|||
Extension: extension, |
|||
Title: filenameToTitle(video.Name(), extension), |
|||
Id: id, |
|||
} |
|||
} |
|||
|
|||
fmt.Println("Archive scan finished.") |
|||
} |
|||
|
|||
func filenameToID(filename string) string { |
|||
r := regexp2.MustCompile("([^[]+(?=]))", regexp2.RegexOptions(0)) |
|||
|
|||
matches := regexp2FindAllString(r, filename) |
|||
if len(matches) == 0 { |
|||
fmt.Println("Got video without square-bracket ID format. Falling back to youtube-dl 11-char string matching (!THIS MAY CAUSE ISSUES!):", filename) |
|||
|
|||
r = regexp2.MustCompile("-[A-Za-z0-9_-]{11}", regexp2.RegexOptions(0)) |
|||
matches = regexp2FindAllString(r, filename) |
|||
if len(matches) == 0 { |
|||
log.Fatal("Got a video file without ID. Please remove or fix: " + filename) |
|||
} |
|||
|
|||
// strips first dash away from the result (yes, I know this is DIRTY.
|
|||
matches = []string{matches[len(matches)-1]} |
|||
matches[0] = matches[0][1:] |
|||
fmt.Println("Recovered ID:", matches[0]) |
|||
} |
|||
return matches[len(matches)-1] // last element = the id between square brackets
|
|||
} |
|||
|
|||
func filenameToTitle(filename string, extension string) string { |
|||
title := strings.Replace(filename, "[" + filenameToID(filename) + "]", "", 1) |
|||
title = strings.Replace(title, "." + extension, "", 1) |
|||
title = strings.Replace(title, filenameToID(filename), "", 1) |
|||
|
|||
return title |
|||
} |
|||
|
|||
func regexp2FindAllString(re *regexp2.Regexp, s string) []string { |
|||
var matches []string |
|||
m, _ := re.FindStringMatch(s) |
|||
for m != nil { |
|||
matches = append(matches, m.String()) |
|||
m, _ = re.FindNextMatch(m) |
|||
} |
|||
return matches |
|||
} |
@ -0,0 +1,7 @@ |
|||
module ytdlp-viewer |
|||
|
|||
go 1.17 |
|||
|
|||
require github.com/dlclark/regexp v1.4.0 |
|||
|
|||
require github.com/dlclark/regexp2 v1.4.0 // indirect |
@ -0,0 +1,158 @@ |
|||
package main |
|||
|
|||
import ( |
|||
_ "embed" |
|||
"fmt" |
|||
"log" |
|||
"net/http" |
|||
"os" |
|||
"strconv" |
|||
"strings" |
|||
"sync" |
|||
"text/template" |
|||
) |
|||
|
|||
//go:embed templates/index.html
|
|||
var indexTmplSource string |
|||
|
|||
//go:embed templates/search.html
|
|||
var searchTmplSource string |
|||
|
|||
//go:embed templates/view.html
|
|||
var viewTmplSource string |
|||
|
|||
type IndexPageData struct { |
|||
FileCount string |
|||
Files map[string]VideoFile |
|||
} |
|||
|
|||
type SearchPageData struct { |
|||
Results []VideoFile |
|||
ResultCount string |
|||
SearchTerm string |
|||
} |
|||
|
|||
type ViewPageData struct { |
|||
Title string |
|||
Filename string |
|||
Id string |
|||
Extension string |
|||
} |
|||
|
|||
func main() { |
|||
path := os.Getenv("directory") |
|||
if path == "" { |
|||
path = "Z:/MainArchive" |
|||
} |
|||
|
|||
var wg sync.WaitGroup |
|||
wg.Add(1) |
|||
|
|||
fmt.Println("Starting scanner at", path) |
|||
go ScanDirectory(&wg, path) |
|||
|
|||
wg.Wait() |
|||
|
|||
FL.mu.Lock() |
|||
fmt.Println(strconv.Itoa(len(FL.files))) |
|||
for _, file := range FL.files { |
|||
fmt.Println("Name:", file.Title,"Extension:", file.Extension, "ID:", file.Id) |
|||
} |
|||
FL.mu.Unlock() |
|||
|
|||
indexTmpl, err := template.New("index.tmpl").Parse(indexTmplSource) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
searchTmpl, err := template.New("search.tmpl").Parse(searchTmplSource) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
viewTmpl, err := template.New("view.tmpl").Parse(viewTmplSource) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
|
|||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { |
|||
FL.mu.Lock() |
|||
defer FL.mu.Unlock() |
|||
|
|||
data := IndexPageData{ |
|||
FileCount: strconv.Itoa(len(FL.files)), |
|||
Files: FL.files, |
|||
} |
|||
|
|||
err := indexTmpl.Execute(writer, data) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
}) |
|||
http.HandleFunc("/search", func(writer http.ResponseWriter, request *http.Request) { |
|||
FL.mu.Lock() |
|||
defer FL.mu.Unlock() |
|||
|
|||
keys, ok := request.URL.Query()["term"] |
|||
if !ok || len(keys[0]) < 1 { |
|||
log.Println("Url Param 'term' is missing") |
|||
return |
|||
} |
|||
|
|||
var results []VideoFile |
|||
|
|||
for _, video := range FL.files { |
|||
if video.Id == keys[0] { |
|||
results = append(results, video) |
|||
break |
|||
} |
|||
if strings.Contains(strings.ToUpper(video.Title), strings.ToUpper(keys[0])) { |
|||
results = append(results, video) |
|||
continue |
|||
} |
|||
} |
|||
|
|||
data := SearchPageData{ |
|||
Results: results, |
|||
ResultCount: strconv.Itoa(len(results)), |
|||
SearchTerm: keys[0], |
|||
} |
|||
|
|||
err := searchTmpl.Execute(writer, data) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
}) |
|||
http.HandleFunc("/view", func(writer http.ResponseWriter, request *http.Request) { |
|||
FL.mu.Lock() |
|||
defer FL.mu.Unlock() |
|||
|
|||
keys, ok := request.URL.Query()["id"] |
|||
if !ok || len(keys[0]) < 1 { |
|||
log.Println("Url Param 'id' is missing") |
|||
return |
|||
} |
|||
|
|||
if _, ok := FL.files[keys[0]]; !ok { |
|||
return |
|||
} |
|||
|
|||
video := FL.files[keys[0]] |
|||
data := ViewPageData{ |
|||
Title: video.Title, |
|||
Filename: video.Filename, |
|||
Id: video.Id, |
|||
Extension: video.Extension, |
|||
} |
|||
|
|||
err := viewTmpl.Execute(writer, data) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
}) |
|||
http.Handle("/videos/", http.StripPrefix("/videos/", http.FileServer(http.Dir(path)))) |
|||
|
|||
err = http.ListenAndServe(":8000", nil) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
os.Exit(1) |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>ytdlp-viewer | {{.FileCount}}</title> |
|||
</head> |
|||
<body> |
|||
<center> |
|||
<h1>yt-dlp Viewer</h1> |
|||
<h2>{{.FileCount}} files in this archive</h2> |
|||
|
|||
<form action="/search" method="get"> |
|||
<label> |
|||
Search term: |
|||
<input type="text" name="term" required> |
|||
</label> |
|||
</form> |
|||
|
|||
{{range .Files}} |
|||
<a href="/view?id={{.Id}}">{{.Id}} - {{.Title}}</a><br /> |
|||
{{end}} |
|||
</center> |
|||
</body> |
|||
</html> |
@ -0,0 +1,19 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Search result | {{.SearchTerm}}</title> |
|||
</head> |
|||
<body> |
|||
<form action="/search" method="get"> |
|||
<label> |
|||
Search term: |
|||
<input type="text" name="term" value="{{.SearchTerm}}" required> |
|||
</label> |
|||
</form> |
|||
|
|||
{{range .Results}} |
|||
<a href="/view?id={{.Id}}">{{.Id}} - {{.Title}}</a><br /> |
|||
{{end}} |
|||
</body> |
|||
</html> |
@ -0,0 +1,30 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>{{.Title}} | {{.Id}}</title> |
|||
<link href="https://vjs.zencdn.net/7.15.4/video-js.css" rel="stylesheet" /> |
|||
<link href="https://cdn.jsdelivr.net/npm/videojs-seek-buttons/dist/videojs-seek-buttons.css" rel="stylesheet"> |
|||
</head> |
|||
<body> |
|||
<video class="video-js vjs-big-play-centered" id="player" height="480" width="720" controls autoplay data-setup="{}"> |
|||
<source src="/videos/{{.Filename}}"> |
|||
</video> |
|||
<h1>{{.Title}}</h1> |
|||
<p>Filetype: {{.Extension}}</p> |
|||
<p>Youtube ID: {{.Id}}</p> |
|||
<p>Filename: {{.Filename}}</p> |
|||
<a href="/videos/{{.Filename}}">direct video link</a> |
|||
|
|||
<script src="https://vjs.zencdn.net/7.15.4/video.min.js"></script> |
|||
<script src="https://cdn.jsdelivr.net/npm/videojs-seek-buttons/dist/videojs-seek-buttons.min.js"></script> |
|||
<script> |
|||
var player = videojs('player'); |
|||
|
|||
player.seekButtons({ |
|||
forward: 30, |
|||
back: 10 |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
Loading…
Reference in new issue