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