first commit
This commit is contained in:
parent
d932fcb85a
commit
f339d25bf5
5
config.toml
Normal file
5
config.toml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
BaseDir = "/mnt/media"
|
||||||
|
Format = "mkv"
|
||||||
|
|
||||||
|
[N_m3u8DLRE]
|
||||||
|
Path = "nre"
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module DRMDTool
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require github.com/BurntSushi/toml v1.4.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
335
main.go
Normal file
335
main.go
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
MPD string
|
||||||
|
Keys string
|
||||||
|
Filename string
|
||||||
|
Description string
|
||||||
|
Subtitles string
|
||||||
|
Poster string
|
||||||
|
Metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Items struct {
|
||||||
|
Items []Item
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Title string
|
||||||
|
Type string
|
||||||
|
Season string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
BaseDir string
|
||||||
|
Format string
|
||||||
|
N_m3u8DLRE struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
var progressMutex sync.Mutex
|
||||||
|
var progress = make(map[string]*ProgressInfo)
|
||||||
|
|
||||||
|
const uploadDir = "uploads"
|
||||||
|
|
||||||
|
type ProgressInfo struct {
|
||||||
|
Percentage float64
|
||||||
|
CurrentFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
var templates *template.Template
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||||
|
fmt.Printf("Error creating upload directory: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
templates = template.Must(template.ParseGlob("templates/*"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
configFile, err := os.Open("config.toml")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error opening config file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer configFile.Close()
|
||||||
|
|
||||||
|
byteValue, _ := io.ReadAll(configFile)
|
||||||
|
|
||||||
|
if _, err := toml.Decode(string(byteValue), &config); err != nil {
|
||||||
|
fmt.Println("Error decoding config file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.N_m3u8DLRE.Path == "" {
|
||||||
|
fmt.Println("Error: N_m3u8DL-RE path is not specified in the config file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inputFile := flag.String("f", "", "Path to the input JSON file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *inputFile == "" {
|
||||||
|
startWebServer()
|
||||||
|
} else {
|
||||||
|
processInputFile(*inputFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startWebServer() {
|
||||||
|
http.HandleFunc("/", handleRoot)
|
||||||
|
http.HandleFunc("/upload", handleUpload)
|
||||||
|
http.HandleFunc("/progress", handleProgress)
|
||||||
|
|
||||||
|
fmt.Println("Starting web server on http://0.0.0.0:8080")
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progressMutex.Lock()
|
||||||
|
jobs := make(map[string]*ProgressInfo)
|
||||||
|
for k, v := range progress {
|
||||||
|
jobs[k] = v
|
||||||
|
}
|
||||||
|
progressMutex.Unlock()
|
||||||
|
|
||||||
|
err := templates.ExecuteTemplate(w, "index", struct{ Jobs map[string]*ProgressInfo }{jobs})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
file, header, err := r.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%d_%s", time.Now().UnixNano(), header.Filename)
|
||||||
|
filepath := filepath.Join(uploadDir, filename)
|
||||||
|
|
||||||
|
newFile, err := os.Create(filepath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer newFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(newFile, file)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := processInputFile(filepath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error processing file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(filepath)
|
||||||
|
}()
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/progress?filename="+filename, http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleProgress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filename := r.URL.Query().Get("filename")
|
||||||
|
fmt.Printf("Handling progress request for filename: %s\n", filename)
|
||||||
|
|
||||||
|
if r.Header.Get("Accept") == "application/json" {
|
||||||
|
progressInfo := getProgress(filename)
|
||||||
|
fmt.Printf("Progress info for %s: %+v\n", filename, progressInfo)
|
||||||
|
|
||||||
|
if progressInfo == nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"error": "No progress information found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
err := json.NewEncoder(w).Encode(progressInfo)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error encoding progress info: %v\n", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := templates.ExecuteTemplate(w, "progress", struct{ Filename string }{filename})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processInputFile(inputFile string) error {
|
||||||
|
jsonFile, err := os.Open(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening file %s: %v", inputFile, err)
|
||||||
|
}
|
||||||
|
defer jsonFile.Close()
|
||||||
|
|
||||||
|
byteValue, err := io.ReadAll(jsonFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading file %s: %v", inputFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items Items
|
||||||
|
err = json.Unmarshal(byteValue, &items)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range items.Items {
|
||||||
|
updateProgress(filepath.Base(inputFile), float64(i)/float64(len(items.Items))*100, item.Filename)
|
||||||
|
err := downloadFile(item)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error downloading file: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateProgress(filepath.Base(inputFile), 100, "")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateProgress(filename string, value float64, currentFile string) {
|
||||||
|
progressMutex.Lock()
|
||||||
|
defer progressMutex.Unlock()
|
||||||
|
progress[filename] = &ProgressInfo{
|
||||||
|
Percentage: value,
|
||||||
|
CurrentFile: currentFile,
|
||||||
|
}
|
||||||
|
fmt.Printf("Progress updated for %s: %.2f%%, Current file: %s\n", filename, value, currentFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProgress(filename string) *ProgressInfo {
|
||||||
|
progressMutex.Lock()
|
||||||
|
defer progressMutex.Unlock()
|
||||||
|
return progress[filename]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeys(keys string) []string {
|
||||||
|
return strings.Split(keys, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMetadata(metadata string) Metadata {
|
||||||
|
parts := strings.Split(metadata, ";")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return Metadata{}
|
||||||
|
}
|
||||||
|
return Metadata{
|
||||||
|
Title: strings.TrimSpace(parts[0]),
|
||||||
|
Type: strings.TrimSpace(parts[1]),
|
||||||
|
Season: "S" + strings.TrimSpace(parts[2]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDownloadCommand(item Item, mpdPath string) string {
|
||||||
|
metadata := parseMetadata(item.Metadata)
|
||||||
|
keys := getKeys(item.Keys)
|
||||||
|
|
||||||
|
command := fmt.Sprintf("%s %s", config.N_m3u8DLRE.Path, mpdPath)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
if key != "" {
|
||||||
|
command += fmt.Sprintf(" --key %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command += " --auto-select"
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("\"%s\"", item.Filename)
|
||||||
|
command += fmt.Sprintf(" --save-name %s", filename)
|
||||||
|
|
||||||
|
command += fmt.Sprintf(" --mux-after-done format=%s", config.Format)
|
||||||
|
|
||||||
|
saveDir := config.BaseDir
|
||||||
|
if metadata.Type == "serie" {
|
||||||
|
saveDir = filepath.Join(saveDir, "Series", metadata.Title, metadata.Season)
|
||||||
|
} else {
|
||||||
|
saveDir = filepath.Join(saveDir, "Movies", metadata.Title)
|
||||||
|
}
|
||||||
|
command += fmt.Sprintf(" --save-dir \"%s\"", saveDir)
|
||||||
|
|
||||||
|
fmt.Println(command)
|
||||||
|
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFile(item Item) error {
|
||||||
|
fmt.Println("Downloading:", item.Filename)
|
||||||
|
|
||||||
|
mpdPath := item.MPD
|
||||||
|
if !isValidURL(item.MPD) {
|
||||||
|
|
||||||
|
decodedMPD, err := base64.StdEncoding.DecodeString(item.MPD)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error decoding base64 MPD: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile, err := os.CreateTemp("", "temp_mpd_*.mpd")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating temporary MPD file: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
if _, err := tempFile.Write(decodedMPD); err != nil {
|
||||||
|
return fmt.Errorf("error writing to temporary MPD file: %v", err)
|
||||||
|
}
|
||||||
|
if err := tempFile.Close(); err != nil {
|
||||||
|
return fmt.Errorf("error closing temporary MPD file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mpdPath = tempFile.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
command := getDownloadCommand(item, mpdPath)
|
||||||
|
|
||||||
|
cmd := exec.Command("bash", "-c", command)
|
||||||
|
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error executing download command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Download completed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidURL(toTest string) bool {
|
||||||
|
_, err := url.ParseRequestURI(toTest)
|
||||||
|
return err == nil
|
||||||
|
}
|
22
templates/index
Normal file
22
templates/index
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Simple Downloader</h1>
|
||||||
|
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" accept=".drmd">
|
||||||
|
<input type="submit" value="Upload and Process">
|
||||||
|
</form>
|
||||||
|
<h2>Currently Running Jobs</h2>
|
||||||
|
<ul>
|
||||||
|
{{range $filename, $info := .Jobs}}
|
||||||
|
<li>
|
||||||
|
<a href="/progress?filename={{$filename}}">{{$filename}}</a>:
|
||||||
|
{{printf "%.2f%%" $info.Percentage}}
|
||||||
|
(Current file: {{$info.CurrentFile}})
|
||||||
|
</li>
|
||||||
|
{{else}}
|
||||||
|
<li>No active jobs</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
27
templates/progress
Normal file
27
templates/progress
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Processing {{.Filename}}</h1>
|
||||||
|
<div id="progress">0%</div>
|
||||||
|
<div id="currentFile"></div>
|
||||||
|
<script>
|
||||||
|
function updateProgress() {
|
||||||
|
fetch('/progress?filename={{.Filename}}', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const progress = Math.round(data.Percentage);
|
||||||
|
document.getElementById('progress').innerText = progress + '%';
|
||||||
|
document.getElementById('currentFile').innerText = 'Current file: ' + (data.CurrentFile || 'None');
|
||||||
|
if (progress < 100) {
|
||||||
|
setTimeout(updateProgress, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateProgress();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user