1
1
Files
audio-catalyst/internal/application/usecases/process_audiobooks.go

240 lines
8.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 usecases
import (
"fmt"
"path/filepath"
"audio-catalyst/internal/domain/entities"
"audio-catalyst/internal/domain/repositories"
"audio-catalyst/internal/domain/services"
)
// ProcessAudioBooksUseCase обрабатывает аудиокниги
type ProcessAudioBooksUseCase struct {
audioBookRepo repositories.AudioBookRepository
rutrackerRepo repositories.RuTrackerRepository
logger repositories.Logger
audioBookSvc *services.AudioBookService
metadataSvc *services.MetadataService
// Репортер прогресса
progress func(entities.ProcessingStatus)
}
// NewProcessAudioBooksUseCase создает новый use case
func NewProcessAudioBooksUseCase(
audioBookRepo repositories.AudioBookRepository,
rutrackerRepo repositories.RuTrackerRepository,
logger repositories.Logger,
) *ProcessAudioBooksUseCase {
return &ProcessAudioBooksUseCase{
audioBookRepo: audioBookRepo,
rutrackerRepo: rutrackerRepo,
logger: logger,
audioBookSvc: services.NewAudioBookService(),
metadataSvc: services.NewMetadataService(),
}
}
// SetProgressReporter задает функцию репортинга прогресса
func (uc *ProcessAudioBooksUseCase) SetProgressReporter(f func(entities.ProcessingStatus)) {
uc.progress = f
}
// Execute выполняет обработку аудиокниг
func (uc *ProcessAudioBooksUseCase) Execute(config *entities.Config) error {
uc.logger.Info("🚀 Начало процесса обработки аудиокниг")
uc.logger.Info("📁 Исходная директория: %s", config.Scanner.SourceDirectory)
// Сканирование папок с аудиокнигами
audioBooks, err := uc.audioBookRepo.ScanDirectory(config.Scanner.SourceDirectory)
if err != nil {
uc.logger.Error("❌ Критическая ошибка сканирования: %v", err)
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: 0, Total: 0, Status: "Ошибка сканирования", Error: err})
}
return fmt.Errorf("ошибка сканирования: %w", err)
}
total := len(audioBooks)
uc.logger.Info("📊 Найдено %d аудиокниг", total)
if total == 0 {
uc.logger.Warning("⚠️ Аудиокниги не найдены")
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: 0, Total: 0, Status: "Книги не найдены"})
}
return nil
}
// Сообщаем общее количество до начала обработки
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: 0, Total: total, Status: "Начинаем обработку"})
}
// Авторизация в RuTracker
if err := uc.rutrackerRepo.Login(); err != nil {
uc.logger.Error("❌ Ошибка авторизации в RuTracker: %v", err)
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: 0, Total: total, Status: "Ошибка авторизации", Error: err})
}
return fmt.Errorf("ошибка авторизации: %w", err)
}
uc.logger.Success("✅ Авторизация в RuTracker успешна")
// Обработка каждой аудиокниги
for i, book := range audioBooks {
uc.logger.Info("📋 Обработка книги %d/%d: \"%s\"", i+1, total, book.Title)
if err := uc.processAudioBook(book); err != nil {
uc.logger.Error("❌ Ошибка обработки книги \"%s\": %v", book.Title, err)
}
// Увеличиваем прогресс после завершения обработки книги (успешно или нет)
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: i + 1, Total: total, Status: fmt.Sprintf("Готово %d/%d", i+1, total)})
}
uc.logger.Success("✅ Книга \"%s\" обработана", book.Title)
}
if uc.progress != nil {
uc.progress(entities.ProcessingStatus{Current: total, Total: total, Status: "Обработка завершена"})
}
uc.logger.Success("🎉 Обработка завершена успешно!")
return nil
}
// processAudioBook обрабатывает одну аудиокнигу
func (uc *ProcessAudioBooksUseCase) processAudioBook(book entities.AudioBook) error {
// Поиск торрентов
torrents, err := uc.rutrackerRepo.Search(book.Title, 1)
if err != nil {
uc.logger.Warning("⚠️ Ошибка поиска торрентов для \"%s\": %v", book.Title, err)
return nil // переходим к следующей книге
}
if len(torrents) == 0 {
uc.logger.Warning("⚠️ Не найдено на RuTracker: \"%s\" — пропускаем", book.Title)
return nil // переходим к следующей книге
}
// Используем первый торрент
bestTorrent := torrents[0]
uc.logger.Info("📦 Выбран торрент: %s", bestTorrent.Title)
// Получаем метаданные
rutrackerResult, err := uc.rutrackerRepo.GetTopicMetadata(bestTorrent.ID)
if err != nil {
uc.logger.Warning("⚠️ Ошибка получения метаданных: %v", err)
return nil // переходим к следующей книге
}
if rutrackerResult.CoverURL != "" {
uc.logger.Info("🖼️ Найдена обложка: %s", rutrackerResult.CoverURL)
} else {
uc.logger.Debug("Обложка на странице не найдена")
}
// Создаем метаданные
metadata := uc.createMetadataFromRuTracker(book, rutrackerResult)
// Переименуем папку книги под Subtitle, если он есть
bookPath := book.Path
if metadata.Subtitle != "" && metadata.Subtitle != filepath.Base(book.Path) {
if newPath, err := uc.audioBookRepo.RenameBookFolder(book.Path, metadata.Subtitle); err != nil {
uc.logger.Warning("⚠️ Не удалось переименовать папку: %v", err)
} else {
uc.logger.Success("📁 Папка переименована: %s", filepath.Base(newPath))
bookPath = newPath
}
}
// Если есть CoverURL — пробуем скачать обложку
if rutrackerResult.CoverURL != "" {
if err := uc.audioBookRepo.DownloadCover(rutrackerResult.CoverURL, bookPath); err != nil {
uc.logger.Warning("⚠️ Не удалось загрузить обложку: %v", err)
} else {
uc.logger.Success("🖼️ Обложка загружена")
}
}
// Сохраняем метаданные
if err := uc.audioBookRepo.SaveMetadata(bookPath, metadata); err != nil {
return fmt.Errorf("ошибка сохранения метаданных: %w", err)
}
uc.logger.Success("💾 Метаданные сохранены")
// Организация в библиотеке organized
if len(metadata.Authors) > 0 {
author := metadata.Authors[0]
targetRoot := "./organized"
if newPath, err := uc.audioBookRepo.OrganizeBookFolder(bookPath, author, targetRoot); err != nil {
uc.logger.Warning("⚠️ Не удалось организовать папку: %v", err)
} else {
uc.logger.Success("📚 Папка перемещена в библиотеку: %s", newPath)
}
}
return nil
}
// createBasicMetadata создает базовые метаданные
func (uc *ProcessAudioBooksUseCase) createBasicMetadata(book entities.AudioBook) error {
metadata := &entities.AudioBookMetadata{
Tags: []string{},
Chapters: uc.audioBookSvc.CreateChapters(book.MP3Files),
Title: book.Title,
Subtitle: book.Title,
Authors: []string{},
Narrators: []string{},
Series: []string{},
Genres: []string{},
Description: book.Description,
Language: "ru",
Explicit: false,
Abridged: false,
}
return uc.audioBookRepo.SaveMetadata(book.Path, metadata)
}
// createMetadataFromRuTracker создает метаданные на основе данных RuTracker
func (uc *ProcessAudioBooksUseCase) createMetadataFromRuTracker(book entities.AudioBook, rutrackerResult *entities.RuTrackerResult) *entities.AudioBookMetadata {
metadata := &entities.AudioBookMetadata{
Tags: []string{},
Chapters: uc.audioBookSvc.CreateChapters(book.MP3Files),
Title: rutrackerResult.Title,
Subtitle: rutrackerResult.Subtitle,
Authors: rutrackerResult.Authors,
Narrators: rutrackerResult.Narrators,
Series: rutrackerResult.Series,
Genres: rutrackerResult.Genres,
Description: rutrackerResult.Description,
Language: "ru",
Explicit: false,
Abridged: false,
}
if rutrackerResult.Year != nil {
metadata.PublishedYear = rutrackerResult.Year
}
if rutrackerResult.Publisher != nil {
metadata.Publisher = rutrackerResult.Publisher
}
if metadata.Title == "" {
metadata.Title = book.Title
}
if metadata.Subtitle == "" {
metadata.Subtitle = metadata.Title
}
if metadata.Description == "" && book.Description != "" {
metadata.Description = book.Description
}
return metadata
}