rtc_backend/apps/music/management/commands/convert_tracks_to_opus.py
repair-agent 51a673e814
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 6m7s
Add story music
2026-03-04 13:11:10 +08:00

123 lines
4.4 KiB
Python
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.

"""
批量将已有音乐的 MP3 音频预转码为 Opus 帧 JSON 并上传 OSS。
使用方法:
python manage.py convert_tracks_to_opus
python manage.py convert_tracks_to_opus --dry-run # 仅统计,不转码
python manage.py convert_tracks_to_opus --limit 10 # 只处理前 10 个
python manage.py convert_tracks_to_opus --force # 重新转码已有 opus_url 的曲目
python manage.py convert_tracks_to_opus --default # 仅处理系统默认曲目
"""
import uuid
import logging
from datetime import datetime
import requests
from django.conf import settings
from django.core.management.base import BaseCommand
from apps.music.models import Track
from apps.stories.services.opus_converter import convert_mp3_to_opus_json
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '批量将已有音乐的 MP3 音频预转码为 Opus 帧 JSON'
def add_arguments(self, parser):
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='重新转码已有 opus_url 的曲目',
)
parser.add_argument(
'--default', action='store_true',
help='仅处理系统默认曲目is_default=True',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
limit = options['limit']
force = options['force']
default_only = options['default']
# 查找需要转码的曲目
qs = Track.objects.filter(
generation_status='completed',
).exclude(audio_url='')
if not force:
qs = qs.filter(opus_url='')
if default_only:
qs = qs.filter(is_default=True)
qs = qs.order_by('id')
total = qs.count()
self.stdout.write(f'需要转码的曲目: {total}')
if dry_run:
self.stdout.write(self.style.NOTICE('[dry-run] 仅统计,不执行转码'))
return
if total == 0:
self.stdout.write(self.style.SUCCESS('所有曲目已转码,无需处理'))
return
# OSS 客户端
from utils.oss import get_oss_client
oss_client = get_oss_client()
oss_config = settings.ALIYUN_OSS
if oss_config.get('CUSTOM_DOMAIN'):
url_prefix = f"https://{oss_config['CUSTOM_DOMAIN']}"
else:
url_prefix = f"https://{oss_config['BUCKET_NAME']}.{oss_config['ENDPOINT']}"
tracks = qs[:limit] if limit > 0 else qs
success_count = 0
fail_count = 0
for i, track in enumerate(tracks.iterator(), 1):
self.stdout.write(f'\n[{i}/{total}] Track#{track.id} "{track.title}"')
self.stdout.write(f' MP3: {track.audio_url[:80]}...')
try:
# 下载 MP3
resp = requests.get(track.audio_url, timeout=60)
resp.raise_for_status()
mp3_bytes = resp.content
self.stdout.write(f' MP3 大小: {len(mp3_bytes) / 1024:.1f} KB')
# 转码
opus_json = convert_mp3_to_opus_json(mp3_bytes)
self.stdout.write(f' Opus JSON 大小: {len(opus_json) / 1024:.1f} KB')
# 上传 OSS
opus_filename = f"{datetime.now().strftime('%Y%m%d')}/{uuid.uuid4().hex}.json"
opus_key = f"music/audio-opus/{opus_filename}"
oss_client.bucket.put_object(opus_key, opus_json.encode('utf-8'))
opus_url = f"{url_prefix}/{opus_key}"
track.opus_url = opus_url
track.save(update_fields=['opus_url'])
success_count += 1
self.stdout.write(self.style.SUCCESS(f' OK: {opus_url}'))
except Exception as e:
fail_count += 1
self.stdout.write(self.style.ERROR(f' FAIL: {e}'))
logger.error(f'Track#{track.id} opus convert failed: {e}')
self.stdout.write(f'\n{"=" * 40}')
self.stdout.write(self.style.SUCCESS(
f'完成: 成功 {success_count}, 失败 {fail_count}, 总计 {success_count + fail_count}'
))