7 Commits

5 changed files with 140 additions and 78 deletions

View File

@ -9,6 +9,7 @@ Create a `config.toml` file in the same directory as the drmdtool executable:
```toml ```toml
BaseDir = "/path/to/save/downloads" BaseDir = "/path/to/save/downloads"
Format = "mkv" Format = "mkv"
TempBaseDir = "/tmp/nre"
[N_m3u8DL-RE] [N_m3u8DL-RE]
Path = "/path/to/N_m3u8DL-RE" Path = "/path/to/N_m3u8DL-RE"
@ -37,9 +38,3 @@ To process a file directly from the command line:
``` ```
This will download the file and save it in the base directory specified in the config. This will download the file and save it in the base directory specified in the config.
## TODO
- ~~Filename Sanitation (Makes new directory on /... oops)~~
- ~~GoPlay Fix~~
- Windows?
- Proper UI?

View File

@ -5,8 +5,10 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
type ProgressInfo struct { type ProgressInfo struct {
@ -50,14 +52,29 @@ func handleRoot(w http.ResponseWriter, r *http.Request) {
} }
func handleUpload(w http.ResponseWriter, r *http.Request) { func handleUpload(w http.ResponseWriter, r *http.Request) {
file, header, err := r.FormFile("file") err := r.ParseMultipartForm(32 << 20)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
files := r.MultipartForm.File["files"]
if len(files) == 0 {
http.Error(w, "No files uploaded", http.StatusBadRequest)
return
}
uploadedFiles := []string{}
for _, fileHeader := range files {
file, err := fileHeader.Open()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close() defer file.Close()
tempFile, err := os.CreateTemp(uploadDir, header.Filename) tempFile, err := os.CreateTemp(uploadDir, fileHeader.Filename)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -70,33 +87,66 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
return return
} }
tempFilename := filepath.Base(tempFile.Name()) uploadedFiles = append(uploadedFiles, filepath.Base(tempFile.Name()))
_, err = parseInputFile(tempFile.Name()) _, err = parseInputFile(tempFile.Name())
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
http.Redirect(w, r, "/select?filename="+tempFilename, http.StatusSeeOther)
} }
func handleSelect(w http.ResponseWriter, r *http.Request) { validFiles := []string{}
filename := r.URL.Query().Get("filename") for _, file := range uploadedFiles {
items, err := parseInputFile(filepath.Join(uploadDir, filename)) if file != "" {
if err != nil { validFiles = append(validFiles, file)
http.Error(w, err.Error(), http.StatusInternalServerError) }
}
if len(validFiles) == 0 {
http.Error(w, "No valid files were uploaded", http.StatusBadRequest)
return return
} }
groupedItems := groupItemsBySeason(items) http.Redirect(w, r, "/select?files="+url.QueryEscape(strings.Join(validFiles, ",")), http.StatusSeeOther)
}
err = templates.ExecuteTemplate(w, "select", struct { func handleSelect(w http.ResponseWriter, r *http.Request) {
Filename string filesParam := r.URL.Query().Get("files")
Items map[string][]Item filenames := strings.Split(filesParam, ",")
allItems := make(map[string]map[string][]Item)
for _, filename := range filenames {
if filename == "" {
continue
}
fullPath := filepath.Join(uploadDir, filename)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
continue
}
items, err := parseInputFile(fullPath)
if err != nil {
continue
}
groupedItems := groupItemsBySeason(items)
allItems[filename] = groupedItems
}
if len(allItems) == 0 {
http.Error(w, "No valid files were processed", http.StatusBadRequest)
return
}
err := templates.ExecuteTemplate(w, "select", struct {
Filenames string
AllItems map[string]map[string][]Item
}{ }{
Filename: filename, Filenames: filesParam,
Items: groupedItems, AllItems: allItems,
}) })
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -104,36 +154,47 @@ func handleSelect(w http.ResponseWriter, r *http.Request) {
} }
func handleProcess(w http.ResponseWriter, r *http.Request) { func handleProcess(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("filename") if err := r.ParseForm(); err != nil {
selectedItems := r.Form["items"] http.Error(w, err.Error(), http.StatusBadRequest)
return
}
items, err := parseInputFile(filepath.Join(uploadDir, filename)) selectedItems := r.Form["items"]
if len(selectedItems) == 0 {
http.Error(w, "No items selected", http.StatusBadRequest)
return
}
itemsByFile := make(map[string][]string)
for _, item := range selectedItems {
parts := strings.SplitN(item, ":", 2)
if len(parts) != 2 {
continue
}
filename, itemName := parts[0], parts[1]
itemsByFile[filename] = append(itemsByFile[filename], itemName)
}
for filename, items := range itemsByFile {
fullPath := filepath.Join(uploadDir, filename)
allItems, err := parseInputFile(fullPath)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
filteredItems := filterSelectedItems(items, selectedItems) selectedItems := filterSelectedItems(allItems, items)
go processItems(filename, selectedItems)
go func() {
err := processItems(filename, filteredItems)
if err != nil {
fmt.Printf("Error processing file: %v\n", err)
} }
os.Remove(filepath.Join(uploadDir, filename)) http.Redirect(w, r, "/", http.StatusSeeOther)
}()
http.Redirect(w, r, "/progress?filename="+filename, http.StatusSeeOther)
} }
func handleProgress(w http.ResponseWriter, r *http.Request) { func handleProgress(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("filename") filename := r.URL.Query().Get("filename")
fmt.Printf("Handling progress request for filename: %s\n", filename)
if r.Header.Get("Accept") == "application/json" { if r.Header.Get("Accept") == "application/json" {
progressInfo := getProgress(filename) progressInfo := getProgress(filename)
fmt.Printf("Progress info for %s: %+v\n", filename, progressInfo)
if progressInfo == nil { if progressInfo == nil {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@ -144,7 +205,6 @@ func handleProgress(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(progressInfo) err := json.NewEncoder(w).Encode(progressInfo)
if err != nil { if err != nil {
fmt.Printf("Error encoding progress info: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError) http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return return
} }
@ -206,7 +266,6 @@ func handleResume(w http.ResponseWriter, r *http.Request) {
jobInfo.Paused = false jobInfo.Paused = false
jobInfo.ResumeChan <- struct{}{} jobInfo.ResumeChan <- struct{}{}
// Update the progress information
progressMutex.Lock() progressMutex.Lock()
if progressInfo, ok := progress[filename]; ok { if progressInfo, ok := progress[filename]; ok {
progressInfo.Paused = false progressInfo.Paused = false
@ -291,5 +350,4 @@ func updateProgress(filename string, value float64, currentFile string) {
Paused: paused, Paused: paused,
} }
} }
fmt.Printf("Progress updated for %s: %.2f%%, Current file: %s, Paused: %v\n", filename, value, currentFile, paused)
} }

View File

@ -112,7 +112,7 @@
<body> <body>
<h1>Simple Downloader</h1> <h1>Simple Downloader</h1>
<form action="/upload" method="post" enctype="multipart/form-data"> <form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" accept=".drmd"> <input type="file" name="files" accept=".drmd" multiple>
<input type="submit" value="Upload and Process"> <input type="submit" value="Upload and Process">
</form> </form>
<h2>Currently Running Jobs</h2> <h2>Currently Running Jobs</h2>

View File

@ -55,23 +55,26 @@
<body> <body>
<h1>Select Items to Download</h1> <h1>Select Items to Download</h1>
<form action="/process" method="post"> <form action="/process" method="post">
<input type="hidden" name="filename" value="{{.Filename}}"> <input type="hidden" name="filenames" value="{{.Filenames}}">
{{range $season, $items := .Items}} {{range $filename, $fileItems := .AllItems}}
<h2>{{$filename}}</h2>
{{range $season, $items := $fileItems}}
<div class="season"> <div class="season">
<div class="season-title"> <div class="season-title">
<input type="checkbox" class="season-checkbox" id="season-{{$season}}" checked onchange="toggleSeason('{{$season}}')"> <input type="checkbox" class="season-checkbox" id="season-{{$filename}}-{{$season}}" checked onchange="toggleSeason('{{$filename}}-{{$season}}')">
<label for="season-{{$season}}">{{$season}}</label> <label for="season-{{$filename}}-{{$season}}">{{$season}}</label>
</div> </div>
{{range $item := $items}} {{range $item := $items}}
<div class="item"> <div class="item">
<label> <label>
<input type="checkbox" name="items" value="{{$item.Filename}}" checked class="episode-{{$season}}"> <input type="checkbox" name="items" value="{{$filename}}:{{$item.Filename}}" checked class="episode-{{$filename}}-{{$season}}">
{{$item.Filename}} {{$item.Filename}}
</label> </label>
</div> </div>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{end}}
<div> <div>
<button type="button" onclick="selectAll(true)">Select All</button> <button type="button" onclick="selectAll(true)">Select All</button>
<button type="button" onclick="selectAll(false)">Select None</button> <button type="button" onclick="selectAll(false)">Select None</button>

View File

@ -138,16 +138,24 @@ func findOrCreateSegmentTimeline(adaptationSet *etree.Element) *etree.Element {
return segmentTemplate.CreateElement("SegmentTimeline") return segmentTemplate.CreateElement("SegmentTimeline")
} }
func parseInputFile(inputFile string) ([]Item, error) { func parseInputFile(filename string) ([]Item, error) {
jsonFile, err := os.Open(inputFile) fileInfo, err := os.Stat(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error opening file %s: %v", inputFile, err) return nil, err
}
if fileInfo.IsDir() {
return nil, fmt.Errorf("%s is a directory", filename)
} }
defer jsonFile.Close()
byteValue, err := io.ReadAll(jsonFile) file, err := os.Open(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading file %s: %v", inputFile, err) return nil, err
}
defer file.Close()
byteValue, err := io.ReadAll(file)
if err != nil {
return nil, err
} }
byteValue = removeBOM(byteValue) byteValue = removeBOM(byteValue)
@ -155,7 +163,7 @@ func parseInputFile(inputFile string) ([]Item, error) {
var items Items var items Items
err = json.Unmarshal(byteValue, &items) err = json.Unmarshal(byteValue, &items)
if err != nil { if err != nil {
return nil, fmt.Errorf("error unmarshaling JSON: %v", err) return nil, err
} }
return items.Items, nil return items.Items, nil
@ -217,7 +225,6 @@ func processItems(filename string, items []Item) error {
select { select {
case <-jobInfo.ResumeChan: case <-jobInfo.ResumeChan:
jobInfo.Paused = false jobInfo.Paused = false
fmt.Printf("Resuming download for %s\n", filename)
case <-jobInfo.AbortChan: case <-jobInfo.AbortChan:
updateProgress(filename, 100, "Aborted") updateProgress(filename, 100, "Aborted")
return fmt.Errorf("download aborted") return fmt.Errorf("download aborted")
@ -231,7 +238,6 @@ func processItems(filename string, items []Item) error {
i-- i--
continue continue
} }
fmt.Printf("Error downloading file: %v\n", err)
} }
} }
} }