""" 批量为故事和音乐生成引导语 Opus 数据并写入数据库。 使用方法: python manage.py generate_intro_opus # 处理所有 python manage.py generate_intro_opus --type story # 仅故事 python manage.py generate_intro_opus --type music # 仅音乐 python manage.py generate_intro_opus --dry-run # 仅统计 python manage.py generate_intro_opus --limit 10 # 只处理前 10 个 python manage.py generate_intro_opus --force # 重新生成已有引导语的记录 """ import logging from django.core.management.base import BaseCommand logger = logging.getLogger(__name__) class Command(BaseCommand): help = '批量为故事和音乐生成引导语 Opus 数据' def add_arguments(self, parser): parser.add_argument( '--type', choices=['story', 'music', 'all'], default='all', help='处理类型:story / music / all(默认 all)', ) parser.add_argument( '--dry-run', action='store_true', help='仅统计需要处理的数量,不实际执行', ) parser.add_argument( '--limit', type=int, default=0, help='最多处理的数量(0=不限)', ) parser.add_argument( '--force', action='store_true', help='重新生成已有引导语的记录', ) def handle(self, *args, **options): content_type = options['type'] dry_run = options['dry_run'] limit = options['limit'] force = options['force'] if content_type in ('story', 'all'): self._process_stories(dry_run, limit, force) if content_type in ('music', 'all'): self._process_tracks(dry_run, limit, force) def _process_stories(self, dry_run, limit, force): from apps.stories.models import Story from apps.stories.services.intro_service import generate_intro_opus self.stdout.write(self.style.MIGRATE_HEADING('\n=== 故事引导语 ===')) qs = Story.objects.exclude(audio_url='') if not force: qs = qs.filter(intro_opus_data='') qs = qs.order_by('id') total = qs.count() self.stdout.write(f'需要处理的故事: {total} 个') if dry_run or total == 0: return items = qs[:limit] if limit > 0 else qs success_count = 0 fail_count = 0 for i, story in enumerate(items.iterator(), 1): self.stdout.write(f'[{i}/{total}] Story#{story.id} "{story.title}"') try: opus_json = generate_intro_opus(story.title, content_type='story') story.intro_opus_data = opus_json story.save(update_fields=['intro_opus_data']) success_count += 1 self.stdout.write(self.style.SUCCESS( f' OK ({len(opus_json) / 1024:.1f} KB)' )) except Exception as e: fail_count += 1 self.stdout.write(self.style.ERROR(f' FAIL: {e}')) logger.error(f'Story#{story.id} intro generate failed: {e}') self.stdout.write(self.style.SUCCESS( f'故事完成: 成功 {success_count}, 失败 {fail_count}' )) def _process_tracks(self, dry_run, limit, force): from apps.music.models import Track from apps.stories.services.intro_service import generate_intro_opus self.stdout.write(self.style.MIGRATE_HEADING('\n=== 音乐引导语 ===')) qs = Track.objects.filter( generation_status='completed', ).exclude(audio_url='') if not force: qs = qs.filter(intro_opus_data='') qs = qs.order_by('id') total = qs.count() self.stdout.write(f'需要处理的曲目: {total} 个') if dry_run or total == 0: return items = qs[:limit] if limit > 0 else qs success_count = 0 fail_count = 0 for i, track in enumerate(items.iterator(), 1): self.stdout.write(f'[{i}/{total}] Track#{track.id} "{track.title}"') try: opus_json = generate_intro_opus(track.title, content_type='music') track.intro_opus_data = opus_json track.save(update_fields=['intro_opus_data']) success_count += 1 self.stdout.write(self.style.SUCCESS( f' OK ({len(opus_json) / 1024:.1f} KB)' )) except Exception as e: fail_count += 1 self.stdout.write(self.style.ERROR(f' FAIL: {e}')) logger.error(f'Track#{track.id} intro generate failed: {e}') self.stdout.write(self.style.SUCCESS( f'音乐完成: 成功 {success_count}, 失败 {fail_count}' ))