├── LICENSE ├── README.md └── matterport-downloader.php /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # matterport downloader 2 | 3 | ## What it does 4 | 5 | matterport-downloader downloads Skybox 360 panos photos of Matterport houses 6 | 7 | ## Dependencies 8 | 9 | php, php-curl, curl 10 | 11 | This code can run over Linux and Windows 12 | 13 | ## Usage 14 | 15 | $ php ./matterport-downloader.php 16 | 17 | e.g: If the url is https://my.matterport.com/show/?m=huhpm6maGbT&mls=1 you have to run the script this way: 18 | 19 | $ php ./matterport-downloader.php huhpm6maGbT 20 | 21 | It will download all the skybox images with the highest quality available (4k, 2k or 1k). 22 | 23 | ## Online downloader version of this script 24 | 25 | https://openpanorama.rf.gd/download/ and https://openpano.rf.gd/download/ mirror, it shows skybox images of matterport virtual tours, with the browser option "save web complete" it downloads all skybox images of a matterport virtual tour. 26 | 27 | ## Viewers 28 | 29 | PC: Linux/Mac/Windows using Panini and opening as Cube Faces: https://github.com/lazarus-pkgs/panini 30 | 31 | Web Browser: Chrome and Firefox: Pannellum (webgl based), self hosted virtual tour, equirectangular and cubemap skybox: https://github.com/mpetroff/pannellum 32 | 33 | ## Equirectangular 34 | 35 | Skybox can be converted to equirectangular format and achieve a higher compatibility among viewers. 36 | 37 | https://github.com/fdd4s/skybox2equirectangular 38 | 39 | Equirectangular can be viewed in players like Ricoh Theta for Android https://play.google.com/store/apps/details?id=com.theta360&hl=en&gl=US 40 | 41 | ## Credits 42 | 43 | Created by fdd4s 44 | Send feedback and questions to fc1471789@gmail.com 45 | All files are public domain https://unlicense.org/ 46 | -------------------------------------------------------------------------------- /matterport-downloader.php: -------------------------------------------------------------------------------- 1 | \n"); exit(0); } 4 | $id = $argv[1]; 5 | 6 | $mp = new Mp($id); 7 | 8 | $catalog = $mp->getCatalog(); 9 | 10 | $sky_size = 0; 11 | foreach($catalog as $item) { 12 | echo("File ".$item."\n"); 13 | if ( str_existe($item, "/4k/") && 14 | str_existe($item, "jpg") && 15 | str_existe($item, "skybox") && 16 | !str_existe($item, "dds") && 17 | !str_existe($item, "zip") && 18 | !str_existe($item, "lased") ) 19 | { 20 | $sky_size = 4; 21 | break; 22 | } 23 | } 24 | 25 | if ($sky_size==0) { 26 | foreach($catalog as $item) { 27 | echo("File ".$item."\n"); 28 | if ( str_existe($item, "/2k/") && 29 | str_existe($item, "jpg") && 30 | str_existe($item, "skybox") && 31 | !str_existe($item, "dds") && 32 | !str_existe($item, "zip") && 33 | !str_existe($item, "lased") ) 34 | { 35 | $sky_size = 2; 36 | break; 37 | } 38 | } 39 | } 40 | 41 | $toSearch = "/high/"; 42 | if ($sky_size==2) $toSearch = "/2k/"; 43 | if ($sky_size==4) $toSearch = "/4k/"; 44 | 45 | foreach($catalog as $item) { 46 | echo("File ".$item."\n"); 47 | if ( str_existe($item, $toSearch) && 48 | str_existe($item, "jpg") && 49 | str_existe($item, "skybox") && 50 | !str_existe($item, "dds") && 51 | !str_existe($item, "zip") && 52 | !str_existe($item, "lased") ) 53 | { 54 | echo("Downloading... ".$item."\n"); 55 | $mp->download($item); 56 | } 57 | } 58 | 59 | echo("done\n"); 60 | 61 | function str_existe($pajar, $aguja) 62 | { 63 | $pos1 = strpos($pajar, $aguja); 64 | if ($pos1===FALSE) return false; 65 | return true; 66 | } 67 | 68 | class Mp { 69 | var $id; 70 | var $ch; 71 | var $url_base; 72 | var $referer; 73 | var $catalog; 74 | 75 | public function __construct($id) { 76 | $this->id = $id; 77 | $this->referer = "https://my.matterport.com/show/?m=".$this->id; 78 | $this->updateUrlBase(); 79 | $this->updateCatalog(); 80 | } 81 | 82 | public function updateCatalog() { 83 | $url = $this->makeUrl("catalog.json"); 84 | $data = $this->httpGet1($url); 85 | $arr = json_decode($data, true); 86 | $this->catalog = $arr["files"]; 87 | } 88 | 89 | public function getCatalog() { 90 | return $this->catalog; 91 | } 92 | 93 | private function makeUrl($path) { 94 | return str_replace("{{filename}}", $path, $this->url_base); 95 | } 96 | 97 | public function updateUrlBase() 98 | { 99 | $url = "https://my.matterport.com/api/player/models/".$this->id."/files?type=3"; 100 | $data = $this->httpGet1($url); 101 | $arr = json_decode($data, true); 102 | $arr2 = $arr["templates"]; 103 | $this->url_base = $arr2[0]; 104 | } 105 | 106 | private function httpGet1($url) { 107 | $ch = curl_init(); 108 | curl_setopt($ch, CURLOPT_URL, $url); 109 | $headers = array(); 110 | $headers[] = "Referer: ".$this->referer; 111 | $headers[] = "Accept: application/json"; 112 | $headers[] = "Accept-Language: en-US,en;q=0.5"; 113 | $headers[] = "Cookie: mp_mixpanel__c=0"; 114 | 115 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 116 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 117 | 118 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 119 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 120 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"); 121 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 122 | 123 | $output = curl_exec($ch); 124 | 125 | curl_close($ch); 126 | 127 | return $output; 128 | } 129 | 130 | public function download($file) { 131 | $res = $this->downloadTry($file); 132 | if (!$res) { echo("error downloading, trying again\n"); $res = $this->downloadTry($file); } else return; 133 | if (!$res) { echo("error downloading, trying again\n"); $res = $this->downloadTry($file); } else return; 134 | echo("error downloading\n"); 135 | exit(0); 136 | } 137 | 138 | private function downloadTry($file) { 139 | $path = dirname(__FILE__)."/".$this->formatFilename($file); 140 | if (file_exists($path)) { 141 | if (filesize($path)>0) { 142 | return true; 143 | } else { 144 | unlink($path); 145 | } 146 | } 147 | //$this->updateUrlBase(); 148 | $url = $this->makeUrl($file); 149 | $res = $this->httpDownload($url, $path); 150 | if ($res==false) { 151 | if (file_exists($path)) unlink($path); 152 | return false; 153 | } 154 | return true; 155 | } 156 | 157 | private function httpDownload($url, $path) { 158 | $fp = fopen($path, 'w+'); 159 | $ch = curl_init(); 160 | curl_setopt($ch, CURLOPT_URL, $url); 161 | $headers = array(); 162 | $headers[] = "Referer: ".$this->referer; 163 | $headers[] = "Accept: image/png,image/*;q=0.8,*/*;q=0.5"; 164 | $headers[] = "Accept-Language: en-US,en;q=0.5"; 165 | $headers[] = "origin: https://my.matterport.com"; 166 | 167 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 168 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 169 | 170 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 171 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 172 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"); 173 | curl_setopt($ch, CURLOPT_FILE, $fp); 174 | curl_exec($ch); 175 | 176 | $res = false; 177 | if(curl_errno($ch)==0) $res = true; else { echo ("error curl ".curl_errno($ch)."\n"); } 178 | 179 | curl_close($ch); 180 | fclose($fp); 181 | 182 | return $res; 183 | } 184 | 185 | public function idValid($id) { 186 | return strlen($id)==11 && preg_match('/^[a-zA-Z0-9_\-]+$/', $id); 187 | } 188 | 189 | private function formatFilename($cad) { 190 | $cad = str_replace("/", "-", $cad); 191 | $cad = str_replace("_", "-", $cad); 192 | $imax = strlen($cad); 193 | $char_val = " "; 194 | $char_num = 0; 195 | $char_valid = false; 196 | 197 | $res = ""; 198 | for ($i=0; $i<$imax; $i++) { 199 | $char_val = substr($cad, $i, 1); 200 | $char_num = ord($char_val); 201 | $char_valid = false; 202 | 203 | if ($char_num >= ord("0") && $char_num <= ord("9")) { 204 | $char_valid = true; 205 | } 206 | 207 | 208 | if ($char_num >= ord("a") && $char_num <= ord("z")) { 209 | $char_valid = true; 210 | } 211 | 212 | 213 | if ($char_num >= ord("A") && $char_num <= ord("Z")) { 214 | $char_valid = true; 215 | } 216 | 217 | if ($char_val==".") $char_valid = true; 218 | if ($char_val=="-") $char_valid = true; 219 | if ($char_val=="(") $char_valid = true; 220 | if ($char_val==")") $char_valid = true; 221 | 222 | if ($char_valid==true) { 223 | $res .= $char_val; 224 | } 225 | 226 | } 227 | return $res; 228 | } 229 | } 230 | 231 | ?> 232 | --------------------------------------------------------------------------------