컴퓨터와 잡동사니 자료

Smart Cutter와 VideoRedo의 동영상 칼컷 기능을 따라해보는 프로그램을 만들고 말겠다.

K66Google 2020. 11. 29. 13:03

Smart Cutter와 VideoRedo 등의 동영상 자르기 프로그램은 인코딩없이 사용자가 정해주는 시간대로 칼같이 동영상을 잘라주는 이른바 '칼컷' 기능을 탑재하고 있다.

보통의 H264 인코딩 동영상을 ffmpeg으로 인코딩없이 자르려고 하면 앞 부분이 좀 더 같이 붙어서 나오는 현상이 벌어지게 되는데, 칼컷 기능을 가진 프로그램들은 그런 현상없이 칼같이 잘라준다.


그럼 먼저, 왜 그러한 현상이 발생하는지 살펴볼 필요가 있다.


( 출처 : VideoHelp 포럼 )


동영상에는 프레임이라는 단위로 이루어져있다. 그리고 프레임은 I프레임, P프레임, B프레임이라는 3가지 종류가 있다.

이때 주목해야 될 프레임은 I프레임이다. H264 동영상에서 I프레임은 키프레임이다. 이때문에 ffmpeg으로 인코딩없이 동영상을 자르려고 하면 사용자가 지정한 시간대의 프레임에서 자르기를 시작하지 않고 그 시간대를 관할하고 있는 키프레임으로 가서 자르기를 시작하게 된다. (자세한 설명은 이 게시물을 참조.)


이런 알고리즘을 무시하고 칼컷을 하려면 재인코딩하는 것 말고는 방법이 없다. 그럼 스마트커터나 비디오리두같은 프로그램들은 어떻게 동영상 칼컷이 가능한 것일까?


바로 동영상을 두 파트로 나누어서 앞부분은 재인코딩해서 자르고, 뒷부분은 인코딩없이 자른 다음 합치는 것이다. 이렇게 말이다.


"사용자 지정 시작시간 ~ 키프레임""키프레임 ~ 사용자 지정 종료시간"


이렇게 하면 앞부분을 재인코딩해야 하므로 약간의 화질열화가 발생하지만, 어차피 앞부분은 길어봤자 1~2초에 불과하므로 영상 내내 신경쓰이지는 않을거라고 본다.

결국 ffmpeg의 자르기·합치기 명령어를 요렇게 저렇게 잘 써먹어보면 동영상 칼컷 기능을 조악하게나마 구현하는 일은 불가능하지 않다는 것이다.

그러나 이미 시중에 해당 기능을 가진 프로그램들이 있으므로, 굳이 내가 이런 프로그램을 만들 필요가 없었다. 따라서 이 아이디어는 기억속에 묻히게 되었는데...



Smart Cutter가 지원하지 않는 동영상 확장자·코덱, 팟플레이어의 주소 스트리밍을 녹화한 영상들을 열때 음성이 2배속으로 재생되는 현상 등 여러가지 문제가 자꾸만 내 신경을 거슬리게 했다.



VideoRedo도 제멋대로 영상을 빨리감기하거나 일부 동영상을 불러오지 못하는 문제가 있고, 왠지 스마트커터보다 프로그램이 더 무겁다는 느낌이 들어서 별로 사용하고 싶지 않았다.


결국 답답해서 직접 칼컷 기능을 따라해보는 프로그램을 만들어보기로 했다.

사용할 프로그래밍 언어는 C#이며, ffmpeg 빌드, VLC.DotNet NuGet 패키지를 사용하기로 하였다.


ffmpeg 빌드 페이지 (zeranoe) : https://ffmpeg.zeranoe.com/builds/ (현재 폐쇄)

VLC.DotNet Github 페이지 : https://github.com/ZeBobo5/Vlc.DotNet


프로그램이 사용하는 ffmpeg 및 ffprobe의 명령어는 다음과 같다. 

(입력하는 파일명은 '입력파일명.mp4' 라고 가정)


* FPS 확인

ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate -of default=noprint_wrappers=1:nokey=1 "입력파일명.mp4"

결과값은 '30000/1001' 이렇게 분수로 출력된다. 이걸 소수로 변환하면 29.97이 된다. 프로그램 상에서는 소수로 보여주게 처리한다.



* 타임스케일(tbn) 확인

ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 "입력파일명.mp4"

결과값은 '1/90000' 이렇게 분수로 출력된다. 이때 분모 자리인 90000이 우리가 인코딩 명령을 내릴때 보이는 영상의 tbn 값이다. 프로그램 상에서는 분모 자리만 읽어들이도록 처리한다.



* 비디오 코덱 확인

ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 "입력파일명.mp4"

결과값은 'h264' 이렇게 비디오 코덱명으로 출력된다.



* 오디오 코덱 확인

ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 "입력파일명.mp4"

결과값은 'aac' 이렇게 오디오 코덱명으로 출력된다.



* 특정 시간의 스크린샷 찍기

ffmpeg -ss 00:01:00.000 -i "입력파일명.mp4" -vf scale=320:240 -vframes 1 -q:v 2 -y "frame_shot.jpg"

-ss에 특정 시간을 입력. -vf scale에 출력될 해상도를 입력. 이러면 스크린샷이 frame_shot.jpg라는 이름으로 출력된다.

만들기는 했지만 별로 사용되지 않는 기능이다.



* 입력받은 시작 시간을 바탕으로 키프레임(I프레임) 검출.

chcp 65001


ffprobe "입력파일명.mp4" -show_entries frame=pkt_pts_time,key_frame,pict_type -select_streams v -of compact -v 0 -sexagesimal -read_intervals 100%120 | findstr "key_frame=1" > keyframe.txt

chcp 65001은 cmd 환경을 유니코드 환경으로 바꾸는 명령어다. 이 명령어가 없으면 배치파일(.bat) 실행형태로는 한글파일명을 제대로 인식하지 못한다.

-read_intervals에는 시작 분석시간과 끝날 분석시간을 지정한다. 위 명령어의 100%120은 100초(1분 40초)부터 120초(2분)까지의 키프레임을 분석한다는 뜻이다.

결과물은 keyframe.txt라는 텍스트 파일로 출력된다.



* '사용자 지정 시작시간 ~ 키프레임' 구간 동영상 추출.

(시작시간은 1분, 키프레임 시간은 1분 1초라고 가정)

chcp 65001


ffmpeg -hwaccel cuvid -c:v h264_cuvid -ss 00:01:00.000 -i "입력파일명.mp4" -to 00:01.01.000 -vcodec h264 -acodec aac -copyts -r 30000/1001 -video_track_timescale 90000 -y "merge1.mp4"

빨간색으로 칠한 부분은 하드웨어 가속 옵션. 여기서 -c:v h264_cuvid는 h264 코덱만 지원하므로 mpeg4 같은 다른 코덱을 사용하는 동영상은 해당 옵션을 쓰면 안된다.

-ss는 사용자 지정 시작시간, -to는 키프레임 시간을 지정. -vcodec과 -acodec은 입력파일과 똑같은 코덱으로 지정.

-r은 FPS 확인에서 가져온 값을 입력. -video_track_timescale은 타임스케일 확인에서 가져온 값의 분모를 입력.

(FPS와 타임스케일을 반드시 지정해줘야 된다. 안 그러면 동영상을 합친뒤 fps가 이상해지거나 영상이 너무 빨라지는 문제가 발생한다. 관련 게시물은 여기를 참조.)


그러면 merge1.mp4로 추출파일이 저장된다.



* '키프레임 ~ 사용자 지정 종료시간' 구간 동영상 추출.

(키프레임 시간은 1분 1초, 종료시간은 2분이라고 가정)

chcp 65001


ffmpeg -hwaccel cuvid -c:v h264_cuvid -ss 00:01:01.000 -i "입력파일명.mp4" -to 00:02:00.000 -vcodec copy -acodec copy -copyts -r 30000/1001 -video_track_timescale 90000 -y "merge2.mp4"

빨간색으로 칠한 부분은 마찬가지로 하드웨어 가속 옵션.

-ss는 키프레임 시간, -to는 사용자 지정 종료시간을 지정. -vcodec과 -acodec은 copy로 해야 인코딩없이 추출이 가능.

-r은 FPS 확인에서 가져온 값을, -video_track_timescale은 타임스케일 확인에서 가져온 값의 분모를 입력.


그러면 merge2.mp4로 추출파일이 저장된다.



* 동영상 합치기를 위한 텍스트파일 생성 구조.

file 'merge1.mp4'

file 'merge2.mp4'

해당 텍스트를 ffmpeg이 있는 폴더에 merge.txt라는 이름으로 저장한다.



* 동영상 합치기.

ffmpeg -hwaccel cuvid -c:v h264_cuvid -f concat -safe 0 -i "merge.txt" -vcodec copy -acodec copy -copyts -strict -2 -y "output.mp4"

빨간색으로 칠한 부분은 하드웨어 가속 옵션.

이렇게 하면 합치기 작업 후 ffmpeg 폴더에 output.mp4라는 완성된 동영상이 출력된다.

프로그램에서는 output이라는 파일명을 사용자가 지정한 파일명으로 바꾸고 Complete라는 별도의 폴더로 이동시키도록 처리하였다.



* 원본동영상과의 FPS 불일치 수정하기.

ffmpeg -i "output.mp4" -filter_complex "setpts=0.332999*PTS" -vcodec h264 -acodec aac -y "output_fix.mp4"

원본동영상과 완성동영상간의 FPS가 1fps 이상 차이가 날 경우에 실행되도록 위의 명령어를 만들어보았다.

이 방법은 하드웨어 가속 옵션을 사용할 수 없으며, 반드시 재인코딩이 필요하다. 다만 -vcodec에 h264_nvenc같은 그래픽카드 관련 코덱을 사용할 수는 있다.

setpts(보라색 칠한 부분)값은 [완성동영상 FPS ÷ 원본동영상 FPS]의 값을 넣어야 한다. 예를 들어 완성동영상이 9.98fps, 원본동영상이 29.97fps라면 9.98 / 29.97 = 0.332999...다. 이 값을 넣으면 된다. 소수 여섯째자리까지 넣어야 원본동영상의 fps에 더욱 수렴하게 된다.

이 명령어를 실행하면 output_fix.mp4라는 파일로 FPS가 수정된 동영상이 나오게 된다. 

그러나 내가 제작한 프로그램에선 이 명령어가 쓸모없게 되어버렸다. 왜냐하면 동영상을 자를때 -r값과 -video_track_timescale값만 제대로 부여하면 FPS가 1fps 이상 차이나는 경우가 없기 때문이다.


프로그램에서 사용하는 명령어는 이 정도면 거의 다 소개했다...

코딩과 약간의 버그 수정을 마치고, 드디어 프로그램이 공개할 수 있는 수준까지 이르렀다.


이름은 그냥 '칼컷실험기'로 정했다. VideoRedo나 Smart Cutter같은 상용 프로그램의 기능을 따라해 본 것이므로 '실험'이라는 명칭을 포함시켰다.

일단 VLC 플러그인을 사용해서 그런지 대부분의 동영상 파일이 지원된다. Smart Cutter에서 불러오지 못하는 .wmv .avi .flv 파일도 잘 불러와진다. 그러나 비디오 코덱이 h264가 아닌 다른 코덱일 경우에는 칼컷 기능이 사실상 무용지물인 것 같다.


조금 더 보완하고 나서 마침내 프로그램을 공개했다. 다운로드 주소는 https://k66google.tistory.com/671 이다. 


그럼 이만...