
튜토리얼
Twelve Labs와 함께 비디오 하이라이트 생성기 만들기

흐리시케시 야다브(Hrishikesh Yadav)
YouTube 챕터 하이라이트 생성기(Chapter Highlight Generator)는 YouTube 동영상에 대한 챕터 타임스탬프를 자동으로 생성해 주는 도구입니다. 비디오 콘텐츠를 직접 분석하여 주요 구간을 식별하고 타임스탬프를 생성함으로써, 보다 원활한 동영상 탐색과 향상된 사용자 경험(UX)을 위한 챕터 구성을 지원합니다.
YouTube 챕터 하이라이트 생성기(Chapter Highlight Generator)는 YouTube 동영상에 대한 챕터 타임스탬프를 자동으로 생성해 주는 도구입니다. 비디오 콘텐츠를 직접 분석하여 주요 구간을 식별하고 타임스탬프를 생성함으로써, 보다 원활한 동영상 탐색과 향상된 사용자 경험(UX)을 위한 챕터 구성을 지원합니다.

목차
No headings found on page
뉴스레터 구독하기
뉴스레터 구독하기
영상 이해 분야의 최신 기술 업데이트, 튜토리얼 및 인사이트를 받아보세요.
영상 이해 분야의 최신 기술 업데이트, 튜토리얼 및 인사이트를 받아보세요.
AI로 영상을 검색하고, 분석하고, 탐색하세요.
2024. 10. 25.
13분
링크 복사하기
소개
🎬 YouTube 동영상의 챕터 타임스탬프를 일일이 직접 만드느라 지치셨나요? 이제 매력적인 동영상 하이라이트를 자동으로 생성하여 지루한 작업 시간을 획기적으로 줄여보세요.
이번 튜토리얼에서는 콘텐츠 크리에이터의 하이라이트 제작 방식을 완전히 바꿔줄 강력한 도구인 Twelve Labs 기반의 'YouTube 챕터 하이라이트 생성기(YouTube Chapter Highlight Generator)'를 살펴보겠습니다. 이 애플리케이션은 동영상 제작 과정에서 가장 많은 시간이 소요되는 작업 중 하나인 정확하고 의미 있는 하이라이트 챕터 타임스탬프 생성을 자동화하여 해결해 줍니다.
이미 자리를 잡은 유튜버이든 이제 막 콘텐츠 제작을 시작한 초보자이든, 이 도구는 워크플로우를 대폭 간소화해 줍니다. 동영상 콘텐츠를 자동으로 분석하여 정확한 하이라이트 타임스탬프를 생성하고, 세분화된 동영상 클립까지 만들어 줍니다. 가장 좋은 점은 무엇일까요? 크리에이터가 숏폼 콘텐츠와 긴 팟캐스트 스타일의 동영상 모두에 활용할 수 있다는 점입니다. 이 애플리케이션이 어떻게 작동하는지, 그리고 여러분의 고유한 요구사항에 맞게 TwelveLabs Python SDK를 사용하여 이 앱을 직접 빌드하는 방법을 함께 알아보겠습니다.
이곳에서 애플리케이션 데모를 직접 체험해 볼 수 있습니다: Video Highlight Chapter Generation
코드를 확인하고 앱을 직접 실험해 보고 싶다면 이 Replit 템플릿을 사용해 보세요.

사전 준비 사항
Twelve Labs Playground에 가입하여 API 키를 생성하세요.
노트북과 이 애플리케이션의 리포지토리는 Video Highlight Chapter Generator Github에서 확인할 수 있습니다.
Python, HTML, Markdown 등 기본적인 개발 환경에 이미 익숙해져 있어야 합니다.
애플리케이션 작동 방식
이 섹션에서는 YouTube 동영상용 챕터 하이라이트를 개발하기 위한 애플리케이션 흐름을 간략히 설명합니다. 이를 통해 시간 절약은 물론 YouTube 콘텐츠에 하이라이트를 추가하는 과정을 훨씬 단순화할 수 있습니다.
팟캐스트 동영상의 경우, 시스템이 서로 다른 동영상 청크(Chunk)의 타임스탬프를 관리하고 결합합니다. 또한 사용자는 이전에 인덱싱된 동영상을 선택하여 인덱스에서 기존 동영상을 검색하고, 해당 URL을 가져와 비디오 ID를 통해 하이라이트 타임스탬프를 생성할 수 있습니다.
단계별 프로세스는 다음과 같습니다:
사용자 인터페이스 (UI)
애플리케이션은 상호작용을 위해 두 개의 메인 탭을 제공합니다.
첫 번째 탭에서는 사용자가 하이라이트 생성을 위해 새 동영상을 업로드할 수 있습니다.
두 번째 탭에서는 이전에 인덱싱된 동영상을 가져와서 볼 수 있습니다.
동영상 업로드 옵션
사용자는 두 가지 유형의 동영상 콘텐츠를 업로드할 수 있습니다.
일반 동영상(30분 미만 분량)과 최대 1시간 분량의 팟캐스트 스타일 동영상입니다.
프로세싱 워크플로우
시스템은 일반 동영상을 직접 처리합니다.
팟캐스트 스타일 동영상은 효율적인 처리를 위해 먼저 관리하기 쉬운 크기의 청크(Chunk)로 나뉩니다.
하이라이트 생성
인덱싱이 완료되면 시스템은 고유한 비디오 ID를 생성합니다.
이 비디오 ID는 generate.summarize 함수의 파라미터로 사용됩니다.
Pegasus 1.1 생성형 엔진(Generative Engine)이 동영상의 하이라이트 기반 타임스탬프를 생성합니다.
아웃풋 (출력)
최종 결과물은 동영상의 핵심 순간을 표시하는 타임스탬프 세트입니다.
이 타임스탬프들은 쉬운 탐색을 돕는 챕터 마커나 하이라이트 역할을 합니다.

동영상 클립 세분화는 동영상 URL과 하이라이트 타임스탬프에 액세스하는 moviepy.editor를 사용하여 수행됩니다. 콘텐츠 제작 편의성을 위해 동영상 세그먼트는 MP4 포맷으로 생성됩니다.
다음은 애플리케이션 구성 요소와 상호작용에 대한 구조적 개요입니다:
사용자 인터페이스 (User Interface) - 사용자 상호작용을 관리하고 처리된 동영상 세그먼트를 표시합니다.

동영상 처리 (Video Processing) - moviepy를 사용하여 동영상 세그먼트 생성 및 처리를 위한 다음과 같은 핵심 기능을 제공합니다:
세그먼트 생성기(Segment Creator) - 동영상 세그먼트를 생성합니다.
동영상 프로세서(Video Processor) - 동영상을 트리밍하고 세그먼트를 파싱합니다.
유틸리티 (Utilities) - 동영상 가져오기, 타임스탬프 생성 등의 작업을 위한 유틸리티 함수들을 제공합니다.
API 연동 (API Integration) - 인덱싱을 위해 다음과 같은 Twelve Labs 서비스 인터페이스와 연동합니다:
작업(Task) 생성 및 관리.
하이라이트 챕터 정보를 포함하는 Gist 객체 생성.
이제 YouTube 크리에이터들을 위한 이 애플리케이션의 워크플로우를 완벽하게 이해했으므로, 다음 단계로 본격적인 빌드 준비를 진행해보겠습니다.
준비 단계
Twelve Labs Playground에 로그인한 후 인덱스(Index)를 만듭니다.
Twelve Labs Playground에서 API 키를 발급받습니다.
생성 작업을 위해 다음 비디오 이해 엔진을 활성화합니다:
Marengo 2.6 (임베딩 엔진): 동영상 검색 및 분류용
Pegasus 1.1 (생성형 엔진): 비디오 투 텍스트(Video-to-Text) 생성용
이러한 엔진들은 비디오 이해를 위한 매우 강력하고 견고한 토대를 제공합니다.

1단계에서 생성한 인덱스를 열어
INDEX_ID를 확인합니다. ID는 URL에서 확인할 수 있습니다: https://playground.twelvelabs.io/indexes/{index_id}API 키 및
INDEX_ID를 메인 파일과 함께.env파일에 설정합니다.
Twelvelabs_API=your_api_key_here API_URL=your_api_url_here
코드 기반의 접근 방식을 선호하신다면 다음 단계를 따르세요:
Twelve Labs Playground에서 API 키를 받아 환경 변수로 준비합니다.
Twelve Labs SDK와 환경 변수를 임포트합니다. 환경 변수의 Twelve Labs API 키를 사용하여 SDK 클라이언트를 초기화합니다.
from twelvelabs import TwelveLabs from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("API_KEY") client = TwelveLabs(api_key=API_KEY)
생성 작업에 사용할 원하는 엔진을 지정합니다:
engines = [ { "name": "marengo2.6", "options": ["visual", "conversation", "text_in_video", "logo"] }, { "name": "pegasus1.1", "options": ["visual", "conversation"] } ]
지정한 인덱스 이름과 엔진 구성 파라미터를 담아
client.index를 호출하여 새 인덱스를 생성합니다. 고유하고 식별하기 쉬운 인덱스 이름을 사용하세요.
index = client.index.create( name="<YOUR_INDEX_NAME>", engines=engines ) print(f"A new index has been created: Index id={index.id} name={index.name} engines={index.engines}")
index.id 필드는 새로 만든 인덱스의 고유 식별자입니다. 이 식별자는 비디오를 올바른 위치에 인덱싱하는 데 필수적입니다.
이 단계들을 완료했으면 이제 본격적으로 애플리케이션 개발에 들어갈 준비가 되었습니다!
비디오 하이라이트 생성기 실습 과정
이 튜토리얼에서는 미니멀한 프론트엔드를 갖춘 Streamlit 애플리케이션을 빌드해 보겠습니다. 디렉터리 구조는 다음과 같습니다:
. ├── app.py ├── requirements.txt ├── utils.py ├── .env └── .gitignore
1 - Streamlit 애플리케이션 생성
위의 단계를 모두 마쳤다면 이제 Streamlit 애플리케이션을 구현할 차례입니다. 이 앱을 통해 동영상을 업로드하고 하이라이트 챕터를 추출하며, 나뉘어진 비디오 클립을 편리하게 생성할 수 있습니다. 애플리케이션은 크게 다음 두 개의 핵심 파일로 구성됩니다:
가상 환경 설정에 필요한 의존성 라이브러리 목록은 requirements.txt 파일에서 확인할 수 있습니다.
시작하려면 Python 가상 환경을 만들고 애플리케이션에 맞게 패키지를 설치합니다:
pip install -r requirements.txt
2 - 핵심 기능을 위한 유틸리티 함수 구현
이 섹션에서는 하이라이트 챕터를 생성하고 인덱싱을 위해 길이가 긴 동영상을 효율적으로 처리하는 방법을 알아보겠습니다. 아울러 섹션 2.2에서 인덱싱한 동영상에 결과를 적용하여, 하이라이트로 지정된 챕터별로 동영상 세그먼트를 추출하는 방법도 다룹니다.
2.1 - 하이라이트 챕터 생성 및 비디오 프로세싱 처리
먼저 필요한 라이브러리인 moviepy.editor, m3u8, io, urllib.parse, yt_dlp 및 Twelve Labs SDK를 임포트합니다. 그리고 API Key와 Index ID 환경 변수를 설정합니다. 이어서 각 라이브러리의 중요성과 수행 역할에 대해 자세히 알아보겠습니다.
import os import requests from moviepy.editor import VideoFileClip from twelvelabs import TwelveLabs from dotenv import load_dotenv import io import m3u8 from urllib.parse import urljoin import yt_dlp # Load environment variables load_dotenv() API_KEY = os.getenv("API_KEY") INDEX_ID = os.getenv("INDEX_ID") def seconds_to_mmss(seconds): minutes, seconds = divmod(int(seconds), 60) return f"{minutes:02d}:{seconds:02d}" def mmss_to_seconds(mmss): minutes, seconds = map(int, mmss.split(':')) return minutes * 60 + seconds def generate_timestamps(client, video_id, start_time=0): try: gist = client.generate.summarize(video_id=video_id, type="chapter") chapter_text = "\n".join([f"{seconds_to_mmss(chapter.start + start_time)}-{chapter.chapter_title}" for chapter in gist.chapters]) return chapter_text, gist.chapters[-1].start + start_time except Exception as e: raise Exception(f"An error occurred while generating timestamps: {str(e)}") # Utitily function to trim the video based on the time stamps def trim_video(input_path, output_path, start_time, end_time): with VideoFileClip(input_path) as video: new_video = video.subclip(start_time, end_time) new_video.write_videofile(output_path, codec="libx264", audio_codec="aac") # Based on the speicific Index_ID, fetching all the video_id def fetch_existing_videos(): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos?page=1&page_limit=10&sort_by=created_at&sort_option=desc" headers = {"accept": "application/json", "x-api-key": API_KEY, "Content-Type": "application/json"} response = requests.get(url, headers=headers) if response.status_code == 200: return response.json()['data'] else: raise Exception(f"Failed to fetch videos: {response.text}") # Utility function to retrieve the URL of the video with video_id def get_video_url(video_id): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{video_id}" headers = {"accept": "application/json", "x-api-key": API_KEY} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data['hls']['video_url'] if 'hls' in data and 'video_url' in data['hls'] else None else: raise Exception(f"Failed to get video URL: {response.text}") # Utility function to handle and process the video clips larger than 30 mins def process_video(client, video_path, video_type): with VideoFileClip(video_path) as clip: duration = clip.duration if duration > 3600: raise Exception("Video duration exceeds 1 hour. Please upload a shorter video.") if video_type == "Basic Video (less than 30 mins)": task = client.task.create(index_id=INDEX_ID, file=video_path) task.wait_for_done(sleep_interval=5) if task.status == "ready": timestamps, _ = generate_timestamps(client, task.video_id) return timestamps, task.video_id else: raise Exception(f"Indexing failed with status {task.status}") elif video_type == "Podcast (30 mins to 1 hour)": trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_1.mp4") trim_video(video_path, trimmed_path, 0, 1800) task1 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task1.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task1.status != "ready": raise Exception(f"Indexing failed with status {task1.status}") timestamps, end_time = generate_timestamps(client, task1.video_id) if duration > 1800: trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_2.mp4") trim_video(video_path, trimmed_path, 1800, int(duration)) task2 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task2.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task2.status != "ready": raise Exception(f"Indexing failed with status {task2.status}") timestamps_2, _ = generate_timestamps(client, task2.video_id, start_time=end_time) timestamps += "\n" + timestamps_2 return timestamps, task1.video_id # Utility function to render the video on the UI def get_hls_player_html(video_url): return f""" <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <style> #video-container {{ position: relative; width: 100%; padding-bottom: 56.25%; overflow: hidden; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }} #video {{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; }} </style> <div id="video-container"> <video id="video" controls></video> </div> <script> var video = document.getElementById('video'); var videoSrc = "{video_url}"; if (Hls.isSupported()) {{ var hls = new Hls(); hls.loadSource(videoSrc); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() {{ video.pause(); }}); }} else if (video.canPlayType('application/vnd.apple.mpegurl')) {{ video.src = videoSrc; video.addEventListener('loadedmetadata', function() {{ video.pause(); }}); }} </script> """
A. 입력: 비디오 콘텐츠 가공
이 애플리케이션은 process_video 함수에 크게 의존합니다. 이 함수는 30분 미만의 짧은 비디오와 최대 1시간 분량의 긴 팟캐스트 스타일 비디오라는 두 가지 유형의 비디오 콘텐츠를 지원합니다. 길이가 긴 비디오의 경우, 보다 효율적인 처리를 위해 trim_video 핵심 유틸리티 함수를 사용해 동영상을 관리 가능한 크기의 청크로 분할 처리함으로써 장시간 녹화본도 누락 없이 분석할 수 있습니다.
B. 처리: 동영상 인덱싱, 분석 및 챕터 생성
30분 미만의 단편 비디오의 경우, 임베딩 엔진인 Marengo 2.6을 통해 인덱싱을 수행하고 고유 비디오 ID를 발급받습니다. 이후 TwelveLabs SDK의 생성형 엔진인 Pegasus 1.1의 generate 함수를 호출하여 비디오 콘텐츠를 면밀히 분석하고 해당 비디오에 대한 타임스탬프 기반 챕터를 얻습니다.
길이가 긴 비디오의 경우 30분 길이의 청크로 쪼갠 후 순차적으로 인덱싱 및 분석을 실행하여 타임스탬프 챕터를 빌드해 나갑니다. 이때 첫 번째 청크의 종료 타임스탬프가 다음 청크의 시작 지점이 됩니다.
get_video_url 함수는 인덱싱된 비디오의 URL을 조회하여 앱 내에 비디오 플레이어를 띄우는 용도로 쓰이며, 실제 렌더링에는 get_hls_player_html(video_url) 함수를 사용합니다.
C. 반환 값: 타임스탬프가 포함된 하이라이트
생성된 원본 타임스탬프 결과는 초(second) 단위로 반환됩니다. 하지만 YouTube 설명란에 하이라이트를 게시하려면 분 대 초(mm:ss) 형식이어야 합니다. 따라서 표시용 변환에는 seconds_to_mmss를 사용하고, 반대로 챕터 기반으로 영상 세그먼트를 잘라낼 때는 분 단위를 초 단위로 역변환해 주는 mmss_to_seconds를 활용합니다.
업로드된 모든 하이라이트 영상은 앞서 생성해 준 인덱스 ID를 타겟으로 인덱싱됩니다. 사용자는 fetch_existing_videos()를 통해 기존 동영상들을 불러온 다음 비디오 ID를 선택하여 생성 엔진에 generate_timestamp를 요청하기만 하면 데이터를 손쉽게 받아볼 수 있습니다.
2.2 - 인덱스에서 비디오 불러오기 및 결과에 맞춰 세그먼트 분할
이 세션에서는 주요 하이라이트 타임스탬프 영역을 바탕으로 동영상 세그먼트를 분할하는 작업을 진행합니다. 각 유틸리티 함수들이 상호작용하며 영상 다운로드, 메타데이터 파싱, 챕터별 세그먼트 추출 작업을 완벽하게 수행합니다.
# 비디오 ID로부터 URL을 조회해 인덱싱된 원본 비디오를 실제로 내려받는 유틸리티 함수 def download_video(url, output_filename): ydl_opts = { 'format': 'best', 'outtmpl': output_filename, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) # 개별 세그먼트 정보를 프로그램이 구별 가능하도록 분석하는 유틸리티 함수 def parse_segments(segment_text): lines = segment_text.strip().split('\n') segments = [] for i, line in enumerate(lines): start, description = line.split('-', 1) start_time = mmss_to_seconds(start.strip()) if i < len(lines) - 1: end = lines[i+1].split('-')[0] end_time = mmss_to_seconds(end.strip()) else: end_time = None segments.append((start_time, end_time, description.strip())) return segments # 분석된 정보에 따라 개별 동영상 세그먼트를 슬라이싱하는 핵심 함수 def create_video_segments(video_url, segment_info): full_video = "full_video.mp4" segments = parse_segments(segment_info) try: # 원본 동영상 클립 우선 다운로드 download_video(video_url, full_video) for i, (start_time, end_time, description) in enumerate(segments): output_file = f"{i+1:02d}_{description.replace(' ', '_').lower()}.mp4" trim_video(full_video, output_file, start_time, end_time) yield output_file, description os.remove(full_video) except yt_dlp.utils.DownloadError as e: raise Exception(f"다운로드 완료 중 오류 발생: {str(e)}") except Exception as e: raise Exception(f"예기치 못한 장애 발생: {str(e)}") # 잘라내기(Trimming) 처리를 마친 각 클립 세그먼트를 내려받는 함수 def download_video_segment(video_id, start_time, end_time=None): video_url = get_video_url(video_id) if not video_url: raise Exception("비디오 URL 조회 실패") playlist = m3u8.load(video_url) start_seconds = mmss_to_seconds(start_time) end_seconds = mmss_to_seconds(end_time) if end_time else None total_duration = 0 segments_to_download = [] for segment in playlist.segments: if total_duration >= start_seconds and (end_seconds is None or total_duration < end_seconds): segments_to_download.append(segment) total_duration += segment.duration if end_seconds is not None and total_duration >= end_seconds: break buffer = io.BytesIO() for segment in segments_to_download: segment_url = urljoin(video_url, segment.uri) response = requests.get(segment_url) if response.status_code == 200: buffer.write(response.content) else: raise Exception(f"세그먼트 다운로드 실패: {segment_url}") buffer.seek(0) return buffer.getvalue()
download_video 함수는 전체 비디오 자산을 수급할 때 yt-dlp 오픈소스 패키지를 활용합니다. 하나의 단일 미디어로부터 복수의 작은 세그먼트들을 분리 및 생성하기 때문에 본 툴 내부적으로는 원본 소스를 모두 끌어옵니다.
parse_segments 함수는 자동으로 계산된 물리적인 타임스탬프 문장을 코드상에서 제어할 수 있는 컬렉션 구조로 치환하는 전처리 단위 역할을 담당합니다. 각 챕터의 인앤아웃 타이밍과 서술부를 논리적으로 정리하여 컷 편집 환경을 대비합니다.
create_video_segments는 위 모든 과정들을 선형적으로 매끄럽게 제어합니다. 전체 뼈대 파일을 받아 유연한 포맷으로 세분화한 뒤, 구성한 규칙에 따라 클립 파일들을 디스크에 써내려갑니다. 처리 과정이 끝날 때마다 결과 파일 및 디스크립션 정보를 차례대로 던져줍니다.
각각의 챕터 클립은 download_video_segment를 경유해 저장할 수도 있습니다. 챕터별 사전 보기나 고배율 인코딩 시 전체 파일을 다시 받을 필요가 없도록, HTTP 기반의 동적 스트리밍(HLS, HTTP Live Streaming) 프로토콜을 통과하여 표적 자산만 즉각 받아내는 스마트한 구조를 취하고 있습니다.
이를 통해 창작 영역에서 소요되는 리스트 메타작성 부담을 더는 것은 물론, 이후 자체 아카이빙이나 소셜 릴리즈를 위한 자산들을 즉석에서 발굴하고 직접 후가공할 수 있는 고부가가치 환경을 구축하게 됩니다 🎬✂️️.
3 - Streamlit 애플리케이션의 지침 흐름
이 세션은 보다 직관적이고 세련된 비주얼을 제공하기 위해 미니멀하게 연출된 UI 지침에 맞춰 제작한 main application 실행 코드에 주목합니다.
이 애플리케이션은 먼저 깔끔하고 매력적인 테마의 인터페이스를 적용하기 위해 세부 CSS를 지정하는 화면 스타일링으로 시작합니다. 그와 동시에 렌더링 주기가 새로 고침되더라도 기존 기록을 영구 관리할 수 있도록 세션 스토리지 변수를 세팅하여 영속성을 확보합니다. 전체 작동 프로그램의 모습은 app.py에서 확인할 수 있으며, 아래에서 세부 구성 모형을 소개합니다.
# 파일 업로드 컨트롤러 및 프론트 정비 함수 def upload_and_process_video(): video_type = st.selectbox("비디오 유형 선택:", ["Basic Video (less than 30 mins)", "Podcast (30 mins to 1 hour)"]) uploaded_file = st.file_uploader("적용할 비디오 파일 지정", type=["mp4", "mov", "avi"]) if uploaded_file and st.button("파일 처리 분석 시행", key="process_video_button"): with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file: tmp_file.write(uploaded_file.read()) video_path = tmp_file.name try: with st.spinner("영상을 활발히 분석 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, video_id = process_video(client, video_path, video_type) st.success("비디오 분석 작업이 모두 성공적으로 끝났습니다!") st.session_state.timestamps = timestamps st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.video(st.session_state.video_url) else: st.error("비디오를 재생하기 위한 유효 URL을 얻지 못했습니다.") except Exception as e: st.error(str(e)) finally: os.unlink(video_path) # 기존에 잘 저장되어 있는 인덱스 비디오를 골라 타임스탬프 생성을 호출하는 함수 def select_existing_video(): try: existing_videos = fetch_existing_videos() video_options = {f"{video['metadata']['filename']} ({video['_id']})": video['_id'] for video in existing_videos} if video_options: selected_video = st.selectbox("작업할 비디오 선택:", list(video_options.keys())) video_id = video_options[selected_video] st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.markdown(f"### 지정된 비디오: {selected_video}") st.video(st.session_state.video_url) else: st.error("비디오 주소를 정상적으로 받지 못했습니다.") if st.button("타임스탬프 자동 생성", key="generate_timestamps_button"): with st.spinner("타임스탬프를 계산 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, _ = generate_timestamps(client, video_id) st.session_state.timestamps = timestamps else: st.warning("보유하신 인덱스 내에 등록된 영상 자산이 하나도 존재하지 않습니다.") except Exception as e: st.error(str(e))
메인 UI 화면은 명쾌하게 분리된 두 가닥의 직관적인 인터페이스를 구성합니다. 하나는 새로운 오리지널 비디오 자산을 즉시 분석 풀로 끌어들이는 업로드 공간이며, 다른 하나는 기존 소스를 바로 가져와 다듬을 수 있는 탐색 공간입니다. 장시간 걸리는 분할 분석 기간에도 실시간 처리 표시기가 상황을 정확하게 사용자에게 전달합니다. 또 발생 상황에 따른 에러 마스킹도 똑똑하게 이뤄지며, 한 인터페이스 안에서 모든 세그먼트 이력을 클릭 한 번으로 새롭게 비워 줄 수 있는 버튼도 함께 내장되어 자원 낭비를 전면 차단합니다.
upload_and_process_video(): 실제 인풋 연계를 통한 전반적인 트래픽 수송과 동영상 다이렉트 분석을 책임집니다. 일반 영상과 롱폼 청크 모델 연계를 다변화하여 다루며, 최종 산출물 도출 단계로 스마트하게 이관해 줍니다.select_existing_video(): 이미 잘 적재되어 있는 플랫폼 내 기존 영구 아카이빙 폴더들을 안전하게 불어내 크리에이터가 임의로 집어 활용할 수 있도록 설계되었습니다.
당 연계 앱은 st.session_state의 영리한 데이터 생존 메커니즘을 타고 작동합니다. 무거운 메타데이터의 누적과 가속 세그먼트 생성이라는 연이은 트랜잭션 과정 속에서도 불필요한 새로고침으로 인한 중간 상태 소실을 원천 기화해 줍니다. 이미 정리된 데이터를 그대로 재활용하여 대기 시간과 불량 업로드 횟수를 과감하게 단축시켜 줍니다.
# 생성 완료된 자사 세그먼트 클립 목록을 이쁘게 표시하고 보존 다운로드할 수 있는 매개 함수 def display_segment(file_name, description, segment_index): if os.path.exists(file_name): st.write(f"### {description}") st.video(file_name) with open(file_name, "rb") as file: file_contents = file.read() unique_key = f"download_{segment_index}_{uuid.uuid4()}" st.download_button( label=f"클립 다운로드: {description}", data=file_contents, file_name=file_name, mime="video/mp4", key=unique_key ) st.markdown("---") else: st.warning(f"파일 자산 {file_name}을 로드하지 못했거나 다른 하드 드라이브로 복사 이동되었습니다.") # 실제 세그먼트를 추출하고 표시하는 핵심 함수 def process_and_display_segments(): if not st.session_state.video_url: st.error("연결된 본 소스의 미디어 URL을 찾을 수 없습니다. 다시 전송 분석을 진행해 주세요.") return segment_generator = create_video_segments(st.session_state.video_url, st.session_state.timestamps) progress_bar = st.progress(0) status_text = st.empty() st.session_state.video_segments = [] # 비디오 세그먼트 목록 초기화 total_segments = len(st.session_state.timestamps.split('\n')) for i, (file_name, description) in enumerate(segment_generator, 1): st.session_state.video_segments.append((file_name, description)) display_segment(file_name, description, i-1) # 인덱스 순번을 동봉 전달 progress = i / total_segments progress_bar.progress(progress) status_text.text(f"클립 파트 처리 세분화 중 {i}/{total_segments}...") progress_bar.progress(1.0) status_text.text("모든 클립의 정밀 세분화가 성공적으로 끝났습니다!") # 생성된 타임라인 인덱스와 분기 세그먼트 카드들을 전반적으로 레이아웃하는 렌더 관리 함수 def display_timestamps_and_segments(): if st.session_state.timestamps: st.subheader("YouTube 설명 등록용 추천 챕터 메타 데이터") st.write("아래 텍스트 영역을 통째로 복사하셔서 비디오 업로드 상세 묘사란에 그대로 기재하세요.") st.code(st.session_state.timestamps, language="") if st.button("개별 비디오 챕터 클립 실물 패키징 생성", key="create_segments_button"): try: process_and_display_segments() except Exception as e: st.error(f"개별 미디어 클립 패키징 도중 에러가 발발했습니다: {str(e)}") st.exception(e) # 역추적을 위한 스택 트레이스 표시 if st.session_state.video_segments: st.subheader("추출 성공한 개별 클립 패키지") for index, (file_name, description) in enumerate(st.session_state.video_segments): display_segment(file_name, description, index) if st.button("임시 저장 공간 비우기", key="clear_segments_button"): for file_name, _ in st.session_state.video_segments: if os.path.exists(file_name): os.remove(file_name) st.session_state.video_segments = [] st.success("생성되었던 모든 임시 세그먼트 파일들을 스토리지에서 깨끗하게 소거했습니다.") st.experimental_rerun()
process_and_display_segments(): 타임라인 데이터 처리에 맞춰 개별 디스크 기반 파일 자산 생성을 진두지휘합니다.display_timestamps_and_segments(): 가시화된 산출 데이터를 일괄 복사해 가기 편하게 다듬고 클립 파일 구축 지시 행동을 총체 지배합니다.display_segment(): 완성품으로 조각난 비디오 목록을 개별 뷰 카드와 안전한 다이렉트 다운로드 슬롯으로 정렬해 줍니다.
display_segment는 추출된 파일 이름, 디스크립션 및 세그먼트 인덱스를 매개변수로 받아 화면 상에 카드를 렌더링하고, Streamlit의 st.video 기능을 경유하여 클립을 즉시 재생하며, 다운로드 패키지 버튼을 유니크 키로 결합합니다. 경로 조회에 이상이 확인될 때는 위험 경고 카드를 대신 노출합니다.
process_and_display_segments는 영상 클립 생성을 한 번에 연속 처리합니다. 타겟팅할 미디어의 정보가 소실되어 있을 때 빠르게 에러 팝업을 발생시키며, 정상 가동 시에는 전체를 순차적으로 빌드하며 실시간 프로세스 진행률 그래프를 사용자에게 인지시킵니다. 변환 결과는 스토리지 세션에 보관되어 후속 작업으로 다각화됩니다.
display_timestamps_and_segments는 모든 흐름의 유기적 관문을 매끄럽게 연결합니다. 도출에 최적화된 마커 정보 목록을 기민하게 정돈하여 출력 처리하며, 단독 추출과 전체 초기화 옵션을 통해 임시 리소스 제어를 투명하고 안전하게 보장합니다.
아래는 완성된 데모 애플리케이션의 구현 예시입니다:

데모 화면에서 보듯, 분석 타겟 비디오를 전송하면 인포 데이터가 순식간에 추출되고 곧바로 YouTube 마커 용도에 맞는 메타 데이터가 조합 출력됩니다. 그리고 다음 지면에서 지시 행동 버튼을 작동하는 즉시 각 세션 파트들이 독자적인 미디어 클립으로 정교하게 나뉘어 가공되는 실황을 확인할 수 있습니다.

Twelve Labs의 뛰어난 비디오 이해 능력을 바탕으로, 본 챕터 추출 모델을 단순한 소셜 릴리즈를 넘어 미디어 트랙 후반 작업, 온라인 원격 교육, 혹은 고정밀 모니터링 등 각자 원하는 도메인 영역에 무궁무진하게 접목하고 개선해 보세요.
추가로 확장해 볼 만한 아이디어
작동 프로세스와 세부 구현 방식을 정밀하게 깨닫는 순간, 세상을 향상시킬 수 있는 신선한 비즈니스 아이디어나 응용 제품을 마음껏 개발할 수 있는 기틀을 닦게 됩니다. 아래는 본 프로그램 아키텍처를 토대로 시도해 볼 만한 가치 높은 응용 분야들입니다:
📽️ 소셜 및 YouTube 콘텐츠 창작자: 자동 챕터링과 쇼츠 소작업에 걸리는 전체적인 워크플로우를 완벽하게 제거하여 가독성을 높입니다.
🎓 비대면 교육 및 지식 플랫폼: 길고 지루한 명강의 영상에서 시청 학습자가 학습 타겟 섹션만 즉각 선택하여 선별 시청할 수 있도록 장벽을 해소합니다.
🎥 비선형 콘텐츠 가공 유통: 복잡한 수동 트리밍 과정 없이 원본 영상 패키지로부터 업로드 전용 수십 개의 분기 포트폴리오를 빠르게 일괄 양산합니다.
결론
본 구현 기술서에서는 Twelve Labs 엔진을 활용해 동영상 창작 생태계에 놀라운 부가가치와 혁신을 불어넣어 줄 하이라이트 챕터 가공 및 클립 생성 단계를 깊이 있게 살펴보았습니다. 튜토리얼을 끝까지 따라와 주셔서 감사드리며, 향후 더 높은 영감과 획기적인 기능 추가로 사용자 경험을 훌륭히 끌어올릴 여러분의 고유한 도전을 적극 응원합니다.
추가 리소스
각 생성 프로세스 구현을 매끄럽게 책임진 핵심 추론 기기, Marengo 2.6 (임베딩 엔진) 및 Pegasus 1.1 (생성형 엔진)을 보다 상세하게 학습하세요. Twelve Labs 엔진을 전방위적으로 제어하며 풍부한 통찰을 획득하고자 한다면 하단에 등록된 커뮤니티 연결 링크들을 활용해 보세요.
Discord 커뮤니티: 지식을 나누는 글로벌 개발자 및 크리에이터들과 뜨겁게 소통하고 지적 영감을 교환하며 함께 작품을 다듬어 보세요. Twelve Labs Discord 참여하기
샘플 애플리케이션 라이브러리: 아이디어를 현실화하는 정수가 담긴 수많은 예제 앱 빌드를 직접 참조해 영감 수치를 배가하고 연동 기술을 극대화하세요.
상세 자습서 둘러보기: 한 차원 깊은 응용을 완성해 주는 고화소 분석 튜토리얼 라이브러리들을 마음껏 탐험하세요.
자유롭게 제공 리소스를 누리고, 기민하게 지식을 이관해 오며, Twelve Labs의 탁월한 비디오 인지 기술을 활용한 최고의 애플리케이션을 창조해 보세요.
소개
🎬 YouTube 동영상의 챕터 타임스탬프를 일일이 직접 만드느라 지치셨나요? 이제 매력적인 동영상 하이라이트를 자동으로 생성하여 지루한 작업 시간을 획기적으로 줄여보세요.
이번 튜토리얼에서는 콘텐츠 크리에이터의 하이라이트 제작 방식을 완전히 바꿔줄 강력한 도구인 Twelve Labs 기반의 'YouTube 챕터 하이라이트 생성기(YouTube Chapter Highlight Generator)'를 살펴보겠습니다. 이 애플리케이션은 동영상 제작 과정에서 가장 많은 시간이 소요되는 작업 중 하나인 정확하고 의미 있는 하이라이트 챕터 타임스탬프 생성을 자동화하여 해결해 줍니다.
이미 자리를 잡은 유튜버이든 이제 막 콘텐츠 제작을 시작한 초보자이든, 이 도구는 워크플로우를 대폭 간소화해 줍니다. 동영상 콘텐츠를 자동으로 분석하여 정확한 하이라이트 타임스탬프를 생성하고, 세분화된 동영상 클립까지 만들어 줍니다. 가장 좋은 점은 무엇일까요? 크리에이터가 숏폼 콘텐츠와 긴 팟캐스트 스타일의 동영상 모두에 활용할 수 있다는 점입니다. 이 애플리케이션이 어떻게 작동하는지, 그리고 여러분의 고유한 요구사항에 맞게 TwelveLabs Python SDK를 사용하여 이 앱을 직접 빌드하는 방법을 함께 알아보겠습니다.
이곳에서 애플리케이션 데모를 직접 체험해 볼 수 있습니다: Video Highlight Chapter Generation
코드를 확인하고 앱을 직접 실험해 보고 싶다면 이 Replit 템플릿을 사용해 보세요.

사전 준비 사항
Twelve Labs Playground에 가입하여 API 키를 생성하세요.
노트북과 이 애플리케이션의 리포지토리는 Video Highlight Chapter Generator Github에서 확인할 수 있습니다.
Python, HTML, Markdown 등 기본적인 개발 환경에 이미 익숙해져 있어야 합니다.
애플리케이션 작동 방식
이 섹션에서는 YouTube 동영상용 챕터 하이라이트를 개발하기 위한 애플리케이션 흐름을 간략히 설명합니다. 이를 통해 시간 절약은 물론 YouTube 콘텐츠에 하이라이트를 추가하는 과정을 훨씬 단순화할 수 있습니다.
팟캐스트 동영상의 경우, 시스템이 서로 다른 동영상 청크(Chunk)의 타임스탬프를 관리하고 결합합니다. 또한 사용자는 이전에 인덱싱된 동영상을 선택하여 인덱스에서 기존 동영상을 검색하고, 해당 URL을 가져와 비디오 ID를 통해 하이라이트 타임스탬프를 생성할 수 있습니다.
단계별 프로세스는 다음과 같습니다:
사용자 인터페이스 (UI)
애플리케이션은 상호작용을 위해 두 개의 메인 탭을 제공합니다.
첫 번째 탭에서는 사용자가 하이라이트 생성을 위해 새 동영상을 업로드할 수 있습니다.
두 번째 탭에서는 이전에 인덱싱된 동영상을 가져와서 볼 수 있습니다.
동영상 업로드 옵션
사용자는 두 가지 유형의 동영상 콘텐츠를 업로드할 수 있습니다.
일반 동영상(30분 미만 분량)과 최대 1시간 분량의 팟캐스트 스타일 동영상입니다.
프로세싱 워크플로우
시스템은 일반 동영상을 직접 처리합니다.
팟캐스트 스타일 동영상은 효율적인 처리를 위해 먼저 관리하기 쉬운 크기의 청크(Chunk)로 나뉩니다.
하이라이트 생성
인덱싱이 완료되면 시스템은 고유한 비디오 ID를 생성합니다.
이 비디오 ID는 generate.summarize 함수의 파라미터로 사용됩니다.
Pegasus 1.1 생성형 엔진(Generative Engine)이 동영상의 하이라이트 기반 타임스탬프를 생성합니다.
아웃풋 (출력)
최종 결과물은 동영상의 핵심 순간을 표시하는 타임스탬프 세트입니다.
이 타임스탬프들은 쉬운 탐색을 돕는 챕터 마커나 하이라이트 역할을 합니다.

동영상 클립 세분화는 동영상 URL과 하이라이트 타임스탬프에 액세스하는 moviepy.editor를 사용하여 수행됩니다. 콘텐츠 제작 편의성을 위해 동영상 세그먼트는 MP4 포맷으로 생성됩니다.
다음은 애플리케이션 구성 요소와 상호작용에 대한 구조적 개요입니다:
사용자 인터페이스 (User Interface) - 사용자 상호작용을 관리하고 처리된 동영상 세그먼트를 표시합니다.

동영상 처리 (Video Processing) - moviepy를 사용하여 동영상 세그먼트 생성 및 처리를 위한 다음과 같은 핵심 기능을 제공합니다:
세그먼트 생성기(Segment Creator) - 동영상 세그먼트를 생성합니다.
동영상 프로세서(Video Processor) - 동영상을 트리밍하고 세그먼트를 파싱합니다.
유틸리티 (Utilities) - 동영상 가져오기, 타임스탬프 생성 등의 작업을 위한 유틸리티 함수들을 제공합니다.
API 연동 (API Integration) - 인덱싱을 위해 다음과 같은 Twelve Labs 서비스 인터페이스와 연동합니다:
작업(Task) 생성 및 관리.
하이라이트 챕터 정보를 포함하는 Gist 객체 생성.
이제 YouTube 크리에이터들을 위한 이 애플리케이션의 워크플로우를 완벽하게 이해했으므로, 다음 단계로 본격적인 빌드 준비를 진행해보겠습니다.
준비 단계
Twelve Labs Playground에 로그인한 후 인덱스(Index)를 만듭니다.
Twelve Labs Playground에서 API 키를 발급받습니다.
생성 작업을 위해 다음 비디오 이해 엔진을 활성화합니다:
Marengo 2.6 (임베딩 엔진): 동영상 검색 및 분류용
Pegasus 1.1 (생성형 엔진): 비디오 투 텍스트(Video-to-Text) 생성용
이러한 엔진들은 비디오 이해를 위한 매우 강력하고 견고한 토대를 제공합니다.

1단계에서 생성한 인덱스를 열어
INDEX_ID를 확인합니다. ID는 URL에서 확인할 수 있습니다: https://playground.twelvelabs.io/indexes/{index_id}API 키 및
INDEX_ID를 메인 파일과 함께.env파일에 설정합니다.
Twelvelabs_API=your_api_key_here API_URL=your_api_url_here
코드 기반의 접근 방식을 선호하신다면 다음 단계를 따르세요:
Twelve Labs Playground에서 API 키를 받아 환경 변수로 준비합니다.
Twelve Labs SDK와 환경 변수를 임포트합니다. 환경 변수의 Twelve Labs API 키를 사용하여 SDK 클라이언트를 초기화합니다.
from twelvelabs import TwelveLabs from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("API_KEY") client = TwelveLabs(api_key=API_KEY)
생성 작업에 사용할 원하는 엔진을 지정합니다:
engines = [ { "name": "marengo2.6", "options": ["visual", "conversation", "text_in_video", "logo"] }, { "name": "pegasus1.1", "options": ["visual", "conversation"] } ]
지정한 인덱스 이름과 엔진 구성 파라미터를 담아
client.index를 호출하여 새 인덱스를 생성합니다. 고유하고 식별하기 쉬운 인덱스 이름을 사용하세요.
index = client.index.create( name="<YOUR_INDEX_NAME>", engines=engines ) print(f"A new index has been created: Index id={index.id} name={index.name} engines={index.engines}")
index.id 필드는 새로 만든 인덱스의 고유 식별자입니다. 이 식별자는 비디오를 올바른 위치에 인덱싱하는 데 필수적입니다.
이 단계들을 완료했으면 이제 본격적으로 애플리케이션 개발에 들어갈 준비가 되었습니다!
비디오 하이라이트 생성기 실습 과정
이 튜토리얼에서는 미니멀한 프론트엔드를 갖춘 Streamlit 애플리케이션을 빌드해 보겠습니다. 디렉터리 구조는 다음과 같습니다:
. ├── app.py ├── requirements.txt ├── utils.py ├── .env └── .gitignore
1 - Streamlit 애플리케이션 생성
위의 단계를 모두 마쳤다면 이제 Streamlit 애플리케이션을 구현할 차례입니다. 이 앱을 통해 동영상을 업로드하고 하이라이트 챕터를 추출하며, 나뉘어진 비디오 클립을 편리하게 생성할 수 있습니다. 애플리케이션은 크게 다음 두 개의 핵심 파일로 구성됩니다:
가상 환경 설정에 필요한 의존성 라이브러리 목록은 requirements.txt 파일에서 확인할 수 있습니다.
시작하려면 Python 가상 환경을 만들고 애플리케이션에 맞게 패키지를 설치합니다:
pip install -r requirements.txt
2 - 핵심 기능을 위한 유틸리티 함수 구현
이 섹션에서는 하이라이트 챕터를 생성하고 인덱싱을 위해 길이가 긴 동영상을 효율적으로 처리하는 방법을 알아보겠습니다. 아울러 섹션 2.2에서 인덱싱한 동영상에 결과를 적용하여, 하이라이트로 지정된 챕터별로 동영상 세그먼트를 추출하는 방법도 다룹니다.
2.1 - 하이라이트 챕터 생성 및 비디오 프로세싱 처리
먼저 필요한 라이브러리인 moviepy.editor, m3u8, io, urllib.parse, yt_dlp 및 Twelve Labs SDK를 임포트합니다. 그리고 API Key와 Index ID 환경 변수를 설정합니다. 이어서 각 라이브러리의 중요성과 수행 역할에 대해 자세히 알아보겠습니다.
import os import requests from moviepy.editor import VideoFileClip from twelvelabs import TwelveLabs from dotenv import load_dotenv import io import m3u8 from urllib.parse import urljoin import yt_dlp # Load environment variables load_dotenv() API_KEY = os.getenv("API_KEY") INDEX_ID = os.getenv("INDEX_ID") def seconds_to_mmss(seconds): minutes, seconds = divmod(int(seconds), 60) return f"{minutes:02d}:{seconds:02d}" def mmss_to_seconds(mmss): minutes, seconds = map(int, mmss.split(':')) return minutes * 60 + seconds def generate_timestamps(client, video_id, start_time=0): try: gist = client.generate.summarize(video_id=video_id, type="chapter") chapter_text = "\n".join([f"{seconds_to_mmss(chapter.start + start_time)}-{chapter.chapter_title}" for chapter in gist.chapters]) return chapter_text, gist.chapters[-1].start + start_time except Exception as e: raise Exception(f"An error occurred while generating timestamps: {str(e)}") # Utitily function to trim the video based on the time stamps def trim_video(input_path, output_path, start_time, end_time): with VideoFileClip(input_path) as video: new_video = video.subclip(start_time, end_time) new_video.write_videofile(output_path, codec="libx264", audio_codec="aac") # Based on the speicific Index_ID, fetching all the video_id def fetch_existing_videos(): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos?page=1&page_limit=10&sort_by=created_at&sort_option=desc" headers = {"accept": "application/json", "x-api-key": API_KEY, "Content-Type": "application/json"} response = requests.get(url, headers=headers) if response.status_code == 200: return response.json()['data'] else: raise Exception(f"Failed to fetch videos: {response.text}") # Utility function to retrieve the URL of the video with video_id def get_video_url(video_id): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{video_id}" headers = {"accept": "application/json", "x-api-key": API_KEY} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data['hls']['video_url'] if 'hls' in data and 'video_url' in data['hls'] else None else: raise Exception(f"Failed to get video URL: {response.text}") # Utility function to handle and process the video clips larger than 30 mins def process_video(client, video_path, video_type): with VideoFileClip(video_path) as clip: duration = clip.duration if duration > 3600: raise Exception("Video duration exceeds 1 hour. Please upload a shorter video.") if video_type == "Basic Video (less than 30 mins)": task = client.task.create(index_id=INDEX_ID, file=video_path) task.wait_for_done(sleep_interval=5) if task.status == "ready": timestamps, _ = generate_timestamps(client, task.video_id) return timestamps, task.video_id else: raise Exception(f"Indexing failed with status {task.status}") elif video_type == "Podcast (30 mins to 1 hour)": trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_1.mp4") trim_video(video_path, trimmed_path, 0, 1800) task1 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task1.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task1.status != "ready": raise Exception(f"Indexing failed with status {task1.status}") timestamps, end_time = generate_timestamps(client, task1.video_id) if duration > 1800: trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_2.mp4") trim_video(video_path, trimmed_path, 1800, int(duration)) task2 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task2.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task2.status != "ready": raise Exception(f"Indexing failed with status {task2.status}") timestamps_2, _ = generate_timestamps(client, task2.video_id, start_time=end_time) timestamps += "\n" + timestamps_2 return timestamps, task1.video_id # Utility function to render the video on the UI def get_hls_player_html(video_url): return f""" <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <style> #video-container {{ position: relative; width: 100%; padding-bottom: 56.25%; overflow: hidden; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }} #video {{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; }} </style> <div id="video-container"> <video id="video" controls></video> </div> <script> var video = document.getElementById('video'); var videoSrc = "{video_url}"; if (Hls.isSupported()) {{ var hls = new Hls(); hls.loadSource(videoSrc); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() {{ video.pause(); }}); }} else if (video.canPlayType('application/vnd.apple.mpegurl')) {{ video.src = videoSrc; video.addEventListener('loadedmetadata', function() {{ video.pause(); }}); }} </script> """
A. 입력: 비디오 콘텐츠 가공
이 애플리케이션은 process_video 함수에 크게 의존합니다. 이 함수는 30분 미만의 짧은 비디오와 최대 1시간 분량의 긴 팟캐스트 스타일 비디오라는 두 가지 유형의 비디오 콘텐츠를 지원합니다. 길이가 긴 비디오의 경우, 보다 효율적인 처리를 위해 trim_video 핵심 유틸리티 함수를 사용해 동영상을 관리 가능한 크기의 청크로 분할 처리함으로써 장시간 녹화본도 누락 없이 분석할 수 있습니다.
B. 처리: 동영상 인덱싱, 분석 및 챕터 생성
30분 미만의 단편 비디오의 경우, 임베딩 엔진인 Marengo 2.6을 통해 인덱싱을 수행하고 고유 비디오 ID를 발급받습니다. 이후 TwelveLabs SDK의 생성형 엔진인 Pegasus 1.1의 generate 함수를 호출하여 비디오 콘텐츠를 면밀히 분석하고 해당 비디오에 대한 타임스탬프 기반 챕터를 얻습니다.
길이가 긴 비디오의 경우 30분 길이의 청크로 쪼갠 후 순차적으로 인덱싱 및 분석을 실행하여 타임스탬프 챕터를 빌드해 나갑니다. 이때 첫 번째 청크의 종료 타임스탬프가 다음 청크의 시작 지점이 됩니다.
get_video_url 함수는 인덱싱된 비디오의 URL을 조회하여 앱 내에 비디오 플레이어를 띄우는 용도로 쓰이며, 실제 렌더링에는 get_hls_player_html(video_url) 함수를 사용합니다.
C. 반환 값: 타임스탬프가 포함된 하이라이트
생성된 원본 타임스탬프 결과는 초(second) 단위로 반환됩니다. 하지만 YouTube 설명란에 하이라이트를 게시하려면 분 대 초(mm:ss) 형식이어야 합니다. 따라서 표시용 변환에는 seconds_to_mmss를 사용하고, 반대로 챕터 기반으로 영상 세그먼트를 잘라낼 때는 분 단위를 초 단위로 역변환해 주는 mmss_to_seconds를 활용합니다.
업로드된 모든 하이라이트 영상은 앞서 생성해 준 인덱스 ID를 타겟으로 인덱싱됩니다. 사용자는 fetch_existing_videos()를 통해 기존 동영상들을 불러온 다음 비디오 ID를 선택하여 생성 엔진에 generate_timestamp를 요청하기만 하면 데이터를 손쉽게 받아볼 수 있습니다.
2.2 - 인덱스에서 비디오 불러오기 및 결과에 맞춰 세그먼트 분할
이 세션에서는 주요 하이라이트 타임스탬프 영역을 바탕으로 동영상 세그먼트를 분할하는 작업을 진행합니다. 각 유틸리티 함수들이 상호작용하며 영상 다운로드, 메타데이터 파싱, 챕터별 세그먼트 추출 작업을 완벽하게 수행합니다.
# 비디오 ID로부터 URL을 조회해 인덱싱된 원본 비디오를 실제로 내려받는 유틸리티 함수 def download_video(url, output_filename): ydl_opts = { 'format': 'best', 'outtmpl': output_filename, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) # 개별 세그먼트 정보를 프로그램이 구별 가능하도록 분석하는 유틸리티 함수 def parse_segments(segment_text): lines = segment_text.strip().split('\n') segments = [] for i, line in enumerate(lines): start, description = line.split('-', 1) start_time = mmss_to_seconds(start.strip()) if i < len(lines) - 1: end = lines[i+1].split('-')[0] end_time = mmss_to_seconds(end.strip()) else: end_time = None segments.append((start_time, end_time, description.strip())) return segments # 분석된 정보에 따라 개별 동영상 세그먼트를 슬라이싱하는 핵심 함수 def create_video_segments(video_url, segment_info): full_video = "full_video.mp4" segments = parse_segments(segment_info) try: # 원본 동영상 클립 우선 다운로드 download_video(video_url, full_video) for i, (start_time, end_time, description) in enumerate(segments): output_file = f"{i+1:02d}_{description.replace(' ', '_').lower()}.mp4" trim_video(full_video, output_file, start_time, end_time) yield output_file, description os.remove(full_video) except yt_dlp.utils.DownloadError as e: raise Exception(f"다운로드 완료 중 오류 발생: {str(e)}") except Exception as e: raise Exception(f"예기치 못한 장애 발생: {str(e)}") # 잘라내기(Trimming) 처리를 마친 각 클립 세그먼트를 내려받는 함수 def download_video_segment(video_id, start_time, end_time=None): video_url = get_video_url(video_id) if not video_url: raise Exception("비디오 URL 조회 실패") playlist = m3u8.load(video_url) start_seconds = mmss_to_seconds(start_time) end_seconds = mmss_to_seconds(end_time) if end_time else None total_duration = 0 segments_to_download = [] for segment in playlist.segments: if total_duration >= start_seconds and (end_seconds is None or total_duration < end_seconds): segments_to_download.append(segment) total_duration += segment.duration if end_seconds is not None and total_duration >= end_seconds: break buffer = io.BytesIO() for segment in segments_to_download: segment_url = urljoin(video_url, segment.uri) response = requests.get(segment_url) if response.status_code == 200: buffer.write(response.content) else: raise Exception(f"세그먼트 다운로드 실패: {segment_url}") buffer.seek(0) return buffer.getvalue()
download_video 함수는 전체 비디오 자산을 수급할 때 yt-dlp 오픈소스 패키지를 활용합니다. 하나의 단일 미디어로부터 복수의 작은 세그먼트들을 분리 및 생성하기 때문에 본 툴 내부적으로는 원본 소스를 모두 끌어옵니다.
parse_segments 함수는 자동으로 계산된 물리적인 타임스탬프 문장을 코드상에서 제어할 수 있는 컬렉션 구조로 치환하는 전처리 단위 역할을 담당합니다. 각 챕터의 인앤아웃 타이밍과 서술부를 논리적으로 정리하여 컷 편집 환경을 대비합니다.
create_video_segments는 위 모든 과정들을 선형적으로 매끄럽게 제어합니다. 전체 뼈대 파일을 받아 유연한 포맷으로 세분화한 뒤, 구성한 규칙에 따라 클립 파일들을 디스크에 써내려갑니다. 처리 과정이 끝날 때마다 결과 파일 및 디스크립션 정보를 차례대로 던져줍니다.
각각의 챕터 클립은 download_video_segment를 경유해 저장할 수도 있습니다. 챕터별 사전 보기나 고배율 인코딩 시 전체 파일을 다시 받을 필요가 없도록, HTTP 기반의 동적 스트리밍(HLS, HTTP Live Streaming) 프로토콜을 통과하여 표적 자산만 즉각 받아내는 스마트한 구조를 취하고 있습니다.
이를 통해 창작 영역에서 소요되는 리스트 메타작성 부담을 더는 것은 물론, 이후 자체 아카이빙이나 소셜 릴리즈를 위한 자산들을 즉석에서 발굴하고 직접 후가공할 수 있는 고부가가치 환경을 구축하게 됩니다 🎬✂️️.
3 - Streamlit 애플리케이션의 지침 흐름
이 세션은 보다 직관적이고 세련된 비주얼을 제공하기 위해 미니멀하게 연출된 UI 지침에 맞춰 제작한 main application 실행 코드에 주목합니다.
이 애플리케이션은 먼저 깔끔하고 매력적인 테마의 인터페이스를 적용하기 위해 세부 CSS를 지정하는 화면 스타일링으로 시작합니다. 그와 동시에 렌더링 주기가 새로 고침되더라도 기존 기록을 영구 관리할 수 있도록 세션 스토리지 변수를 세팅하여 영속성을 확보합니다. 전체 작동 프로그램의 모습은 app.py에서 확인할 수 있으며, 아래에서 세부 구성 모형을 소개합니다.
# 파일 업로드 컨트롤러 및 프론트 정비 함수 def upload_and_process_video(): video_type = st.selectbox("비디오 유형 선택:", ["Basic Video (less than 30 mins)", "Podcast (30 mins to 1 hour)"]) uploaded_file = st.file_uploader("적용할 비디오 파일 지정", type=["mp4", "mov", "avi"]) if uploaded_file and st.button("파일 처리 분석 시행", key="process_video_button"): with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file: tmp_file.write(uploaded_file.read()) video_path = tmp_file.name try: with st.spinner("영상을 활발히 분석 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, video_id = process_video(client, video_path, video_type) st.success("비디오 분석 작업이 모두 성공적으로 끝났습니다!") st.session_state.timestamps = timestamps st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.video(st.session_state.video_url) else: st.error("비디오를 재생하기 위한 유효 URL을 얻지 못했습니다.") except Exception as e: st.error(str(e)) finally: os.unlink(video_path) # 기존에 잘 저장되어 있는 인덱스 비디오를 골라 타임스탬프 생성을 호출하는 함수 def select_existing_video(): try: existing_videos = fetch_existing_videos() video_options = {f"{video['metadata']['filename']} ({video['_id']})": video['_id'] for video in existing_videos} if video_options: selected_video = st.selectbox("작업할 비디오 선택:", list(video_options.keys())) video_id = video_options[selected_video] st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.markdown(f"### 지정된 비디오: {selected_video}") st.video(st.session_state.video_url) else: st.error("비디오 주소를 정상적으로 받지 못했습니다.") if st.button("타임스탬프 자동 생성", key="generate_timestamps_button"): with st.spinner("타임스탬프를 계산 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, _ = generate_timestamps(client, video_id) st.session_state.timestamps = timestamps else: st.warning("보유하신 인덱스 내에 등록된 영상 자산이 하나도 존재하지 않습니다.") except Exception as e: st.error(str(e))
메인 UI 화면은 명쾌하게 분리된 두 가닥의 직관적인 인터페이스를 구성합니다. 하나는 새로운 오리지널 비디오 자산을 즉시 분석 풀로 끌어들이는 업로드 공간이며, 다른 하나는 기존 소스를 바로 가져와 다듬을 수 있는 탐색 공간입니다. 장시간 걸리는 분할 분석 기간에도 실시간 처리 표시기가 상황을 정확하게 사용자에게 전달합니다. 또 발생 상황에 따른 에러 마스킹도 똑똑하게 이뤄지며, 한 인터페이스 안에서 모든 세그먼트 이력을 클릭 한 번으로 새롭게 비워 줄 수 있는 버튼도 함께 내장되어 자원 낭비를 전면 차단합니다.
upload_and_process_video(): 실제 인풋 연계를 통한 전반적인 트래픽 수송과 동영상 다이렉트 분석을 책임집니다. 일반 영상과 롱폼 청크 모델 연계를 다변화하여 다루며, 최종 산출물 도출 단계로 스마트하게 이관해 줍니다.select_existing_video(): 이미 잘 적재되어 있는 플랫폼 내 기존 영구 아카이빙 폴더들을 안전하게 불어내 크리에이터가 임의로 집어 활용할 수 있도록 설계되었습니다.
당 연계 앱은 st.session_state의 영리한 데이터 생존 메커니즘을 타고 작동합니다. 무거운 메타데이터의 누적과 가속 세그먼트 생성이라는 연이은 트랜잭션 과정 속에서도 불필요한 새로고침으로 인한 중간 상태 소실을 원천 기화해 줍니다. 이미 정리된 데이터를 그대로 재활용하여 대기 시간과 불량 업로드 횟수를 과감하게 단축시켜 줍니다.
# 생성 완료된 자사 세그먼트 클립 목록을 이쁘게 표시하고 보존 다운로드할 수 있는 매개 함수 def display_segment(file_name, description, segment_index): if os.path.exists(file_name): st.write(f"### {description}") st.video(file_name) with open(file_name, "rb") as file: file_contents = file.read() unique_key = f"download_{segment_index}_{uuid.uuid4()}" st.download_button( label=f"클립 다운로드: {description}", data=file_contents, file_name=file_name, mime="video/mp4", key=unique_key ) st.markdown("---") else: st.warning(f"파일 자산 {file_name}을 로드하지 못했거나 다른 하드 드라이브로 복사 이동되었습니다.") # 실제 세그먼트를 추출하고 표시하는 핵심 함수 def process_and_display_segments(): if not st.session_state.video_url: st.error("연결된 본 소스의 미디어 URL을 찾을 수 없습니다. 다시 전송 분석을 진행해 주세요.") return segment_generator = create_video_segments(st.session_state.video_url, st.session_state.timestamps) progress_bar = st.progress(0) status_text = st.empty() st.session_state.video_segments = [] # 비디오 세그먼트 목록 초기화 total_segments = len(st.session_state.timestamps.split('\n')) for i, (file_name, description) in enumerate(segment_generator, 1): st.session_state.video_segments.append((file_name, description)) display_segment(file_name, description, i-1) # 인덱스 순번을 동봉 전달 progress = i / total_segments progress_bar.progress(progress) status_text.text(f"클립 파트 처리 세분화 중 {i}/{total_segments}...") progress_bar.progress(1.0) status_text.text("모든 클립의 정밀 세분화가 성공적으로 끝났습니다!") # 생성된 타임라인 인덱스와 분기 세그먼트 카드들을 전반적으로 레이아웃하는 렌더 관리 함수 def display_timestamps_and_segments(): if st.session_state.timestamps: st.subheader("YouTube 설명 등록용 추천 챕터 메타 데이터") st.write("아래 텍스트 영역을 통째로 복사하셔서 비디오 업로드 상세 묘사란에 그대로 기재하세요.") st.code(st.session_state.timestamps, language="") if st.button("개별 비디오 챕터 클립 실물 패키징 생성", key="create_segments_button"): try: process_and_display_segments() except Exception as e: st.error(f"개별 미디어 클립 패키징 도중 에러가 발발했습니다: {str(e)}") st.exception(e) # 역추적을 위한 스택 트레이스 표시 if st.session_state.video_segments: st.subheader("추출 성공한 개별 클립 패키지") for index, (file_name, description) in enumerate(st.session_state.video_segments): display_segment(file_name, description, index) if st.button("임시 저장 공간 비우기", key="clear_segments_button"): for file_name, _ in st.session_state.video_segments: if os.path.exists(file_name): os.remove(file_name) st.session_state.video_segments = [] st.success("생성되었던 모든 임시 세그먼트 파일들을 스토리지에서 깨끗하게 소거했습니다.") st.experimental_rerun()
process_and_display_segments(): 타임라인 데이터 처리에 맞춰 개별 디스크 기반 파일 자산 생성을 진두지휘합니다.display_timestamps_and_segments(): 가시화된 산출 데이터를 일괄 복사해 가기 편하게 다듬고 클립 파일 구축 지시 행동을 총체 지배합니다.display_segment(): 완성품으로 조각난 비디오 목록을 개별 뷰 카드와 안전한 다이렉트 다운로드 슬롯으로 정렬해 줍니다.
display_segment는 추출된 파일 이름, 디스크립션 및 세그먼트 인덱스를 매개변수로 받아 화면 상에 카드를 렌더링하고, Streamlit의 st.video 기능을 경유하여 클립을 즉시 재생하며, 다운로드 패키지 버튼을 유니크 키로 결합합니다. 경로 조회에 이상이 확인될 때는 위험 경고 카드를 대신 노출합니다.
process_and_display_segments는 영상 클립 생성을 한 번에 연속 처리합니다. 타겟팅할 미디어의 정보가 소실되어 있을 때 빠르게 에러 팝업을 발생시키며, 정상 가동 시에는 전체를 순차적으로 빌드하며 실시간 프로세스 진행률 그래프를 사용자에게 인지시킵니다. 변환 결과는 스토리지 세션에 보관되어 후속 작업으로 다각화됩니다.
display_timestamps_and_segments는 모든 흐름의 유기적 관문을 매끄럽게 연결합니다. 도출에 최적화된 마커 정보 목록을 기민하게 정돈하여 출력 처리하며, 단독 추출과 전체 초기화 옵션을 통해 임시 리소스 제어를 투명하고 안전하게 보장합니다.
아래는 완성된 데모 애플리케이션의 구현 예시입니다:

데모 화면에서 보듯, 분석 타겟 비디오를 전송하면 인포 데이터가 순식간에 추출되고 곧바로 YouTube 마커 용도에 맞는 메타 데이터가 조합 출력됩니다. 그리고 다음 지면에서 지시 행동 버튼을 작동하는 즉시 각 세션 파트들이 독자적인 미디어 클립으로 정교하게 나뉘어 가공되는 실황을 확인할 수 있습니다.

Twelve Labs의 뛰어난 비디오 이해 능력을 바탕으로, 본 챕터 추출 모델을 단순한 소셜 릴리즈를 넘어 미디어 트랙 후반 작업, 온라인 원격 교육, 혹은 고정밀 모니터링 등 각자 원하는 도메인 영역에 무궁무진하게 접목하고 개선해 보세요.
추가로 확장해 볼 만한 아이디어
작동 프로세스와 세부 구현 방식을 정밀하게 깨닫는 순간, 세상을 향상시킬 수 있는 신선한 비즈니스 아이디어나 응용 제품을 마음껏 개발할 수 있는 기틀을 닦게 됩니다. 아래는 본 프로그램 아키텍처를 토대로 시도해 볼 만한 가치 높은 응용 분야들입니다:
📽️ 소셜 및 YouTube 콘텐츠 창작자: 자동 챕터링과 쇼츠 소작업에 걸리는 전체적인 워크플로우를 완벽하게 제거하여 가독성을 높입니다.
🎓 비대면 교육 및 지식 플랫폼: 길고 지루한 명강의 영상에서 시청 학습자가 학습 타겟 섹션만 즉각 선택하여 선별 시청할 수 있도록 장벽을 해소합니다.
🎥 비선형 콘텐츠 가공 유통: 복잡한 수동 트리밍 과정 없이 원본 영상 패키지로부터 업로드 전용 수십 개의 분기 포트폴리오를 빠르게 일괄 양산합니다.
결론
본 구현 기술서에서는 Twelve Labs 엔진을 활용해 동영상 창작 생태계에 놀라운 부가가치와 혁신을 불어넣어 줄 하이라이트 챕터 가공 및 클립 생성 단계를 깊이 있게 살펴보았습니다. 튜토리얼을 끝까지 따라와 주셔서 감사드리며, 향후 더 높은 영감과 획기적인 기능 추가로 사용자 경험을 훌륭히 끌어올릴 여러분의 고유한 도전을 적극 응원합니다.
추가 리소스
각 생성 프로세스 구현을 매끄럽게 책임진 핵심 추론 기기, Marengo 2.6 (임베딩 엔진) 및 Pegasus 1.1 (생성형 엔진)을 보다 상세하게 학습하세요. Twelve Labs 엔진을 전방위적으로 제어하며 풍부한 통찰을 획득하고자 한다면 하단에 등록된 커뮤니티 연결 링크들을 활용해 보세요.
Discord 커뮤니티: 지식을 나누는 글로벌 개발자 및 크리에이터들과 뜨겁게 소통하고 지적 영감을 교환하며 함께 작품을 다듬어 보세요. Twelve Labs Discord 참여하기
샘플 애플리케이션 라이브러리: 아이디어를 현실화하는 정수가 담긴 수많은 예제 앱 빌드를 직접 참조해 영감 수치를 배가하고 연동 기술을 극대화하세요.
상세 자습서 둘러보기: 한 차원 깊은 응용을 완성해 주는 고화소 분석 튜토리얼 라이브러리들을 마음껏 탐험하세요.
자유롭게 제공 리소스를 누리고, 기민하게 지식을 이관해 오며, Twelve Labs의 탁월한 비디오 인지 기술을 활용한 최고의 애플리케이션을 창조해 보세요.
소개
🎬 YouTube 동영상의 챕터 타임스탬프를 일일이 직접 만드느라 지치셨나요? 이제 매력적인 동영상 하이라이트를 자동으로 생성하여 지루한 작업 시간을 획기적으로 줄여보세요.
이번 튜토리얼에서는 콘텐츠 크리에이터의 하이라이트 제작 방식을 완전히 바꿔줄 강력한 도구인 Twelve Labs 기반의 'YouTube 챕터 하이라이트 생성기(YouTube Chapter Highlight Generator)'를 살펴보겠습니다. 이 애플리케이션은 동영상 제작 과정에서 가장 많은 시간이 소요되는 작업 중 하나인 정확하고 의미 있는 하이라이트 챕터 타임스탬프 생성을 자동화하여 해결해 줍니다.
이미 자리를 잡은 유튜버이든 이제 막 콘텐츠 제작을 시작한 초보자이든, 이 도구는 워크플로우를 대폭 간소화해 줍니다. 동영상 콘텐츠를 자동으로 분석하여 정확한 하이라이트 타임스탬프를 생성하고, 세분화된 동영상 클립까지 만들어 줍니다. 가장 좋은 점은 무엇일까요? 크리에이터가 숏폼 콘텐츠와 긴 팟캐스트 스타일의 동영상 모두에 활용할 수 있다는 점입니다. 이 애플리케이션이 어떻게 작동하는지, 그리고 여러분의 고유한 요구사항에 맞게 TwelveLabs Python SDK를 사용하여 이 앱을 직접 빌드하는 방법을 함께 알아보겠습니다.
이곳에서 애플리케이션 데모를 직접 체험해 볼 수 있습니다: Video Highlight Chapter Generation
코드를 확인하고 앱을 직접 실험해 보고 싶다면 이 Replit 템플릿을 사용해 보세요.

사전 준비 사항
Twelve Labs Playground에 가입하여 API 키를 생성하세요.
노트북과 이 애플리케이션의 리포지토리는 Video Highlight Chapter Generator Github에서 확인할 수 있습니다.
Python, HTML, Markdown 등 기본적인 개발 환경에 이미 익숙해져 있어야 합니다.
애플리케이션 작동 방식
이 섹션에서는 YouTube 동영상용 챕터 하이라이트를 개발하기 위한 애플리케이션 흐름을 간략히 설명합니다. 이를 통해 시간 절약은 물론 YouTube 콘텐츠에 하이라이트를 추가하는 과정을 훨씬 단순화할 수 있습니다.
팟캐스트 동영상의 경우, 시스템이 서로 다른 동영상 청크(Chunk)의 타임스탬프를 관리하고 결합합니다. 또한 사용자는 이전에 인덱싱된 동영상을 선택하여 인덱스에서 기존 동영상을 검색하고, 해당 URL을 가져와 비디오 ID를 통해 하이라이트 타임스탬프를 생성할 수 있습니다.
단계별 프로세스는 다음과 같습니다:
사용자 인터페이스 (UI)
애플리케이션은 상호작용을 위해 두 개의 메인 탭을 제공합니다.
첫 번째 탭에서는 사용자가 하이라이트 생성을 위해 새 동영상을 업로드할 수 있습니다.
두 번째 탭에서는 이전에 인덱싱된 동영상을 가져와서 볼 수 있습니다.
동영상 업로드 옵션
사용자는 두 가지 유형의 동영상 콘텐츠를 업로드할 수 있습니다.
일반 동영상(30분 미만 분량)과 최대 1시간 분량의 팟캐스트 스타일 동영상입니다.
프로세싱 워크플로우
시스템은 일반 동영상을 직접 처리합니다.
팟캐스트 스타일 동영상은 효율적인 처리를 위해 먼저 관리하기 쉬운 크기의 청크(Chunk)로 나뉩니다.
하이라이트 생성
인덱싱이 완료되면 시스템은 고유한 비디오 ID를 생성합니다.
이 비디오 ID는 generate.summarize 함수의 파라미터로 사용됩니다.
Pegasus 1.1 생성형 엔진(Generative Engine)이 동영상의 하이라이트 기반 타임스탬프를 생성합니다.
아웃풋 (출력)
최종 결과물은 동영상의 핵심 순간을 표시하는 타임스탬프 세트입니다.
이 타임스탬프들은 쉬운 탐색을 돕는 챕터 마커나 하이라이트 역할을 합니다.

동영상 클립 세분화는 동영상 URL과 하이라이트 타임스탬프에 액세스하는 moviepy.editor를 사용하여 수행됩니다. 콘텐츠 제작 편의성을 위해 동영상 세그먼트는 MP4 포맷으로 생성됩니다.
다음은 애플리케이션 구성 요소와 상호작용에 대한 구조적 개요입니다:
사용자 인터페이스 (User Interface) - 사용자 상호작용을 관리하고 처리된 동영상 세그먼트를 표시합니다.

동영상 처리 (Video Processing) - moviepy를 사용하여 동영상 세그먼트 생성 및 처리를 위한 다음과 같은 핵심 기능을 제공합니다:
세그먼트 생성기(Segment Creator) - 동영상 세그먼트를 생성합니다.
동영상 프로세서(Video Processor) - 동영상을 트리밍하고 세그먼트를 파싱합니다.
유틸리티 (Utilities) - 동영상 가져오기, 타임스탬프 생성 등의 작업을 위한 유틸리티 함수들을 제공합니다.
API 연동 (API Integration) - 인덱싱을 위해 다음과 같은 Twelve Labs 서비스 인터페이스와 연동합니다:
작업(Task) 생성 및 관리.
하이라이트 챕터 정보를 포함하는 Gist 객체 생성.
이제 YouTube 크리에이터들을 위한 이 애플리케이션의 워크플로우를 완벽하게 이해했으므로, 다음 단계로 본격적인 빌드 준비를 진행해보겠습니다.
준비 단계
Twelve Labs Playground에 로그인한 후 인덱스(Index)를 만듭니다.
Twelve Labs Playground에서 API 키를 발급받습니다.
생성 작업을 위해 다음 비디오 이해 엔진을 활성화합니다:
Marengo 2.6 (임베딩 엔진): 동영상 검색 및 분류용
Pegasus 1.1 (생성형 엔진): 비디오 투 텍스트(Video-to-Text) 생성용
이러한 엔진들은 비디오 이해를 위한 매우 강력하고 견고한 토대를 제공합니다.

1단계에서 생성한 인덱스를 열어
INDEX_ID를 확인합니다. ID는 URL에서 확인할 수 있습니다: https://playground.twelvelabs.io/indexes/{index_id}API 키 및
INDEX_ID를 메인 파일과 함께.env파일에 설정합니다.
Twelvelabs_API=your_api_key_here API_URL=your_api_url_here
코드 기반의 접근 방식을 선호하신다면 다음 단계를 따르세요:
Twelve Labs Playground에서 API 키를 받아 환경 변수로 준비합니다.
Twelve Labs SDK와 환경 변수를 임포트합니다. 환경 변수의 Twelve Labs API 키를 사용하여 SDK 클라이언트를 초기화합니다.
from twelvelabs import TwelveLabs from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("API_KEY") client = TwelveLabs(api_key=API_KEY)
생성 작업에 사용할 원하는 엔진을 지정합니다:
engines = [ { "name": "marengo2.6", "options": ["visual", "conversation", "text_in_video", "logo"] }, { "name": "pegasus1.1", "options": ["visual", "conversation"] } ]
지정한 인덱스 이름과 엔진 구성 파라미터를 담아
client.index를 호출하여 새 인덱스를 생성합니다. 고유하고 식별하기 쉬운 인덱스 이름을 사용하세요.
index = client.index.create( name="<YOUR_INDEX_NAME>", engines=engines ) print(f"A new index has been created: Index id={index.id} name={index.name} engines={index.engines}")
index.id 필드는 새로 만든 인덱스의 고유 식별자입니다. 이 식별자는 비디오를 올바른 위치에 인덱싱하는 데 필수적입니다.
이 단계들을 완료했으면 이제 본격적으로 애플리케이션 개발에 들어갈 준비가 되었습니다!
비디오 하이라이트 생성기 실습 과정
이 튜토리얼에서는 미니멀한 프론트엔드를 갖춘 Streamlit 애플리케이션을 빌드해 보겠습니다. 디렉터리 구조는 다음과 같습니다:
. ├── app.py ├── requirements.txt ├── utils.py ├── .env └── .gitignore
1 - Streamlit 애플리케이션 생성
위의 단계를 모두 마쳤다면 이제 Streamlit 애플리케이션을 구현할 차례입니다. 이 앱을 통해 동영상을 업로드하고 하이라이트 챕터를 추출하며, 나뉘어진 비디오 클립을 편리하게 생성할 수 있습니다. 애플리케이션은 크게 다음 두 개의 핵심 파일로 구성됩니다:
가상 환경 설정에 필요한 의존성 라이브러리 목록은 requirements.txt 파일에서 확인할 수 있습니다.
시작하려면 Python 가상 환경을 만들고 애플리케이션에 맞게 패키지를 설치합니다:
pip install -r requirements.txt
2 - 핵심 기능을 위한 유틸리티 함수 구현
이 섹션에서는 하이라이트 챕터를 생성하고 인덱싱을 위해 길이가 긴 동영상을 효율적으로 처리하는 방법을 알아보겠습니다. 아울러 섹션 2.2에서 인덱싱한 동영상에 결과를 적용하여, 하이라이트로 지정된 챕터별로 동영상 세그먼트를 추출하는 방법도 다룹니다.
2.1 - 하이라이트 챕터 생성 및 비디오 프로세싱 처리
먼저 필요한 라이브러리인 moviepy.editor, m3u8, io, urllib.parse, yt_dlp 및 Twelve Labs SDK를 임포트합니다. 그리고 API Key와 Index ID 환경 변수를 설정합니다. 이어서 각 라이브러리의 중요성과 수행 역할에 대해 자세히 알아보겠습니다.
import os import requests from moviepy.editor import VideoFileClip from twelvelabs import TwelveLabs from dotenv import load_dotenv import io import m3u8 from urllib.parse import urljoin import yt_dlp # Load environment variables load_dotenv() API_KEY = os.getenv("API_KEY") INDEX_ID = os.getenv("INDEX_ID") def seconds_to_mmss(seconds): minutes, seconds = divmod(int(seconds), 60) return f"{minutes:02d}:{seconds:02d}" def mmss_to_seconds(mmss): minutes, seconds = map(int, mmss.split(':')) return minutes * 60 + seconds def generate_timestamps(client, video_id, start_time=0): try: gist = client.generate.summarize(video_id=video_id, type="chapter") chapter_text = "\n".join([f"{seconds_to_mmss(chapter.start + start_time)}-{chapter.chapter_title}" for chapter in gist.chapters]) return chapter_text, gist.chapters[-1].start + start_time except Exception as e: raise Exception(f"An error occurred while generating timestamps: {str(e)}") # Utitily function to trim the video based on the time stamps def trim_video(input_path, output_path, start_time, end_time): with VideoFileClip(input_path) as video: new_video = video.subclip(start_time, end_time) new_video.write_videofile(output_path, codec="libx264", audio_codec="aac") # Based on the speicific Index_ID, fetching all the video_id def fetch_existing_videos(): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos?page=1&page_limit=10&sort_by=created_at&sort_option=desc" headers = {"accept": "application/json", "x-api-key": API_KEY, "Content-Type": "application/json"} response = requests.get(url, headers=headers) if response.status_code == 200: return response.json()['data'] else: raise Exception(f"Failed to fetch videos: {response.text}") # Utility function to retrieve the URL of the video with video_id def get_video_url(video_id): url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{video_id}" headers = {"accept": "application/json", "x-api-key": API_KEY} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data['hls']['video_url'] if 'hls' in data and 'video_url' in data['hls'] else None else: raise Exception(f"Failed to get video URL: {response.text}") # Utility function to handle and process the video clips larger than 30 mins def process_video(client, video_path, video_type): with VideoFileClip(video_path) as clip: duration = clip.duration if duration > 3600: raise Exception("Video duration exceeds 1 hour. Please upload a shorter video.") if video_type == "Basic Video (less than 30 mins)": task = client.task.create(index_id=INDEX_ID, file=video_path) task.wait_for_done(sleep_interval=5) if task.status == "ready": timestamps, _ = generate_timestamps(client, task.video_id) return timestamps, task.video_id else: raise Exception(f"Indexing failed with status {task.status}") elif video_type == "Podcast (30 mins to 1 hour)": trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_1.mp4") trim_video(video_path, trimmed_path, 0, 1800) task1 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task1.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task1.status != "ready": raise Exception(f"Indexing failed with status {task1.status}") timestamps, end_time = generate_timestamps(client, task1.video_id) if duration > 1800: trimmed_path = os.path.join(os.path.dirname(video_path), "trimmed_2.mp4") trim_video(video_path, trimmed_path, 1800, int(duration)) task2 = client.task.create(index_id=INDEX_ID, file=trimmed_path) task2.wait_for_done(sleep_interval=5) os.remove(trimmed_path) if task2.status != "ready": raise Exception(f"Indexing failed with status {task2.status}") timestamps_2, _ = generate_timestamps(client, task2.video_id, start_time=end_time) timestamps += "\n" + timestamps_2 return timestamps, task1.video_id # Utility function to render the video on the UI def get_hls_player_html(video_url): return f""" <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <style> #video-container {{ position: relative; width: 100%; padding-bottom: 56.25%; overflow: hidden; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }} #video {{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; }} </style> <div id="video-container"> <video id="video" controls></video> </div> <script> var video = document.getElementById('video'); var videoSrc = "{video_url}"; if (Hls.isSupported()) {{ var hls = new Hls(); hls.loadSource(videoSrc); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() {{ video.pause(); }}); }} else if (video.canPlayType('application/vnd.apple.mpegurl')) {{ video.src = videoSrc; video.addEventListener('loadedmetadata', function() {{ video.pause(); }}); }} </script> """
A. 입력: 비디오 콘텐츠 가공
이 애플리케이션은 process_video 함수에 크게 의존합니다. 이 함수는 30분 미만의 짧은 비디오와 최대 1시간 분량의 긴 팟캐스트 스타일 비디오라는 두 가지 유형의 비디오 콘텐츠를 지원합니다. 길이가 긴 비디오의 경우, 보다 효율적인 처리를 위해 trim_video 핵심 유틸리티 함수를 사용해 동영상을 관리 가능한 크기의 청크로 분할 처리함으로써 장시간 녹화본도 누락 없이 분석할 수 있습니다.
B. 처리: 동영상 인덱싱, 분석 및 챕터 생성
30분 미만의 단편 비디오의 경우, 임베딩 엔진인 Marengo 2.6을 통해 인덱싱을 수행하고 고유 비디오 ID를 발급받습니다. 이후 TwelveLabs SDK의 생성형 엔진인 Pegasus 1.1의 generate 함수를 호출하여 비디오 콘텐츠를 면밀히 분석하고 해당 비디오에 대한 타임스탬프 기반 챕터를 얻습니다.
길이가 긴 비디오의 경우 30분 길이의 청크로 쪼갠 후 순차적으로 인덱싱 및 분석을 실행하여 타임스탬프 챕터를 빌드해 나갑니다. 이때 첫 번째 청크의 종료 타임스탬프가 다음 청크의 시작 지점이 됩니다.
get_video_url 함수는 인덱싱된 비디오의 URL을 조회하여 앱 내에 비디오 플레이어를 띄우는 용도로 쓰이며, 실제 렌더링에는 get_hls_player_html(video_url) 함수를 사용합니다.
C. 반환 값: 타임스탬프가 포함된 하이라이트
생성된 원본 타임스탬프 결과는 초(second) 단위로 반환됩니다. 하지만 YouTube 설명란에 하이라이트를 게시하려면 분 대 초(mm:ss) 형식이어야 합니다. 따라서 표시용 변환에는 seconds_to_mmss를 사용하고, 반대로 챕터 기반으로 영상 세그먼트를 잘라낼 때는 분 단위를 초 단위로 역변환해 주는 mmss_to_seconds를 활용합니다.
업로드된 모든 하이라이트 영상은 앞서 생성해 준 인덱스 ID를 타겟으로 인덱싱됩니다. 사용자는 fetch_existing_videos()를 통해 기존 동영상들을 불러온 다음 비디오 ID를 선택하여 생성 엔진에 generate_timestamp를 요청하기만 하면 데이터를 손쉽게 받아볼 수 있습니다.
2.2 - 인덱스에서 비디오 불러오기 및 결과에 맞춰 세그먼트 분할
이 세션에서는 주요 하이라이트 타임스탬프 영역을 바탕으로 동영상 세그먼트를 분할하는 작업을 진행합니다. 각 유틸리티 함수들이 상호작용하며 영상 다운로드, 메타데이터 파싱, 챕터별 세그먼트 추출 작업을 완벽하게 수행합니다.
# 비디오 ID로부터 URL을 조회해 인덱싱된 원본 비디오를 실제로 내려받는 유틸리티 함수 def download_video(url, output_filename): ydl_opts = { 'format': 'best', 'outtmpl': output_filename, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) # 개별 세그먼트 정보를 프로그램이 구별 가능하도록 분석하는 유틸리티 함수 def parse_segments(segment_text): lines = segment_text.strip().split('\n') segments = [] for i, line in enumerate(lines): start, description = line.split('-', 1) start_time = mmss_to_seconds(start.strip()) if i < len(lines) - 1: end = lines[i+1].split('-')[0] end_time = mmss_to_seconds(end.strip()) else: end_time = None segments.append((start_time, end_time, description.strip())) return segments # 분석된 정보에 따라 개별 동영상 세그먼트를 슬라이싱하는 핵심 함수 def create_video_segments(video_url, segment_info): full_video = "full_video.mp4" segments = parse_segments(segment_info) try: # 원본 동영상 클립 우선 다운로드 download_video(video_url, full_video) for i, (start_time, end_time, description) in enumerate(segments): output_file = f"{i+1:02d}_{description.replace(' ', '_').lower()}.mp4" trim_video(full_video, output_file, start_time, end_time) yield output_file, description os.remove(full_video) except yt_dlp.utils.DownloadError as e: raise Exception(f"다운로드 완료 중 오류 발생: {str(e)}") except Exception as e: raise Exception(f"예기치 못한 장애 발생: {str(e)}") # 잘라내기(Trimming) 처리를 마친 각 클립 세그먼트를 내려받는 함수 def download_video_segment(video_id, start_time, end_time=None): video_url = get_video_url(video_id) if not video_url: raise Exception("비디오 URL 조회 실패") playlist = m3u8.load(video_url) start_seconds = mmss_to_seconds(start_time) end_seconds = mmss_to_seconds(end_time) if end_time else None total_duration = 0 segments_to_download = [] for segment in playlist.segments: if total_duration >= start_seconds and (end_seconds is None or total_duration < end_seconds): segments_to_download.append(segment) total_duration += segment.duration if end_seconds is not None and total_duration >= end_seconds: break buffer = io.BytesIO() for segment in segments_to_download: segment_url = urljoin(video_url, segment.uri) response = requests.get(segment_url) if response.status_code == 200: buffer.write(response.content) else: raise Exception(f"세그먼트 다운로드 실패: {segment_url}") buffer.seek(0) return buffer.getvalue()
download_video 함수는 전체 비디오 자산을 수급할 때 yt-dlp 오픈소스 패키지를 활용합니다. 하나의 단일 미디어로부터 복수의 작은 세그먼트들을 분리 및 생성하기 때문에 본 툴 내부적으로는 원본 소스를 모두 끌어옵니다.
parse_segments 함수는 자동으로 계산된 물리적인 타임스탬프 문장을 코드상에서 제어할 수 있는 컬렉션 구조로 치환하는 전처리 단위 역할을 담당합니다. 각 챕터의 인앤아웃 타이밍과 서술부를 논리적으로 정리하여 컷 편집 환경을 대비합니다.
create_video_segments는 위 모든 과정들을 선형적으로 매끄럽게 제어합니다. 전체 뼈대 파일을 받아 유연한 포맷으로 세분화한 뒤, 구성한 규칙에 따라 클립 파일들을 디스크에 써내려갑니다. 처리 과정이 끝날 때마다 결과 파일 및 디스크립션 정보를 차례대로 던져줍니다.
각각의 챕터 클립은 download_video_segment를 경유해 저장할 수도 있습니다. 챕터별 사전 보기나 고배율 인코딩 시 전체 파일을 다시 받을 필요가 없도록, HTTP 기반의 동적 스트리밍(HLS, HTTP Live Streaming) 프로토콜을 통과하여 표적 자산만 즉각 받아내는 스마트한 구조를 취하고 있습니다.
이를 통해 창작 영역에서 소요되는 리스트 메타작성 부담을 더는 것은 물론, 이후 자체 아카이빙이나 소셜 릴리즈를 위한 자산들을 즉석에서 발굴하고 직접 후가공할 수 있는 고부가가치 환경을 구축하게 됩니다 🎬✂️️.
3 - Streamlit 애플리케이션의 지침 흐름
이 세션은 보다 직관적이고 세련된 비주얼을 제공하기 위해 미니멀하게 연출된 UI 지침에 맞춰 제작한 main application 실행 코드에 주목합니다.
이 애플리케이션은 먼저 깔끔하고 매력적인 테마의 인터페이스를 적용하기 위해 세부 CSS를 지정하는 화면 스타일링으로 시작합니다. 그와 동시에 렌더링 주기가 새로 고침되더라도 기존 기록을 영구 관리할 수 있도록 세션 스토리지 변수를 세팅하여 영속성을 확보합니다. 전체 작동 프로그램의 모습은 app.py에서 확인할 수 있으며, 아래에서 세부 구성 모형을 소개합니다.
# 파일 업로드 컨트롤러 및 프론트 정비 함수 def upload_and_process_video(): video_type = st.selectbox("비디오 유형 선택:", ["Basic Video (less than 30 mins)", "Podcast (30 mins to 1 hour)"]) uploaded_file = st.file_uploader("적용할 비디오 파일 지정", type=["mp4", "mov", "avi"]) if uploaded_file and st.button("파일 처리 분석 시행", key="process_video_button"): with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file: tmp_file.write(uploaded_file.read()) video_path = tmp_file.name try: with st.spinner("영상을 활발히 분석 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, video_id = process_video(client, video_path, video_type) st.success("비디오 분석 작업이 모두 성공적으로 끝났습니다!") st.session_state.timestamps = timestamps st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.video(st.session_state.video_url) else: st.error("비디오를 재생하기 위한 유효 URL을 얻지 못했습니다.") except Exception as e: st.error(str(e)) finally: os.unlink(video_path) # 기존에 잘 저장되어 있는 인덱스 비디오를 골라 타임스탬프 생성을 호출하는 함수 def select_existing_video(): try: existing_videos = fetch_existing_videos() video_options = {f"{video['metadata']['filename']} ({video['_id']})": video['_id'] for video in existing_videos} if video_options: selected_video = st.selectbox("작업할 비디오 선택:", list(video_options.keys())) video_id = video_options[selected_video] st.session_state.video_id = video_id st.session_state.video_url = get_video_url(video_id) if st.session_state.video_url: st.markdown(f"### 지정된 비디오: {selected_video}") st.video(st.session_state.video_url) else: st.error("비디오 주소를 정상적으로 받지 못했습니다.") if st.button("타임스탬프 자동 생성", key="generate_timestamps_button"): with st.spinner("타임스탬프를 계산 중입니다..."): client = TwelveLabs(api_key=API_KEY) timestamps, _ = generate_timestamps(client, video_id) st.session_state.timestamps = timestamps else: st.warning("보유하신 인덱스 내에 등록된 영상 자산이 하나도 존재하지 않습니다.") except Exception as e: st.error(str(e))
메인 UI 화면은 명쾌하게 분리된 두 가닥의 직관적인 인터페이스를 구성합니다. 하나는 새로운 오리지널 비디오 자산을 즉시 분석 풀로 끌어들이는 업로드 공간이며, 다른 하나는 기존 소스를 바로 가져와 다듬을 수 있는 탐색 공간입니다. 장시간 걸리는 분할 분석 기간에도 실시간 처리 표시기가 상황을 정확하게 사용자에게 전달합니다. 또 발생 상황에 따른 에러 마스킹도 똑똑하게 이뤄지며, 한 인터페이스 안에서 모든 세그먼트 이력을 클릭 한 번으로 새롭게 비워 줄 수 있는 버튼도 함께 내장되어 자원 낭비를 전면 차단합니다.
upload_and_process_video(): 실제 인풋 연계를 통한 전반적인 트래픽 수송과 동영상 다이렉트 분석을 책임집니다. 일반 영상과 롱폼 청크 모델 연계를 다변화하여 다루며, 최종 산출물 도출 단계로 스마트하게 이관해 줍니다.select_existing_video(): 이미 잘 적재되어 있는 플랫폼 내 기존 영구 아카이빙 폴더들을 안전하게 불어내 크리에이터가 임의로 집어 활용할 수 있도록 설계되었습니다.
당 연계 앱은 st.session_state의 영리한 데이터 생존 메커니즘을 타고 작동합니다. 무거운 메타데이터의 누적과 가속 세그먼트 생성이라는 연이은 트랜잭션 과정 속에서도 불필요한 새로고침으로 인한 중간 상태 소실을 원천 기화해 줍니다. 이미 정리된 데이터를 그대로 재활용하여 대기 시간과 불량 업로드 횟수를 과감하게 단축시켜 줍니다.
# 생성 완료된 자사 세그먼트 클립 목록을 이쁘게 표시하고 보존 다운로드할 수 있는 매개 함수 def display_segment(file_name, description, segment_index): if os.path.exists(file_name): st.write(f"### {description}") st.video(file_name) with open(file_name, "rb") as file: file_contents = file.read() unique_key = f"download_{segment_index}_{uuid.uuid4()}" st.download_button( label=f"클립 다운로드: {description}", data=file_contents, file_name=file_name, mime="video/mp4", key=unique_key ) st.markdown("---") else: st.warning(f"파일 자산 {file_name}을 로드하지 못했거나 다른 하드 드라이브로 복사 이동되었습니다.") # 실제 세그먼트를 추출하고 표시하는 핵심 함수 def process_and_display_segments(): if not st.session_state.video_url: st.error("연결된 본 소스의 미디어 URL을 찾을 수 없습니다. 다시 전송 분석을 진행해 주세요.") return segment_generator = create_video_segments(st.session_state.video_url, st.session_state.timestamps) progress_bar = st.progress(0) status_text = st.empty() st.session_state.video_segments = [] # 비디오 세그먼트 목록 초기화 total_segments = len(st.session_state.timestamps.split('\n')) for i, (file_name, description) in enumerate(segment_generator, 1): st.session_state.video_segments.append((file_name, description)) display_segment(file_name, description, i-1) # 인덱스 순번을 동봉 전달 progress = i / total_segments progress_bar.progress(progress) status_text.text(f"클립 파트 처리 세분화 중 {i}/{total_segments}...") progress_bar.progress(1.0) status_text.text("모든 클립의 정밀 세분화가 성공적으로 끝났습니다!") # 생성된 타임라인 인덱스와 분기 세그먼트 카드들을 전반적으로 레이아웃하는 렌더 관리 함수 def display_timestamps_and_segments(): if st.session_state.timestamps: st.subheader("YouTube 설명 등록용 추천 챕터 메타 데이터") st.write("아래 텍스트 영역을 통째로 복사하셔서 비디오 업로드 상세 묘사란에 그대로 기재하세요.") st.code(st.session_state.timestamps, language="") if st.button("개별 비디오 챕터 클립 실물 패키징 생성", key="create_segments_button"): try: process_and_display_segments() except Exception as e: st.error(f"개별 미디어 클립 패키징 도중 에러가 발발했습니다: {str(e)}") st.exception(e) # 역추적을 위한 스택 트레이스 표시 if st.session_state.video_segments: st.subheader("추출 성공한 개별 클립 패키지") for index, (file_name, description) in enumerate(st.session_state.video_segments): display_segment(file_name, description, index) if st.button("임시 저장 공간 비우기", key="clear_segments_button"): for file_name, _ in st.session_state.video_segments: if os.path.exists(file_name): os.remove(file_name) st.session_state.video_segments = [] st.success("생성되었던 모든 임시 세그먼트 파일들을 스토리지에서 깨끗하게 소거했습니다.") st.experimental_rerun()
process_and_display_segments(): 타임라인 데이터 처리에 맞춰 개별 디스크 기반 파일 자산 생성을 진두지휘합니다.display_timestamps_and_segments(): 가시화된 산출 데이터를 일괄 복사해 가기 편하게 다듬고 클립 파일 구축 지시 행동을 총체 지배합니다.display_segment(): 완성품으로 조각난 비디오 목록을 개별 뷰 카드와 안전한 다이렉트 다운로드 슬롯으로 정렬해 줍니다.
display_segment는 추출된 파일 이름, 디스크립션 및 세그먼트 인덱스를 매개변수로 받아 화면 상에 카드를 렌더링하고, Streamlit의 st.video 기능을 경유하여 클립을 즉시 재생하며, 다운로드 패키지 버튼을 유니크 키로 결합합니다. 경로 조회에 이상이 확인될 때는 위험 경고 카드를 대신 노출합니다.
process_and_display_segments는 영상 클립 생성을 한 번에 연속 처리합니다. 타겟팅할 미디어의 정보가 소실되어 있을 때 빠르게 에러 팝업을 발생시키며, 정상 가동 시에는 전체를 순차적으로 빌드하며 실시간 프로세스 진행률 그래프를 사용자에게 인지시킵니다. 변환 결과는 스토리지 세션에 보관되어 후속 작업으로 다각화됩니다.
display_timestamps_and_segments는 모든 흐름의 유기적 관문을 매끄럽게 연결합니다. 도출에 최적화된 마커 정보 목록을 기민하게 정돈하여 출력 처리하며, 단독 추출과 전체 초기화 옵션을 통해 임시 리소스 제어를 투명하고 안전하게 보장합니다.
아래는 완성된 데모 애플리케이션의 구현 예시입니다:

데모 화면에서 보듯, 분석 타겟 비디오를 전송하면 인포 데이터가 순식간에 추출되고 곧바로 YouTube 마커 용도에 맞는 메타 데이터가 조합 출력됩니다. 그리고 다음 지면에서 지시 행동 버튼을 작동하는 즉시 각 세션 파트들이 독자적인 미디어 클립으로 정교하게 나뉘어 가공되는 실황을 확인할 수 있습니다.

Twelve Labs의 뛰어난 비디오 이해 능력을 바탕으로, 본 챕터 추출 모델을 단순한 소셜 릴리즈를 넘어 미디어 트랙 후반 작업, 온라인 원격 교육, 혹은 고정밀 모니터링 등 각자 원하는 도메인 영역에 무궁무진하게 접목하고 개선해 보세요.
추가로 확장해 볼 만한 아이디어
작동 프로세스와 세부 구현 방식을 정밀하게 깨닫는 순간, 세상을 향상시킬 수 있는 신선한 비즈니스 아이디어나 응용 제품을 마음껏 개발할 수 있는 기틀을 닦게 됩니다. 아래는 본 프로그램 아키텍처를 토대로 시도해 볼 만한 가치 높은 응용 분야들입니다:
📽️ 소셜 및 YouTube 콘텐츠 창작자: 자동 챕터링과 쇼츠 소작업에 걸리는 전체적인 워크플로우를 완벽하게 제거하여 가독성을 높입니다.
🎓 비대면 교육 및 지식 플랫폼: 길고 지루한 명강의 영상에서 시청 학습자가 학습 타겟 섹션만 즉각 선택하여 선별 시청할 수 있도록 장벽을 해소합니다.
🎥 비선형 콘텐츠 가공 유통: 복잡한 수동 트리밍 과정 없이 원본 영상 패키지로부터 업로드 전용 수십 개의 분기 포트폴리오를 빠르게 일괄 양산합니다.
결론
본 구현 기술서에서는 Twelve Labs 엔진을 활용해 동영상 창작 생태계에 놀라운 부가가치와 혁신을 불어넣어 줄 하이라이트 챕터 가공 및 클립 생성 단계를 깊이 있게 살펴보았습니다. 튜토리얼을 끝까지 따라와 주셔서 감사드리며, 향후 더 높은 영감과 획기적인 기능 추가로 사용자 경험을 훌륭히 끌어올릴 여러분의 고유한 도전을 적극 응원합니다.
추가 리소스
각 생성 프로세스 구현을 매끄럽게 책임진 핵심 추론 기기, Marengo 2.6 (임베딩 엔진) 및 Pegasus 1.1 (생성형 엔진)을 보다 상세하게 학습하세요. Twelve Labs 엔진을 전방위적으로 제어하며 풍부한 통찰을 획득하고자 한다면 하단에 등록된 커뮤니티 연결 링크들을 활용해 보세요.
Discord 커뮤니티: 지식을 나누는 글로벌 개발자 및 크리에이터들과 뜨겁게 소통하고 지적 영감을 교환하며 함께 작품을 다듬어 보세요. Twelve Labs Discord 참여하기
샘플 애플리케이션 라이브러리: 아이디어를 현실화하는 정수가 담긴 수많은 예제 앱 빌드를 직접 참조해 영감 수치를 배가하고 연동 기술을 극대화하세요.
상세 자습서 둘러보기: 한 차원 깊은 응용을 완성해 주는 고화소 분석 튜토리얼 라이브러리들을 마음껏 탐험하세요.
자유롭게 제공 리소스를 누리고, 기민하게 지식을 이관해 오며, Twelve Labs의 탁월한 비디오 인지 기술을 활용한 최고의 애플리케이션을 창조해 보세요.




