Update 'inc/Media.php'

This commit is contained in:
2021-06-28 20:48:43 +02:00
parent 2cb5a2c6d6
commit 2bb25347dd

View File

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