diff --git a/inc/media.php b/inc/Media.php similarity index 96% rename from inc/media.php rename to inc/Media.php index 405e6a7..a1da486 100644 --- a/inc/media.php +++ b/inc/Media.php @@ -1,294 +1,294 @@ -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; - } -} +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; + } +}