5

Нужна помощь - сканы PDF

Нужна помощь - сканы PDF

Товарищи, помогите.

Знает ли кто-нибудь, использует ли кто-нибудь бесплатную простенькую программу по типу WinScan2PDF, с помощью которой можно отсканированные страницы сохранять не в один огромный файл pdf со всеми отсканированными страницами, или каждую отдельную страницу в отдельные файлы pdf/png/jpeg, что собственно WinScan2PDF и умеет делать, а сохранять из общей кучи страницы группами по две страницы в отдельные файлы pdf, по три страницы в отдельные файлы pdf и так далее.

Задача: есть стопка писем/уведомлений для рассылки клиентам в количестве 250 штук. Все письма имеют по 2 страницы. Для рассылки писем по электронной почте их необходимо отсканировать, затратив на это как можно меньше времени.

На МФУ Kyocera можно конечно сканировать каждое письмо отдельно (сейчас именно так и делаю), но для этого необходимо стоять возле МФУ и тупо механически подкладывать каждое новое письмо и нажимать кнопочки "повтор" и "старт". По времени это занимает примерно один час.
Однако на данном МФУ, с помощью программы WinScan2PDF можно отсканировать всю пачку сразу, получив при этом как вариант 500 файлов png. Для сканирования всей пачки потребуется 20-30 минут времени (потому что в процессе отсутствуют паузы на подкладывание новых писем и нажатие кнопочек), при этом не нужно стоять и тратить время у самого МФУ - можно заняться какой-нибудь параллельной работой, пока МФУ сканирует. Но в таком случае требуется какая-то программа, или макрос в excel (сам писать макросы я пока-что не умею) или какой-то иной метод, с помощью которого можно было бы быстро объединить 500 файлов png группами по два файла по порядку, что бы получились 250 двухстраничных файлов pdf,
Ну или если это 250 писем по 4 страницы, то сохранить из 1000 сканов png 250 файлов pdf по 4 страницы, и тому подобные варианты.

Кто знает готовое решение, подскажите пожалуйста.

Дополнено:
Огромное спасибо @almelion за помощь! Готовое решение находится в комментах по ссылке: #comment_351913358

Вы смотрите срез комментариев. Показать все
2
Автор поста оценил этот комментарий

одна минута в гемини. не устаю офигевать от нейронок.


Конечно, я напишу для вас скрипт на Python, который будет конвертировать PNG-файлы в PDF. Вы сможете указать, сколько PNG-изображений должно быть объединено в один PDF-файл.

Вот как будет работать скрипт:

Он найдет все файлы .png в указанной вами папке, имена которых состоят из цифр (например, 1.png, 2.png, 10.png).

Отсортирует найденные файлы по числовым значениям их имен.

Запросит у вас, сколько PNG-файлов вы хотите объединить в один PDF-документ.

Сгруппирует PNG-файлы в соответствии с вашим выбором и создаст PDF-файлы. Например, если вы выберете объединять по 3 PNG, то файлы 1.png, 2.png, 3.png будут сохранены в 1.pdf; 4.png, 5.png, 6.png — в 2.pdf, и так далее.

Для работы скрипта потребуется установить библиотеку Pillow. Если она у вас не установлена, вы можете сделать это с помощью pip: pip install Pillow

Как использовать скрипт:

Сохраните код в файл с расширением .py (например, png_to_pdf.py).

Откройте терминал или командную строку.

Перейдите в директорию, где вы сохранили файл.

Запустите скрипт командой: python png_to_pdf.py

Скрипт запросит у вас путь к папке с PNG-файлами.

Затем он спросит, куда сохранить resultante PDF-файлы (можно указать ту же папку, оставив поле пустым и нажав Enter).

И, наконец, он спросит, сколько PNG-файлов вы хотите видеть на одной странице PDF.

Например, если у вас есть файлы 1.png, 2.png, 3.png, 4.png, 5.png и вы укажете объединять по 2 файла, то будут созданы:

1.pdf (содержащий 1.png, 2.png)

2.pdf (содержащий 3.png, 4.png)

3.pdf (содержащий 5.png)

в архиве скрипт, готовый к работе экзешник (скомпилированный скрипт, если нет питона и не хочется возиться - запускаем его) и образцы файлов.
работает.

https://wdfiles.ru/2a3U2

раскрыть ветку (20)
0
Автор поста оценил этот комментарий
А exe для 64 битной винды? У меня винда 32 битная и говорит: "невозможно запустить это приложение на вашем пк, обратитесь к издателю приложения"
раскрыть ветку (18)
1
Автор поста оценил этот комментарий

наверное)
у меня 64, скомпилять для х32 не смогу, т.к. вся установленная питоновская база также 64-х битная.
либо поставьте питон (в целом, это полезно и несложно), библиотечку Pillow и запускайте сам скрипт, либо поищите онлайн-компиляторы для питона, если таковые существуют, и загоните туда скрипт

раскрыть ветку (17)
0
Автор поста оценил этот комментарий
Я так понимаю нейросеть поможет мне написать какой-нибудь макрос для проверки трек-номера почты России для отслеживания внутри excel-таблички) Тоже нужная вещь)
раскрыть ветку (4)
0
Автор поста оценил этот комментарий

насчет получения статуса трека - не уверен, т.к. для этого нужен api для работы с сервисами отслеживания (например, с тем же сайтом почты), а это вещь немного специфичная и нейронка может о ней не знать, но в целом сам алгоритм - да, напишет

раскрыть ветку (3)
0
Автор поста оценил этот комментарий
Ну там по большому счету в HTML коде нужно ведь найти поле для ввода трек-номера, а потом на обновленной странице из этого кода вычленить результат. Алекс-Гайвер вроде так делал для погодной станции на arduino и для оперативного отображения подписчиков на you-tube.

На всяких форумах я находил ветки обсуждений как раз именно такого метода - excel табличка с номерами, напротив которых автоматически проставляются результаты отправки, но решения устаревшие, так как дизайн сайта Почты России с тех пор уже несколько раз менялся.

Я сам конечно в этом почти ничего не понимаю, но так или иначе вопрос всё таки со временем придется решить.
раскрыть ветку (2)
0
Автор поста оценил этот комментарий

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

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
Ну вот для этого придется это дело мне будет освоить, что бы я макрос мог сам подправить)) Есть чем заняться)
0
Автор поста оценил этот комментарий
Да вот блин надо уже питон осваивать, уже 15 лет его все нахваливают, а я всё торможу.
Мой уровень сейчас это школьный basic, вожусь сейчас с формулами в excel, использую чужие надстройки, макросы и отдельные VBA модули, хотя в коде VBA ничего не понимаю - по инструкции вставляю куда нужно и работает=), ну и пару раз использовал чужой код на Arduino, меняя внутри кода примитивные настройки.
раскрыть ветку (11)
1
Автор поста оценил этот комментарий

пробуйте.
нужен интернет и не так удобно, но вроде все еще работает)

https://wdfiles.ru/O90a

а питон поучить полезно, даже просто в рамках понимания, как программы вообще работают

раскрыть ветку (10)
0
Автор поста оценил этот комментарий
Интернет кстати не нужен, html-код в офлайне работает, этим он и хорош!
раскрыть ветку (4)
1
Автор поста оценил этот комментарий

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


ну да,
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

можно открыть скрипты по ссылкам в виде текста и скопипастить в целевой хтмл, тогда будет полностью локальный вариант)

раскрыть ветку (3)
0
Автор поста оценил этот комментарий

Ну я при первом запуске HTML, в котором каждый отдельный pdf надо отдельно сохранять, запустил с выключенным wifi специально для этого (хотя может и забыл). Сейчас, перезагружу комп и проверю еще раз.
...
Перезагрузил, работает без подключения к wifi. Ок попробую сейчас скинуть HTML на другой комп с помощью флешки.
...
Запустил на другом компе, предварительно выключив интернет. HTML запускается, но не срабатывает выбор нужных файлов. Ну и соответственно ничего не сохраняется.
Включил интернет, прога заработала как надо мгновенно.
Перезагрузил комп с выключенным интернетом, снова запустил прогу, и она заработала без интернета.

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

Ну и перекинул я прогу на рабочий комп, проверил - работает. Не знаю, откуда он подтягивает библиотеки, но главное Касперский на рабочем компе не ругается. Так что всё супер.

можно открыть скрипты по ссылкам в виде текста и скопипастить в целевой хтмл, тогда будет полностью локальный вариант)
Хммм, ну это я тогда чуть по позже для себя сделаю, офлайн версия в любом случае пригодится.

раскрыть ветку (2)
1
Автор поста оценил этот комментарий

открываем хтмл в блокноте.
ищем эти две строчки в начале файла
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

заменяем на
<script>здесь будет содержимое ссылки 1</script>

<script>здесь будет содержимое ссылки 2</script>


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

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
Сохранил. Работает, значит ничего не сломал =)
Но проверить, очевидно я сейчас уже не смогу, так как для этого понадобится третий компьютер, которого под рукой у меня нет (я то в отпуске).
Спасибо за то, что подсказал как правильно скрипты вставить!
0
Автор поста оценил этот комментарий
Хмм, для 250 писем придётся 250 раз нажать сохранить (я в принципе для подобного прикола заказал соленоиды и микросхемы с таймером, что бы сделать робота, который сам будет на кнопку мыши нажимать (из-за того, что OutLook ругается на то, что внешняя программа (надстройка из excel) пытается письма отправить, и на каждое отправление нужно нажимать - "разрешить").
Дружище, за html-ку и за то, что заморочился - спасибо преогромнейшее, она мне на первое время очень пригодится.
Мне неудобно просить, но есть ли возможность сохранение результата преобразования изменить с двухсот пятидесяти отдельных скачиваний изменить на скачивание, например, одного zip архива?
раскрыть ветку (4)
2
Автор поста оценил этот комментарий

хм, действительно

<!DOCTYPE html>

<html lang="ru">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Конвертер PNG в PDF с ZIP</title>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

<style>

body {

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

margin: 0;

padding: 20px;

background-color: #f0f2f5;

color: #1c1e21;

display: flex;

justify-content: center;

align-items: flex-start;

min-height: 100vh;

}

.container {

background-color: #ffffff;

padding: 25px;

border-radius: 8px;

box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 8px 16px rgba(0,0,0,0.1);

width: 100%;

max-width: 600px;

}

h1 {

color: #007bff;

text-align: center;

margin-bottom: 25px;

}

label {

display: block;

margin-top: 15px;

margin-bottom: 8px;

font-weight: 600;

color: #4b4f56;

}

input[type="file"], input[type="number"], button {

padding: 12px;

margin-top: 5px;

border-radius: 6px;

border: 1px solid #ccd0d5;

width: 100%;

box-sizing: border-box;

font-size: 16px;

}

input[type="file"] {

cursor: pointer;

}

button {

background-color: #007bff;

color: white;

cursor: pointer;

margin-top: 25px;

font-weight: bold;

border: none;

}

button:hover {

background-color: #0056b3;

}

button:disabled {

background-color: #a0c7e8;

cursor: not-allowed;

}

#status {

margin-top: 20px;

font-style: italic;

color: #606770;

padding: 10px;

background-color: #f0f8ff;

border-radius: 4px;

border: 1px solid #cfe2f3;

min-height: 20px;

word-wrap: break-word;

}

#selectedFilesDisplay {

margin-top:10px;

max-height: 200px;

overflow-y: auto;

border: 1px solid #ccd0d5;

padding: 10px;

border-radius: 6px;

background-color: #f8f9fa;

}

#fileList {

list-style-type: none;

padding-left: 0;

margin: 0;

}

.file-list-item {

padding: 6px 0;

border-bottom: 1px solid #e9ecef;

font-size: 14px;

color: #333;

}

.file-list-item:last-child {

border-bottom: none;

}

.loader {

border: 4px solid #f3f3f3;

border-top: 4px solid #007bff;

border-radius: 50%;

width: 20px;

height: 20px;

animation: spin 1s linear infinite;

display: none;

margin: 10px auto;

}

@keyframes spin {

0% { transform: rotate(0deg); }

100% { transform: rotate(360deg); }

}

</style>

</head>

<body>

<div class="container">

<h1>Конвертер PNG в PDF (с ZIP)</h1>

<label for="pngFiles">1. Выберите PNG файлы (например, 1.png, 2.png, ...):</label>

<input type="file" id="pngFiles" accept=".png" multiple>

<div id="selectedFilesDisplay">

<p style="margin-top:0; margin-bottom: 5px; font-weight:500;">Выбранные и отсортированные файлы:</p>

<ul id="fileList">

<li style="color:#888;">Файлы не выбраны</li>

</ul>

</div>

<label for="pagesPerPdf">2. Количество PNG на один PDF:</label>

<input type="number" id="pagesPerPdf" value="1" min="1">

<button id="convertButton" onclick="convertToPdfAndZip()">3. Конвертировать и скачать ZIP</button>

<div class="loader" id="loader"></div>

<div id="status">Готов к работе.</div>

</div>

<script>

// Ensure jsPDF and JSZip are loaded from window object

const { jsPDF } = window.jspdf;

// JSZip will be available as `window.JSZip` or just `JSZip`

let sortedPngFiles = []; // Stores File objects after sorting

document.getElementById('pngFiles').addEventListener('change', handleFileSelection);

function handleFileSelection(event) {

const files = Array.from(event.target.files);

const statusDiv = document.getElementById('status');

const fileListUl = document.getElementById('fileList');

fileListUl.innerHTML = '';

statusDiv.textContent = 'Анализ файлов...';

const numericPngRegex = /^(\d+)\.png$/i;

sortedPngFiles = files

.filter(file => numericPngRegex.test(file.name))

.map(file => ({

fileObject: file,

number: parseInt(numericPngRegex.exec(file.name)[1], 10)

}))

.sort((a, b) => a.number - b.number)

.map(item => item.fileObject);

if (sortedPngFiles.length === 0) {

statusDiv.textContent = 'Не найдено PNG файлов с именами вида "число.png" (например, 1.png). Пожалуйста, выберите другие файлы.';

fileListUl.innerHTML = '<li style="color:#888;">Подходящие файлы не найдены</li>';

return;

}

statusDiv.textContent = `Найдено и отсортировано ${sortedPngFiles.length} PNG файлов. Готов к конвертации.`;

sortedPngFiles.forEach(file => {

const listItem = document.createElement('li');

listItem.className = 'file-list-item';

listItem.textContent = file.name;

fileListUl.appendChild(listItem);

});

}

async function convertToPdfAndZip() {

const pagesPerPdfInput = document.getElementById('pagesPerPdf');

const statusDiv = document.getElementById('status');

const convertButton = document.getElementById('convertButton');

const loader = document.getElementById('loader');

if (sortedPngFiles.length === 0) {

statusDiv.textContent = 'Пожалуйста, сначала выберите PNG файлы, соответствующие формату "число.png".';

return;

}

const pagesPerPdf = parseInt(pagesPerPdfInput.value, 10);

if (isNaN(pagesPerPdf) || pagesPerPdf < 1) {

statusDiv.textContent = 'Ошибка: Количество страниц на PDF должно быть положительным числом.';

return;

}

if (typeof JSZip === 'undefined') {

statusDiv.textContent = 'Ошибка: Библиотека JSZip не загружена. Пожалуйста, проверьте интернет-соединение.';

console.error("JSZip is not loaded.");

return;

}

convertButton.disabled = true;

loader.style.display = 'block';

statusDiv.textContent = 'Начинается конвертация... Это может занять некоторое время.';

let pdfCounter = 1;

const generatedPdfBlobs = []; // To store { name: '1.pdf', data: blob }

try {

for (let i = 0; i < sortedPngFiles.length; i += pagesPerPdf) {

const chunkOfFiles = sortedPngFiles.slice(i, i + pagesPerPdf);

if (chunkOfFiles.length === 0) continue;

statusDiv.textContent = `Создание PDF ${pdfCounter} (Файлы: ${chunkOfFiles.map(f => f.name).join(', ')})...`;

const pdf = new jsPDF({

orientation: 'portrait',

unit: 'mm',

format: 'a4'

});

let firstImageInPdf = true;

for (const file of chunkOfFiles) {

try {

const imageDataUrl = await readFileAsDataURL(file);

const imgProps = await getImageProperties(imageDataUrl);

if (!firstImageInPdf) {

pdf.addPage();

}

firstImageInPdf = false;

const pageWidth = pdf.internal.pageSize.getWidth();

const pageHeight = pdf.internal.pageSize.getHeight();

let imgOnPageWidth = imgProps.width;

let imgOnPageHeight = imgProps.height;

const pageAspectRatio = pageWidth / pageHeight;

const imageAspectRatio = imgProps.width / imgProps.height;

const marginFactor = 0.95;

if (imageAspectRatio > pageAspectRatio) {

imgOnPageWidth = pageWidth * marginFactor;

imgOnPageHeight = imgOnPageWidth / imageAspectRatio;

} else {

imgOnPageHeight = pageHeight * marginFactor;

imgOnPageWidth = imgOnPageHeight * imageAspectRatio;

}

const x = (pageWidth - imgOnPageWidth) / 2;

const y = (pageHeight - imgOnPageHeight) / 2;

pdf.addImage(imageDataUrl, 'PNG', x, y, imgOnPageWidth, imgOnPageHeight);

statusDiv.textContent = `Добавлен ${file.name} в PDF ${pdfCounter}...`;

} catch (error) {

console.error(`Error processing file ${file.name} for PDF ${pdfCounter}:`, error);

statusDiv.textContent = `Ошибка при обработке ${file.name} для PDF ${pdfCounter}: ${error.message}.`;

// Continue to next file or PDF

}

await new Promise(resolve => setTimeout(resolve, 30)); // UI update pause

}

// Store PDF blob instead of saving immediately

const pdfBlob = pdf.output('blob');

generatedPdfBlobs.push({ name: `${pdfCounter}.pdf`, data: pdfBlob });

statusDiv.textContent = `PDF ${pdfCounter}.pdf сгенерирован и готов к архивации.`;

pdfCounter++;

await new Promise(resolve => setTimeout(resolve, 100)); // UI update pause

}

if (generatedPdfBlobs.length > 0) {

statusDiv.textContent = 'Архивация PDF файлов...';

const zip = new JSZip();

generatedPdfBlobs.forEach(pdfFile => {

zip.file(pdfFile.name, pdfFile.data);

});

zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } })

.then(function(content) {

downloadBlob(content, "converted_pdfs.zip");

statusDiv.textContent = 'ZIP архив "converted_pdfs.zip" успешно создан и загружен.';

})

.catch(function (err) {

console.error("Error generating ZIP:", err);

statusDiv.textContent = 'Ошибка при создании ZIP архива: ' + err.message;

});

} else if (sortedPngFiles.length > 0) {

statusDiv.textContent = 'Не удалось создать PDF файлы для архивации. Проверьте консоль.';

} else {

statusDiv.textContent = 'Нет файлов для конвертации.'; // Should be caught earlier

}

} catch (e) {

console.error("An unexpected error occurred:", e);

statusDiv.textContent = "Произошла непредвиденная ошибка: " + e.message + ". Проверьте консоль.";

} finally {

convertButton.disabled = false;

loader.style.display = 'none';

}

}

function readFileAsDataURL(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader();

reader.onload = () => resolve(reader.result);

reader.onerror = (error) => reject(error);

reader.readAsDataURL(file);

});

}

function getImageProperties(imageDataUrl) {

return new Promise((resolve, reject) => {

const img = new Image();

img.onload = () => resolve({ width: img.width, height: img.height });

img.onerror = (error) => reject(error);

img.src = imageDataUrl;

});

}

function downloadBlob(blob, filename) {

const url = URL.createObjectURL(blob);

const a = document.createElement('a');

a.style.display = 'none'; // Hide the link

a.href = url;

a.download = filename;

document.body.appendChild(a);

a.click();

// Cleanup: remove element and revoke object URL

document.body.removeChild(a);

URL.revokeObjectURL(url);

}

</script>

</body>

</html>


раскрыть ветку (3)
1
Автор поста оценил этот комментарий
У тебя есть карта Сбера? Можно тебя чисто символически хотя бы отблагодарить, по номеру телефона?
Это очень крутая помощь!
Коммент с номером телефона через 30 секунд можешь сразу удалить
раскрыть ветку (2)
2
Автор поста оценил этот комментарий

не нужно)
все делал Gemini, я лишь тренировался в запросах к нему - сейчас изучаю эту тему, поэтому и взялся в качестве практики

раскрыть ветку (1)
1
Автор поста оценил этот комментарий
Ну всё равно, прям огромнейшее спасибо, очень круто!
Это решение поможет не только мне, но и моим коллегам.
Автор поста оценил этот комментарий

Не устаю офигевать от нейронок.
Как и я не устаю разочаровываться в людях (в своих коллегах), которые не могут и не хотят научиться формировать поисковые запросы в интернете для нахождения тех или иных решений.
Но нейросети - это конечно новый уровень - типа такой "не получается нагуглить готовое решение, к которому до тебя пришли другие специалисты? Не переживай, сейчас я сам (Gemini) создам его для тебя" =)

Вы смотрите срез комментариев. Чтобы написать комментарий, перейдите к общему списку

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества