Compare commits
6 Commits
v0.2.4
...
855bdcc3f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 855bdcc3f9 | |||
| 4646dd2b70 | |||
| 6a642beb5c | |||
| 9b3d959ce3 | |||
| e06b55ad07 | |||
| c3056010eb |
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ae_anons"
|
name = "ae_anons"
|
||||||
version = "0.2.4"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"askama",
|
"askama",
|
||||||
@@ -33,6 +33,7 @@ dependencies = [
|
|||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
"winres",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1768,6 +1769,15 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -2167,6 +2177,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winres"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||||
|
dependencies = [
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ae_anons"
|
name = "ae_anons"
|
||||||
version = "0.2.4"
|
version = "0.2.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Alexey Barabanov <a.barabanov@tvstart.ru>"]
|
authors = ["Alexey Barabanov <a.barabanov@tvstart.ru>"]
|
||||||
@@ -48,3 +48,9 @@ opt-level = "z"
|
|||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winres = "0.1"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
winres = "0.1"
|
||||||
51
README.md
51
README.md
@@ -12,10 +12,15 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии v0.2.4
|
## Новое в версии v0.2.6
|
||||||
|
|
||||||
- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Улучшена читаемость логов отправки (добавлен UID)
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -610,54 +615,64 @@ ae_anons/
|
|||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
### v0.2.4 (текущая)
|
### v0.2.6 (текущая)
|
||||||
|
|
||||||
- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Улучшена читаемость логов отправки (добавлен UID)
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
|
### v0.2.5
|
||||||
|
|
||||||
|
- Оптимизация памяти: очистка неиспользуемых логотипов
|
||||||
|
- Групповая отправка заданий по 3 для сохранения порядка
|
||||||
|
- Добавлен UID в логи отправки
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Полностью автономный режим (встроенный Font Awesome)
|
||||||
|
- Добавлен favicon
|
||||||
|
- Все статические ресурсы встроены в бинарник
|
||||||
|
|
||||||
### v0.2.3
|
### v0.2.3
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
|
- Тёмная/светлая тема с автоопределением ОС
|
||||||
|
- Сортировка по всем колонкам в интерфейсе
|
||||||
|
- Фильтрация по имени файла/UID
|
||||||
|
- Остановка всех активных заданий
|
||||||
|
- Автообновление каждые 60 секунд
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
- Исправлена обработка UNC/SMB путей
|
- Исправлена обработка UNC/SMB путей
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
### v0.2.2
|
### v0.2.2
|
||||||
|
|
||||||
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
||||||
|
|
||||||
- Улучшена цветовая схема
|
- Улучшена цветовая схема
|
||||||
|
|
||||||
- Добавлена сортировка по всем колонкам
|
- Добавлена сортировка по всем колонкам
|
||||||
|
|
||||||
- Вынесены стили в отдельный CSS файл
|
- Вынесены стили в отдельный CSS файл
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
|
|
||||||
- Добавлен веб-интерфейс для управления заданиями
|
- Добавлен веб-интерфейс для управления заданиями
|
||||||
|
|
||||||
- Реализован REST API
|
- Реализован REST API
|
||||||
|
|
||||||
- Добавлена поддержка тёмной/светлой темы
|
- Добавлена поддержка тёмной/светлой темы
|
||||||
|
|
||||||
- Автообновление статуса заданий
|
- Автообновление статуса заданий
|
||||||
|
|
||||||
- Возможность остановки всех активных заданий
|
- Возможность остановки всех активных заданий
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Оптимизирована работа с памятью
|
- Оптимизирована работа с памятью
|
||||||
|
|
||||||
- Убрана функция создания .json
|
- Убрана функция создания .json
|
||||||
|
|
||||||
### v0.1.0
|
### v0.1.0
|
||||||
|
|
||||||
- Первый выпуск
|
- Первый выпуск
|
||||||
|
|
||||||
- Интеграция с Synology Office
|
- Интеграция с Synology Office
|
||||||
|
|
||||||
- Базовая генерация заданий Nexrender
|
- Базовая генерация заданий Nexrender
|
||||||
|
|
||||||
- Парсинг Excel с динамическим обнаружением листов
|
- Парсинг Excel с динамическим обнаружением листов
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Binary file not shown.
BIN
assets/logo.ico
Normal file
BIN
assets/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
9
build.rs
Normal file
9
build.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// build.rs
|
||||||
|
fn main() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("assets/logo.ico");
|
||||||
|
res.compile().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
63
makefile
63
makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: all clean build-mac build-windows build-linux build-linux-musl package help quick package-single
|
.PHONY: all clean build-mac build-windows build-linux build-linux-musl package help quick package-single icons favicon
|
||||||
|
|
||||||
# Название проекта
|
# Название проекта
|
||||||
PROJECT_NAME := ae_anons
|
PROJECT_NAME := ae_anons
|
||||||
@@ -18,11 +18,14 @@ BLUE := \033[0;34m
|
|||||||
CYAN := \033[0;36m
|
CYAN := \033[0;36m
|
||||||
MAGENTA := \033[0;35m
|
MAGENTA := \033[0;35m
|
||||||
BOLD := \033[1m
|
BOLD := \033[1m
|
||||||
NC := \033[0m # No Color
|
NC := \033[0m
|
||||||
|
|
||||||
# Файл для хранения времени сборки
|
# Файл для хранения времени сборки
|
||||||
TIMING_FILE := .build_timing
|
TIMING_FILE := .build_timing
|
||||||
|
|
||||||
|
# Определение команды ImageMagick (v6 = convert, v7 = magick)
|
||||||
|
IMAGEMAGICK := $(shell command -v magick 2>/dev/null || command -v convert 2>/dev/null || echo "false")
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "$(BOLD)$(CYAN)AE Anons - Makefile команды$(NC)"
|
@echo "$(BOLD)$(CYAN)AE Anons - Makefile команды$(NC)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@@ -41,6 +44,10 @@ help:
|
|||||||
@echo " make package - Создать пакеты для всех платформ"
|
@echo " make package - Создать пакеты для всех платформ"
|
||||||
@echo " make package-single - Создать пакет для текущей платформы"
|
@echo " make package-single - Создать пакет для текущей платформы"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@echo "$(GREEN)Иконки:$(NC)"
|
||||||
|
@echo " make icons - Создать все иконки (logo.ico, logo.icns, favicon.ico)"
|
||||||
|
@echo " make favicon - Создать только favicon.ico"
|
||||||
|
@echo ""
|
||||||
@echo "$(GREEN)Очистка:$(NC)"
|
@echo "$(GREEN)Очистка:$(NC)"
|
||||||
@echo " make clean - Очистить все сборки"
|
@echo " make clean - Очистить все сборки"
|
||||||
@echo " make clean-timing - Очистить файл с временами сборки"
|
@echo " make clean-timing - Очистить файл с временами сборки"
|
||||||
@@ -49,24 +56,28 @@ help:
|
|||||||
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
|
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# Копирование общих файлов в пакет
|
|
||||||
define copy_common_files
|
define copy_common_files
|
||||||
@cp .env.example $(1)/ 2>/dev/null || true
|
@cp .env.example $(1)/ 2>/dev/null || true
|
||||||
@cp README.md $(1)/ 2>/dev/null || true
|
@cp README.md $(1)/ 2>/dev/null || true
|
||||||
@if [ -f LICENSE ]; then cp LICENSE $(1)/; else echo "$(YELLOW)⚠️ LICENSE не найден, пропускаем$(NC)"; fi
|
@if [ -f LICENSE ]; then cp LICENSE $(1)/; else echo "$(YELLOW)⚠️ LICENSE не найден, пропускаем$(NC)"; fi
|
||||||
endef
|
endef
|
||||||
|
|
||||||
# Функция для замера времени
|
|
||||||
define measure_time
|
define measure_time
|
||||||
@start=$$(date +%s); \
|
@start=$$(date +%s); \
|
||||||
$(2); \
|
$(2); \
|
||||||
end=$$(date +%s); \
|
end=$$(date +%s); \
|
||||||
duration=$$((end - start)); \
|
duration=$$((end - start)); \
|
||||||
echo "$(1): $$duration сек" >> $(TIMING_FILE); \
|
if [ $$duration -ge 60 ]; then \
|
||||||
echo "$(GREEN)✓ $(1) завершён за $$duration сек$(NC)"
|
min=$$((duration / 60)); \
|
||||||
|
sec=$$((duration % 60)); \
|
||||||
|
time_str="$${min}m $${sec}s"; \
|
||||||
|
else \
|
||||||
|
time_str="$${duration}s"; \
|
||||||
|
fi; \
|
||||||
|
echo "$(1): $$time_str" >> $(TIMING_FILE); \
|
||||||
|
echo "$(GREEN)✓ $(1) завершён за $$time_str$(NC)"
|
||||||
endef
|
endef
|
||||||
|
|
||||||
# Функция для вывода итоговой таблицы
|
|
||||||
define print_timing_summary
|
define print_timing_summary
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "$(BOLD)$(CYAN)═══════════════════════════════════════════════════════════════$(NC)"
|
@echo "$(BOLD)$(CYAN)═══════════════════════════════════════════════════════════════$(NC)"
|
||||||
@@ -88,6 +99,43 @@ define print_timing_summary
|
|||||||
@rm -f $(TIMING_FILE)
|
@rm -f $(TIMING_FILE)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# ИКОНКИ
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
icons: favicon
|
||||||
|
@echo "$(GREEN)Создание иконок для Windows и macOS...$(NC)"
|
||||||
|
@if [ "$(IMAGEMAGICK)" = "false" ]; then \
|
||||||
|
echo "$(RED)❌ ImageMagick не установлен. Установите: brew install imagemagick$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
# Windows .ico
|
||||||
|
@$(IMAGEMAGICK) assets/logo.png -define icon:auto-resize=256,128,64,48,32,16 assets/logo.ico
|
||||||
|
@echo "$(GREEN) ✓ assets/logo.ico$(NC)"
|
||||||
|
# macOS .icns
|
||||||
|
@mkdir -p assets/icon.iconset
|
||||||
|
@sips -z 16 16 assets/logo.png --out assets/icon.iconset/icon_16x16.png 2>/dev/null
|
||||||
|
@sips -z 32 32 assets/logo.png --out assets/icon.iconset/icon_16x16@2x.png 2>/dev/null
|
||||||
|
@sips -z 32 32 assets/logo.png --out assets/icon.iconset/icon_32x32.png 2>/dev/null
|
||||||
|
@sips -z 64 64 assets/logo.png --out assets/icon.iconset/icon_32x32@2x.png 2>/dev/null
|
||||||
|
@sips -z 128 128 assets/logo.png --out assets/icon.iconset/icon_128x128.png 2>/dev/null
|
||||||
|
@sips -z 256 256 assets/logo.png --out assets/icon.iconset/icon_128x128@2x.png 2>/dev/null
|
||||||
|
@sips -z 256 256 assets/logo.png --out assets/icon.iconset/icon_256x256.png 2>/dev/null
|
||||||
|
@sips -z 512 512 assets/logo.png --out assets/icon.iconset/icon_256x256@2x.png 2>/dev/null
|
||||||
|
@sips -z 512 512 assets/logo.png --out assets/icon.iconset/icon_512x512.png 2>/dev/null
|
||||||
|
@iconutil -c icns assets/icon.iconset -o assets/logo.icns 2>/dev/null
|
||||||
|
@rm -rf assets/icon.iconset
|
||||||
|
@echo "$(GREEN) ✓ assets/logo.icns$(NC)"
|
||||||
|
|
||||||
|
favicon:
|
||||||
|
@echo "$(GREEN)Создание favicon.ico...$(NC)"
|
||||||
|
@if [ "$(IMAGEMAGICK)" = "false" ]; then \
|
||||||
|
echo "$(RED)❌ ImageMagick не установлен. Установите: brew install imagemagick$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@$(IMAGEMAGICK) assets/logo.png -define icon:auto-resize=48,32,16 src/static/favicon.ico
|
||||||
|
@echo "$(GREEN) ✓ src/static/favicon.ico$(NC)"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# ОПРЕДЕЛЕНИЕ ПЛАТФОРМЫ
|
# ОПРЕДЕЛЕНИЕ ПЛАТФОРМЫ
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -113,7 +161,6 @@ endif
|
|||||||
|
|
||||||
quick: detect-platform
|
quick: detect-platform
|
||||||
|
|
||||||
# Быстрая сборка без упаковки (только бинарник)
|
|
||||||
quick-release:
|
quick-release:
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
@echo "$(GREEN)🍎 Сборка для macOS...$(NC)"
|
@echo "$(GREEN)🍎 Сборка для macOS...$(NC)"
|
||||||
|
|||||||
@@ -12,17 +12,15 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии 0.2.3
|
## Новое в версии v0.2.6
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Тёмная/светлая тема с автоопределением ОС
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
- Сортировка по всем колонкам
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
- Фильтрация по имени файла/UID
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
- Остановка всех активных заданий
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
- Автообновление каждые 60 секунд
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
- Улучшенное debug-логирование
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
- Исправлена обработка UNC/SMB путей
|
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -576,6 +574,7 @@ ae_anons/
|
|||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
### Nexrender
|
### Nexrender
|
||||||
|
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
@@ -587,7 +586,7 @@ ae_anons/
|
|||||||
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
© 2023 Fonticons, Inc. — https://fontawesome.com
|
© 2023 Fonticons, Inc. — <https://fontawesome.com>
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
@@ -616,49 +615,64 @@ ae_anons/
|
|||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
### v0.2.3 (текущая)
|
### v0.2.6 (текущая)
|
||||||
|
|
||||||
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
|
### v0.2.5
|
||||||
|
|
||||||
|
- Оптимизация памяти: очистка неиспользуемых логотипов
|
||||||
|
- Групповая отправка заданий по 3 для сохранения порядка
|
||||||
|
- Добавлен UID в логи отправки
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Полностью автономный режим (встроенный Font Awesome)
|
||||||
|
- Добавлен favicon
|
||||||
|
- Все статические ресурсы встроены в бинарник
|
||||||
|
|
||||||
|
### v0.2.3
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
|
- Тёмная/светлая тема с автоопределением ОС
|
||||||
|
- Сортировка по всем колонкам в интерфейсе
|
||||||
|
- Фильтрация по имени файла/UID
|
||||||
|
- Остановка всех активных заданий
|
||||||
|
- Автообновление каждые 60 секунд
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
- Исправлена обработка UNC/SMB путей
|
- Исправлена обработка UNC/SMB путей
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
### v0.2.2
|
### v0.2.2
|
||||||
|
|
||||||
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
||||||
|
|
||||||
- Улучшена цветовая схема
|
- Улучшена цветовая схема
|
||||||
|
|
||||||
- Добавлена сортировка по всем колонкам
|
- Добавлена сортировка по всем колонкам
|
||||||
|
|
||||||
- Вынесены стили в отдельный CSS файл
|
- Вынесены стили в отдельный CSS файл
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
|
|
||||||
- Добавлен веб-интерфейс для управления заданиями
|
- Добавлен веб-интерфейс для управления заданиями
|
||||||
|
|
||||||
- Реализован REST API
|
- Реализован REST API
|
||||||
|
|
||||||
- Добавлена поддержка тёмной/светлой темы
|
- Добавлена поддержка тёмной/светлой темы
|
||||||
|
|
||||||
- Автообновление статуса заданий
|
- Автообновление статуса заданий
|
||||||
|
|
||||||
- Возможность остановки всех активных заданий
|
- Возможность остановки всех активных заданий
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Оптимизирована работа с памятью
|
- Оптимизирована работа с памятью
|
||||||
|
|
||||||
- Убрана функция создания .json
|
- Убрана функция создания .json
|
||||||
|
|
||||||
### v0.1.0
|
### v0.1.0
|
||||||
|
|
||||||
- Первый выпуск
|
- Первый выпуск
|
||||||
|
|
||||||
- Интеграция с Synology Office
|
- Интеграция с Synology Office
|
||||||
|
|
||||||
- Базовая генерация заданий Nexrender
|
- Базовая генерация заданий Nexrender
|
||||||
|
|
||||||
- Парсинг Excel с динамическим обнаружением листов
|
- Парсинг Excel с динамическим обнаружением листов
|
||||||
|
|
||||||
---
|
---
|
||||||
Binary file not shown.
@@ -12,17 +12,15 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии 0.2.3
|
## Новое в версии v0.2.6
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Тёмная/светлая тема с автоопределением ОС
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
- Сортировка по всем колонкам
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
- Фильтрация по имени файла/UID
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
- Остановка всех активных заданий
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
- Автообновление каждые 60 секунд
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
- Улучшенное debug-логирование
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
- Исправлена обработка UNC/SMB путей
|
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -576,6 +574,7 @@ ae_anons/
|
|||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
### Nexrender
|
### Nexrender
|
||||||
|
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
@@ -587,7 +586,7 @@ ae_anons/
|
|||||||
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
© 2023 Fonticons, Inc. — https://fontawesome.com
|
© 2023 Fonticons, Inc. — <https://fontawesome.com>
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
@@ -616,49 +615,64 @@ ae_anons/
|
|||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
### v0.2.3 (текущая)
|
### v0.2.6 (текущая)
|
||||||
|
|
||||||
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
|
### v0.2.5
|
||||||
|
|
||||||
|
- Оптимизация памяти: очистка неиспользуемых логотипов
|
||||||
|
- Групповая отправка заданий по 3 для сохранения порядка
|
||||||
|
- Добавлен UID в логи отправки
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Полностью автономный режим (встроенный Font Awesome)
|
||||||
|
- Добавлен favicon
|
||||||
|
- Все статические ресурсы встроены в бинарник
|
||||||
|
|
||||||
|
### v0.2.3
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
|
- Тёмная/светлая тема с автоопределением ОС
|
||||||
|
- Сортировка по всем колонкам в интерфейсе
|
||||||
|
- Фильтрация по имени файла/UID
|
||||||
|
- Остановка всех активных заданий
|
||||||
|
- Автообновление каждые 60 секунд
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
- Исправлена обработка UNC/SMB путей
|
- Исправлена обработка UNC/SMB путей
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
### v0.2.2
|
### v0.2.2
|
||||||
|
|
||||||
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
||||||
|
|
||||||
- Улучшена цветовая схема
|
- Улучшена цветовая схема
|
||||||
|
|
||||||
- Добавлена сортировка по всем колонкам
|
- Добавлена сортировка по всем колонкам
|
||||||
|
|
||||||
- Вынесены стили в отдельный CSS файл
|
- Вынесены стили в отдельный CSS файл
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
|
|
||||||
- Добавлен веб-интерфейс для управления заданиями
|
- Добавлен веб-интерфейс для управления заданиями
|
||||||
|
|
||||||
- Реализован REST API
|
- Реализован REST API
|
||||||
|
|
||||||
- Добавлена поддержка тёмной/светлой темы
|
- Добавлена поддержка тёмной/светлой темы
|
||||||
|
|
||||||
- Автообновление статуса заданий
|
- Автообновление статуса заданий
|
||||||
|
|
||||||
- Возможность остановки всех активных заданий
|
- Возможность остановки всех активных заданий
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Оптимизирована работа с памятью
|
- Оптимизирована работа с памятью
|
||||||
|
|
||||||
- Убрана функция создания .json
|
- Убрана функция создания .json
|
||||||
|
|
||||||
### v0.1.0
|
### v0.1.0
|
||||||
|
|
||||||
- Первый выпуск
|
- Первый выпуск
|
||||||
|
|
||||||
- Интеграция с Synology Office
|
- Интеграция с Synology Office
|
||||||
|
|
||||||
- Базовая генерация заданий Nexrender
|
- Базовая генерация заданий Nexrender
|
||||||
|
|
||||||
- Парсинг Excel с динамическим обнаружением листов
|
- Парсинг Excel с динамическим обнаружением листов
|
||||||
|
|
||||||
---
|
---
|
||||||
Binary file not shown.
@@ -12,17 +12,15 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии 0.2.3
|
## Новое в версии v0.2.6
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Тёмная/светлая тема с автоопределением ОС
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
- Сортировка по всем колонкам
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
- Фильтрация по имени файла/UID
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
- Остановка всех активных заданий
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
- Автообновление каждые 60 секунд
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
- Улучшенное debug-логирование
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
- Исправлена обработка UNC/SMB путей
|
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -576,6 +574,7 @@ ae_anons/
|
|||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
### Nexrender
|
### Nexrender
|
||||||
|
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
@@ -587,7 +586,7 @@ ae_anons/
|
|||||||
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
© 2023 Fonticons, Inc. — https://fontawesome.com
|
© 2023 Fonticons, Inc. — <https://fontawesome.com>
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
@@ -616,49 +615,64 @@ ae_anons/
|
|||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
### v0.2.3 (текущая)
|
### v0.2.6 (текущая)
|
||||||
|
|
||||||
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
|
### v0.2.5
|
||||||
|
|
||||||
|
- Оптимизация памяти: очистка неиспользуемых логотипов
|
||||||
|
- Групповая отправка заданий по 3 для сохранения порядка
|
||||||
|
- Добавлен UID в логи отправки
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Полностью автономный режим (встроенный Font Awesome)
|
||||||
|
- Добавлен favicon
|
||||||
|
- Все статические ресурсы встроены в бинарник
|
||||||
|
|
||||||
|
### v0.2.3
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
|
- Тёмная/светлая тема с автоопределением ОС
|
||||||
|
- Сортировка по всем колонкам в интерфейсе
|
||||||
|
- Фильтрация по имени файла/UID
|
||||||
|
- Остановка всех активных заданий
|
||||||
|
- Автообновление каждые 60 секунд
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
- Исправлена обработка UNC/SMB путей
|
- Исправлена обработка UNC/SMB путей
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
### v0.2.2
|
### v0.2.2
|
||||||
|
|
||||||
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
||||||
|
|
||||||
- Улучшена цветовая схема
|
- Улучшена цветовая схема
|
||||||
|
|
||||||
- Добавлена сортировка по всем колонкам
|
- Добавлена сортировка по всем колонкам
|
||||||
|
|
||||||
- Вынесены стили в отдельный CSS файл
|
- Вынесены стили в отдельный CSS файл
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
|
|
||||||
- Добавлен веб-интерфейс для управления заданиями
|
- Добавлен веб-интерфейс для управления заданиями
|
||||||
|
|
||||||
- Реализован REST API
|
- Реализован REST API
|
||||||
|
|
||||||
- Добавлена поддержка тёмной/светлой темы
|
- Добавлена поддержка тёмной/светлой темы
|
||||||
|
|
||||||
- Автообновление статуса заданий
|
- Автообновление статуса заданий
|
||||||
|
|
||||||
- Возможность остановки всех активных заданий
|
- Возможность остановки всех активных заданий
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Оптимизирована работа с памятью
|
- Оптимизирована работа с памятью
|
||||||
|
|
||||||
- Убрана функция создания .json
|
- Убрана функция создания .json
|
||||||
|
|
||||||
### v0.1.0
|
### v0.1.0
|
||||||
|
|
||||||
- Первый выпуск
|
- Первый выпуск
|
||||||
|
|
||||||
- Интеграция с Synology Office
|
- Интеграция с Synology Office
|
||||||
|
|
||||||
- Базовая генерация заданий Nexrender
|
- Базовая генерация заданий Nexrender
|
||||||
|
|
||||||
- Парсинг Excel с динамическим обнаружением листов
|
- Парсинг Excel с динамическим обнаружением листов
|
||||||
|
|
||||||
---
|
---
|
||||||
Binary file not shown.
@@ -12,17 +12,15 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии 0.2.3
|
## Новое в версии v0.2.6
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
- Тёмная/светлая тема с автоопределением ОС
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
- Сортировка по всем колонкам
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
- Фильтрация по имени файла/UID
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
- Остановка всех активных заданий
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
- Автообновление каждые 60 секунд
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
- Улучшенное debug-логирование
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
- Исправлена обработка UNC/SMB путей
|
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -576,6 +574,7 @@ ae_anons/
|
|||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
### Nexrender
|
### Nexrender
|
||||||
|
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
@@ -587,7 +586,7 @@ ae_anons/
|
|||||||
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
||||||
|
|
||||||
© 2023 Fonticons, Inc. — https://fontawesome.com
|
© 2023 Fonticons, Inc. — <https://fontawesome.com>
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
@@ -616,49 +615,64 @@ ae_anons/
|
|||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
### v0.2.3 (текущая)
|
### v0.2.6 (текущая)
|
||||||
|
|
||||||
|
- Сортировка заданий по дате и времени события (самые ранние — первыми)
|
||||||
|
- Сортировка по варианту (оригинал → today → tomorrow)
|
||||||
|
- Последовательная отправка для гарантированного порядка в очереди
|
||||||
|
- Исправлена нормализация дефисов (2 и более дефиса подряд → 1)
|
||||||
|
- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS)
|
||||||
|
- Добавлена поддержка кеширования ассетов (cache: true/false)
|
||||||
|
- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются
|
||||||
|
|
||||||
|
### v0.2.5
|
||||||
|
|
||||||
|
- Оптимизация памяти: очистка неиспользуемых логотипов
|
||||||
|
- Групповая отправка заданий по 3 для сохранения порядка
|
||||||
|
- Добавлен UID в логи отправки
|
||||||
|
|
||||||
|
### v0.2.4
|
||||||
|
|
||||||
|
- Полностью автономный режим (встроенный Font Awesome)
|
||||||
|
- Добавлен favicon
|
||||||
|
- Все статические ресурсы встроены в бинарник
|
||||||
|
|
||||||
|
### v0.2.3
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
|
- Тёмная/светлая тема с автоопределением ОС
|
||||||
|
- Сортировка по всем колонкам в интерфейсе
|
||||||
|
- Фильтрация по имени файла/UID
|
||||||
|
- Остановка всех активных заданий
|
||||||
|
- Автообновление каждые 60 секунд
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
- Исправлена обработка UNC/SMB путей
|
- Исправлена обработка UNC/SMB путей
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
### v0.2.2
|
### v0.2.2
|
||||||
|
|
||||||
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
- Исправлено отображение имён выходных файлов в веб-интерфейсе
|
||||||
|
|
||||||
- Улучшена цветовая схема
|
- Улучшена цветовая схема
|
||||||
|
|
||||||
- Добавлена сортировка по всем колонкам
|
- Добавлена сортировка по всем колонкам
|
||||||
|
|
||||||
- Вынесены стили в отдельный CSS файл
|
- Вынесены стили в отдельный CSS файл
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
|
|
||||||
- Добавлен веб-интерфейс для управления заданиями
|
- Добавлен веб-интерфейс для управления заданиями
|
||||||
|
|
||||||
- Реализован REST API
|
- Реализован REST API
|
||||||
|
|
||||||
- Добавлена поддержка тёмной/светлой темы
|
- Добавлена поддержка тёмной/светлой темы
|
||||||
|
|
||||||
- Автообновление статуса заданий
|
- Автообновление статуса заданий
|
||||||
|
|
||||||
- Возможность остановки всех активных заданий
|
- Возможность остановки всех активных заданий
|
||||||
|
|
||||||
### v0.1.1
|
### v0.1.1
|
||||||
|
|
||||||
- Оптимизирована работа с памятью
|
- Оптимизирована работа с памятью
|
||||||
|
|
||||||
- Убрана функция создания .json
|
- Убрана функция создания .json
|
||||||
|
|
||||||
### v0.1.0
|
### v0.1.0
|
||||||
|
|
||||||
- Первый выпуск
|
- Первый выпуск
|
||||||
|
|
||||||
- Интеграция с Synology Office
|
- Интеграция с Synology Office
|
||||||
|
|
||||||
- Базовая генерация заданий Nexrender
|
- Базовая генерация заданий Nexrender
|
||||||
|
|
||||||
- Парсинг Excel с динамическим обнаружением листов
|
- Парсинг Excel с динамическим обнаружением листов
|
||||||
|
|
||||||
---
|
---
|
||||||
Binary file not shown.
@@ -155,6 +155,8 @@ pub enum Asset {
|
|||||||
asset_type: String,
|
asset_type: String,
|
||||||
#[serde(rename = "layerName")]
|
#[serde(rename = "layerName")]
|
||||||
layer_name: String,
|
layer_name: String,
|
||||||
|
#[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||||
|
cache: bool,
|
||||||
},
|
},
|
||||||
Video {
|
Video {
|
||||||
src: String,
|
src: String,
|
||||||
@@ -162,6 +164,8 @@ pub enum Asset {
|
|||||||
asset_type: String,
|
asset_type: String,
|
||||||
#[serde(rename = "layerName")]
|
#[serde(rename = "layerName")]
|
||||||
layer_name: String,
|
layer_name: String,
|
||||||
|
#[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||||
|
cache: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +212,10 @@ pub struct JobData {
|
|||||||
pub channel_logo: String,
|
pub channel_logo: String,
|
||||||
pub outfile_name: String,
|
pub outfile_name: String,
|
||||||
pub row_index: usize,
|
pub row_index: usize,
|
||||||
|
// Поля для сортировки
|
||||||
|
pub sort_date: NaiveDate,
|
||||||
|
pub sort_time: String,
|
||||||
|
pub variant_order: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobData {
|
impl JobData {
|
||||||
@@ -233,6 +241,7 @@ impl JobData {
|
|||||||
|
|
||||||
let (date_obj, data_display) = parse_date(&data_str);
|
let (date_obj, data_display) = parse_date(&data_str);
|
||||||
let (time_h, time_m) = parse_time(&time);
|
let (time_h, time_m) = parse_time(&time);
|
||||||
|
let sort_time = format!("{}:{}", time_h, time_m);
|
||||||
|
|
||||||
let pack_path = packs.get(&sport).cloned().unwrap_or_default();
|
let pack_path = packs.get(&sport).cloned().unwrap_or_default();
|
||||||
let channel_logo = channels.get(&channel).cloned().unwrap_or_default();
|
let channel_logo = channels.get(&channel).cloned().unwrap_or_default();
|
||||||
@@ -268,8 +277,8 @@ impl JobData {
|
|||||||
.replace(' ', "-")
|
.replace(' ', "-")
|
||||||
.replace('\'', "");
|
.replace('\'', "");
|
||||||
|
|
||||||
// Если 3 и более дефисов подряд -> заменяем на один
|
// Заменяем 2 и более дефисов подряд на один
|
||||||
let re = Regex::new(r"-{3,}").unwrap();
|
let re = Regex::new(r"-{2,}").unwrap();
|
||||||
outfile_name = re.replace_all(&outfile_name, "-").to_string();
|
outfile_name = re.replace_all(&outfile_name, "-").to_string();
|
||||||
|
|
||||||
// Убираем дефисы в начале и конце
|
// Убираем дефисы в начале и конце
|
||||||
@@ -294,19 +303,34 @@ impl JobData {
|
|||||||
channel_logo: unc_to_uri(&channel_logo),
|
channel_logo: unc_to_uri(&channel_logo),
|
||||||
outfile_name,
|
outfile_name,
|
||||||
row_index,
|
row_index,
|
||||||
|
sort_date: date_obj,
|
||||||
|
sort_time,
|
||||||
|
variant_order: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_variants(&self) -> Vec<Self> {
|
pub fn create_variants(&self) -> Vec<Self> {
|
||||||
let mut variants = vec![self.clone()];
|
let mut variants = Vec::new();
|
||||||
|
|
||||||
|
// Оригинал (0)
|
||||||
|
let mut original = self.clone();
|
||||||
|
original.variant_order = 0;
|
||||||
|
variants.push(original);
|
||||||
|
|
||||||
|
// Today (1)
|
||||||
let mut today = self.clone();
|
let mut today = self.clone();
|
||||||
today.data_display = "сегодня".to_string();
|
today.data_display = "сегодня".to_string();
|
||||||
today.outfile_name = format!("{}_Today", self.outfile_name);
|
today.outfile_name = format!("{}_Today", self.outfile_name);
|
||||||
|
today.variant_order = 1;
|
||||||
variants.push(today);
|
variants.push(today);
|
||||||
|
|
||||||
|
// Tomorrow (2)
|
||||||
let mut tomorrow = self.clone();
|
let mut tomorrow = self.clone();
|
||||||
tomorrow.data_display = "завтра".to_string();
|
tomorrow.data_display = "завтра".to_string();
|
||||||
tomorrow.outfile_name = format!("{}_Tomorrow", self.outfile_name);
|
tomorrow.outfile_name = format!("{}_Tomorrow", self.outfile_name);
|
||||||
|
tomorrow.variant_order = 2;
|
||||||
variants.push(tomorrow);
|
variants.push(tomorrow);
|
||||||
|
|
||||||
variants
|
variants
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +370,7 @@ impl JobData {
|
|||||||
src: self.pack_path.clone(),
|
src: self.pack_path.clone(),
|
||||||
asset_type: "video".to_string(),
|
asset_type: "video".to_string(),
|
||||||
layer_name: "TOP".to_string(),
|
layer_name: "TOP".to_string(),
|
||||||
|
cache: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +382,7 @@ impl JobData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_text_assets(&self, assets: &mut Vec<Asset>) {
|
fn add_text_assets(&self, assets: &mut Vec<Asset>) {
|
||||||
|
// DATA — всегда
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
layer_name: "DATA".to_string(),
|
layer_name: "DATA".to_string(),
|
||||||
@@ -365,7 +391,9 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
self.add_date_adjustments(assets);
|
self.add_date_adjustments(assets);
|
||||||
|
|
||||||
|
// TIME — зависит от шаблона
|
||||||
if !self.team_b.is_empty() {
|
if !self.team_b.is_empty() {
|
||||||
|
// DOUBLE: TIME_H и TIME_M
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
layer_name: "TIME_H".to_string(),
|
layer_name: "TIME_H".to_string(),
|
||||||
@@ -380,6 +408,7 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
self.add_time_adjustments(assets);
|
self.add_time_adjustments(assets);
|
||||||
} else {
|
} else {
|
||||||
|
// SINGLE: TIME
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
layer_name: "TIME".to_string(),
|
layer_name: "TIME".to_string(),
|
||||||
@@ -388,6 +417,7 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LEAGUE — всегда
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
layer_name: "LEAGUE".to_string(),
|
layer_name: "LEAGUE".to_string(),
|
||||||
@@ -396,6 +426,7 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
self.add_league_adjustments(assets);
|
self.add_league_adjustments(assets);
|
||||||
|
|
||||||
|
// SPORT — всегда
|
||||||
if !self.sport.is_empty() {
|
if !self.sport.is_empty() {
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
@@ -405,14 +436,9 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let teams_text = if !self.team_a.is_empty() && !self.team_b.is_empty() {
|
// TEAMS — ТОЛЬКО для DOUBLE шаблона
|
||||||
format!("{} - {}", self.team_a, self.team_b)
|
if !self.team_b.is_empty() {
|
||||||
} else if !self.team_a.is_empty() {
|
let teams_text = format!("{} - {}", self.team_a, self.team_b);
|
||||||
self.team_a.clone()
|
|
||||||
} else {
|
|
||||||
self.team_b.clone()
|
|
||||||
};
|
|
||||||
if !teams_text.is_empty() {
|
|
||||||
assets.push(Asset::Data {
|
assets.push(Asset::Data {
|
||||||
asset_type: "data".to_string(),
|
asset_type: "data".to_string(),
|
||||||
layer_name: "TEAMS".to_string(),
|
layer_name: "TEAMS".to_string(),
|
||||||
@@ -485,11 +511,13 @@ impl JobData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_image_assets(&self, assets: &mut Vec<Asset>) {
|
fn add_image_assets(&self, assets: &mut Vec<Asset>) {
|
||||||
|
// Логотипы команд — НЕ кешируем (часто меняются)
|
||||||
if !self.team_a_logo.is_empty() {
|
if !self.team_a_logo.is_empty() {
|
||||||
assets.push(Asset::Image {
|
assets.push(Asset::Image {
|
||||||
src: self.team_a_logo.clone(),
|
src: self.team_a_logo.clone(),
|
||||||
asset_type: "image".to_string(),
|
asset_type: "image".to_string(),
|
||||||
layer_name: "TEAM_A_LOGO".to_string(),
|
layer_name: "TEAM_A_LOGO".to_string(),
|
||||||
|
cache: false,
|
||||||
});
|
});
|
||||||
if let Some(res) = &self.team_a_logo_res {
|
if let Some(res) = &self.team_a_logo_res {
|
||||||
assets.push(Asset::DataExpression {
|
assets.push(Asset::DataExpression {
|
||||||
@@ -505,6 +533,7 @@ impl JobData {
|
|||||||
src: self.team_b_logo.clone(),
|
src: self.team_b_logo.clone(),
|
||||||
asset_type: "image".to_string(),
|
asset_type: "image".to_string(),
|
||||||
layer_name: "TEAM_B_LOGO".to_string(),
|
layer_name: "TEAM_B_LOGO".to_string(),
|
||||||
|
cache: false,
|
||||||
});
|
});
|
||||||
if let Some(res) = &self.team_b_logo_res {
|
if let Some(res) = &self.team_b_logo_res {
|
||||||
assets.push(Asset::DataExpression {
|
assets.push(Asset::DataExpression {
|
||||||
@@ -515,11 +544,13 @@ impl JobData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Логотип канала — КЕШИРУЕМ (редко меняется)
|
||||||
if !self.channel_logo.is_empty() {
|
if !self.channel_logo.is_empty() {
|
||||||
assets.push(Asset::Image {
|
assets.push(Asset::Image {
|
||||||
src: self.channel_logo.clone(),
|
src: self.channel_logo.clone(),
|
||||||
asset_type: "image".to_string(),
|
asset_type: "image".to_string(),
|
||||||
layer_name: "CHANELL".to_string(),
|
layer_name: "CHANELL".to_string(),
|
||||||
|
cache: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,7 +576,6 @@ impl LogoRegistry {
|
|||||||
self.logos.insert((team, sport), link);
|
self.logos.insert((team, sport), link);
|
||||||
}
|
}
|
||||||
|
|
||||||
// В impl LogoRegistry, функция find()
|
|
||||||
pub fn find(&self, team: &str, sport: &str) -> Option<String> {
|
pub fn find(&self, team: &str, sport: &str) -> Option<String> {
|
||||||
log::debug!("LogoRegistry::find: team='{}', sport='{}'", team, sport);
|
log::debug!("LogoRegistry::find: team='{}', sport='{}'", team, sport);
|
||||||
|
|
||||||
@@ -564,6 +594,19 @@ impl LogoRegistry {
|
|||||||
log::warn!("No logo found for team='{}', sport='{}'", team, sport);
|
log::warn!("No logo found for team='{}', sport='{}'", team, sport);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Оставляет только указанные команды, удаляя все остальные
|
||||||
|
pub fn retain<F>(&mut self, mut predicate: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&str, &str) -> bool,
|
||||||
|
{
|
||||||
|
self.logos.retain(|(team, sport), _| predicate(team, sport));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Возвращает количество записей в реестре
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.logos.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_date(s: &str) -> (NaiveDate, String) {
|
fn parse_date(s: &str) -> (NaiveDate, String) {
|
||||||
@@ -639,6 +682,7 @@ fn unc_to_uri(path: &str) -> String {
|
|||||||
|
|
||||||
format!("file://{}", trimmed)
|
format!("file://{}", trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logo_scale_expression(target_size: &str) -> String {
|
fn logo_scale_expression(target_size: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"if (width > height) {{max_size = width;}} else {{max_size = height;}} var real_size = {}/max_size*100;[real_size,real_size]",
|
"if (width > height) {{max_size = width;}} else {{max_size = height;}} var real_size = {}/max_size*100;[real_size,real_size]",
|
||||||
|
|||||||
247
src/processor.rs
247
src/processor.rs
@@ -274,52 +274,50 @@ fn display_workbook_structure(workbook: &ExcelWorkbook) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Вспомогательная функция для поиска значения по части ключа
|
||||||
|
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
|
||||||
|
if let Some(val) = row.get(key_part) {
|
||||||
|
if !val.is_empty() {
|
||||||
|
return Some(val.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k, v) in row.iter() {
|
||||||
|
if k.to_lowercase().contains(&key_part.to_lowercase()) && !v.is_empty() {
|
||||||
|
return Some(v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
async fn generate_and_submit_jobs(
|
async fn generate_and_submit_jobs(
|
||||||
workbook: &ExcelWorkbook,
|
workbook: &ExcelWorkbook,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Vec<(String, String)>> {
|
) -> Result<Vec<(String, String)>> {
|
||||||
info!("Preparing Nexrender jobs...");
|
info!("Preparing Nexrender jobs...");
|
||||||
|
|
||||||
let start_sheet = workbook
|
// ========== ЗАГРУЖАЕМ SPORT PACKS ==========
|
||||||
.get_sheet("Start")
|
|
||||||
.ok_or_else(|| anyhow!("Sheet 'Start' not found"))?;
|
|
||||||
let sport_sheet = workbook
|
let sport_sheet = workbook
|
||||||
.get_sheet("SPORT")
|
.get_sheet("SPORT")
|
||||||
.ok_or_else(|| anyhow!("Sheet 'SPORT' not found"))?;
|
.ok_or_else(|| anyhow!("Sheet 'SPORT' not found"))?;
|
||||||
let teams_sheet = workbook
|
|
||||||
.get_sheet("TEAMS")
|
|
||||||
.ok_or_else(|| anyhow!("Sheet 'TEAMS' not found"))?;
|
|
||||||
let channel_sheet = workbook
|
|
||||||
.get_sheet("CHANELL")
|
|
||||||
.ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?;
|
|
||||||
|
|
||||||
// Загружаем sport packs
|
|
||||||
let packs: HashMap<String, String> = sport_sheet
|
let packs: HashMap<String, String> = sport_sheet
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|row| Some((row.get("SPORT")?.clone(), row.get("LINK")?.clone())))
|
.filter_map(|row| Some((row.get("SPORT")?.clone(), row.get("LINK")?.clone())))
|
||||||
.collect();
|
.collect();
|
||||||
info!("Loaded {} sport packs", packs.len());
|
info!("Loaded {} sport packs", packs.len());
|
||||||
|
debug!("Processed SPORT sheet ({} rows)", sport_sheet.rows.len());
|
||||||
|
|
||||||
// Вспомогательная функция для поиска значения по части ключа
|
// ========== ЗАГРУЖАЕМ TEAM LOGOS ==========
|
||||||
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
|
let teams_sheet = workbook
|
||||||
if let Some(val) = row.get(key_part) {
|
.get_sheet("TEAMS")
|
||||||
if !val.is_empty() {
|
.ok_or_else(|| anyhow!("Sheet 'TEAMS' not found"))?;
|
||||||
return Some(val.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (k, v) in row.iter() {
|
|
||||||
if k.to_lowercase().contains(&key_part.to_lowercase()) && !v.is_empty() {
|
|
||||||
return Some(v.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем team logos
|
|
||||||
debug!("TEAMS headers: {:?}", teams_sheet.headers);
|
debug!("TEAMS headers: {:?}", teams_sheet.headers);
|
||||||
for (i, row) in teams_sheet.rows.iter().take(3).enumerate() {
|
if log_enabled!(Level::Debug) {
|
||||||
debug!("TEAMS row {}: {:?}", i, row);
|
for (i, row) in teams_sheet.rows.iter().take(3).enumerate() {
|
||||||
|
debug!("TEAMS row {}: {:?}", i, row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len());
|
let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len());
|
||||||
@@ -332,7 +330,8 @@ async fn generate_and_submit_jobs(
|
|||||||
logos.insert(team.clone(), sport.clone(), link.clone());
|
logos.insert(team.clone(), sport.clone(), link.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Loaded {} team logos", teams_sheet.rows.len());
|
let total_teams = teams_sheet.rows.len();
|
||||||
|
info!("Loaded {} team logos", total_teams);
|
||||||
|
|
||||||
// Дебаг: выводим статистику по TEAMS
|
// Дебаг: выводим статистику по TEAMS
|
||||||
if log_enabled!(Level::Debug) {
|
if log_enabled!(Level::Debug) {
|
||||||
@@ -343,19 +342,23 @@ async fn generate_and_submit_jobs(
|
|||||||
.collect();
|
.collect();
|
||||||
debug!("Sports found in TEAMS sheet: {:?}", sports);
|
debug!("Sports found in TEAMS sheet: {:?}", sports);
|
||||||
|
|
||||||
for sport in sports.iter() {
|
for sport in sports.iter().take(5) {
|
||||||
let examples: Vec<_> = teams_sheet
|
let examples: Vec<_> = teams_sheet
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.get("SPORT") == Some(*sport))
|
.filter(|r| r.get("SPORT") == Some(*sport))
|
||||||
.take(5)
|
.take(3)
|
||||||
.filter_map(|r| r.get("TEAM"))
|
.filter_map(|r| r.get("TEAM"))
|
||||||
.collect();
|
.collect();
|
||||||
debug!(" TEAMS examples for '{}': {:?}", sport, examples);
|
debug!(" TEAMS examples for '{}': {:?}", sport, examples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загружаем channel logos
|
// ========== ЗАГРУЖАЕМ CHANNEL LOGOS ==========
|
||||||
|
let channel_sheet = workbook
|
||||||
|
.get_sheet("CHANELL")
|
||||||
|
.ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?;
|
||||||
|
|
||||||
let channels: HashMap<String, String> = channel_sheet
|
let channels: HashMap<String, String> = channel_sheet
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
@@ -367,40 +370,76 @@ async fn generate_and_submit_jobs(
|
|||||||
debug!("Channels loaded: {:?}", channels.keys().collect::<Vec<_>>());
|
debug!("Channels loaded: {:?}", channels.keys().collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut jobs: Vec<JobData> = Vec::with_capacity(start_sheet.rows.len() * 3);
|
// ========== ОБРАБАТЫВАЕМ START (только активные строки) ==========
|
||||||
|
let start_sheet = workbook
|
||||||
|
.get_sheet("Start")
|
||||||
|
.ok_or_else(|| anyhow!("Sheet 'Start' not found"))?;
|
||||||
|
|
||||||
// Собираем статистику по ненайденным логотипам для дебага
|
let total_start_rows = start_sheet.rows.len();
|
||||||
|
|
||||||
|
// Сразу фильтруем только строки с STATE = "FALSE"
|
||||||
|
let active_rows: Vec<(usize, &HashMap<String, String>)> = start_sheet
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, row)| row.get("STATE").map(|s| s.as_str()) == Some("FALSE"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Found {} active rows (STATE='FALSE') out of {} total",
|
||||||
|
active_rows.len(),
|
||||||
|
total_start_rows
|
||||||
|
);
|
||||||
|
|
||||||
|
// Собираем использованные команды для очистки logos
|
||||||
|
let mut used_teams: HashSet<(String, String)> = HashSet::new();
|
||||||
|
let mut jobs: Vec<JobData> = Vec::with_capacity(active_rows.len() * 3);
|
||||||
|
|
||||||
|
// Для дебага
|
||||||
let mut missing_teams: HashSet<String> = HashSet::new();
|
let mut missing_teams: HashSet<String> = HashSet::new();
|
||||||
let mut missing_sports: HashSet<String> = HashSet::new();
|
let mut missing_sports: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
for (idx, row) in start_sheet.rows.iter().enumerate() {
|
for (idx, row) in active_rows {
|
||||||
if let Some(state) = row.get("STATE") {
|
let team_a = row.get("TEAM A").cloned().unwrap_or_default();
|
||||||
if state == "FALSE" {
|
let team_b = row.get("TEAM B").cloned().unwrap_or_default();
|
||||||
let team_a = row.get("TEAM A").cloned().unwrap_or_default();
|
let sport = row.get("SPORT").cloned().unwrap_or_default();
|
||||||
let team_b = row.get("TEAM B").cloned().unwrap_or_default();
|
|
||||||
let sport = row.get("SPORT").cloned().unwrap_or_default();
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
// Запоминаем использованные команды
|
||||||
let logo_a = logos.find(&team_a, &sport);
|
if !team_a.is_empty() {
|
||||||
let logo_b = logos.find(&team_b, &sport);
|
used_teams.insert((team_a.clone(), sport.clone()));
|
||||||
|
}
|
||||||
|
if !team_b.is_empty() {
|
||||||
|
used_teams.insert((team_b.clone(), sport.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
if !team_a.is_empty() && logo_a.is_none() {
|
if log_enabled!(Level::Debug) {
|
||||||
missing_teams.insert(team_a.clone());
|
let logo_a = logos.find(&team_a, &sport);
|
||||||
missing_sports.insert(sport.clone());
|
let logo_b = logos.find(&team_b, &sport);
|
||||||
}
|
|
||||||
if !team_b.is_empty() && logo_b.is_none() {
|
|
||||||
missing_teams.insert(team_b.clone());
|
|
||||||
missing_sports.insert(sport.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(job) = JobData::from_row(row, idx, &packs, &logos, &channels) {
|
if !team_a.is_empty() && logo_a.is_none() {
|
||||||
jobs.extend(job.create_variants());
|
missing_teams.insert(team_a.clone());
|
||||||
}
|
missing_sports.insert(sport.clone());
|
||||||
|
}
|
||||||
|
if !team_b.is_empty() && logo_b.is_none() {
|
||||||
|
missing_teams.insert(team_b.clone());
|
||||||
|
missing_sports.insert(sport.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(job) = JobData::from_row(row, idx, &packs, &logos, &channels) {
|
||||||
|
jobs.extend(job.create_variants());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Очищаем logos от неиспользуемых команд
|
||||||
|
let logos_before = logos.len();
|
||||||
|
logos.retain(|team, sport| used_teams.contains(&(team.to_string(), sport.to_string())));
|
||||||
|
info!(
|
||||||
|
"Retained {} used team logos (cleaned up {} unused)",
|
||||||
|
logos.len(),
|
||||||
|
logos_before - logos.len()
|
||||||
|
);
|
||||||
|
|
||||||
// Дебаг: выводим сводку по отсутствующим логотипам
|
// Дебаг: выводим сводку по отсутствующим логотипам
|
||||||
if log_enabled!(Level::Debug) && !missing_teams.is_empty() {
|
if log_enabled!(Level::Debug) && !missing_teams.is_empty() {
|
||||||
debug!("=== MISSING LOGOS SUMMARY ===");
|
debug!("=== MISSING LOGOS SUMMARY ===");
|
||||||
@@ -415,72 +454,68 @@ async fn generate_and_submit_jobs(
|
|||||||
|
|
||||||
info!("Generated {} total jobs (including variants)", jobs.len());
|
info!("Generated {} total jobs (including variants)", jobs.len());
|
||||||
|
|
||||||
|
// ========== СОРТИРОВКА ПО ДАТЕ И ВРЕМЕНИ ==========
|
||||||
|
jobs.sort_by(|a, b| match a.sort_date.cmp(&b.sort_date) {
|
||||||
|
std::cmp::Ordering::Equal => match a.sort_time.cmp(&b.sort_time) {
|
||||||
|
std::cmp::Ordering::Equal => a.variant_order.cmp(&b.variant_order),
|
||||||
|
other => other,
|
||||||
|
},
|
||||||
|
other => other,
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("Jobs sorted by date and time (earliest first)");
|
||||||
|
|
||||||
|
if log_enabled!(Level::Debug) {
|
||||||
|
for (i, job) in jobs.iter().take(10).enumerate() {
|
||||||
|
debug!(
|
||||||
|
" {}: {} {} - {} (variant: {})",
|
||||||
|
i + 1,
|
||||||
|
job.sort_date,
|
||||||
|
job.sort_time,
|
||||||
|
job.outfile_name,
|
||||||
|
job.variant_order
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if jobs.is_empty() {
|
if jobs.is_empty() {
|
||||||
info!("No jobs with STATE='FALSE' found");
|
info!("No jobs with STATE='FALSE' found");
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== ОТПРАВКА ЗАДАНИЙ (ПОСЛЕДОВАТЕЛЬНО) ==========
|
||||||
cleanup_finished_jobs(&config.nexrender_api_url).await?;
|
cleanup_finished_jobs(&config.nexrender_api_url).await?;
|
||||||
|
|
||||||
let http_client = Client::new();
|
let http_client = Client::new();
|
||||||
let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len());
|
let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len());
|
||||||
|
|
||||||
// Отправляем задания группами по 3 (оригинал + today + tomorrow)
|
for job in jobs {
|
||||||
for chunk in jobs.chunks(3) {
|
let nexrender_job = job.to_nexrender_job(config);
|
||||||
let mut tasks = Vec::with_capacity(3);
|
info!("Submitting job: {}", job.outfile_name);
|
||||||
|
|
||||||
for job in chunk {
|
if log_enabled!(Level::Debug) {
|
||||||
let client = http_client.clone();
|
debug!(
|
||||||
let api_url = config.nexrender_api_url.clone();
|
"Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'",
|
||||||
let config_clone = config.clone();
|
job.sport, job.league, job.channel, job.team_a, job.team_b
|
||||||
let job_owned = job.clone();
|
);
|
||||||
|
|
||||||
tasks.push(tokio::spawn(async move {
|
|
||||||
let nexrender_job = job_owned.to_nexrender_job(&config_clone);
|
|
||||||
info!("Submitting job: {}", job_owned.outfile_name);
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
debug!(
|
|
||||||
"Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'",
|
|
||||||
job_owned.sport, job_owned.league, job_owned.channel, job_owned.team_a, job_owned.team_b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = client.post(&api_url).json(&nexrender_job).send().await?;
|
|
||||||
|
|
||||||
if response.status().is_success() {
|
|
||||||
let result: Value = response.json().await?;
|
|
||||||
if let Some(uid) = result.get("uid").and_then(|u| u.as_str()) {
|
|
||||||
Ok::<_, anyhow::Error>(Some((uid.to_string(), job_owned.outfile_name)))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let status = response.status();
|
|
||||||
let text = response.text().await.unwrap_or_default();
|
|
||||||
error!("Failed to submit job ({}): {}", status, text);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ждём завершения группы
|
let response = http_client
|
||||||
for task in tasks {
|
.post(&config.nexrender_api_url)
|
||||||
match task.await {
|
.json(&nexrender_job)
|
||||||
Ok(Ok(Some((uid, outfile_name)))) => {
|
.send()
|
||||||
info!("Job submitted: {} (UID: {})", outfile_name, uid);
|
.await?;
|
||||||
submitted_jobs.push((uid, outfile_name));
|
|
||||||
}
|
if response.status().is_success() {
|
||||||
Ok(Ok(None)) => {
|
let result: Value = response.json().await?;
|
||||||
debug!("Job submission returned no UID");
|
if let Some(uid) = result.get("uid").and_then(|u| u.as_str()) {
|
||||||
}
|
info!("Job submitted: {} (UID: {})", job.outfile_name, uid);
|
||||||
Ok(Err(e)) => {
|
submitted_jobs.push((uid.to_string(), job.outfile_name.clone()));
|
||||||
error!("Job submission error: {}", e);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Task join error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await.unwrap_or_default();
|
||||||
|
error!("Failed to submit job ({}): {}", status, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/static/favicon.ico
Normal file
BIN
src/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -4,12 +4,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>AE Anons - Nexrender Job Manager</title>
|
<title>AE Anons</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
<link rel="apple-touch-icon" href="/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="/assets/logo.png">
|
||||||
|
<!-- CSS -->
|
||||||
<link rel="stylesheet" href="/static/fontawesome/all.min.css">
|
<link rel="stylesheet" href="/static/fontawesome/all.min.css">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
<i class="fas fa-bolt"></i>
|
<i class="fas fa-bolt"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1>AE Anons - Nexrender Job Manager</h1>
|
<h1>AE Anons</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<button class="theme-toggle" onclick="toggleTheme()">
|
<button class="theme-toggle" onclick="toggleTheme()">
|
||||||
|
|||||||
25
src/web.rs
25
src/web.rs
@@ -89,13 +89,16 @@ pub async fn run_web_server(config: Config) -> anyhow::Result<()> {
|
|||||||
.route("/favicon.ico", get(favicon))
|
.route("/favicon.ico", get(favicon))
|
||||||
.route("/static/style.css", get(style_css))
|
.route("/static/style.css", get(style_css))
|
||||||
.route("/static/fontawesome/all.min.css", get(fontawesome_css))
|
.route("/static/fontawesome/all.min.css", get(fontawesome_css))
|
||||||
.nest_service("/static/fontawesome/webfonts", ServeDir::new("src/static/fontawesome/webfonts"))
|
|
||||||
.route("/assets/logo.png", get(logo_png))
|
.route("/assets/logo.png", get(logo_png))
|
||||||
.route("/api/jobs", get(list_jobs))
|
.route("/api/jobs", get(list_jobs))
|
||||||
.route("/api/generate", post(generate_jobs))
|
.route("/api/generate", post(generate_jobs))
|
||||||
.route("/api/cleanup", post(cleanup_jobs))
|
.route("/api/cleanup", post(cleanup_jobs))
|
||||||
.route("/api/status", get(get_status))
|
.route("/api/status", get(get_status))
|
||||||
.route("/api/jobs/stop-all", post(stop_all_jobs))
|
.route("/api/jobs/stop-all", post(stop_all_jobs))
|
||||||
|
.nest_service(
|
||||||
|
"/static/fontawesome/webfonts",
|
||||||
|
ServeDir::new("src/static/fontawesome/webfonts"),
|
||||||
|
)
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
@@ -112,19 +115,31 @@ async fn index_page() -> Html<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn style_css() -> impl IntoResponse {
|
async fn style_css() -> impl IntoResponse {
|
||||||
([(CONTENT_TYPE, "text/css")], include_str!("static/style.css"))
|
(
|
||||||
|
[(CONTENT_TYPE, "text/css")],
|
||||||
|
include_str!("static/style.css"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fontawesome_css() -> impl IntoResponse {
|
async fn fontawesome_css() -> impl IntoResponse {
|
||||||
([(CONTENT_TYPE, "text/css")], include_str!("static/fontawesome/all.min.css"))
|
(
|
||||||
|
[(CONTENT_TYPE, "text/css")],
|
||||||
|
include_str!("static/fontawesome/all.min.css"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn logo_png() -> impl IntoResponse {
|
async fn logo_png() -> impl IntoResponse {
|
||||||
([(CONTENT_TYPE, "image/png")], include_bytes!("../assets/logo.png").as_slice())
|
(
|
||||||
|
[(CONTENT_TYPE, "image/png")],
|
||||||
|
include_bytes!("../assets/logo.png").as_slice(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn favicon() -> impl IntoResponse {
|
async fn favicon() -> impl IntoResponse {
|
||||||
([(CONTENT_TYPE, "image/png")], include_bytes!("../assets/logo.png").as_slice())
|
(
|
||||||
|
[(CONTENT_TYPE, "image/x-icon")],
|
||||||
|
include_bytes!("static/favicon.ico").as_slice(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_jobs(State(state): State<AppState>) -> Result<Json<Vec<JobInfo>>, AppError> {
|
async fn list_jobs(State(state): State<AppState>) -> Result<Json<Vec<JobInfo>>, AppError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user