1
1

Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.

- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы.
- Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов.
- Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов.
- Добавить тесты для нормализации URL в репозиторий RuTracker.
- Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер.
- Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
This commit is contained in:
Dmitriy Fofanov
2025-09-29 20:40:05 +03:00
parent 49bea780aa
commit 72a66f1664
32 changed files with 4073 additions and 22 deletions

View File

@@ -0,0 +1,132 @@
package services
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"audio-catalyst/internal/domain/entities"
)
// AudioBookService сервис для работы с аудиокнигами
type AudioBookService struct{}
// NewAudioBookService создает новый сервис аудиокниг
func NewAudioBookService() *AudioBookService {
return &AudioBookService{}
}
// CreateChapters создает главы из MP3 файлов
func (s *AudioBookService) CreateChapters(mp3Files []string) []entities.Chapter {
// Гарантируем пустой слайс, а не nil (в JSON будет [] вместо null)
chapters := make([]entities.Chapter, 0, len(mp3Files))
// Сортировка файлов по имени
sortedFiles := make([]string, len(mp3Files))
copy(sortedFiles, mp3Files)
sort.Strings(sortedFiles)
currentStart := 0.0
for i, file := range sortedFiles {
fileName := filepath.Base(file)
baseName := strings.TrimSuffix(fileName, ".mp3")
chapterTitle := s.extractChapterTitle(baseName)
// Оцениваем длительность файла в секундах
durSec := s.estimateDuration(file)
if durSec < 0 {
durSec = 0
}
chapter := entities.Chapter{
ID: i,
Start: currentStart,
End: currentStart + durSec,
Title: chapterTitle,
Duration: int(durSec),
}
currentStart += durSec
chapters = append(chapters, chapter)
}
return chapters
}
// extractChapterTitle извлекает название главы из имени файла
func (s *AudioBookService) extractChapterTitle(fileName string) string {
name := strings.TrimSuffix(fileName, ".mp3")
patterns := []string{
`^(\d{1,3})[\s\-_.]*(.*)$`,
`^[Cc]hapter[\s_]*(\d+)(.*)$`,
`^[Гг]лава[\s_]*(\d+)(.*)$`,
`^[Чч]асть[\s_]*(\d+)(.*)$`,
}
for _, pattern := range patterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(name); len(matches) >= 2 {
number := matches[1]
title := ""
if len(matches) >= 3 {
title = strings.TrimSpace(matches[2])
title = strings.TrimLeft(title, "-_. ")
}
if title != "" {
return fmt.Sprintf("%s - %s", number, title)
}
return number
}
}
return name
}
// estimateDuration оценивает продолжительность MP3 файла (в секундах)
// Приближение: 1 MB ≈ 0.86 минуты (≈51.6 секунды) при средних битрейтах
func (s *AudioBookService) estimateDuration(filePath string) float64 {
info, err := os.Stat(filePath)
if err != nil {
return 0
}
// Размер файла в MB
sizeMB := float64(info.Size()) / (1024.0 * 1024.0)
// Примерная продолжительность в минутах
estimatedMinutes := sizeMB * 0.86
return estimatedMinutes * 60.0
}
// CleanTitle очищает название книги от технической информации
func (s *AudioBookService) CleanTitle(title string) string {
// Удаляем информацию в квадратных скобках в конце
title = regexp.MustCompile(`\s*\[.*?\]\s*$`).ReplaceAllString(title, "")
// Удаляем информацию в круглых скобках в конце
title = regexp.MustCompile(`\s*\(.*?\)\s*$`).ReplaceAllString(title, "")
// Удаляем технические обозначения
techPatterns := []string{
`\s*MP3\s*$`,
`\s*FLAC\s*$`,
`\s*\d+\s*kbps\s*$`,
`\s*\d+\s*кбит/с\s*$`,
`\s*VBR\s*$`,
`\s*CBR\s*$`,
`\s*\d{4}\s*$`,
}
for _, pattern := range techPatterns {
title = regexp.MustCompile(`(?i)`+pattern).ReplaceAllString(title, "")
}
// Убираем лишние пробелы
title = strings.TrimSpace(title)
title = regexp.MustCompile(`\s+`).ReplaceAllString(title, " ")
return title
}