Browse Source

Initial framework

master
PrivateGER 3 years ago
commit
a72404a977
No known key found for this signature in database GPG Key ID: B9918E382ED08059
  1. 108
      DirectoryIndexer.go
  2. 7
      go.mod
  3. 158
      main.go
  4. 24
      templates/index.html
  5. 19
      templates/search.html
  6. 30
      templates/view.html

108
DirectoryIndexer.go

@ -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
}

7
go.mod

@ -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

158
main.go

@ -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)
}
}

24
templates/index.html

@ -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>

19
templates/search.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>

30
templates/view.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…
Cancel
Save