oDb = &$oDb; $this->oProject = &$oProject; $this->asMedia = array(); $this->asMedias = array(); $this->sSystemType = (substr(php_uname(), 0, 7) == "Windows")?'win':'unix'; $this->setMediaId($iMediaId); } public function setMediaId($iMediaId) { $this->iMediaId = $iMediaId; } public function getMediaId() { return $this->iMediaId; } public function getProjectCodeName() { return $this->oProject->getProjectCodeName(); } public function setComment($sComment) { $sError = ''; $asData = array(); if($this->iMediaId > 0) { $bResult = $this->oDb->updateRow(self::MEDIA_TABLE, $this->iMediaId, array('comment'=>$sComment)); if(!$bResult) $sError = 'error_commit_db'; else $asData = $this->getInfo(); } else $sError = 'media_no_id'; return Spot::getResult(($sError==''), $sError, $asData); } public function getMediasInfo($iMediaId=0) { $bOwnMedia = ($iMediaId > 0); if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias)) { if($this->oProject->getProjectId()) { $asParams = array( 'select' => array(Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'rotate', 'type AS subtype', 'comment'), 'from' => self::MEDIA_TABLE, 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()) ); if($bOwnMedia) $asParams['constraint'][Db::getId(self::MEDIA_TABLE)] = $iMediaId; $asMedias = $this->oDb->selectRows($asParams); foreach($asMedias as &$asMedia) { $asMedia['media_path'] = self::getMediaPath($asMedia['filename']); $asMedia['thumb_path'] = $this->getMediaThumbnail($asMedia['filename']); } if(!empty($asMedias)) { if($bOwnMedia) $this->asMedia = array_shift($asMedias); else $this->asMedias = $asMedias; } } } return $bOwnMedia?$this->asMedia:$this->asMedias; } public function getInfo() { return $this->getMediasInfo($this->iMediaId); } public function isProjectModeValid() { return ($this->oProject->getMode() == Project::MODE_BLOG); } public function addMedia($sMediaName, $sMethod='upload') { $sError = ''; $asParams = array(); if(!$this->isProjectModeValid() && $sMethod!='sync') { $sError = 'upload_wrong_mode'; $asParams[] = $this->oProject->getProjectCodeName(); } elseif($this->oDb->pingValue(self::MEDIA_TABLE, array('filename'=>$sMediaName)) && $sMethod!='sync') { $sError = 'upload_media_exist'; $asParams[] = $sMediaName; } else { $asMediaInfo = $this->getMediaInfoFromFile($sMediaName); //Converting times to DB Time Zone, by using date() $asDbInfo = array( Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), 'filename' => $sMediaName, 'taken_on' => ($asMediaInfo['taken_ts'] > 0)?date(Db::TIMESTAMP_FORMAT, $asMediaInfo['taken_ts']):0, 'posted_on' => date(Db::TIMESTAMP_FORMAT, $asMediaInfo['file_ts']), 'timezone' => $asMediaInfo['timezone'], 'rotate' => $asMediaInfo['rotate'], 'type' => $asMediaInfo['type'] ); if($sMethod=='sync') $iMediaId = $this->oDb->insertUpdateRow(self::MEDIA_TABLE, $asDbInfo, array(Db::getId(Project::PROJ_TABLE), 'filename')); else $iMediaId = $this->oDb->insertRow(self::MEDIA_TABLE, $asDbInfo); if(!$iMediaId) $sError = 'error_commit_db'; else { $this->setMediaId($iMediaId); $asParams = $this->getInfo(); //Creates thumbnail } } return Spot::getResult(($sError==''), $sError, $asParams); } /** * One-shot function to initialize DB with existing images */ public function syncFileFolder() { $asMediaPaths = glob(self::getMediaPath('*.{jpg,JPG,jpeg,JPEG,png,PNG,mov,MOV}'), GLOB_BRACE); foreach($asMediaPaths as $sMediaPath) { $sMediaName = pathinfo($sMediaPath, PATHINFO_BASENAME); $this->addMedia($sMediaName, 'sync'); } $this->setExtractMode(PhpObject::MODE_HTML); return $this->getCleanMessageStack(); } private function getMediaInfoFromFile($sMediaName) { $sMediaPath = self::getMediaPath($sMediaName); $sType = self::getMediaType($sMediaName); $iTimeStamp = $iTakenOn = 0; $iPostedOn = filemtime($sMediaPath); $sTimeZone = date_default_timezone_get(); $sRotate = '0'; $sTakenOn = ''; switch($sType) { case 'video': $asResult = array(); $sParams = implode(' ', array( '-loglevel error', //Remove comments '-select_streams v:0', //First video channel '-show_entries '. //filter tags : Creation Time & Rotation 'format_tags=creation_time,com.apple.quicktime.creationdate'.':'. 'stream_tags=rotate,creation_time', '-print_format json', //output format: json '-i' //input file )); exec('ffprobe '.$sParams.' "'.$sMediaPath.'"', $asResult); $asExif = json_decode(implode('', $asResult), true); //Taken On if(isset($asExif['format']['tags']['com.apple.quicktime.creationdate'])) { $sTakenOn = $asExif['format']['tags']['com.apple.quicktime.creationdate']; //contains Time Zone $sTimeZone = Spot::getTimeZoneFromDate($sTakenOn) ?? $sTimeZone; } else $sTakenOn = $asExif['format']['tags']['creation_time'] ?? $asExif['streams'][0]['tags']['creation_time']; //Orientation if(isset($asExif['streams'][0]['tags']['rotate'])) $sRotate = $asExif['streams'][0]['tags']['rotate']; break; case 'image': $asExif = @exif_read_data($sMediaPath, 0, true); //Posted On if(array_key_exists('FILE', $asExif) && array_key_exists('FileDateTime', $asExif['FILE'])) $iPostedOn = $asExif['FILE']['FileDateTime']; //Taken On & Timezone if(array_key_exists('EXIF', $asExif)) { if(array_key_exists('DateTimeOriginal', $asExif['EXIF'])) $sTakenOn = $asExif['EXIF']['DateTimeOriginal']; /* Priorities: * 1. OffsetTimeOriginal: timezone for DateTimeOriginal (exif version >= 2.31) * 2. 0x9011: same as above, but unidentified * 3. Timezone extracted from DateTimeOriginal * 4. Uploader Browser Time Zone (PHP Default Time Zone) */ $sTimeZone = $asExif['EXIF']['OffsetTimeOriginal'] ?? $asExif['EXIF']['UndefinedTag:0x9011'] ?? Spot::getTimeZoneFromDate($sTakenOn) ?? $sTimeZone; } //Orientation if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) { switch($asExif['IFD0']['Orientation']) { case 1: $sRotate = '0'; break; //None case 3: $sRotate = '180'; break; //Flip over case 6: $sRotate = '90'; break; //Clockwise case 8: $sRotate = '-90'; break; //Trigo } } break; } //Assign the correct Time Zone to $sTakenOn if it is not already contained in it. Then get Unix Timestamp //Time Zone (2nd parameter) will be ignored if already contained in $sTakenOn if($sTakenOn != '') { $oTakenOn = new \DateTime($sTakenOn, new \DateTimeZone($sTimeZone)); $iTakenOn = $oTakenOn->format('U'); } //Merge timestamps $iTimeStamp = ($iTakenOn > 0)?$iTakenOn:$iPostedOn; return array( 'timestamp' => $iTimeStamp, 'timezone' => $sTimeZone, 'taken_ts' => $iTakenOn, 'file_ts' => $iPostedOn, 'rotate' => $sRotate, 'type' => $sType ); } private function getMediaThumbnail($sMediaName) { $sMediaPath = self::getMediaPath($sMediaName); $sThumbPath = self::getMediaPath($sMediaName, 'thumbnail'); if(!file_exists($sThumbPath)) { $sType = self::getMediaType($sMediaName); switch($sType) { case 'image': $asThumbInfo = ToolBox::createThumbnail($sMediaPath, self::THUMB_MAX_WIDTH, 0, $sThumbPath); break; case 'video': //Get a screenshot of the video 1 second in $sTempPath = self::getMediaPath(uniqid('temp_').'.png'); $asResult = array(); $sParams = implode(' ', array( '-i "'.$sMediaPath.'"', //input file '-ss 00:00:01.000', //Image taken after x seconds '-vframes 1', //number of video frames to output '"'.$sTempPath.'"', //output file )); exec('ffmpeg '.$sParams, $asResult); //Resize $asThumbInfo = ToolBox::createThumbnail($sTempPath, self::THUMB_MAX_WIDTH, 0, $sThumbPath, true); break; } } else $asThumbInfo = array('error'=>'', 'out'=>$sThumbPath); return ($asThumbInfo['error']=='')?$asThumbInfo['out']:$sMediaPath; } private static function getMediaPath($sMediaName, $sFileType='media') { if($sFileType=='thumbnail') return self::THUMB_FOLDER.$sMediaName.(strtolower(substr($sMediaName, -3))=='mov'?'.png':''); else return self::MEDIA_FOLDER.$sMediaName; } private static function getMediaType($sMediaName) { $sMediaPath = self::getMediaPath($sMediaName); $sMediaMime = mime_content_type($sMediaPath); switch($sMediaMime) { case 'video/quicktime': $sType = 'video'; break; default: $sType = 'image'; break; } return $sType; } }