1

I've got a directory of 'source' images that I'm saving out multiple resized files. I'd like all jpg and png files in a given directory to be resized and saved with new names based on their original file names (a la filename-small.jpg, filename-medium.jpg, etc.) I'm using ImageMagick.

The regex stuff to get all the files was found in another question here on SO and has helped—but I don't totally get what all is going on here.

What I have currently plops the original file extension in the middle of the file name. While I understand why this is happening, I'm not sure the best approach to fix this.

How can I get this to to be filename-large.jpg instead of filename.jpg-large.jpg?

Here is what I have currently:

<?php

    $directory = new RecursiveDirectoryIterator('source/assets/uploads/test');
    $iterator = new RecursiveIteratorIterator($directory);
    $images = new RegexIterator($iterator, '/^.+(.jpe?g|.png)$/i', RecursiveRegexIterator::GET_MATCH);

    $widths = [
        'small' => 400,
        'medium' => 800,
        'large' => 1000,
        'xlarge' => 1600,
    ];
    
    foreach($images as $image => $value) {

        // Set the original filename w/o the extension
        $originalFilename = $image;

        // set the extension based on the original
        // ideally conform the extension jpeg to jpg
        $originalExtension = '.jpg';
        
        // create a new Imagick instance
        $newImage = new Imagick($image);

        // strip out some junk
        $newImage->stripImage();

        // write a new file for each width 
        // and name it based on the original filename
        foreach ($widths as $key => $value) {
            // using 0 in the second $arg will keep the same image ratio
            $newImage->resizeImage($value,0, imagick::FILTER_LANCZOS, 0.9);
            $newImage->writeImage($originalFilename . '-' . $key . $originalExtension);
        }
    }
Nathan Gross
  • 98
  • 10
  • Try `/^.+\.(?<!-small.|-xlarge.|-large.|-medium.)(jpe?g|png)$/i` pattern, to exclude all qualifying strings with `$widths` suffixes. – Wiktor Stribiżew Feb 03 '21 at 17:58
  • Or, `/^(.+?)(?:-(?:small|x?large|medium))?\.(?:jpe?g|png)$/i` – Wiktor Stribiżew Feb 03 '21 at 18:17
  • @WiktorStribiżew I like where this is headed—this might help in the future when new images are added to the repo and I want to update only those. However—I could also see wanting to update 'large' to 1200 though. – Nathan Gross Feb 03 '21 at 18:51
  • Perhaps longterm would be better to just name the files based on the image size value rather than the abstracted key. If I need a new size, adding it would be less of a problem. – Nathan Gross Feb 03 '21 at 19:33

1 Answers1

1

Nathan,

You are capturing that extension with the parens. You want to capture the file name, not just the extension. You can use non-caputring parens ((?:)) for grouping the extension variations.

$images = new RegexIterator($iterator, '/^(.+)\.(?:jpe?g|png)$/i', RecursiveRegexIterator::GET_MATCH);

I also escaped the literal dots (.) before the extensions.

Alternatively, you could just split up the extension and filename from the $image variable:

preg_match('/^(.+)\.([^.]+)$/', $image, $matches);
// Set the original filename w/o the extension
$originalFilename = $matches[1];
// Set the original extension
$originalExtension = $matches[2];

Lastly, FWIW, were I to do this I would not use the RegexIterator but tather just iterate the directory and just use preg_match only on each filename.

$directory = dir('source/assets/uploads/test');
while(false !== ($entry = $directory->read())) {
    $result = preg_match('/^(.+)\.(jpe?g|png)$/i', $entry, $matches);
    if($result === false) {
      die('ERROR: `preg_match` failed on '.$entry);
    } else if($result === 0) {
      // not an image file
      continue;
    }
    // Set the original filename w/o the extension
    $originalFilename = $matches[1];
    // Set the original extension
    $originalExtension = $matches[2];

    // ... do the other things on the file
}

Refs:

EDIT:

For named references inside of $matches you can use this modified regexp:

preg_match('/^(?P<filename>.+)\.(?P<extension>jpe?g|png)$/i', $entry, $matches);

The ?P<key> is a "placeholder" that assigns key as a reference to that caputred match.

You are still going to get $matches back as an array, not an object, but then you can access the values with that placeholder as the index.

// Set the original filename w/o the extension
$originalFilename = $matches['filename'];
// Set the original extension
$originalExtension = $matches['extension'];

FWIW, the numeric indexes are still there too.

Karl Wilbur
  • 5,898
  • 3
  • 44
  • 54
  • 1
    This is very helpful—thank you! To make this even more clear for my inexperienced programming brain, is it possible to capture the data in the array in a more key/value type of way? So rather than $matches[2], something like $matches->extension? – Nathan Gross Feb 03 '21 at 18:48