구직 활동을 다시 시작했을 때(예, 저는 여전히 #OpenToWork입니다. 핑을 보냅니다!) 구직 지원서 중 하나에서 비디오 데이터를 처리하는 프로토타입을 구현해 달라는 요청을 받았습니다. 프로젝트를 진행하면서 상대적으로 해당 분야에 대한 경험이 부족한 관계로 생성 AI 챗봇의 도움을 많이 받았습니다.
제목에서 언급했듯이 ffmpeg는 일부 전처리 작업을 수행하는 데 사용되었습니다. 프로젝트의 목표 중 하나는 여러 비디오 파일을 차례로 재생할 수 있는 것이었습니다. 이를 달성하는 방법은 여러 가지가 있지만, 저는 이 두 가지를 하나로 연결하는 가장 확실한 솔루션을 선택하기로 결정했습니다.
$ cat video1 video2 video3 | python further-work.py
이를 달성하려면 먼저 파일을 허용되는 형식으로 다시 인코딩해야 했습니다. 이에 대해 Google Gemini와 '협의'한 후 챗봇에서는 목적에 맞게 MPEG-TS를 사용하라고 추천했습니다.
MPEG-TS(MPEG 전송 스트림)는 패킷화된 기본 스트림을 캡슐화하여 작동합니다. 이러한 스트림에는 작은 세그먼트로 패킷화되는 오디오, 비디오 및 PSIP 데이터가 포함됩니다. 각 스트림은 188바이트 섹션으로 잘리고 함께 인터리브됩니다. 이 프로세스는 지연 시간을 줄이고 오류 복원력을 높여 큰 프레임에서 오디오 지연이 발생할 수 있는 화상 회의에 이상적입니다.
https://castr.com/blog/mpeg-transport-stream-mpeg-ts/에서 인용
다른 파일 형식도 해당 목적으로 사용할 수 있으나 논의와는 관련이 없습니다. 비디오를 이 형식으로 다시 인코딩한 후 비디오 데이터는 대기열로 전송되어 다른 프로세스에서 실행되는 다른 모듈에서 사용됩니다.
입력(온라인으로 가져올 비디오 파일 목록)과 출력(다시 인코딩된 비디오 파일 콘텐츠)을 모두 정의한 후 이를 수행하는 방법을 알아낼 시간이었습니다. 불행하게도 ffmpeg는 너무 많은 일을 하는 복잡한 유틸리티입니다. 사용자에게 도움이 되는 인터페이스를 제공하려는 시도가 여러 번 있었습니다(정말 해보고 싶었지만 지금은 중단된 것 같습니다). 하지만 요즘에는 생성 AI가 얼마나 유용한지 알기 때문에 몇 번의 프롬프트만으로 올바른 명령을 내리는 것이 가능합니다.
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
아래 스크린샷과 같이 각 인수의 의미에 대한 설명도 제공했습니다.
ffmpeg 명령을 설명하려는 Gemini의 시도
간단히 말하면 이 명령은 stdin을 통해 비디오 파일 내용을 받아들이고 다시 인코딩된 비디오 파일 내용을 stdout으로 출력합니다.
이제 구현을 코딩할 시간입니다. ffmpeg에서 동시에 읽고 쓰기를 원했기 때문에 이는 asyncio 애플리케이션이 될 것입니다. 이번에 사용하고 있는 http 클라이언트 라이브러리는 httpx이며, 다운로드를 더 작은 배치로 가져오는 방법이 있습니다.
$ cat video1 video2 video3 | python further-work.py
나중에 실제 처리에 대해 걱정합니다. 지금은 청크를 화면에 인쇄하는 코드만 가져오면 됩니다.
다음으로 asyncio.create_subprocess_exec를 통해 ffmpeg를 호출하는 함수를 작성합니다
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
이상적으로는 문서에 설명된 대로 여기서 process.communicate(file_content)를 사용하는 것이 좋습니다. 하지만 그렇게 하면 먼저 전체 파일을 다운로드해야 하므로 필연적으로 응답이 지연되므로 이는 이상적이지 않습니다.
대신 process.stdin.write()를 사용할 수 있으며 원래 write_input 함수를 업데이트해 보겠습니다.
import httpx client = httpx.AsyncClient() async def write_input( client: httpx.AsyncClient, video_link: str, process: asyncio.subprocess.Process ) -> None: async with client.stream("GET", video_link) as response: async for chunk in response.aiter_raw(1024): print(chunk) # this is the downloaded video file, in chunks
청크를 다운로드할 때마다
video_send 함수로 돌아가서 process.stdout을 통해 읽어 함수를 계속 진행합니다. 읽기와 쓰기를 모두 할 수 있다는 점이 바로 우리가 asyncio를 통해 이 작업을 수행하는 이유입니다. 이전에는 동기 설정에서는 고정된 순서로 하나씩만 수행할 수 있었지만 이제는 스케줄러가 순서에 대해 걱정하도록 할 수 있습니다. 이제 함수에는 다시 인코딩된 파일 콘텐츠를 읽고 이를 큐에 게시하기 위해 다음 코드가 추가되었습니다.
async def video_send(client: httpx.AsyncClient, video_link: str) -> None: logger.info("DATA: Fetching video from link", link=video_link) process = await asyncio.create_subprocess_exec( "ffmpeg", "-hwaccel", "cuda", "-i", "pipe:0", "-c:v", "h264_nvenc", # "libx264", "-b:v", "1.5M", "-c:a", "aac", "-b:a", "128k", "-f", "mpegts", "-y", "pipe:1", stdin=subprocess.PIPE, stdout=subprocess.PIPE, ) asyncio.create_task(write_input(client, video_link, process))
우리는 순환적으로
지금은 매우 간단해 보이지만 실제로 이 작업을 올바르게 수행하는 데 밤새도록 걸렸습니다(이 글을 작성하는 동안에도 여전히 코드를 수정하고 있었습니다). 절반의 시간은 누락된 내용이 없는지 확인하기 위해 문서를 확인하고, 다른 시간에는 Gemini에게 내 코드를 검토하도록 요청했습니다.
이 내용이 도움이 되셨기를 바랍니다. 오늘은 여기까지입니다. 다음 주에 이전에 약속했던 Advent of Code 콘텐츠로 다시 돌아오길 바랍니다.
위 내용은 ffmpeg 하위 프로세스를 통한 비디오 데이터 IO의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!