跪拜 Guibai
← Back to the summary

An AI Podcast Agent Gains Video Recognition and a Self-Cleaning Storage Layer

Project Introduction

This is an AI-driven podcast creation assistant focused on the full workflow from text/video/audio content recognition to podcast production. You can converse with it using natural language, or upload videos and audio; it will understand your intent, invoke the corresponding speech synthesis tools, and turn them into real podcast audio.

For a detailed introduction to the project, see the following articles:

  1. First AI Agent Project: Building an AI Audio Creation Assistant from Scratch (Beginner's Guide): This is the first version, which mainly covers the initial framework setup, including a Vue3 + ai-elements-vue frontend interface, and a backend driven by LangChain 1.0 × AG-UI Protocol × Qwen TTS, supporting custom timbres, voice cloning, and dubbing functions.

  2. First AI Agent Project: Building an AI Audio Creation Assistant from Scratch (Advanced Guide): This is the second version, which added complete podcast post-production capabilities, including three core functions: audio splicing, intelligent BGM selection, and track mixing. It also added an audio resource management API and voice input functionality, achieving a full-chain closed loop from frontend upload and voice input to backend processing.

This article focuses more on the features added in the high-level guide.

Update Overview

This update adds audio and video recognition capabilities, a temporary-permanent dual-layer storage architecture, a scheduled cleanup mechanism, and a smarter prompt system, upgrading the podcast Agent from a simple "speech synthesis tool" to a "full-chain audio content creation platform."


1. Backend Updates

1. Audio/Video File Storage Path Restructuring

Changed File: backend/app/tools/audio_index.py

Previously, audio files were uniformly saved in the storage/audios/ directory. Now they are saved to the temporary directory storage/temp/, achieving separate management of temporary and permanent files.

Core Change: Added the save_media_to_temp() function, supporting three input types:

def save_media_to_temp(media_data, filename: str) -> str:
    """Save audio/video files to the temporary directory"""
    temp_dir = STORAGE_DIR / "temp"
    temp_dir.mkdir(parents=True, exist_ok=True)
    file_path = temp_dir / filename

    if isinstance(media_data, str):
        # base64 string: decode and save
        file_data = base64.b64decode(media_data)
        file_path.write_bytes(file_data)
    elif isinstance(media_data, bytes):
        # bytes object: save directly
        file_path.write_bytes(media_data)
    else:
        # AudioSegment object: export file
        file_format = filename.split('.')[-1]
        media_data.export(str(file_path), format=file_format)

    return f"storage/temp/{filename}"

Design Rationale: Intermediate products generated by tools like timbre design and voice cloning are uniformly placed in storage/temp/. Once the user confirms satisfaction, the timbre is permanently migrated to storage/audios/ via the save_voice tool. This avoids permanent directory bloat and makes file lifecycle management clearer.


2. Scheduled Temporary File Cleanup Task

Changed File: backend/app/utils/temp_cleanup.py (New)

A new scheduled cleanup mechanism automatically removes expired files from the storage/temp/ directory that exceed a specified time limit, preventing temporary file accumulation from consuming disk space.

Core Implementation:

def cleanup_temp_files(max_age_minutes: int = 10) -> dict:
    """Clean up expired temporary files"""
    cutoff_time = time.time() - (max_age_minutes * 60)

    for file_path in TEMP_DIR.rglob("*"):
        if not file_path.is_file():
            continue
        file_mtime = file_path.stat().st_mtime
        if file_mtime < cutoff_time:
            file_size = file_path.stat().st_size
            file_path.unlink()
            stats["deleted_files"] += 1
            stats["freed_bytes"] += file_size

    # Clean up empty directories
    cleanup_empty_dirs(TEMP_DIR)
    return stats

Key Features:


3. Voice Saving Tool

Changed File: backend/app/tools/voice_save.py (New)

A new save_voice tool permanently saves user-approved custom timbres from the temporary directory to storage/audios/ and records them in the voice_index.json index file.

Core Logic:

@tool("save_voice", args_schema=VoiceSaveInput)
def save_voice_tool(audio_source: str, voice_id: str, text: str, model_name: str = "") -> str:
    # Determine input type: file path or base64 data
    if audio_source.startswith("storage/") or audio_source.startswith("/"):
        # Copy from temporary directory to permanent directory
        source_path = BASE_DIR / audio_source.lstrip("/")
        dest_path = AUDIOS_DIR / source_path.name
        shutil.copy2(source_path, dest_path)
        local_path = f"storage/audios/{source_path.name}"
    else:
        # base64 data: decode and save
        local_path = save_audio_from_base64(audio_source, text, "saved_voice")

    # Record to index file
    record_voice_index(local_path, voice_id, model_name, path="audios")
    return json.dumps({"audio_url": local_path, "voice_id": voice_id, ...})

Design Highlights:


4. Speech Recognition Tool (ASR)

Changed File: backend/app/tools/qwen_asr.py (New)

Integrates the Alibaba Cloud DashScope qwen3-asr-flash model, providing high-precision speech recognition capabilities.

Core Call Chain:

@tool("qwen_asr_tool", args_schema=ASRInput)
def qwen_asr_tool(audio: str, enable_itn: bool = False) -> str:
    # 1. Parse audio source (URL / local path → data URI)
    audio_url = _resolve_audio_source(audio)

    # 2. Call ASR API
    response = _call_asr_api(audio_url, enable_itn)

    # 3. Extract recognized text
    text = _extract_text_from_response(response)
    return text

Key Capabilities:


5. Multimodal Recognition Tool

Changed File: backend/app/tools/qwen_multimodal.py (New)

Integrates the Alibaba Cloud DashScope qwen3.5-omni-plus model, supporting unified multimodal understanding of images, videos, and audio.

Two Core Tools:

Tool Name Function Applicable Scenario
qwen_multimodal_tool Single-media multimodal recognition Analyze a single image/video/audio clip
qwen_combined_multimodal_tool Combined multimodal recognition Analyze multiple media simultaneously (e.g., image + audio combination)

Large Video Intelligent Segmentation: When a video file exceeds 21MB, it is automatically split into multiple segments using moviepy for separate processing:

def _split_and_encode_video(video_path: Path, suffix: str) -> List[dict]:
    video = VideoFileClip(str(video_path))
    target_size = 10 * 1024 * 1024  # 10MB per segment
    num_segments = max(1, int(file_size / target_size))
    segment_duration = duration / num_segments

    for i in range(num_segments):
        segment = video.subclipped(start_time, end_time)
        # Encode and add to segment list
        segments.append(_build_media_content(data_uri, suffix, is_url=False))
    return segments

Additional Capabilities:


6. Prompt Optimization

Changed File: backend/app/services/prompt.py

The system prompt for the podcast Agent has been comprehensively refined and enhanced:

Optimization Highlights:

New Audio/Video-to-Podcast Workflow Description:

**Audio/Video to Podcast Workflow**:
1. Content Recognition: Call qwen_multimodal_tool or qwen_asr_tool to recognize content in the audio/video
2. Script Arrangement: Based on the recognized content, organize it into a podcast script
3. Podcast Production: Continue executing according to the workflow steps to complete podcast production

2. Frontend Updates

There are no frontend changes in this update, focusing mainly on the expansion and optimization of backend capabilities. The existing ChatAgent.vue on the frontend already possesses capabilities for audio/video file upload, playback preview, and tool call result display, fully capable of supporting the newly added ASR and multimodal recognition features on the backend.


3. Architecture Summary

The core thread of this update:

User uploads audio/video
        │
        ▼
┌─────────────────────────────────────────────┐
│  Content Recognition Layer                  │
│  ├─ qwen_asr_tool         Speech→Text       │
│  └─ qwen_multimodal_tool  Audio/Video→Understanding │
└─────────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────────┐
│  Podcast Production Layer                   │
│  ├─ Timbre Design → Temp Dir (storage/temp/)│
│  ├─ save_voice → Permanent Dir (storage/audios/) │
│  └─ Audio Synthesis / Mixing / Splicing     │
└─────────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────────┐
│  System Maintenance Layer                   │
│  └─ temp_cleanup  Scheduled cleanup of temp files │
└─────────────────────────────────────────────┘

Through the "temporary-permanent" dual-layer storage architecture + scheduled cleanup mechanism, both the flexibility of the podcast production process and effective management of system resources are ensured.


4. Technical Highlights

1. Temporary-Permanent Dual-Layer Storage Architecture

The core architectural design of this update. Intermediate products generated by tools (timbre designs, voice cloning results) are uniformly written to storage/temp/. Only timbres explicitly confirmed as satisfactory by the user are migrated to storage/audios/ via the save_voice tool. This design:

2. Large Video Intelligent Segmentation Strategy

In qwen_multimodal.py, when a video file exceeds 21MB, it is automatically split evenly along the timeline into multiple ≤10MB segments using moviepy for separate upload and recognition. This solves the limitation of large model APIs on single file size while ensuring recognition completeness.

3. Concurrency-Safe Index File Writing

voice_save.py uses fcntl.flock to apply an exclusive lock on voice_index.json, ensuring no data races or index corruption occur during concurrent writes from multiple requests.

4. Modular Prompt Engineering

prompt.py splits the system prompt into 6 independent modules (capability positioning, working methods, tool rules, communication norms, context understanding, execution flow). Each module has a single responsibility and clear boundaries, significantly reducing the probability of LLM misinvoking tools and facilitating subsequent iterative maintenance.


5. Results Display

image.png image.png image.png

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

breeze微风

Is it open source? I want to try it out.