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"
@ -36,10 +37,4 @@ To process a file directly from the command line:
./drmdtool -f /path/to/file.drmd ./drmdtool -f /path/to/file.drmd
``` ```
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,53 +52,101 @@ 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
} }
defer file.Close()
tempFile, err := os.CreateTemp(uploadDir, header.Filename) files := r.MultipartForm.File["files"]
if err != nil { if len(files) == 0 {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, "No files uploaded", http.StatusBadRequest)
return
}
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
tempFilename := filepath.Base(tempFile.Name()) uploadedFiles := []string{}
_, err = parseInputFile(tempFile.Name()) for _, fileHeader := range files {
if err != nil { file, err := fileHeader.Open()
http.Error(w, err.Error(), http.StatusInternalServerError) if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
tempFile, err := os.CreateTemp(uploadDir, fileHeader.Filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
uploadedFiles = append(uploadedFiles, filepath.Base(tempFile.Name()))
_, err = parseInputFile(tempFile.Name())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
validFiles := []string{}
for _, file := range uploadedFiles {
if file != "" {
validFiles = append(validFiles, file)
}
}
if len(validFiles) == 0 {
http.Error(w, "No valid files were uploaded", http.StatusBadRequest)
return return
} }
http.Redirect(w, r, "/select?filename="+tempFilename, http.StatusSeeOther) http.Redirect(w, r, "/select?files="+url.QueryEscape(strings.Join(validFiles, ",")), http.StatusSeeOther)
} }
func handleSelect(w http.ResponseWriter, r *http.Request) { func handleSelect(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("filename") filesParam := r.URL.Query().Get("files")
items, err := parseInputFile(filepath.Join(uploadDir, filename)) filenames := strings.Split(filesParam, ",")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) 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 return
} }
groupedItems := groupItemsBySeason(items) err := templates.ExecuteTemplate(w, "select", struct {
Filenames string
err = templates.ExecuteTemplate(w, "select", struct { AllItems map[string]map[string][]Item
Filename string
Items 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)
items, err := parseInputFile(filepath.Join(uploadDir, filename))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
filteredItems := filterSelectedItems(items, selectedItems) selectedItems := r.Form["items"]
if len(selectedItems) == 0 {
http.Error(w, "No items selected", http.StatusBadRequest)
return
}
go func() { itemsByFile := make(map[string][]string)
err := processItems(filename, filteredItems) 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 {
fmt.Printf("Error processing file: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
os.Remove(filepath.Join(uploadDir, filename)) selectedItems := filterSelectedItems(allItems, items)
}() go processItems(filename, selectedItems)
}
http.Redirect(w, r, "/progress?filename="+filename, http.StatusSeeOther) http.Redirect(w, r, "/", 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,22 +55,25 @@
<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}}
<div class="season"> <h2>{{$filename}}</h2>
<div class="season-title"> {{range $season, $items := $fileItems}}
<input type="checkbox" class="season-checkbox" id="season-{{$season}}" checked onchange="toggleSeason('{{$season}}')"> <div class="season">
<label for="season-{{$season}}">{{$season}}</label> <div class="season-title">
</div> <input type="checkbox" class="season-checkbox" id="season-{{$filename}}-{{$season}}" checked onchange="toggleSeason('{{$filename}}-{{$season}}')">
{{range $item := $items}} <label for="season-{{$filename}}-{{$season}}">{{$season}}</label>
<div class="item">
<label>
<input type="checkbox" name="items" value="{{$item.Filename}}" checked class="episode-{{$season}}">
{{$item.Filename}}
</label>
</div> </div>
{{end}} {{range $item := $items}}
</div> <div class="item">
<label>
<input type="checkbox" name="items" value="{{$filename}}:{{$item.Filename}}" checked class="episode-{{$filename}}-{{$season}}">
{{$item.Filename}}
</label>
</div>
{{end}}
</div>
{{end}}
{{end}} {{end}}
<div> <div>
<button type="button" onclick="selectAll(true)">Select All</button> <button type="button" onclick="selectAll(true)">Select All</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)
} }
} }
} }