فهرست منبع

Added working ffmpeg in agi module function

Toby Chui 1 سال پیش
والد
کامیت
bfc1f42464

+ 60 - 71
mod/agi/agi.ffmpeg.go

@@ -1,14 +1,16 @@
 package agi
 
 import (
+	"errors"
 	"fmt"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 
 	"github.com/robertkrimen/otto"
+	uuid "github.com/satori/go.uuid"
 	"imuslab.com/arozos/mod/agi/static"
+	"imuslab.com/arozos/mod/agi/static/ffmpegutil"
 	"imuslab.com/arozos/mod/utils"
 )
 
@@ -29,76 +31,6 @@ func (g *Gateway) FFmpegLibRegister() {
 	}
 }
 
-/*
-	FFmepg functions
-*/
-
-func ffmpeg_conv(input string, output string, compression int) error {
-	var cmd *exec.Cmd
-
-	switch {
-	case isVideo(input) && isVideo(output):
-		// Video to video with resolution compression
-		cmd = exec.Command("ffmpeg", "-i", input, "-vf", fmt.Sprintf("scale=-1:%d", compression), output)
-
-	case (isAudio(input) || isVideo(input)) && isAudio(output):
-		// Audio or video to audio with bitrate compression
-		cmd = exec.Command("ffmpeg", "-i", input, "-b:a", fmt.Sprintf("%dk", compression), output)
-
-	case isImage(output):
-		// Resize image with width compression
-		cmd = exec.Command("ffmpeg", "-i", input, "-vf", fmt.Sprintf("scale=%d:-1", compression), output)
-
-	default:
-		// Handle other cases or leave it for the user to implement
-		return fmt.Errorf("unsupported conversion: %s to %s", input, output)
-	}
-
-	// Set the output of the command to os.Stdout so you can see it in your console
-	cmd.Stdout = os.Stdout
-
-	// Set the output of the command to os.Stderr so you can see any errors
-	cmd.Stderr = os.Stderr
-
-	// Run the command
-	err := cmd.Run()
-	if err != nil {
-		return fmt.Errorf("error running ffmpeg command: %v", err)
-	}
-
-	return nil
-}
-
-// Helper functions to check file types
-func isVideo(filename string) bool {
-	videoFormats := []string{
-		".mp4", ".mkv", ".avi", ".mov", ".flv", ".webm",
-	}
-	return utils.StringInArray(videoFormats, filepath.Ext(filename))
-}
-
-func isAudio(filename string) bool {
-	audioFormats := []string{
-		".mp3", ".wav", ".aac", ".ogg", ".flac",
-	}
-	return utils.StringInArray(audioFormats, filepath.Ext(filename))
-}
-
-func isImage(filename string) bool {
-	imageFormats := []string{
-		".jpg", ".png", ".gif", ".bmp", ".tiff", ".webp",
-	}
-	return utils.StringInArray(imageFormats, filepath.Ext(filename))
-}
-
-func main() {
-	// Example usage
-	err := ffmpeg_conv("input.mp4", "output.mp4", 720)
-	if err != nil {
-		fmt.Println("Error:", err)
-	}
-}
-
 func (g *Gateway) injectFFmpegFunctions(payload *static.AgiLibInjectionPayload) {
 	vm := payload.VM
 	u := payload.User
@@ -126,6 +58,12 @@ func (g *Gateway) injectFFmpegFunctions(payload *static.AgiLibInjectionPayload)
 			return otto.FalseValue()
 		}
 
+		compression, err := call.Argument(2).ToInteger()
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
 		//Rewrite the vpath if it is relative
 		vinput = static.RelativeVpathRewrite(scriptFsh, vinput, vm, u)
 		voutput = static.RelativeVpathRewrite(scriptFsh, voutput, vm, u)
@@ -155,6 +93,57 @@ func (g *Gateway) injectFFmpegFunctions(payload *static.AgiLibInjectionPayload)
 
 		fmt.Println(rinput, routput, bufferedFilepath)
 
+		//Convert it to target format using ffmpeg
+		outputTmpFilename := uuid.NewV4().String() + filepath.Ext(routput)
+		outputBufferPath := filepath.Join(filepath.Dir(bufferedFilepath), outputTmpFilename)
+		err = ffmpegutil.FFmpeg_conv(bufferedFilepath, outputBufferPath, int(compression))
+		if err != nil {
+			//FFmpeg conversion failed
+			g.RaiseError(err)
+
+			//Delete the buffered file
+			os.Remove(bufferedFilepath)
+			return otto.FalseValue()
+		}
+
+		if !utils.FileExists(outputBufferPath) {
+			//Fallback check, to see if the output file actually exists
+			g.RaiseError(errors.New("output file not found. Assume ffmpeg conversion failed"))
+			//Delete the buffered file
+			os.Remove(bufferedFilepath)
+			return otto.FalseValue()
+		}
+
+		//Conversion completed
+
+		//Delete the buffered file
+		os.Remove(bufferedFilepath)
+
+		//Upload the converted file to target disk
+		src, err := os.OpenFile(outputBufferPath, os.O_RDONLY, 0755)
+		if err != nil {
+			g.RaiseError(err)
+			//Delete the output buffer if failed
+			os.Remove(outputBufferPath)
+			return otto.FalseValue()
+		}
+		defer src.Close()
+
+		err = fsh.FileSystemAbstraction.WriteStream(routput, src, 0775)
+		if err != nil {
+			g.RaiseError(err)
+			//Delete the output buffer if failed
+			os.Remove(outputBufferPath)
+			return otto.FalseValue()
+		}
+
+		//Upload completed. Remove the remaining buffer file
+		os.Remove(outputBufferPath)
 		return otto.TrueValue()
 	})
+
+	vm.Run(`
+		var ffmpeg = {};
+		ffmpeg.convert = _ffmpeg_conv;
+	`)
 }

+ 98 - 0
mod/agi/static/ffmpegutil/ffmpegutil.go

@@ -0,0 +1,98 @@
+package ffmpegutil
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"imuslab.com/arozos/mod/utils"
+)
+
+/*
+	FFmepg Utilities function
+	for agi.ffmpeg.go
+
+
+*/
+
+/*
+ffmpeg_conv support input of a limited video, audio and image formats
+Compression value can be set if compression / resize is needed.
+Different conversion type have different meaning for compression values
+Video -> Video | compression means resolution in scale, e.g. 720 = 720p
+Video / Audio -> Audio | compression means bitrate, e.g. 128 = 128kbps
+Image -> Image | compression means final width of compression e.g. 1024 * 2048 with compression value
+set to 512, then the output will be 512 * 1024
+
+Set compression to 0 if resizing / compression is not required
+*/
+func FFmpeg_conv(input string, output string, compression int) error {
+	var cmd *exec.Cmd
+
+	switch {
+	case isVideo(input) && isVideo(output):
+		// Video to video with resolution compression
+		if compression == 0 {
+			cmd = exec.Command("ffmpeg", "-i", input, output)
+		} else {
+			cmd = exec.Command("ffmpeg", "-i", input, "-vf", fmt.Sprintf("scale=-1:%d", compression), output)
+		}
+
+	case (isAudio(input) || isVideo(input)) && isAudio(output):
+		// Audio or video to audio with bitrate compression
+		if compression == 0 {
+			cmd = exec.Command("ffmpeg", "-i", input, output)
+		} else {
+			cmd = exec.Command("ffmpeg", "-i", input, "-b:a", fmt.Sprintf("%dk", compression), output)
+		}
+
+	case (isImage(input) && isImage(output)) || (isVideo(input) && filepath.Ext(output) == ".gif"):
+		// Resize image with width compression
+		if compression == 0 {
+			cmd = exec.Command("ffmpeg", "-i", input, output)
+		} else {
+			cmd = exec.Command("ffmpeg", "-i", input, "-vf", fmt.Sprintf("scale=%d:-1", compression), output)
+		}
+
+	default:
+		// Handle other cases or leave it for the user to implement
+		return fmt.Errorf("unsupported conversion: %s to %s", input, output)
+	}
+
+	// Set the output of the command to os.Stdout so you can see it in your console
+	cmd.Stdout = os.Stdout
+
+	// Set the output of the command to os.Stderr so you can see any errors
+	cmd.Stderr = os.Stderr
+
+	// Run the command
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("error running ffmpeg command: %v", err)
+	}
+
+	return nil
+}
+
+// Helper functions to check file types
+func isVideo(filename string) bool {
+	videoFormats := []string{
+		".mp4", ".mkv", ".avi", ".mov", ".flv", ".webm",
+	}
+	return utils.StringInArray(videoFormats, filepath.Ext(filename))
+}
+
+func isAudio(filename string) bool {
+	audioFormats := []string{
+		".mp3", ".wav", ".aac", ".ogg", ".flac",
+	}
+	return utils.StringInArray(audioFormats, filepath.Ext(filename))
+}
+
+func isImage(filename string) bool {
+	imageFormats := []string{
+		".jpg", ".png", ".gif", ".bmp", ".tiff", ".webp",
+	}
+	return utils.StringInArray(imageFormats, filepath.Ext(filename))
+}

+ 5 - 8
mod/filesystem/filesystem.go

@@ -339,13 +339,8 @@ func (fsh *FileSystemHandler) IsNetworkDrive() bool {
 	return arozfs.IsNetworkDrive(fsh.Filesystem)
 }
 
-// Buffer a remote file to local tmp folder and return the tmp location for further processing
+// Buffer a file to local tmp folder and return the tmp location for further processing
 func (fsh *FileSystemHandler) BufferRemoteToLocal(rpath string) (string, error) {
-	//Check if the target fsh is actually a network drive
-	if !fsh.IsNetworkDrive() {
-		return "", errors.New("target file system handler is not a network drive")
-	}
-
 	//Check if the remote file exists
 	fsa := fsh.FileSystemAbstraction
 	if !fsa.FileExists(rpath) {
@@ -367,18 +362,20 @@ func (fsh *FileSystemHandler) BufferRemoteToLocal(rpath string) (string, error)
 
 	//Generate a filename for the buffer file
 	tmpFilename := uuid.NewV4().String()
-	tmpFilepath := arozfs.ToSlash(filepath.Join(tmpdir, tmpFilename))
+	tmpFilepath := arozfs.ToSlash(filepath.Join(tmpdir, tmpFilename+filepath.Ext(rpath)))
 
 	//Copy the file from remote location to local
 	src, err := fsh.FileSystemAbstraction.ReadStream(rpath)
 	if err != nil {
 		return "", err
 	}
+	defer src.Close()
 
-	dest, err := os.OpenFile(tmpFilepath, os.O_WRONLY, 0777)
+	dest, err := os.OpenFile(tmpFilepath, os.O_CREATE|os.O_WRONLY, 0777)
 	if err != nil {
 		return "", errors.New("unable to write to buffer location: " + err.Error())
 	}
+	defer dest.Close()
 
 	_, err = io.Copy(dest, src)
 	if err != nil {

+ 49 - 0
web/FFmpeg Factory/backend/ffmpeg.js

@@ -0,0 +1,49 @@
+/*
+    FFmpeg.js
+
+    ArozOS FFMPEG FACTORY conversion backend
+    This script is designed to do server side conversion only.
+    For the client-side conversion function, see
+    ffmpeg.wasm and the related files for more information.
+
+    Required parameters
+    INPUT (input filepath)
+    OUTPUT (output filepath)
+    COMPRESS (optional, output compression)
+*/
+
+requirelib("filelib");
+requirelib("ffmpeg");
+
+function main(){
+    //Check if the input and output are valid
+    if (typeof(INPUT) == "undefined" || typeof(OUTPUT) == "undefined"){
+        //Invalid input or output
+        sendJSONResp(JSON.stringify({
+            "error":"invalid INTPUT or OUTPUT given"
+        }));
+        return;
+    }
+
+    //Check if input exists
+    if (!filelib.fileExists(INPUT)){
+        sendJSONResp(JSON.stringify({
+            "error":"INTPUT file not exists"
+        }));
+        return;
+    }
+
+    //Ok. Proceed with conversion
+    var succ = ffmpeg.convert(INPUT, OUTPUT);
+    if (succ){
+        sendJSONResp(JSON.stringify("ok"));
+    }else{
+        sendJSONResp(JSON.stringify({
+            "error":"ffmpeg reports a conversion error"
+        }));
+    }
+    
+}
+
+//Run the main conversion logic
+main();

+ 18 - 2
web/FFmpeg Factory/index.html

@@ -3,12 +3,28 @@
     <meta name="apple-mobile-web-app-capable" content="yes" />
     <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
     <link rel="stylesheet" href="../script/semantic/semantic.css">
-    <script src="jquery.min.js"></script>
+    <script src="../script/jquery.min.js"></script>
     <script src="../script/semantic/semantic.js"></script>
     <script src="../script/ao_module.js"></script>
     <title>FFmpeg Factory</title>
 </head>
 <body>
-
+    <button onclick="runffmpeg()">Run Test</button>
+    <script>
+        function runffmpeg(){
+            ao_module_agirun("FFmpeg Factory/backend/ffmpeg.js",
+                {
+                    "INPUT": "user:/Desktop/test.flac",
+                    "OUTPUT":"user:/Desktop/out.mp3",
+                    "COMPRESS": 128 //128k
+                }, function(data){
+                    console.log(data);
+                    alert(data);
+                }, function(){
+                    alert("Conversion failed")
+                });
+            }
+        
+    </script>
 </body>
 </html> 

+ 1 - 0
web/SystemAO/info/gomod-license.csv

@@ -75,6 +75,7 @@ gopkg.in/sourcemap.v1,https://github.com/go-sourcemap/sourcemap/blob/v1.0.5/LICE
 gopkg.in/warnings.v0,https://github.com/go-warnings/warnings/blob/v0.1.2/LICENSE,BSD-2-Clause
 imuslab.com/arozos,Unknown,Unknown
 imuslab.com/arozos/mod/agi,Unknown,Unknown
+imuslab.com/arozos/mod/agi/static,Unknown,Unknown
 imuslab.com/arozos/mod/apt,Unknown,Unknown
 imuslab.com/arozos/mod/auth,Unknown,Unknown
 imuslab.com/arozos/mod/auth/accesscontrol,Unknown,Unknown