Adobe AIRでニコニコ動画にアクセスして、FLVをダウンロードしてみる
ここ2~3日、Adobe AIRを使ってニコニコ動画にアクセス・ログイン後、FLVをダウンロードするプログラムを作っていました。
名前は安直にNicoNicoDownloader。
とりあえずダウンロードできるようになったのでご報告。
ムービーのURLとメールアドレス、パスワードを入力します。
そして"開始"ボタンを押すとダウンロード開始!
Macだと各ユーザーの 書類/NND/temp.flv という形で保存されます。
tempとかやる気無い名前だね!自覚してますよ!
作成した環境は、
MacOS X 10.5.2
Flex Builder 3
です。
まずはニコニコ動画のAPIについてお勉強。
「Perlでニコニコ動画のflvとコメントxmlをダウンロードする」を参照してもらうとわかると思いますが、
ポイント1
http://www.nicovideo.jp/api/getflv?v=ビデオID にアクセス(GET)して、FLVの場所を取得する(実際に帰ってくるのはFLVのURLだけじゃ無いんだけどね。今回はそこしか使わないから。)
ポイント2
ポイント1のAPIをたたくときとポイント1で取得したURLからFLVをダウンロードするときはその動画のページにアクセスしていなければならない
という2つのポイントが有ります。
これを理解して・・・もPerlが読めないのであんまり意味がない。私が読める言語で同じようなものが書かれていないかな?と探したところ、
ニコニコ動画マイリストを一括ダウンロード&iPod用に変換(JAVA)
を発見。
今回はこれのマネをしてActionScriptで書いてみました。
処理は基本的に1本道です。上から順に、
・ログイン処理
・見てるフリ処理開始
・APIにアクセス
・FLVのURLを解析・アクセス
・FLVを保存
・ストリームとかを閉じる
というふうに処理が進んでいきます。
詳しい内容は、ソースの方にしつこいほどコメントを入れたのでそちらをどうぞ。
なお、Flexプロジェクトとして作成したものの一部なので、下のソースコードだけでは動きません。念のため。
以下、NicoNicoDownloader.asです。
import flash.events.Event; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.net.URLVariables; import mx.controls.Alert; private var mURL:String; private var mailAddress:String; private var password:String; private var videoID:String; private var watchLoader:URLLoader; private var getLoader:URLLoader; private var downLoader:URLLoader; public var LOGIN_URL:String = "https://secure.nicovideo.jp/secure/login?site=niconico"; public var TOP_PAGE_URL:String = "http://www.nicovideo.jp/"; public function initNicoNicoDownloader():void { } /** * ボタンが押されたときの処理。処理の開始。 */ public function startButtonClick():void { //各種情報を収集する。 //ムービーのURL。 mURL = textInput_mUrl.text; //ログイン用メールアドレス。 mailAddress = textInput_mailAddress.text; //パスワード password = textInput_password.text; //ムービーのID。ムービーのURLの一番最後についている英単語と数字のアレ。 videoID = mURL.substring(mURL.lastIndexOf("/")+1); //ログイン処理開始 this.login(); } /** * ログイン処理。 */ private function login():void { //以降のURLRequestが全て認証情報付きで行われるように、デフォルト値としてセット URLRequestDefaults.setLoginCredentialsForHost(TOP_PAGE_URL, mailAddress, password); //ログインURLにアクセス var req:URLRequest = new URLRequest(LOGIN_URL); //POSTメソッドです req.method = "POST"; //メールアドレスとパスワードをURLエンコードしてリクエストに付加 var variables : URLVariables = new URLVariables (); variables.mail = mailAddress; variables.password = password; req.data = variables; //ログイン成功時のリスナーを追加してリクエストを実行 var loader:URLLoader = new URLLoader(); loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onLoginSuccess); loader.load(req); } /** * ログイン作業が成功した場合に呼ばれるリスナー */ private function onLoginSuccess(event:HTTPStatusEvent):void { this.label_status.text = "ログインに成功(" + event.status + ")"; trace("ログインに成功"+event); //見てるフリ処理を実施する this.watch(TOP_PAGE_URL, mailAddress, password); } /** * 見てるフリ処理を実施する。 */ private function watch(url:String, userName:String, password:String):void { //以降のURLRequestが全て認証情報付きで行われるように、デフォルト値としてセット URLRequestDefaults.setLoginCredentialsForHost(TOP_PAGE_URL, userName, password); //そのページを見ているフリをする為のリクエストを準備する。 var watchURL:URLRequest = new URLRequest(mURL); watchURL.method = "GET"; watchLoader = new URLLoader(); watchLoader.addEventListener(Event.COMPLETE, accessSuccess); watchLoader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); watchLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); //HTTPリクエストを実行。 watchLoader.load(watchURL); } /** * 見ているフリ処理で行っているリクエストが完了したら呼ばれる。 */ private function accessSuccess(evt:Event):void { this.label_status.text = "アクセス成功"; trace("アクセス成功" + evt); //FLVのURLを取得する処理を実行。 this.getAPIResult(videoID); } /** * FLVのURLを取得する為のAPIへのアクセスを行う */ private function getAPIResult(videoID:String):void { //FLVのURLを取得する為にニコニコ動画のAPIにアクセスする var getAPIResult:URLRequest; getAPIResult = new URLRequest("http://www.nicovideo.jp/api/getflv?v=" + videoID); getAPIResult.method = "GET"; getLoader = new URLLoader(); getLoader.addEventListener(Event.COMPLETE, getAPIResultSuccess); getLoader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); getLoader.addEventListener(ProgressEvent.PROGRESS, progressHandler); getLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); getLoader.load(getAPIResult); } /** * APIからの応答が得られたら呼ばれる */ private function getAPIResultSuccess(evt:Event):void { this.label_status.text = "アドレスの取得に成功"; trace("アドレスの取得に成功" + evt); //得られた応答を元にvideoを取得 this.getVideo(getLoader); } /** * APIから得られたデータを元に動画をダウンロードする */ private function getVideo(loader:URLLoader):void { //APIから得られたデータの"&url="にあるURLを探す var videoURL:String = new String(); videoURL = getLoader.data.substring(getLoader.data.indexOf("&url=")+5,getLoader.data.indexOf("&",getLoader.data.indexOf("&url")+1)); videoURL = unescape(videoURL); trace(videoURL); //探したURLを使ってFLVのダウンロードを行う。 var getVideo:URLRequest = new URLRequest(videoURL) loader = new URLLoader(); loader.dataFormat=URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, loadSuccess); loader.addEventListener(ProgressEvent.PROGRESS, progressHandler); loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); loader.load(getVideo); } /** * FLVのダウンロードが完了したら呼ばれる */ private function loadSuccess(evt:Event):void{ this.label_status.text = "ダウンロード成功" trace("ダウンロード成功" + evt); //ダウンロードしたFLVを保存する。 this.saveFLV(downLoader); } /** * 取得したURLLoader(FLVデータが含まれる)を使ってFLVディスクに書き出す。 */ private function saveFLV(loader:URLLoader):void { //ファイルを保持するFileオブジェクト var file:File = File.documentsDirectory; //ファイルストリーム。ファイルへの入出力に使う var fileStream:FileStream; file.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); file.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler); file.addEventListener(ProgressEvent.PROGRESS, progressHandler); this.label_status.text = "ファイルを保存中..."; file = file.resolvePath("NND/temp.flv"); fileStream = new FileStream(); fileStream.addEventListener(ProgressEvent.PROGRESS, progressHandler); fileStream.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); fileStream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, progressHandler); fileStream.open(file, FileMode.WRITE); fileStream.writeBytes(loader.data); this.label_status.text = "保存完了"; //以下、閉じる処理。 fileStream.close(); loader.close(); this.allClose(); } /** * 一括してクローズ処理を行う */ private function allClose():void { this.downLoader.close(); this.watchLoader.close(); this.getLoader.close(); } //以下、エラー処理とか。 private function securityErrorHandler(evt:SecurityErrorEvent):void{ Alert.show(evt.text, "SecurityError"); label_status.text = "停止"; } private function ioErrorHandler(evt:IOErrorEvent):void{ Alert.show(evt.text, "IOError"); label_status.text = "停止"; } //進捗状況を表示するハンドラ private function progressHandler(evt:ProgressEvent):void{ label_status.text = "ダウンロード中" + evt.bytesLoaded + "/" + evt.bytesTotal; trace(evt.bytesLoaded + "/" + evt.bytesTotal); }