Вопрос или проблема
Я ищу способ предоставить настраиваемую/конфигурируемую рандомизацию для инструмента слайд-шоу, такого как impressive
или feh
(что-то легковесное, что я могу запускать автоматически при старте). В основном, мне нужно взвесить определенные каталоги в зависимости от того, сколько фотографий в каждом из них.
Например, если есть два каталога, один из которых содержит 90 фотографий, а другой только 10, время, проведенное в каждом каталоге, должно зависеть не только от общего количества фотографий, но и от того, сколько каталогов существует. Таким образом, в какой-то степени от 10% до 50% времени вы можете смотреть на одну из 10, а оставшиеся 50-90% времени – на одну из 90, согласно какой-то пользовательской логике/математике, а не просто имея деление 10/90. По сути, я хочу установить нижний предел смещения к какому-либо данному каталогу, но при этом сохранить некоторое подобие относительных пропорций каждого.
Я, возможно, смогу сделать это каким-то самодельным образом, но, возможно, кто-то знает о способе сделать это “из коробки”. Есть ли что-нибудь подобное или что-то, что может достичь подобного результата? Спасибо!
Правка:
Следуя отличному совету ниже, я настроил следующую функцию рандомизации, добавляя смещение к каталогам с большим количеством фотографий, добавляя коэффициент, который умножается на текущее значение вероятности, чтобы определить, сколько из нее освобождается. Затем, вместо того чтобы перераспределять вероятности согласно фиксированным величинам, я решил сделать это в соответствии с существующими “ставками” на остальное. Полученная динамика такова, что те, кто имеет смещение к меньшим потерям, предпочитают получать освобожденную вероятность и лучше могут поддерживать свои запасы, просто имея больше, тем самым привлекая больше того, что освобождено в другом месте, но не так, чтобы это никогда не давалось другим каталогам, которые время от времени приходят к своему, сравнительно более слабому смещению. (Результат расчета смещения пропорционален процентному вкладу каталога в общее количество фотографий, но нормализован вокруг значения для равной доли, которая была бы получена, когда все каталоги вносят равный вклад, так что те, кто выше этого порога, представлены несколько меньше, чем они были бы в противном случае, а те, кто ниже, – несколько больше.)
К более важному вопросу о том, как использовать эту функциональность в отношении утилиты изображения/слайд-шоу: она взаимодействует с каталогом, содержащим фотографии, которые активно используются (feh
, единственный, который я смог заставить работать (так что не fbi
)) и обновляет свой взгляд на каталог по мере изменения файлов (так что не impressive
). Есть возможность активировать последовательность фотографий одновременно, от того времени, когда я думал, что смогу использовать impressive
и сделать переходы, но это теперь кажется маловероятным. Вся выборка каталога и фотографий и перераспределение вероятностей происходит в этом скрипте Python, один раз для каждого изменения изображения в слайд-шоу (и это самокорректирующееся по системным часам, с надеждой, что feh
не потеряет или не получит время, чтобы они в конце концов не расхождились). Я надеялся, что, возможно, есть официальный способ сделать это, но не уверен, что он существует. В любом случае, похоже, что это работает. Если кто-то это увидит и обидится на зрелище моего Python или оболочки, не стесняйтесь указать мне на ошибки, если хотите.
У меня есть еще один компонент, который нужно решить, а именно, чтобы он выполнялся автоматически после старта и установления сетевого соединения, но это не так актуально для вопроса, который я задавал. Но в любом случае вот все на данный момент:
# Слайд-шоу
import os
from datetime import datetime, timedelta
from random import random
import shutil
import time
src_dir="./photos/src"
active_dir="./photos/active"
active_photo_interval = 24
valid_exts = ['.' + e for e in ['jpg', 'jpeg', 'png']]
photos = [[f.path for f in os.scandir(d.path) if f.is_file and os.path.splitext(f.path)[-1] in valid_exts] for d in os.scandir(src_dir) if d.is_dir()]
photos = [p for p in photos if len(p)]
total_members = len(photos)
total_photos = sum(len(p) for p in photos)
members = [
{
'prob': [
i/len(photos)
, (i + 1)/len(photos)
]
, 'bias': 1 - (len(photos[i])/total_photos - (len(photos[i])/total_photos - 1/total_members)*((total_members - 1)/total_members)**total_members)
, 'photos': [
{
'prob': [
k/len(photos[i])
, (k + 1)/len(photos[i])
]
, 'path': photos[i][k]
} for k in range(0, len(photos[i]))
]
} for i in range(0, len(photos))
]
def get_image():
mrand = random()
midx = next(i for i,m in enumerate(members) if m['prob'][0] <= mrand < m['prob'][1])
member = members[midx]
if len(members) > 1:
freed_prob = (member['prob'][1] - member['prob'][0]) * member['bias']
total_standing_prob = sum(m['prob'][1] - m['prob'][0] for i,m in enumerate(members) if i != midx)
for i,m in enumerate(members):
standing_prob = m['prob'][1] - m['prob'][0]
prob_stake = standing_prob/total_standing_prob
m['prob'][0] = 0 if i == 0 else members[i - 1]['prob'][1]
m['prob'][1] = 1 if i + 1 == len(members) else m['prob'][0] + standing_prob + (freed_prob * prob_stake if i != midx else -freed_prob)
photos = member['photos']
prand = random()
pidx = next(i for i,p in enumerate(photos) if p['prob'][0] <= prand < p['prob'][1])
photo = photos[pidx]
if len(photos) > 1:
freed_prob = photo['prob'][1] - photo['prob'][0]
share_freed_prob = freed_prob/(len(photos) - 1)
for i,p in enumerate(photos):
standing_prob = p['prob'][1] - p['prob'][0]
p['prob'][0] = 0 if i == 0 else photos[i - 1]['prob'][1]
p['prob'][1] = 1 if i + 1 == len(photos) else p['prob'][0] + standing_prob + (share_freed_prob if i != pidx else -freed_prob)
return photo['path']
active_photos_max = 1
active_photos = []
if os.path.isdir(active_dir):
active_photos = [f.path for f in os.scandir(active_dir) if f.is_file]
else:
os.mkdir(active_dir)
time_next = datetime.now()
while True:
time_next = time_next + timedelta(seconds=active_photo_interval)
active_photos.insert(0, get_image())
shutil.copy(active_photos[0], active_dir)
old_photos = [os.path.join(active_dir, p.split("https://unix.stackexchange.com/")[-1]) for p in active_photos[active_photos_max:]]
for p in old_photos: os.remove(p)
active_photos = active_photos[:active_photos_max]
while datetime.now() < time_next:
time.sleep(1)
#!/usr/bin/env bash
# Основной
SYNC_INTERVAL=$(( 24 * 60 * 60 ))
while true
do
python ./slideshow.py &
sleep $SYNC_INTERVAL
kill $!
bash ./sync.sh
done
#!/usr/bin/env bash
# Запуск/стартап
. ./set_vars.sh
bash ./sync.sh
bash ./main.sh &
sleep 2
feh -R 24 -D 24 -Z -F -Y $ACTIVE_DIR
Как человек, который преподавал теорию вероятностей / комбинаторику, у меня есть замечания по тому, что вы описали выше (я думаю, это не совсем продумано; по крайней мере, это не описано ясно).
Тем не менее,
согласно какой-то пользовательской логике/математике,
и
но, возможно, кто-то знает о способе сделать это “из коробки”.
Либо это пользовательское решение, либо “из коробки” 🙂
Серьезно, я думаю, что то, что вы описываете, так индивидуально, что никто другой не испытывал в этом необходимости раньше, так что вам придется реализовать это самим.
То, что вы описываете, – это довольно сложный алгоритм, потому что “сеансы просмотра” не бесконечно длинные. Так что в рамках истинно бесконечно запоминающейся случайной последовательности, какая-то папка, которая появляется 10% времени, может просто не появиться в первых 200 слайдах – это было бы вполне нормально, и вы можете рассчитать вероятность этого.
Также так же вероятно, что одно и то же изображение будет выбрано дважды, как и то, что два разных изображения с одинаковой вероятностью будут выбраны.
Так что, я думаю, что вам не нужна действительно случайная последовательность, а такая, которая на каждом “следующем” изображении принимает (случайное!) решение, но с вероятностями, ограниченными слайдами, которые произошли в пределах “длины памяти” (т.е. все изображения до, или последние 100 изображений и т.п.).
Такие алгоритмы существуют для функции случайного выбора музыкальных проигрывателей (потому что то, что действительно является независимо случайным, довольно отличается от того, что слушатель считает “хорошей” случайностью; даже очевидное “взять все песни и случайно переставить их” в практике довольно плохо, потому что люди не любят не только когда одна и та же песня воспроизводится дважды за короткий промежуток времени, но и две песни из одного альбома, идущие подряд, но в обратном порядке, и много таких вещей).
Тем не менее, хороший базовый алгоритм для реализации в C++, Python, Rust или любом другом языке, который имеет эффективные ассоциативные массивы (так что, определенно, не оболочка):
- считать все имена файлов-кандидатов (т.е. обойти все каталоги, чтобы собрать все файлы) и все имена каталогов.
- назначить каждому из них вероятность в соответствии с вашей спецификацией. Это вероятно означает, что все файлы из одного каталога получают одинаковую вероятность; сделайте то же самое для всех каталогов.
- выберите каталог, основываясь на равномерной выборке из интервала [0,1]. Обычно вы просто используете арифметическое кодирование для этого (вы просто делите интервал [0,1] на подс intervals, соответствующие каждому каталогу, и каждый подинтервал такой же ширины, какой вероятен каталог).
- после выбора каталога вы корректируете вероятности каталога: вероятно, вы захотите сделать что-то вроде уменьшения вероятности вашего каталога на некоторый фиксированный процент и распределить “освобожденную” вероятность среди других каталогов, чтобы “долго оставаться в самом большом каталоге” было менее проблематично.
- то же самое, что вы сделали, чтобы выбрать каталог с изображениями в этом каталоге: выберите число между 0 и 1 и примените арифметический декодер для выбора изображения, а затем обновите вероятности изображений в этом каталоге.
- вы помещаете выбранное изображение в конец вашей очереди изображений.
- вы возвращаетесь и выбираете следующий каталог.
Ответ или решение
Оптимизация слайд-шоу с кастомизированной рандомизацией
Ваша задача по созданию слайд-шоу с настраиваемой рандомизацией изображений, основываясь на количествах фотографий в разных директориях, представляет собой интересный вызов. Давайте подробно рассмотрим, как можно создать систему, которая будет удовлетворять вашим требованиям, используя Python и утилиту feh
.
Основные требования
- Кастомизированная рандомизация: необъодимо учитывать не только количественные параметры, но и заданные доли, что позволит лучше регулировать вероятность появления изображений из различных директорий.
- Легкость в использовании: утилита должна быть достаточно легковесной и простой в запуске, что делает
feh
предпочтительным выбором. - Автоматизация запуска: необходимо также автоматизировать запуск скрипта после загрузки системы.
Подход к реализации
Согласно вашему объяснению, ваша основная идея заключается в создании рандомизатора, который будет учитывать структуру фотографий в директориях. Алгоритм будет следующим:
- Сбор информации о фотографиях: Пройтись по всем выбранным директориям и собрать данные о количестве фотографий.
- Определение вероятностей: Рассчитать вероятности для каждой директории и каждого изображения, основываясь на заданной логике (включая ограничение на минимальный вес для менееолимитных директорий).
- Выбор изображений: Используя рандомизацию, выбирать изображения из каждой директории, при этом корректируя вероятности в соответствии с логикой.
- Автоматическая перезапись изображений: Копировать выбранные изображения в активную директорию для отображения в
feh
.
Пример кода
Ваш код уже включает множество нужных элементов. Вот немного улучшенный и более структурированный вариант:
import os
import random
import shutil
from datetime import datetime, timedelta
src_dir = "./photos/src"
active_dir = "./photos/active"
photo_display_time = 24
# Поддерживаемые расширения
valid_exts = ['.jpg', '.jpeg', '.png']
# Сбор фотографий по директориям
def collect_photos():
photos = []
for entry in os.scandir(src_dir):
if entry.is_dir():
files = [f for f in os.scandir(entry.path) if f.is_file() and os.path.splitext(f.path)[-1] in valid_exts]
if files:
photos.append(files)
return photos
# Расчет вероятностей
def calculate_probabilities(photos):
total_photos = sum(len(p) for p in photos)
probabilities = []
for group in photos:
weight = len(group) / total_photos
probabilities.append((group, weight))
return probabilities
# Выбор изображения согласно вероятностям
def select_image(probabilities):
total_weight = sum(weight for _, weight in probabilities)
random_weight = random.uniform(0, total_weight)
cumulative_weight = 0
for group, weight in probabilities:
cumulative_weight += weight
if random_weight <= cumulative_weight:
return random.choice(group).path
# Основная логика
def main():
if not os.path.exists(active_dir):
os.makedirs(active_dir)
while True:
photos = collect_photos()
probabilities = calculate_probabilities(photos)
next_image_time = datetime.now() + timedelta(seconds=photo_display_time)
while datetime.now() < next_image_time:
selected_image = select_image(probabilities)
shutil.copy(selected_image, active_dir)
# Запуск программы
if __name__ == "__main__":
main()
Автоматизация запуска
Для автоматической работы скрипта после запуска системы, можно использовать systemd
или cron
. Вот пример systemd
:
-
Создайте файл
/etc/systemd/system/slideshow.service
:[Unit] Description=Slideshow Automation [Service] ExecStart=/usr/bin/python3 /path/to/slideshow.py Restart=always [Install] WantedBy=multi-user.target
-
Активируйте и запустите его:
sudo systemctl enable slideshow.service sudo systemctl start slideshow.service
Заключение
Система, описанная выше, удовлетворяет вашим требованиям к рандомизации изображений с учетом их распределения в директориях, а также автоматизированному запуску. Такой подход позволяет добиться оптимизации слайд-шоу, учитывая заданные вами условия. Если у вас возникнут дополнительные вопросы или потребуется помощь в доработке конкретных аспектов, не стесняйтесь обращаться.