Pull to refresh

Как я exif данные из JPEG вытаскивал и в БД запихивал (а потом доставал)

Reading time7 min
Views4.8K
Задача была такова: Есть общая папка с изображениями, к каждому из которых проставлены ключевые слова (определяющие конкретного человека/организацию). Необходимо получать перечень изображений с определенным тегом для последующего вывода галереи на странице этого человека.

Просьба для тех, кто будет читать, особенно с целью критиковать
Основная задумка статьи, помочь новичкам типа меня настроить один из частных случаев взаимодействия exif-php-mysql-php-html.
Я за объективную критику, если у вас есть рациональные предложения по улучшению кода, я с радостью их послушаю и приму к сведению.
Еще раз напомню, что это чуть ли не первое моё написание php-скрипта, где необходимо работать с БД на сервере, поэтому у меня действительно по-тупому всё сделано в одном скрипте.


Просьба не судить очень строго, я лишь начинающий в php и mysql, но основная проблема и цель написания данной статьи в том, что в поисках решения своей проблемы я перерыл очень много форумов и сайтов, но адекватного ответа так и не нашел.

В связи с тем, что изображений достаточно много и работать с ними в последствии надо с помощью скриптов/запросов, было принято решение заносить имена файлов и ключевые слова в БД, а после этого SQL-запросами получать необходимые данные.

Был выработан следующий алгоритм действий:

  1. Получить список файлов (*.jpeg);
  2. Циклом пройти по всем файлам и вытащить exif данные (а именно тег keywords);
  3. Занести имена файлов и ключевые слова в базу данных;
  4. Получение списка файлов по определенному тегу;
  5. Самое приятное — исправление ошибок.

Что происходит дальше пока не сильно важно, потому что основные проблемы родились именно на пункте 4.

Итак, разберемся что у нас происходит на каждом этапе:

1. Получить список файлов (*.jpeg)


Тут особо проблем не возникло, спасибо функции glob();

$filelist = glob("*.jpg"); // получаем список файлов (скрипт лежит в папке с файлами)

2. Вытащить exif данные (тег keywords)


Здесь тоже проблем особо не возникает. Единственный момент, для того, чтобы метатеги выводились в адекватном виде, то было бы хорошо, чтобы они были написаны латиницей.
Пара циклов, проверка, как говорится тяп-ляп и готово.

foreach ($filelist as $name) { // Перебираем файлы, имена заносим в переменную $name
    $exif = exif_read_data($name, 0, true); // Получаем метаданные из файла
    foreach ($exif as $key => $section) { // Циклично получаем все теги метаданных
      foreach ($section as $setting => $val) { // Получаем значение тега
        if ($key == "IFD0" && $setting == "Keywords") { // Ждем когда доберемся до тега keywords
          echo "$key.$name: $val<br />"; // Выводим полученное значение
        }
      }
    }
}

3. Занести полученную информацию в базу данных


Данная статья не про развертывание бд на сервере, поэтому сразу перейдём к делу.

Нам необходимо:

  • Подключиться к БД

     $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Подключение к БД с выводом ошибки, если подключение не удалось
    

  • Создать таблицу (если её нет)
    Я использовал поля Number (порядковый номер/id), filename (имя файла оригинального изображения) и keywords (ключевые слова)

    $sql_ins_first = "CREATE TABLE IF NOT EXISTS test (id int(11) NOT NULL AUTO_INCREMENT, filename varchar(255) CHARACTER SET utf8 NOT NULL, keywords varchar(255) CHARACTER SET utf8 NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1"; // Создание таблицы (если её нет)
    mysqli_query($con, $sql_ins_first); // Отправить запрос, который описан выше
    

  • Добавление записей в таблицу

    $sql_ins_second = "INSERT INTO <table_name> (filename, keywords) VALUES ('$name', '$val')"; // Запрос на добавление записей в таблицу (на каждой итерации цикла)
    if (mysqli_query($con, $sql_ins_second)) { // Проверка выполнения запроса
        echo "$name - New record created successfully<br />"; // Информация успешно занесена в базу
    } else {
        echo "Error: " . $sql_ins_second . "<br>" . $con->error; // Ошибка записи данных в БД
    }
    

4. Получение списка файлов по определенному тегу


Здесь начинается самое интересное, потому что именно отсюда мы плавно втекаем в ошибки, которые устраняются не совсем очевидным образом

Привожу сразу пример моей функции с комментариями

function select_sql($key) // На вход подаём тег, который нам нужен для вывода конкретного набора файлов 
{
    /* Запрос: Выбрать поле "filename" из таблицы "test",
    где символьная строка "keywords" соответствует шаблону '%$key%' */
    $sql_select = "SELECT filename FROM test WHERE keywords LIKE '%$key%'";
    $result = mysqli_query($con, $sql_select); // Заносим результат в переменную

    /* извлечение ассоциативного массива */
    while ($row = $result->fetch_assoc()) { // Обрабатываем все полученные записи в результате нашей выборки
        echo "<div><p>".$row['filename']."</p><img src=\"".$row['filename']."\" style=\" width: 200px\" /></div>"; // Вывод на страницу имени файла и превью нашего изображения
    }
}



А перед тем, как мы перейдём к следующему пункту, я скажу, что если собрать этот код воедино, то мы получим проблему (возникает не у всех, если сервер настроен правильно со всех сторон, но это не точно), которая решается не совсем тривиально (как мне кажется).

У меня получилась следующая ситуация.

Полученная строка ключевых слов к изображениям в формате ASCII, подаваемая на вход переменная с нужным тегом также в формате ASCII, но при отправке запроса (не важно, через скрипт или через PhpMyAdmin) на выходе получаем NULL.

Попробовав множество вариантов (среди них обязательно был вариант с использованием utf8_encode(), который, кстати, не работал).

5. Самое приятное — исправление ошибок


Первое, что предлагает нам интернет и люди, не до конца разобравшиеся с документацией, это проверить «А склеили ли вы переменную с запросом?», «А нет ли у вас лишних пробелов?», «А правильно у вас собирается запрос?», «А что у вас за БД?» и т.д. т.п

По факту, мы просто получили ситуацию, когда всё работает (теги записались в БД, запрос собирается правильно, но на выходе мы всё равно имеем пустую выборку).
Логически приходим к выводу, что проблема в кодировке… Но что делать, если стандартная функция utf8_encode() не работает?

Скажу честно, я перепробовал безумно много вариантов, но в итоге у меня родилась идея «с промежуточной кодировкой» — я сначала попробовал перевести из ASCII в другую кодировку, а потом уже в UTF-8, но снова ничего не получалось.
Здесь я почуял неладное, а есть ли какой-то список кодировок и есть ли у меня там что-либо?

Немного погуглив, нашел и попробовал установить в начале скрипта mb_detect_order(«eucjp-win,sjis-win,UTF-8»); и сразу же получил кодировку всех строк в eucjp-win. Осознав, что можно попробовать переставить utf-8 на первое место, у меня всё пошло как по маслу, но было очень важно после считывания метаданных из картинок — экранировать символы.

Представляю вашему вниманию итоговый скрипт:

<?php
  mb_detect_order("UTF-8,ascii,eucjp-win,sjis-win"); // Установка списка кодировок

  $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Коннект к БД

  $sql_ins_first = "CREATE TABLE IF NOT EXISTS test (id int(11) NOT NULL AUTO_INCREMENT, filename varchar(255) CHARACTER SET utf8 NOT NULL, keywords varchar(255) CHARACTER SET utf8 NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1";
  mysqli_query($con, $sql_ins_first); // Проверка наличия/создание таблицы

  $filelist = glob("*.jpg"); // Получение списка файлов в директории с расширением *.jpg

  foreach ($filelist as $name) { // Цикл по файлам
    $exif = exif_read_data($name, 0, true); // Считывание метаданных
    foreach ($exif as $key => $section) { // Цикличный проход по тегам
      foreach ($section as $setting => $val) { // Получение значения тега
        if ($key == "IFD0" && $setting == "Keywords") { // Проверка соответствия нужного нам тега (keywords)
          $val1 = mysqli_real_escape_string($con, $val); // Экранирование символов в полученной строке значений тега Keywords
          $val1 = str_ireplace(";", "; ", $val1); // Заменяем ";" на "; "
/* При проверке текущего состояния строки после экранирования
мы увидим между всеми символами "\0",
а это значит, что нам необходимо их удалить,
чтобы получить адекватную строку */
          $val1 = str_ireplace("\\0", "", $val1); // Заменяем "\0" на "" (пустую строку) и не забываем экранировать слеш

          $sql_ins_second = "INSERT INTO test (filename, keywords) VALUES ('$name', '$val1')"; // Запрос на добавление записей в БД

/* Проверка записи данных в БД */
          if (mysqli_query($con, $sql_ins_second)) {
               echo "$name - New record created successfully<br />";
          } else {
               echo "Error: " . $sql_ins_second . "<br>" . $con->error;
          }
        }
      }
    }
  }
$con->close(); // Закрыть подключение

/* Объявление функции select_sql() */
function select_sql($key)
{
  $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Коннект к БД
  $sql_select = "SELECT filename FROM test WHERE keywords LIKE '%$key%'"; // Запрос на выборку данных по определенному тегу
  $result = mysqli_query($con, $sql_select); // Заносим результат в переменную

  while ($row = $result->fetch_assoc()) { // извлечение ассоциативного массива
    echo "<div><p>".$row['filename']."</p><img src=\"".$row['filename']."\" style=\" width: 200px\" /></div>"; // Вывод названий файлов и изображений на страницу
  }
$con->close(); // Закрыть подключение
}
$string_key = utf8_encode('test'); // Преобразование переменной в кодировку UTF-8 (на всякий случай)
select_sql($string_key); // Вызов функции с нужным тегом
?>

Скорее всего, второе подключение нам не нужно, потому что по хорошему, его подсасывать с другого php-скрипта, но это в будущем, потому что статья ориентировалась на другой аспект.



И, как говорится:
Спасибо за внимание! Я готов выслушать ваши вопросы!
Tags:
Hubs:
Total votes 9: ↑5 and ↓4+3
Comments60

Articles