Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы. - Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов. - Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов. - Добавить тесты для нормализации URL в репозиторий RuTracker. - Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер. - Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
This commit is contained in:
132
internal/domain/services/audiobook_service.go
Normal file
132
internal/domain/services/audiobook_service.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user