문제가 된다면 삭제하겠습니다-_-

전에 WireShark를 이용해서 알송패킷을 캡쳐했을 때 보면 특정주소의 WebService를 요청해서 가져오게 되어있었습니다. http://mudchobo.tomeii.com/tt/434

실제 전송하는 데이터는 MD5값인데, 이게 어떻게 생성되는지 몰라서 구글에서 검색을 하니 어떤 블로그에서 시도한 흔적을 발견했습니다. 거기에 있는 댓글에서 발견했습니다^^
http://dialup.egloos.com/152001

"MP3 파일의 경우 strChecksum 값은 ID3태그등을 제외한 순수 MP3 음악 데이터를 앞에서부터 163840 바이트 읽어서 MD5로 돌린 값입니다. " 라고 친절하게 답변이....-_-

그래서 이렇게 하니까 잘 되더라구요. 아래는 샘플 코드입니다.
필요한 라이브러리는 MD5생성해주는 corelib가 필요합니다.
http://code.google.com/p/as3corelib/
[code]<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    applicationComplete="applicationCompleteHandler()">
    <mx:Script>
        <![CDATA[
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
            import flash.text.engine.ContentElement;
            import mx.messaging.messages.HTTPRequestMessage;
            import mx.rpc.http.HTTPService;
            import com.adobe.crypto.MD5;
            private function applicationCompleteHandler():void
            {
                var fs:FileStream = new FileStream();
                fs.open(new File("C:/Users/mudchobo/Downloads/임재범 - 사랑이라서.mp3"), FileMode.READ);
                var fileSize:int = fs.bytesAvailable;
               
                for (var i:int = 0; i < 500000; i++)
                {
                    if (fs.bytesAvailable >= 3 && fs.readUTFBytes(3) == "ID3")
                    {
                        var sizeByte:ByteArray = new ByteArray();
                       
                        // ID3v2길이구하기
                        fs.position += 3;
                        fs.readBytes(sizeByte, 0, 4);
                        var id3Size:int = sizeByte[0] << 21 | sizeByte[1] << 14 | sizeByte[2] << 7 | sizeByte[3];
                        fs.position = id3Size + 10;
                        break;
                    }
                }
               
                // ID3태그 없는 경우
                if (i == 500000)
                {
                    fs.position = 0;
                }
               
                // 공백있는 ID3태그에 대한 처리
                for (i = 0; i < 50000; i++)
                {
                    if (fs.readUnsignedByte() == 255)
                    {
                        var a:int = fs.readUnsignedByte();
                        if ((a >> 5) == 7)
                        {
                            fs.position += -2;
                            break;
                        }
                    }
                }
               
                // 163840만큼 읽어서 md5생성
                var data:ByteArray = new ByteArray();
                fs.readBytes(data, 0, 163840);
                var md5:String = MD5.hashBytes(data);
               
                // 웹서비스 요청
                var httpService:HTTPService = new HTTPService();
                httpService.method = HTTPRequestMessage.POST_METHOD;
                httpService.contentType = "application/soap+xml";
                httpService.url = "http://lyrics.alsong.co.kr/alsongwebservice/service1.asmx";
                httpService.request = new XML('<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="ALSongWebServer/Service1Soap" xmlns:ns1="ALSongWebServer" xmlns:ns3="ALSongWebServer/Service1Soap12"><SOAP-ENV:Body><ns1:GetLyric5><ns1:stQuery><ns1:strChecksum>' + md5 + '</ns1:strChecksum><ns1:strVersion>2.0 beta2</ns1:strVersion><ns1:strMACAddress>ffffffffffff</ns1:strMACAddress><ns1:strIPAddress>255.255.255.0</ns1:strIPAddress></ns1:stQuery></ns1:GetLyric5></SOAP-ENV:Body></SOAP-ENV:Envelope>');
                httpService.addEventListener(ResultEvent.RESULT, resultHandler);
                httpService.addEventListener(FaultEvent.FAULT, faultHandler);
                httpService.send();
            }
           
            private function resultHandler(event:ResultEvent):void
            {
                trace("싱크가사 = " + event.result.Envelope.Body.GetLyric5Response.GetLyric5Result.strLyric);
            }
           
            private function faultHandler(event:FaultEvent):void
            {
                trace(event.toString());
            }
           
        ]]>
    </mx:Script>
</mx:WindowedApplication>
[/code]
여기서 알송가사에서 제공하는 웹서비스는 WebService인데, HTTPService로 요청한 이유는 wsdl문서가 어디있는지 못찾겠음 ㄷㄷ 그래서 그냥 httpservice로 xml통채로 넘겨버리니-_- 되더라구요 ^^
xml넘길 때 strCheckSum값만 md5구한걸로 넘기면 돼요^^

[code]싱크가사 = [00:00.00]하루 하루 그대만 보여서<br>[00:23.28]매일매일 눈을 가리고 살아<br>[00:31.34]그대 곁을 나조차도 모르게 머물며<br>[00:38.91]메마른 내 가슴이 그댈 잊어버리지 못한 이유는<br>[00:00.00]<br>[00:00.00]<br>[00:49.93]사랑이라서 사랑이라서<br>[00:56.06]두번다시 못볼 사랑이라서<br>[00:00.00]<br>[01:03.32]하늘이 하는 일 돌릴 수 없는 일<br>[01:11.39]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[01:19.51]죽어서 보라고 그래서 보라고<br>[01:26.42]그때라도 사랑한 마음이 남아있게된다면<br>[01:34.53]그때쯤에<br>[00:00.00]<br>[01:42.93]어딜가도 그대만 보여서<br>[01:49.88]매일매일 나를 지우고 살아<br>[01:57.28]한순간도 바람처럼 떠날줄 모르고<br>[02:04.71]죽어도 내가슴이 그댈 떠나보내지 못한 이유는<br>[00:00.00]<br>[02:15.49]사랑이라서 사랑이라서<br>[02:23.68]가슴가득 맺힌 사랑이라서<br>[00:00.00]<br>[02:29.68]하늘이 하는 일 돌릴 수 없는 일<br>[02:37.81]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[02:45.79]죽어서 보라고 그래서 보라고<br>[02:52.51]그때라도 사랑한 마음이 남아있게 된다면<br>[03:00.90]그때쯤에<br>[00:00.00]<br>[03:04.66]간절히 바랬어 너에게 가는일<br>[00:00.00]<br>[03:33.19]하늘이 하는 일 돌릴 수 없는 일<br>[03:41.80]이렇게 사는게 힘들면 그녈 보내줄텐데<br>[03:49.23]죽어서 보라고 그래서 보라고<br>[03:56.56]그때라도 사랑한 마음이 남아있게된다면<br>[04:04.29]그때쯤에<br>[/code]

PS. HTTPService에서 xml전송해서 하는 거 어떻게 하는지 몰랐는데, 그냥 httpservice에 있는 request객체에 xml을 생성해서 넣어주면 된다는....-_-

 
Posted by 머드초보
,
 
Flex ANT관련 자료는 지돌스타님이 자세히 써주셨기때문에 참고해주세요.
http://blog.jidolstar.com/505

mxmlc로 컴파일을 해본적이 없어서 작성하는데에 욕봤습니다.
우선 ANT에서 제공하는 태그들이 있는데요. 그걸 이용해서 하려고 하는데 잘 안돼서-_- 그냥 exec로 작성했습니다 ㅠㅠ

그냥 rsl을 사용하지 않은 프로젝트는 ant로 매우 쉽게 할 수 있는데, RSL을 사용하면 조금 복잡해집니다.

RSL로 사용할 Flex Library Project를 하나 만듭니다. 그리고 클래스나 MXML을 추가하게 되면 bin디렉토리에 *.swc파일이 생깁니다. 이걸이용해서 메인프로젝트에서 컴파일을 해야합니다. 또한 *.swc파일을 이용해서 optimizer된 *.swf를 만들어야합니다.
[code]<target name="optimizer">
    <unzip src="${SWC파일}" dest="${SWC파일을 풀어놓을 디렉토리}" />
    <exec executable="${optimizer.exe파일 경로}">
        <arg line="-input '${SWC파일 풀어놓은 디렉토리}/library.swf'" />
        <arg line="-output '{아웃풋할 파일명.swf}'" />
    </exec>
</target>[/code]
몰랐는데, SWC파일 풀면 library.swf가 나오는데, 그걸 optimizer.exe한 것이 Flex Builder에서 나오는 swf파일이랑 같은것이더라구요(알고보니 나만 몰랐던거....다 알고있었음!-_-)
이렇게 하면 swf파일이 만들어집니다.
그럼 메인프로젝트 컴파일 하려면 이런식으로 하면 됩니다.
[code]<target name="compile">
    <exec executable="${mxmlc.exe파일 경로}">
        <arg line="-verify-digests=false" />
        <arg line="-runtime-shared-library-path '${rsl swc파일경로}' '${rsl경로위치url}'" />
        <arg line="-o '${아웃풋 경로}'" />
        <arg line="'${소스mxml메인파일}'" />
    </exec>
</target>[/code]
compile하기전에 optimizer target을 depends해야함^^
아마 verify-digests는 properties에서 설정할 때 체크하는 그것일꺼에요. 음..저는 이런식으로 하니까 되더라구요-_- -runtime-shared-library-path에서 swc파일 경로랑 rsl경로위치url(나중에 파일을 올릴 때 해당 위치에 있어야할 url입니다)로 지정하니 되더군요. 이것때문에 고생을 좀 해서-_-
메인프로젝트에 SWC파일을 LIB로 사용한다면 이런식으로 해야하구요^^
[code]<arg line="-library-path+='${basedir}/libs/Mate_08_8_1.swc'" />[/code]
ftp올리는 것도 지돌스타님 블로그에 잘 정리 되어있음!
http://blog.jidolstar.com/506
이러면 원클릭 배포가 가능해짐-_- 귀차니즘을 위한 ANT임!-_-
 
Posted by 머드초보
,
 
음... air.swf를 이용하면 설치된 애플리케이션을 실행할 수 있으며, Adobe AIR가 설치가 되어있지 않으면 설치도 할 수 있는 기능이 있는 SWF입니다.
http://airdownload.adobe.com/air/browserapi/air.swf

음....문제점이 한쪽에서 air.swf를 로딩하고 나서 getApplicationVersion(애플리케이션이 설치되었는지 확인하는 함수)을 하고 있습니다. 그런데 다른 한쪽에서 air.swf를 로딩해서 getStatus(air가 설치되어있는지 상태값 받기)를 하면 air가 설치 되어있음에도 불구하고 available(AIR설치는 안되어있으나 설치가 가능함)을 받는 경우가 발생합니다.

그래서 더 찾아보니 우야꼬님의 글을 보다가 알았는데요. air.swf 없이도 AIR애플리케이션을 설치하고 실행할 수 있다고 한다는 글을 보았습니다. 그래서 이 현상이 버그인지 확인해보려고 직접 구현하려고 했지만........-_-
생각보다 조낸 어렵네요-_- 우야꼬님께서 ProductManager클래스를 이용해야 한다는 힌트만 주셔서 ㅠ
ProductManger는 뭐하는 놈인지 검색해도 잘 안나와요-_- 어디서 뜯어봐야하는지ㅠ 근데 이놈을 이용해서 AIR애플리케이션을 리스타트하는 프로그램을 만들 수도 있더라구요.
http://www.hufkens.net/2009/03/how-to-restart-an-air-application-from-code/
productmanager가 air프로그램 실행하고 그러는 것 같긴 한데, launch라는 함수가 있는데 이것에 대한 커맨드라인 명령어 같은 게 설명이 잘 안나왔네요 ㅠ

암튼, air.swf를 로딩해서 getApplicationVersion을 무한 반복하는 애플리케이션을 만들고, 그걸 2개를 띄워볼께요-_-
AirService.as
[code]package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
   
    public class AirService extends EventDispatcher
    {
        private var _loader:Loader;
        public var _service:Object;
       
        public function AirService()
        {
            _loader = new Loader();
            var context:LoaderContext = new LoaderContext();
            context.applicationDomain = ApplicationDomain.currentDomain;
            _loader.contentLoaderInfo.addEventListener(Event.INIT, initHandler);
            var swf:String = "http://airdownload.adobe.com/air/browserapi/air.swf";
            var request:URLRequest = new URLRequest(swf);
            _loader.load(request, context);
        }
       
        private function initHandler(event:Event):void
        {
            _service = _loader.content;
            dispatchEvent(new Event(Event.COMPLETE));
        }
       
        public function getStatus():String {
            return _service.getStatus();
        }
       
        public function getApplicationVersion(applicationId:String, publisherId:String, callback:Function):void
        {
            _service.getApplicationVersion(applicationId, publisherId, callback);
        }
       
        public function installApplication(url:String, runtimeVersion:String, parameters:Array = null):void
         {
            _service.installApplication(url, runtimeVersion, parameters);        
         }
        
         public function launchApplication(applicationId:String, publisherId:String, parameters:Array = null):void
         {
             _service.launchApplication(applicationId, publisherId, parameters);
         }
    }
}[/code]
Main.mxml
[code]<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical" verticalAlign="middle"
    applicationComplete="applicationCompleteHandler()" width="100%" height="100%">
   
    <mx:Script>
        <![CDATA[
            import adobe.utils.ProductManager;
           
            private var airService:AirService;
           
            private function applicationCompleteHandler():void
            {
                airService = new AirService();
                airService.addEventListener(Event.COMPLETE, completeHandler);
            }
           
            private function completeHandler(event:Event):void
            {
                trace(airService.getStatus());
                ta.text += airService.getStatus() + "\n";
                if (airService.getStatus() == "installed")
                {
                    airService.getApplicationVersion("AirApplication",
                        "2D0F512A27635B8D26E3FE2622F4AAEDDA9D3CFC.1",
                        applicationVersion);
                }
            }
           
            private function applicationVersion(version:String):void
            {
                trace("version=" + version + ", status = " + airService.getStatus());
                ta.text += "version=" + version + ", status = " + airService.getStatus() + "\n";
                ta.verticalScrollPosition = ta.maxVerticalScrollPosition;
                airService.getApplicationVersion("AirApplication",
                    "2D0F512A27635B8D26E3FE2622F4AAEDDA9D3CFC.1",
                    applicationVersion);
            }
           
        ]]>
    </mx:Script>
    <mx:TextArea id="ta" height="100%" width="50%"/>
</mx:Application>[/code]
두개를 띄워보면
사용자 삽입 이미지
한놈은 available을 받습니다-_- 왜그러지-_-
대체 getApplicationVersion에서 어떤 일을 하는지 궁금해지기 시작했습니다.

그래서 대처 방법으로는....-_- getApplicationVersion을 동시에 하지 않으면 돼요-_- 사용자가 클릭할 때 하면 되는데, 이렇게 되면 launchApplication을 할 때에 또 클릭해줘야 해요. launchApplication은 사용자 액션에 의해서만 되더라구요.

PS. 혹시......이 글을 보게 되시는 분들중에서.....AIR애플리케이션을 launch하는 사이트를 목격 하시면 댓글 달아주세요ㅠ

 
Posted by 머드초보
,
 
ActionScript3가 제공하는 Sound클래스에서 구하는 재생시간은 구하는데 너무 오래걸립니다.
Sound객체를 생성에 Complete이벤트가 발생한 다음에야 재생시간을 구할 수 있습니다.
재생목록에 추가를 해서 그냥 간단히 재생시간을 보여줘야하는데, 100곡을 재생목록에 추가를 해버리면 AIR애플리케이션이 미친듯이 메모리를 잡아먹는 것을 볼 수 있습니다-_-;

그래서 찾아보니, mp3 Header정보를 이용해서 재생시간을 구할 수 있습니다.

재생시간 = 파일크기 * 8 / 비트레이트 로 구할 수 있습니다.
그러면 비트레이트만 구하면 되는데, 이건 MP3Header에서 찾을 수 있습니다.

MP3 BITRATE는 http://www.datavoyage.com/mpgscript/mpeghdr.htm에 의하면 MPEG버전, LAYER, Bitrate Index로 구할 수 있습니다. MP3는 각각 Frame별로 Header가 존재하는데, 거기서 위에 정보를 구할 수 있는 것 같습니다.

ID3v2태그가 있는 경우는 ID3v2태그 다음에 MP3Header가 나옵니다. 그렇다면 ID3v2태그의 길이를 구해서 그 다음부터 MP3Header를 찾아야 합니다. ID3v2태그는 길이가 가변적입니다.그래서 총길이를 알아야하는데, 총길이는 ID3v2태그 맨 앞에 나오는 10byte Header부분에서 구할 수 있습니다.

이 Header부분의 6byte~10byte까지가 ID3v2의 총 길이인데요. 여기의 값이 00 00 1F 76(00011111 01110110)이라면 각각 MSB를 제거하여 붙인 값이 총 길이가 된다더군요. 00111111110110 -> 4086byte.

우선 C#으로 구현해놓은 소스가 있습니다. 그것을 Actionscript3로 변환했습니다(구현하려니 힘들어서 ㅠ)
C#소스 -> http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=79

제가 변환한 AS3용 MP3Header클래스입니다. 제가 가지고 있는 MP3 대부분 테스트해봤는데 잘 되더라구요.
invalid-file

MP3Header클래스


사용법은 이렇게 쓰시면 됩니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import util.MP3Header;
            private function init():void
            {
                var mp3Header:MP3Header = new MP3Header();
                mp3Header.readMP3Information("D:/눈물이 글썽 - 서진영.MP3");
                trace("BITRATE = " + mp3Header.intBitRate);
                trace("Frequency = " + mp3Header.intFrequency);
                trace("Mode = " + mp3Header.strMode);
                trace("LengthFormatted = " + mp3Header.strLengthFormatted);
                trace("Length = " + mp3Header.intLength);
            }
        ]]>
    </mx:Script>   
</mx:WindowedApplication>
[/code]
[code]
BITRATE = 64
Frequency = 44100
Mode = Stereo
LengthFormatted = 04:00
Length = 240
[/code]

 
Posted by 머드초보
,
 
다...다른 방법을 검색해서 구했습니다-_-;
하지만, 이건 한글은 이제 잘 읽는 것 같은데......한자를 못 읽습니다-_-; 원래 안되는건가....-_-;

우선 ID3Parser 링크입니다. 매우 빠른 속도로 ID3데이터를 가져옵니다.
http://blog.ashier.com/2007/11/08/id3-parser/

여기서 한글을 읽을 수 있게 수정하는 부분이......
보면 ID3데이터를 추출해오는 부분이 있습니다.
거기서 한글로 추출할 수 있게 변경합니다.

[code]
private function parseFrames():void {
    var id:String = "";
    var size:uint = 0;
    if(fs.position <length) {
        try {
            id = fs.readUTFBytes(frameIdSize);
            size = fs.readUnsignedInt();
            if (version>= 3) {
                fs.readByte();
                fs.readByte();
            }
            if(id.match(regEx)) {
                var obj:Object = new Object();
                obj.encoding = fs.readByte();
                obj.text = fs.readUTFBytes(size - 1);
                frames[id] = obj;
            }
            parseFrames();
        }catch(e:Error) {}
    }
}
[/code]
이 부분이 있는데요. 한글을 읽어오는 readUTFBytes 부분을 바꿔줍니다.
[code]
//obj.text = fs.readUTFBytes(size - 1);
if (obj.encoding == "1")
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "unicode"));
}
else
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "EUC-KR"));
}
[/code]
보니까 obj.encoding이 1인 값은 unicode로 인코딩 된 것 같아요. obj.encoding이 0인 값은 EUC-KR로...
이렇게 하니까 잘 되네요....가끔 공백을 추출해오고 그래서 trim처리했습니다.

아래글은 ID3v1방식 추출 방법 및 ID3v2의 다른 추출 방식 입니다^^ 참고하세요~
http://mudchobo.tomeii.com/tt/356
 
Posted by 머드초보
,