【PHP】anitube保存用クラスを作った。

動画保存系クラスシリーズ。主に自分用。

/**
 * anitube保存用クラス
 * 
 * $ani = new anitubeDownloader();
 * $ani->saveDir = '動画保存用ディレクトリ'; // 末端DS無し
 * $ani->tmpDir = '作業用ディレクトリ'; // 末端DS無し
 * $ani->prefix = 'ANITUBE_'; // 動画保存時のファイル名先頭プレフィックス文字列
 * $res = $ani->download('*******');
 * 
 * @author Owner
 *
 */
class anitubeDownloader {
	
	// 動画保存ディレクトリ(末端スラッシュ無し)
	public $saveDir = '';
	
	// 一時作業ディレクトリ(末端スラッシュ無し)
	public $tmpDir = '';
	
	// 動画保存時のファイル名に付与するプレフィックス文字列
	public $prefix = 'ani_';
	
	// VIDEO_ID(http://www.anitube.se/video/*******/...)
	protected $vid = '';
	
	// エラーメッセージの入れ物
	protected $errors = array();
	
	// 動画閲覧ページのベースURL
	protected $watchBaseUrl = 'http://www.anitube.se/video/';
	
	// 動画情報を取得するためのAPIURL
	protected $apiUrl = 'http://www.anitube.se/nuevo/config.php?key=';
	
	// APIURLを括るデリミタ文字(ユニークキーを含む)
	protected $delimiter = "'";
	
	// HD動画の取得に成功したか否か
	protected $isHd = false;
	
	/**
	 * ダウンロードの実行
	 * @param string $mid
	 * @return multitype:|boolean
	 */
	public function download($vid = null) {
		$this->vid = $vid;
		$this->checkParams();
		if(!empty($this->errors)) return $this->errors;
		$tmpWorkPath = $this->tmpDir . DIRECTORY_SEPARATOR . md5(microtime());
		$headerFilePath = $this->tmpDir . DIRECTORY_SEPARATOR . 'h_' . $this->vid;
		self::tp($tmpWorkPath);
		self::tp($headerFilePath);
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_URL, $this->watchBaseUrl.$this->vid);
		$output = curl_exec($ch);
		
		// 動画のユニークキーストリングを取得する
		$keyStr = $this->getKeyStr($output);
		
		// 上記で取得したキーを元にAPIを叩き、XMLを取得
		$xml = @simplexml_load_string(file_get_contents($this->apiUrl.$keyStr));
		if(!$xml) return false;
		
		// 取得する動画サイズを決定する。(hd or normal)
		$videoUrl = $this->getVideoUrl($xml);
		
		curl_setopt($ch, CURLOPT_URL, $videoUrl);		
		// 動画DLセクション
		$execResult = false;
		$hdFp = fopen($headerFilePath, 'w');
		if($hdFp) {
			curl_setopt($ch, CURLOPT_WRITEHEADER, $hdFp);
			$twFp = fopen($tmpWorkPath, 'wb');
			if($twFp) {
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
				curl_setopt($ch, CURLOPT_FILE, $twFp);
				$execResult = curl_exec($ch);
				curl_close($ch);
				fclose($twFp);
			}
			fclose($hdFp);
		}
		if(!$execResult) goto exception;
		
		// headerFileの解析(ファイルタイプ、拡張子を取得)
		$contentType = self::parseHeaderFile($headerFilePath);
		$ext = self::getFileExtension($contentType);
		// HDファイルのダウンロードに成功していた場合、ファイル名のプレフィックスにhd_を追加
		if($this->isHd) $this->prefix .= 'hd_';
		// 実ファイルのパスを指定(お名前指定)
		$filePath = $this->saveDir . DIRECTORY_SEPARATOR . $this->prefix . $this->vid . $ext;
		// ファイル移動
		rename($tmpWorkPath, $filePath);
		
		// 作業ファイル削除
		self::u($tmpWorkPath);
		self::u($headerFilePath);
		return true;
		
		if(false) {
			exception:
			self::u($tmpWorkPath);
			self::u($headerFilePath);
			curl_close($ch);
			return false;
		}	
	}
	
	/**
	 * ファイルの存在確認と作成、及び権限の付与
	 * @param unknown $path
	 */
	protected function tp($path) {
		if(!file_exists($path)) touch($path);
		chmod($path, 0777);
	}
	
	/**
	 * ファイルの存在確認と削除
	 * @param unknown $path
	 */
	protected function u($path) {
		if(file_exists($path)) unlink($path);
	}

	/**
	 * パラメーターチェック
	 */
	protected function checkParams() {
		if(!is_dir($this->saveDir)) array_push($this->errors, 'saveDir must be a directory.');
		if(fileperms($this->saveDir) != '16895') array_push($this->errors, 'saveDir required to be permission 777.');
		if(!is_dir($this->tmpDir)) array_push($this->errors, 'tmpDir must be a directory.');
		if(fileperms($this->tmpDir) != '16895') array_push($this->errors, 'tmpDir required to be permission 777.');
		if(empty($this->vid)) array_push($this->errors, 'vid is required but not set.');
		if(!self::validateVid($this->vid)) array_push($this->errors, 'VID must be numeric.');
	}
	
	/**
	 * 動画IDの正規バリデーション
	 * @param unknown $check
	 * @return boolean
	 */
	protected function validateVid($check) {
		$regexNumOnly = '/^[0-9]+$/';
		if (preg_match($regexNumOnly, $check)) return true;
		return false;
	}
	
	/**
	 * HTMLファイルをパースして動画のユニークキーを取得
	 * @param unknown $html
	 * @return Ambigous <NULL, string>
	 */
	protected function getKeyStr($html) {
		$key = null;
		$apiPos = stripos($html, $this->apiUrl);
		if($apiPos) {
			$st = $apiPos + strlen($this->apiUrl);
			$ed = strpos($html, $this->delimiter, $st);
			$l = $ed - $st;
			$key = substr($html, $st, $l);
		}
		return $key;
	}
	
	protected function getVideoUrl($xml) {
		if(!empty($xml->filehd)) {
			$this->isHd = true;
			return (string)$xml->filehd;
		}
		if(!empty($xml->file)) return (string)$xml->file;
		return false;
	}
	
	/**
	 * ヘッダーファイルの情報を解析
	 * @param unknown $path
	 * @return Ambigous <NULL, string>
	 */
	protected function parseHeaderFile($path) {
		$hd = file_get_contents($path);
		$ct = null;
		$k = 'Content-Type:';
		$st = stripos($hd, $k);
		if($st !== false) {
			$st += strlen($k);
			$ed = strpos($hd, "\n", $st);
			if($ed === false) $ed = strlen($hd);
			$l = $ed - $st;
			$ct = strtolower(trim(substr($hd, $st, $l)));
		}
		return $ct;	
	}
	
	/**
	 * コンテントタイプより動画ファイルの拡張子を分析し返却
	 * @param unknown $ct
	 * @return string
	 */
	protected function getFileExtension($ct) {
		$e = '';
		switch($ct) {
			case 'video/3gpp':
				$e = '.3gp';
				return $e;
			case 'video/mp4':
				$e = '.mp4';
				return $e;
			case 'video/x-flv':
			default:
				$e = '.flv';
				return $e;
		}
	}
}

例の如く使い方はクラス上部のコメントアウト参照。
といいつつ一応使い方メモ。

$ani = new anitubeDownloader();
$ani->saveDir = '/path/to/saveDir';
$ani->tmpDir = '/path/to/tmpDir';
$res = $ani->download('*****');
var_dump($res);

ニコ動にしろYoutubeにしろ、これ系のクラスはやってること少しずつしか変わらないからスクレイピングの部分だけ分岐させればよいかも?