Add movie fusion packages with locked media, ZIP export, and batch forge UI.
Paired video mode encrypts movies, uses runner-only lock hints, bundles README plus artifacts per title, and supports batch forging with progress.
This commit is contained in:
88
fusion/media_crypto.go
Normal file
88
fusion/media_crypto.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const mediaLockMagic = "CMVD"
|
||||
|
||||
func decryptMediaFile(encPath, keyB64, playName string) (string, func(), error) {
|
||||
key, err := base64.StdEncoding.DecodeString(keyB64)
|
||||
if err != nil || len(key) == 0 {
|
||||
return "", nil, fmt.Errorf("invalid media key")
|
||||
}
|
||||
in, err := os.Open(encPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
head := make([]byte, len(mediaLockMagic))
|
||||
if _, err := io.ReadFull(in, head); err != nil || string(head) != mediaLockMagic {
|
||||
return "", nil, fmt.Errorf("not a locked media file")
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "cm-fusion-play-*")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
outName := filepath.Base(playName)
|
||||
if outName == "" || outName == "." {
|
||||
outName = "movie.mkv"
|
||||
}
|
||||
outPath := filepath.Join(dir, outName)
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 256*1024)
|
||||
ki := 0
|
||||
for {
|
||||
n, readErr := in.Read(buf)
|
||||
if n > 0 {
|
||||
plain := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
plain[i] = buf[i] ^ key[ki%len(key)]
|
||||
ki++
|
||||
}
|
||||
if _, err := out.Write(plain); err != nil {
|
||||
out.Close()
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
if readErr != nil {
|
||||
out.Close()
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, readErr
|
||||
}
|
||||
}
|
||||
out.Close()
|
||||
return outPath, func() { _ = os.RemoveAll(dir) }, nil
|
||||
}
|
||||
|
||||
func encryptedMediaBesideRunner(mediaEncFile, keyB64, playName string) (string, func(), error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
name := strings.TrimSpace(mediaEncFile)
|
||||
if name == "" {
|
||||
return "", nil, fmt.Errorf("missing encrypted media name")
|
||||
}
|
||||
encPath := filepath.Join(filepath.Dir(exe), filepath.Base(name))
|
||||
if st, err := os.Stat(encPath); err != nil || st.IsDir() {
|
||||
return "", nil, fmt.Errorf("encrypted media not found")
|
||||
}
|
||||
return decryptMediaFile(encPath, keyB64, playName)
|
||||
}
|
||||
Reference in New Issue
Block a user