File_thumbnail fixes (run scripts/upgrade.php)
We're now capable of doing image rotation for thumbnails based on EXIF orientation data. Also, thumbnails are tracked by filenames and thus we can delete them from storage when we feel like it.
This commit is contained in:
parent
cd3cff451f
commit
214a10ddec
|
@ -235,7 +235,7 @@ class File extends Managed_DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize and make the original filename more URL friendly.
|
// Normalize and make the original filename more URL friendly.
|
||||||
$origname = basename($origname);
|
$origname = basename($origname, $ext);
|
||||||
if (class_exists('Normalizer')) {
|
if (class_exists('Normalizer')) {
|
||||||
// http://php.net/manual/en/class.normalizer.php
|
// http://php.net/manual/en/class.normalizer.php
|
||||||
// http://www.unicode.org/reports/tr15/
|
// http://www.unicode.org/reports/tr15/
|
||||||
|
@ -405,13 +405,16 @@ class File extends Managed_DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get proper aspect ratio width and height before lookup
|
// Get proper aspect ratio width and height before lookup
|
||||||
|
// We have to do it through an ImageFile object because of orientation etc.
|
||||||
|
// Only other solution would've been to rotate + rewrite uploaded files.
|
||||||
|
$image = ImageFile::fromFileObject($this);
|
||||||
list($width, $height, $x, $y, $w2, $h2) =
|
list($width, $height, $x, $y, $w2, $h2) =
|
||||||
ImageFile::getScalingValues($this->width, $this->height, $width, $height, $crop);
|
$image->scaleToFit($width, $height, $crop);
|
||||||
|
|
||||||
// Doublecheck that parameters are sane and integers.
|
// Doublecheck that parameters are sane and integers.
|
||||||
if ($width < 1 || $width > common_config('thumbnail', 'maxsize')
|
if ($width < 1 || $width > common_config('thumbnail', 'maxsize')
|
||||||
|| $height < 1 || $height > common_config('thumbnail', 'maxsize')) {
|
|| $height < 1 || $height > common_config('thumbnail', 'maxsize')) {
|
||||||
// Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile::getScalingValues
|
// Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
|
||||||
throw new ServerException('Bad thumbnail size parameters.');
|
throw new ServerException('Bad thumbnail size parameters.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,35 +422,11 @@ class File extends Managed_DataObject
|
||||||
'width' => $width,
|
'width' => $width,
|
||||||
'height' => $height);
|
'height' => $height);
|
||||||
$thumb = File_thumbnail::pkeyGet($params);
|
$thumb = File_thumbnail::pkeyGet($params);
|
||||||
if ($thumb === null) {
|
if ($thumb instanceof File_thumbnail) {
|
||||||
// throws exception on failure to generate thumbnail
|
|
||||||
$thumb = $this->generateThumbnail($width, $height, $crop);
|
|
||||||
}
|
|
||||||
return $thumb;
|
return $thumb;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// throws exception on failure to generate thumbnail
|
||||||
* Generate and store a thumbnail image for the uploaded file, if applicable.
|
|
||||||
* Call this only if you know what you're doing.
|
|
||||||
*
|
|
||||||
* @param $width int Maximum thumbnail width in pixels
|
|
||||||
* @param $height int Maximum thumbnail height in pixels, if null, crop to $width
|
|
||||||
*
|
|
||||||
* @return File_thumbnail or null
|
|
||||||
*/
|
|
||||||
protected function generateThumbnail($width, $height, $crop)
|
|
||||||
{
|
|
||||||
$width = intval($width);
|
|
||||||
if ($height === null) {
|
|
||||||
$height = $width;
|
|
||||||
$crop = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$image = ImageFile::fromFileObject($this);
|
|
||||||
|
|
||||||
list($width, $height, $x, $y, $w2, $h2) =
|
|
||||||
$image->scaleToFit($width, $height, $crop);
|
|
||||||
|
|
||||||
$outname = "thumb-{$width}x{$height}-" . $this->filename;
|
$outname = "thumb-{$width}x{$height}-" . $this->filename;
|
||||||
$outpath = self::path($outname);
|
$outpath = self::path($outname);
|
||||||
|
|
||||||
|
@ -459,7 +438,8 @@ class File extends Managed_DataObject
|
||||||
}
|
}
|
||||||
return File_thumbnail::saveThumbnail($this->id,
|
return File_thumbnail::saveThumbnail($this->id,
|
||||||
self::url($outname),
|
self::url($outname),
|
||||||
$width, $height);
|
$width, $height,
|
||||||
|
$outname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath()
|
public function getPath()
|
||||||
|
@ -524,4 +504,9 @@ class File extends Managed_DataObject
|
||||||
|
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isLocal()
|
||||||
|
{
|
||||||
|
return !empty($this->filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class File_thumbnail extends Managed_DataObject
|
||||||
public $__table = 'file_thumbnail'; // table name
|
public $__table = 'file_thumbnail'; // table name
|
||||||
public $file_id; // int(4) primary_key not_null
|
public $file_id; // int(4) primary_key not_null
|
||||||
public $url; // varchar(255) unique_key
|
public $url; // varchar(255) unique_key
|
||||||
|
public $filename; // varchar(255)
|
||||||
public $width; // int(4) primary_key
|
public $width; // int(4) primary_key
|
||||||
public $height; // int(4) primary_key
|
public $height; // int(4) primary_key
|
||||||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||||
|
@ -40,6 +41,7 @@ class File_thumbnail extends Managed_DataObject
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'thumbnail for what URL/file'),
|
'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'thumbnail for what URL/file'),
|
||||||
'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of thumbnail'),
|
'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of thumbnail'),
|
||||||
|
'filename' => array('type' => 'varchar', 'length' => 255, 'description' => 'if stored locally, filename is put here'),
|
||||||
'width' => array('type' => 'int', 'description' => 'width of thumbnail'),
|
'width' => array('type' => 'int', 'description' => 'width of thumbnail'),
|
||||||
'height' => array('type' => 'int', 'description' => 'height of thumbnail'),
|
'height' => array('type' => 'int', 'description' => 'height of thumbnail'),
|
||||||
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
|
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
|
||||||
|
@ -88,19 +90,48 @@ class File_thumbnail extends Managed_DataObject
|
||||||
* @param int $width
|
* @param int $width
|
||||||
* @param int $height
|
* @param int $height
|
||||||
*/
|
*/
|
||||||
static function saveThumbnail($file_id, $url, $width, $height)
|
static function saveThumbnail($file_id, $url, $width, $height, $filename=null)
|
||||||
{
|
{
|
||||||
$tn = new File_thumbnail;
|
$tn = new File_thumbnail;
|
||||||
$tn->file_id = $file_id;
|
$tn->file_id = $file_id;
|
||||||
$tn->url = $url;
|
$tn->url = $url;
|
||||||
|
$tn->filename = $filename;
|
||||||
$tn->width = intval($width);
|
$tn->width = intval($width);
|
||||||
$tn->height = intval($height);
|
$tn->height = intval($height);
|
||||||
$tn->insert();
|
$tn->insert();
|
||||||
return $tn;
|
return $tn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function path($filename)
|
||||||
|
{
|
||||||
|
// TODO: Store thumbnails in their own directory and don't use File::path here
|
||||||
|
return File::path($filename);
|
||||||
|
}
|
||||||
|
|
||||||
public function getUrl()
|
public function getUrl()
|
||||||
{
|
{
|
||||||
return $this->url;
|
return $this->url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete($useWhere=false)
|
||||||
|
{
|
||||||
|
if (!empty($this->filename) && file_exists(File_thumbnail::path($this->filename))) {
|
||||||
|
$deleted = @unlink(self::path($this->filename));
|
||||||
|
if (!$deleted) {
|
||||||
|
common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::delete($useWhere);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile()
|
||||||
|
{
|
||||||
|
$file = new File();
|
||||||
|
$file->id = $this->file_id;
|
||||||
|
if (!$file->find(true)) {
|
||||||
|
throw new NoResultException($file);
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ class ImageFile
|
||||||
var $type;
|
var $type;
|
||||||
var $height;
|
var $height;
|
||||||
var $width;
|
var $width;
|
||||||
|
var $rotate=0; // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
|
||||||
|
|
||||||
function __construct($id=null, $filepath=null, $type=null, $width=null, $height=null)
|
function __construct($id=null, $filepath=null, $type=null, $width=null, $height=null)
|
||||||
{
|
{
|
||||||
|
@ -74,6 +75,26 @@ class ImageFile
|
||||||
$this->type = ($info) ? $info[2]:$type;
|
$this->type = ($info) ? $info[2]:$type;
|
||||||
$this->width = ($info) ? $info[0]:$width;
|
$this->width = ($info) ? $info[0]:$width;
|
||||||
$this->height = ($info) ? $info[1]:$height;
|
$this->height = ($info) ? $info[1]:$height;
|
||||||
|
|
||||||
|
// Orientation value to rotate thumbnails properly
|
||||||
|
$exif = exif_read_data($this->filepath);
|
||||||
|
if (isset($exif['Orientation'])) {
|
||||||
|
switch ((int)$exif['Orientation']) {
|
||||||
|
case 1: // top is top
|
||||||
|
$this->rotate = 0;
|
||||||
|
break;
|
||||||
|
case 3: // top is bottom
|
||||||
|
$this->rotate = 180;
|
||||||
|
break;
|
||||||
|
case 6: // top is right
|
||||||
|
$this->rotate = -90;
|
||||||
|
break;
|
||||||
|
case 8: // top is left
|
||||||
|
$this->rotate = 90;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If we ever write this back, Orientation should be set to '1'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromFileObject(File $file)
|
public static function fromFileObject(File $file)
|
||||||
|
@ -247,6 +268,10 @@ class ImageFile
|
||||||
throw new Exception(_('Unknown file type'));
|
throw new Exception(_('Unknown file type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->rotate != 0) {
|
||||||
|
$image_src = imagerotate($image_src, $this->rotate, 0);
|
||||||
|
}
|
||||||
|
|
||||||
$image_dest = imagecreatetruecolor($width, $height);
|
$image_dest = imagecreatetruecolor($width, $height);
|
||||||
|
|
||||||
if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
|
if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
|
||||||
|
@ -367,7 +392,7 @@ class ImageFile
|
||||||
public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
|
public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
|
||||||
{
|
{
|
||||||
return self::getScalingValues($this->width, $this->height,
|
return self::getScalingValues($this->width, $this->height,
|
||||||
$maxWidth, $maxHeight, $crop);
|
$maxWidth, $maxHeight, $crop, $this->rotate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -384,7 +409,7 @@ class ImageFile
|
||||||
*/
|
*/
|
||||||
public static function getScalingValues($width, $height,
|
public static function getScalingValues($width, $height,
|
||||||
$maxW=null, $maxH=null,
|
$maxW=null, $maxH=null,
|
||||||
$crop=null)
|
$crop=null, $rotate=0)
|
||||||
{
|
{
|
||||||
$maxW = $maxW ?: common_config('thumbnail', 'width');
|
$maxW = $maxW ?: common_config('thumbnail', 'width');
|
||||||
$maxH = $maxH ?: common_config('thumbnail', 'height');
|
$maxH = $maxH ?: common_config('thumbnail', 'height');
|
||||||
|
@ -397,6 +422,13 @@ class ImageFile
|
||||||
$crop = true;
|
$crop = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Because GD doesn't understand EXIF orientation etc.
|
||||||
|
if (abs($rotate) == 90) {
|
||||||
|
$tmp = $width;
|
||||||
|
$width = $height;
|
||||||
|
$height = $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
// Cropping data (for original image size). Default values, 0 and null,
|
// Cropping data (for original image size). Default values, 0 and null,
|
||||||
// imply no cropping and with preserved aspect ratio (per axis).
|
// imply no cropping and with preserved aspect ratio (per axis).
|
||||||
$cx = 0; // crop x
|
$cx = 0; // crop x
|
||||||
|
|
|
@ -44,6 +44,8 @@ function main()
|
||||||
initConversation();
|
initConversation();
|
||||||
fixupGroupURI();
|
fixupGroupURI();
|
||||||
fixupFileGeometry();
|
fixupFileGeometry();
|
||||||
|
deleteLocalFileThumbnailsWithoutFilename();
|
||||||
|
deleteMissingLocalFileThumbnails();
|
||||||
|
|
||||||
initGroupProfileId();
|
initGroupProfileId();
|
||||||
initLocalGroup();
|
initLocalGroup();
|
||||||
|
@ -451,4 +453,55 @@ function fixupFileGeometry()
|
||||||
printfnq("DONE.\n");
|
printfnq("DONE.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File_thumbnail objects for local Files store their own filenames in the database.
|
||||||
|
*/
|
||||||
|
function deleteLocalFileThumbnailsWithoutFilename()
|
||||||
|
{
|
||||||
|
printfnq("Removing all local File_thumbnail entries without filename property...");
|
||||||
|
|
||||||
|
$file = new File();
|
||||||
|
$file->whereAdd('filename IS NOT NULL'); // local files
|
||||||
|
|
||||||
|
if ($file->find()) {
|
||||||
|
// Looping through local File entries
|
||||||
|
while ($file->fetch()) {
|
||||||
|
$thumbs = new File_thumbnail();
|
||||||
|
$thumbs->file_id = $file->id;
|
||||||
|
$thumbs->whereAdd('filename IS NULL');
|
||||||
|
// Checking if there were any File_thumbnail entries without filename
|
||||||
|
if (!$thumbs->find()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// deleting incomplete entry to allow regeneration
|
||||||
|
while ($thumbs->fetch()) {
|
||||||
|
$thumbs->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printfnq("DONE.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete File_thumbnail entries where the referenced file does not exist.
|
||||||
|
*/
|
||||||
|
function deleteMissingLocalFileThumbnails()
|
||||||
|
{
|
||||||
|
printfnq("Removing all local File_thumbnail entries without existing files...");
|
||||||
|
|
||||||
|
$thumbs = new File_thumbnail();
|
||||||
|
$thumbs->whereAdd('filename IS NOT NULL'); // only fill in names where they're missing
|
||||||
|
// Checking if there were any File_thumbnail entries without filename
|
||||||
|
if ($thumbs->find()) {
|
||||||
|
while ($thumbs->fetch()) {
|
||||||
|
if (!file_exists(File_thumbnail::path($thumbs->filename))) {
|
||||||
|
$thumbs->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printfnq("DONE.\n");
|
||||||
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user