Flash -> ByteArray -> AMFPHP -> Invalid Image? - actionscript-3

Im loading images into Flash and using JPGEncoder to encode the image to a ByteArray and send this to AMF PHP which writes out the bytearray to a file. This all appears to work correctly and I can download the resulting file in Photoshop CS4 absolutely fine. When i try to open it from the desktop or open it back in Flash it doesnt work... Picasa my default image browser says "Invalid"
Here is the code i use to write the bytearray to a file -
$jpg = $GLOBALS["HTTP_RAW_POST_DATA"];
file_put_contents($filename, $jpg);
That's it ... I use the NetConnection class to connect and call the service, do I need to say Im sending jpg data? I assumed that JPGEncoder took care of that. How can I validate the bytearray before writing the file? Do I need to set MIME type or something .. excuse the slightly noob questions, a little knowledge can be a dangerous thing.
Thanks
--------------------------------------- PART II ------------------------------------------
Here is some code -
1) load the image into Flash player
item.load();
function _onImageDataLoaded(evt:Event):void {
var tmpFileRef:FileReference=FileReference(evt.target);
image_loader=new Loader ;
image_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _onImageLoaded);
image_loader.loadBytes(tmpFileRef.data);
}
function _onImageLoaded(evt:Event):void {
bitmap=Bitmap(evt.target.content);
bitmap.smoothing=true;
if (bitmap.width>MAX_WIDTH||bitmap.height>MAX_HEIGHT) {
resizeBitmap(bitmap);
}
uploadResizedImage(bitmap);
}
function resizeBitmap(target:Bitmap):void {
if (target.height>target.width) {
target.width=MAX_WIDTH;
target.scaleY=target.scaleX;
} else if (target.width >= target.height) {
target.height=MAX_HEIGHT;
target.scaleX=target.scaleY;
}
}
function uploadResizedImage(target:Bitmap):void {
var _bmd:BitmapData=new BitmapData(target.width,target.height);
_bmd.draw(target, new Matrix(target.scaleX, 0, 0, target.scaleY));
var encoded_jpg:JPGEncoder=new JPGEncoder(90);
var jpg_binary:ByteArray=encoded_jpg.encode(_bmd);
_uploadService=new NetConnection();
_uploadService.objectEncoding=ObjectEncoding.AMF3
_uploadService.connect("http://.../amfphp/gateway.php");
_uploadService.call("UploadService.receiveByteArray",new Responder(success, error), jpg_binary, currentImageFilename);
}
Many thanks for you help

can you check the endian type of the bytearray? maybe it is defaulted to big endian

Your problem is in your PHP service. In AMFPHP the POST data is abstracted, so what you need in your AMFPHP UploadService script is a function that accepts the two input arguments in your _uploadService.call --jpg_binary and currentImageFilename-- like this:
<?php
class UploadService {
function receiveByteArray( $ba, $filename ) {
$result = file_put_contents($filename, $ba->data);
if ( $result == FALSE ) {
trigger_error( "File save failed" );
}
}
}
?>

var dataToBeSent:ByteArray =jpgEncoder.encode(theBitmapData);
var url:String = "upload.php";
var urlReq:URLRequest = new URLRequest(url);
urlReq.data = dataToBeSent;
urlReq.method = URLRequestMethod.POST;
urlReq.contentType = "application/octet-stream";
var urlLoader:URLLoader = new URLLoader();
urlLoader.load(urlReq);
Please mind the contentType line
PHP
$fp = fopen( $fname, "wb" );
fwrite( $fp, $GLOBALS[ 'HTTP_RAW_POST_DATA' ] );
fclose( $fp );
For me it works perfectly!
Oliver

Related

IO_Error opening file using Adobe Air App on a Mac

I have an Adobe Air Application that would open an csv file on Mac. I am getting an error which I am not getting with the same app for Windows, so I am thinking something about file locations, etc is amiss. Here is the code:
var file: File = File.applicationStorageDirectory;
file = file.resolvePath("A&P plans");
file.addEventListener(FileListEvent.SELECT_MULTIPLE, filesSelected);
function filesSelected(event: FileListEvent): void {
//trace(event.files.length);
fileList = new Array();
fileNames = new Array();
for (var i: uint = 0; i < event.files.length; i++) {
fileList.push(event.files[i].nativePath);
trace("name of file loaded is ", event.files[i].name, "where :", event.files[i].nativePath);
}
}
var urlRequest: URLRequest = new URLRequest(fileList[0]);
function openCSVFile():void{
csv = new CSV(urlRequest);
csv.addEventListener(Event.COMPLETE,onComplete);
csv.addEventListener(IOErrorEvent.IO_ERROR, onErrorOpening);
function onComplete(event:Event):void{
trace("file open successful");
}
function onErrorOpening(event:IOErrorEvent):void{
trace ("error opening file");
}
}
The URLRequest trace shows the location where it should be, so the app knows where to look, and it does find the file. Here is the result of the trace :/Applications/myApp.app/Contents/Resources/A&P plans/majors/NationalLeague.csv. Yet, instead of the completeEvent showing the trace, the errorEvent is inovked. Any ideas where to look for the issue? The file does not have any weird characters in its name or anything. Tracing the error shows the following: Error #2032: Stream Error. Thanks
Quite possibly the ampersand and the space in 'A&P plans' might be troublesome.
Try removing/changing those.

ActionScript 3 FFMPEG losing Metadata of video

I am converting video files to the .flv format using FFMPEG so that I can use LoaderMax (GreenSocks) to play the video files. The issue is that when the video is converted with FFMPEG the metadata is lost so I cannot later on with LoaderMax get the duration or current play time with the code below.
video.getTime();
video.duration();
I could get the duration of the video before converting it with FFMPEG easily enough but this doesn't solve the issue of being able to get the current play time. My goal is to allow the user to click on the seek bar and jump to any point in the video which works but for obvious reasons I need to be able to show the current time and video length.
I'm attempting to now use FFMPEG with something called flvtool2 which should rebuild the metadata?
My code currently for this:
nativeProcessInfo = new NativeProcessStartupInfo();
nativeProcessInfo.executable = File.applicationDirectory.resolvePath(ffmpegPath); //path to ffmpeg (included in project files)
//nativeProcessInfo.executable = File.applicationDirectory.resolvePath(flvtool2Path); //path to flvtool2 (included in project files)
var processArgument:Vector.<String> = new Vector.<String>(); //holds command line arguments for converting video
processArgument.push("-i"); //filename
processArgument.push(filePath);
processArgument.push("-s"); //size
processArgument.push("640x480");
processArgument.push("-b:v"); //bitrate - video
processArgument.push("4800k");
processArgument.push("-b:a"); //bitrate -
processArgument.push("6400k");
processArgument.push("-ar"); //audio sampling frequency
processArgument.push("44100");
processArgument.push("-ac"); //audio channels
processArgument.push("2");
processArgument.push("-ab"); //audio bitrate frequency
processArgument.push("160k");
processArgument.push("-f"); //force
processArgument.push("flv");
processArgument.push("-");
/*processArgument.push("|");
processArgument.push("flvtool2");
processArgument.push("-U");
processArgument.push("stdin");
processArgument.push(filePath);*/
nativeProcessInfo.arguments = processArgument;
if (NativeProcess.isSupported) {
nativeProcess = new NativeProcess();
nativeProcess.start(nativeProcessInfo); //start video buffering
nativeProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, ProgressEventOutputHandler);
nativeProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, ProgressEventErrorHandler);
nativeProcess.addEventListener(NativeProcessExitEvent.EXIT, NativeProcessExitHandler);
nativeProcess.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, standardIOErrorHandler);
nativeProcess.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, standardIOErrorHandler);
} else {
trace("!NativeProcess.isSupported");
}
I've uploaded an example project to download which should help explain the problem. To use it you will need to point the ActionScript Properties to the location of Greensock to use LoaderMax and have a video somewhere on your computer to test with. The link is: http://www.prospectportal.co.uk/example.zip
Take this example of a working code to convert a video (an AVI in my case) to an FLV video file using ffmpeg via AIR's NativeProcess :
var loader:VideoLoader,
exe:File = File.applicationDirectory.resolvePath('ffmpeg.exe'),
video_in:File = File.applicationDirectory.resolvePath('video.avi'),
video_out:File = File.applicationDirectory.resolvePath('video.flv');
var args:Vector.<String> = new Vector.<String>();
args.push("-i", video_in.nativePath, "-b:v", "800k", "-ar", "44100", "-ab", "96k", "-f", "flv", video_out.nativePath);
var npsi:NativeProcessStartupInfo = new NativeProcessStartupInfo();
npsi.executable = exe;
npsi.arguments = args;
var process:NativeProcess = new NativeProcess();
process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onErrorData);
process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onIOError);
process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, onIOError);
process.addEventListener(NativeProcessExitEvent.EXIT, onExit);
process.start(npsi);
function onOutputData(event:ProgressEvent):void
{
trace("Got: ", process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable));
}
function onErrorData(event:ProgressEvent):void
{
trace("ERROR -", process.standardError.readUTFBytes(process.standardError.bytesAvailable));
}
function onExit(event:NativeProcessExitEvent):void
{
playFLV();
}
function onIOError(event:IOErrorEvent):void
{
trace(event.toString());
}
function playFLV()
{
loader = new VideoLoader(
video_out.nativePath,
{
container: this,
width: 400,
height: 300,
scaleMode: "proportionalInside",
bgColor: 0x000000,
autoPlay: true,
volume: 0.5
}
);
loader.addEventListener(LoaderEvent.COMPLETE, onVideoLoad);
loader.load();
}
function onVideoLoad(e:LoaderEvent): void {
trace(loader.duration); // gives for example : 67.238
loader.playVideo();
}
Hope that can help.

Zip a folder in as3 Adobe AIR

I have created an Air app that after user interaction will creat a folder with bmp's, xml and text doc's.
All works apart from making the final zip which needs to be automatic.
I have been looking for a solution to this but cant find it.
Perhaps I am just not seeing it, and if so, can someone show me please.
My original posting on this is here ---
zip many files with Air as3 flash
The closest thing I have found was this one --- zip a folder using fzip
But for some reason my coment was deleted which was --
I like this. It is the closest I have come to a working solution to my own problem. That said, I tested this and it works nicely as is. Can this script be moded to run without interaction??? I am in need of it for a program that I have written. ANY asistance is welcom........ apart from just pointing me to Adobe referance as it dose not have anything like what I need. (well that I can see or find)
So now I am re-asking the comunity.
For some reason it will work with manual selection and manual save-to, but not aotonomusly.
There must be a workround to this even if it requires another full page of script.
====================================================================
UPDATE:
For closing this off, I have finally got my solution.
You can find it here. "zip file contents have no data".
Hope that my problem can help someone in the future.
Try using the as3 commons zip library.
http://www.as3commons.org/as3-commons-zip/index.html
In order to do this you're going to need to load your directory, loop through all its contents and load each asset.
This code snippet includes a bulk loader to handle that for you.
warning
I pulled most of this code out of a project where I was doing something similar but I have not tested it as is. There may be some syntax errors!
private var zip:Zip;
zip = new Zip();
zip.addEventListener(IOErrorEvent.IO_ERROR, this.createNewZip); //creates a new zip
zip.addEventListener(Event.COMPLETE, handleZipLoaded); //loads the current zip, this is not shown here
zip.load(new URLRequest(File.applicationStorageDirectory.resolvePath("myZip.zip").url)); //path to your zip file
Method to create your new zip file
private function createNewZip(e:IOErrorEvent):void{
trace("no zip");
var stream:FileStream = new FileStream();
stream.open(File.applicationStorageDirectory.resolvePath("myZip.zip"), FileMode.WRITE);
zip.serialize(stream);
stream.close();
}
You can use this to add all items in a directory to your zip file.
private function addDirToZip():void{
var f:File = File.resolvePath("Your Dir");
//this will be called when your directory listing has loaded
f.addEventListener(FileListEvent.DIRECTORY_LISTING, handleDirLoaded);
//you can also get the dir listing inline and not use a listener
//doing it async will prevent ui lock
f.getDirectoryListingAsync();
}
Next your going to need to load all of the files
protected function handleDirLoaded(e:FileListEvent):void{
loadExternal = new Vector.<File>; //vector used to keep a handle on all files
e.target.removeEventListener(FileListEvent.DIRECTORY_LISTING, handleDirLoaded);
for(var i:int = 0 ; i < files.length ; i++){
var f:File = files[i] as File;
if(f.extension == "File Types you want"){ //you can do some file type checking here
loadExternal.push(f);
}
}
//start loading in the files
loadFile();
}
This will go through the loadExternal vector and load all files
private function loadFile():void{
currentFile = loadExternal.shift(); //returns the first item off the array
//load the file
var l:Loader = new Loader();
l.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoaded);
l.load(new URLRequest(currentFile.url));
}
Once each item is loaded you can store it for addition into the zip
private function handleLoaded(e:Event):void{
var l:Loader = e.target.loader as Loader;
l.contentLoaderInfo.removeEventListener(Event.COMPLETE, handleLoaded);
//storing everything in a dictionary
assets[currentFile.name] = l.content;
//if we still have items to load go and do it all again
if(loadExternal.length != 0){
loadFile();
} else {
//now all files are loaded so lets add them to the zip
addAssetsToZip();
}
}
This is where all the loaded files actually get put into the zip and it is saved
private funcion addAssetsToZip():void{
for(var fileName:String in assets){
var ba:ByteArray = new ByteArray(); //going to write this to the zip
//make an object that holds the data and filename
var data:Object = {};
data.name = fileName;
data.content = assets[fileName];
ba.writeObject(data);
//write this file to the zip
zip.addFile(key, ba, false);
}
//and finally save everything out
zip.close();
var stream:FileStream = new FileStream();
stream.open(File.applicationStorageDirectory.resolvePath("myZip.zip"), FileMode.WRITE);
zip.serialize(stream);
stream.close();
}

Upload file to server without using FileReference.browse()

I am trying to upload file to server with the FileReference class.
the file is mp3 that was encoded in runtime without saving him on the local hard drive.
Is it possible to fit in the mp3Encoder into the FileReference somehow?
Thanks
Assuming you have the mp3 as a ByteArray (not sure how else it would exist at runtime if you haven't saved it to disk), you can use the URLLoader class, like this:
var scriptRequest:URLRequest = new URLRequest("PATH_TO_YOUR_UPLOAD_SCRIPT");
var scriptLoader:URLLoader = new URLLoader();
var scriptVars:URLVariables = new URLVariables();
scriptLoader.addEventListener(Event.COMPLETE, handleLoadSuccessful);
scriptLoader.addEventListener(IOErrorEvent.IO_ERROR, handleLoadError);
scriptVars.var1 = MP3_AS_BYTE_ARRAY;
scriptRequest.method = URLRequestMethod.POST;
scriptRequest.data = scriptVars;
scriptLoader.load(scriptRequest);
function handleLoadSuccessful($evt:Event):void
{
trace("Message sent.");
}
function handleLoadError($evt:IOErrorEvent):void
{
trace("Message failed.");
}
Then on the server, you just need to save the stream to a file and give it the correct mp3 mime type.
There's a decent tutorial, from which this example is adapted, here: http://evolve.reintroducing.com/2008/01/27/as2-to-as3/as2-%E2%86%92-as3-loadvars-as3-equivalent/
And if you need to send your request as multipart/form-data (so it appears to be an attached file), there's a good example of this here: http://marstonstudio.com/2007/10/19/how-to-take-a-snapshot-of-a-flash-movie-and-automatically-upload-the-jpg-to-a-server-in-three-easy-steps/

getting a shared object to expire after browser session in FLASH?

I'm looking for a way to play flash animated content once ( even as the user navigates to different HTML pages with similar flash content) and expire after a set amount of time or after the browser is closed.
I'm aware I could use a shared object for this but can't seem to find info about how to clear them at browser session end.
I am open to using javascript or PHP to assist .
your help is appreciated -
thanks -MW
Instead of using a SharedObject, you could create two simple server-side services: one that maintains the session, and one that exposes the session through a generated XML file that your flash application could use.
The first service would set some session variables and should be called whenever the video is played. It could look like this:
<?php
// start-video.php
session_start();
$_SESSION['hasWatchedVideo'] = true;
$_SESSION['watchedVideoAt'] = time();
?>
The second service is the one that generates the XML response based on the session. It might look like this:
<?php
// flash-config.php
session_start();
// set expiration to 5 min
define(VIDEO_TIMEOUT, 300);
$playVideo = "true";
if($_SESSION['hasWatchedVideo']
&& (time() - $_SESSION['watchedVideoAt']) < VIDEO_TIMEOUT) {
$playVideo = "false";
}
header("Content-Type: text/xml");
echo "<config><playVideo>{$playVideo}</playVideo></config>";
?>
Then from your Flash application, you could do this:
/**
* Called whenever the app is loaded.
*/
protected function init():void {
var u:URLLoader = new URLLoader();
u.addEventListener(Event.COMPLETE, onComplete);
u.load(new URLRequest("http://example.com/flash-config.php"));
}
/**
* Determines whether or not the video should play based on the
* config service response.
*/
protected function onComplete(e:Event):void {
var x:XML = new XML(e.target.data);
if(x.playVideo == 'true') {
playVideo();
}
}
/**
* Should be called either when the video starts playing. I just tied
* it to a user click here.
*/
protected function playVideo():void {
// call the service to update the session
var u:URLLoader = new URLLoader();
u.load(new URLRequest("http://example.com/start-video.php"));
// ... play video code ...
}
I think this approach gives you a bit more flexibility than using a SharedObject. Hope that helps.
UPDATE:
You could use a session cookie in the browser as well. Basically set the expiry date to '0' and the cookie will expire when the user closes the browser. (Note: when I tested this in Firefox, closing the tab was not enough to kill the cookie. The entire browser had to be closed.)
You can use ExternalInterface, or a utility library like this. Using the library, you could have code like this in your flash application:
function playVideo():void {
if(!CookieUtil.getCookie('playvideo')) {
CookieUtil.setCookie('playvideo', 'true', 0);
// ... play video code ...
}
}
Whenever the user closes the browser, the cookie will be cleared. Next time they visit your site, the video will play again. Not sure if this is more inline with what you're look for, but hope it helps.
I modified your code a tad so it will be killed on sesion end ...
the PHP ...
<?php
// flash_php_session_cookie.php
$cookie= "false";
if (isset($_COOKIE["cookie"]))
$cookie= "true";
else
setcookie("cookie", "true", 0);
echo "<config><cookie>{$cookie}</cookie></config>";
?>
the FLASH ...
// the folowing functions handle call coresponding PHP files to handle cookies ...
// re-configure these to the location of the swf ...
var flashConfigURL:String ="flash_php_session_cookie.php";
//Called whenever the app is loaded ...
function initCookieFunc():void {
var u:URLLoader = new URLLoader();
u.addEventListener(Event.COMPLETE, onComplete);
u.load(new URLRequest(flashConfigURL));
}
// Determines whether or not the cookie exists / (assumes theres a text field named T on the stage) ...
function onComplete(e:Event):void {
var x:XML = new XML(e.target.data);
if (x.cookie == 'false') {
T.appendText("cookie doesn't exist yet");
} else {
// cookie exists ...
T.appendText("cookie exists");
}
}
initCookieFunc();
I am going to keep a loose version of the "TIMEOUT"
version as well.
It's great to have an answer to this
Thanks again RJ for the invaluable code
-MW
You'll just have to expire the SharedObject yourself. It's not complicated.
This way your .swf will be completely self contained, not relying on anything external which IMO is a good thing.
package {
import flash.display.Sprite;
import flash.net.SharedObject;
import flash.net.SharedObjectFlushStatus;
public class SharedObjectExample extends Sprite {
private var _so:SharedObject;
private var _now:Date;
private var _last_played:Number;
private static const EXPIRE_TIME:Number = 1000 * 60 * 60 * 24; // 24hrs in msec
public function SharedObjectExample() {
// create a new date for the current time to compare against
_now = new Date;
// create a new shared object
_so = SharedObject.getLocal("application-name", "/");
// try read from the shared object
if (_so.data.last_played) _last_played = _now;
// if no value is set we play the video and set the current time
if (!_last_played) {
// play video here
_last_played = _now.time;
// check if the "cookie" has expired and it's time to play again
} else if ( _now.time - _last_played > EXPIRE_TIME) {
// play video here
_last_played = _now.time;
} else {
// do nothing
}
// and finally, save
saveValue();
}
private function saveValue(event:MouseEvent):void {
// i've removed the code that asks the user for permission if the request for storage is denied
_so.data.last_played = _last_played;
var flushStatus:String = null;
try {
flushStatus = _so.flush(1000);
} catch (error:Error) {
trace("Could not write SharedObject to disk");
}
}
}
}