1
1
Files
audio-catalyst/internal/domain/services/audiobook_service.go
Dmitriy Fofanov 72a66f1664 Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы.
- Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов.
- Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов.
- Добавить тесты для нормализации URL в репозиторий RuTracker.
- Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер.
- Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
2025-09-29 20:40:05 +03:00

133 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}