Smart Cutter와 VideoRedo의 동영상 칼컷 기능을 따라해보는 프로그램을 만들고 말겠다.
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 이다.
그럼 이만...