2014년 6월 18일 수요일

YouTube 영상을 저장해보자

개인적인 수집 목적이든 필요한 자료의 획득을 위해서든 최근 VOD 제공 업체의 동영상을 저장하는 일이 많아졌다. 필자의 경우 업무때문에 여러 종류의 뮤직 비디오나 짧은 영상들을 최대한 많이 확보해 트랜스코딩 및 필터링 샘플로 사용하는게 좋기 때문에 더욱 그렇다.
하지만 VOD 제공 업체에서 자신들의 밥줄을 그냥 다운로드하게 해줄 이유가 없기 때문에 어떻게든 동영상들을 저장할 방법을 찾아야 했다. 그렇게 방법을 찾다가 약 3년 전부터 애용하기 시작한 툴이 있는데 바로 keepvid.com이다. 이 툴은 JAVA 기반에서 동작하며 YouTube에 있는 영상을 저장할 수 있도록 도와준다. 사용법은 아래와 같다.


  1. JAVA 설치(이미 설치되어 있다면 넘어가자)
  2. keepvid.com 접속
  3. YouTube에서 저장하고자 하는 영상 선택
  4. 선택한 영상의 웹 주소 복사
  5. keepvid.com 내 [웹 주소 입력 텍스트 박스]에 주소 입력 후 우측 [DOWNLOAD 버튼] 클릭
  6. 나열되는 목록 중 자신이 원하는 포맷과 해상도의 영상을 [다른 이른으로 저장]
    (3GP, FLV, MP4, M4A, WEBM 지원)

근데 문제가 있다. MP4 포맷 720p 이하 해상도의 경우 영상과 음성이 먹싱된 동영상으로 지원하지만 그 외 1080p 해상도 MP4와 WEBM의 경우 영상과 음성을 따로 제공한다. 아마도 유튜브의 스트리밍 방식 때문인걸로 추측된다. 그럼 울며 겨자먹기로 영상만 보아야 하는 것인가? 그건 아니다 다 방법이 있다. 먹싱되지 않은 두 영상과 음성이 확보만 된다면 두 소스를 먹싱하기만 하면 된다. 다행히도 keepvid.com은 M4A로 음성 소스를 따로 제공하고 있다. 아래는 이 1080p MP4와 M4A를 이용해 두 소스를 하나의 동영상으로 먹싱하는 방법이다. 

  1. ffmpeg 소스를 받아 빌드한다.(윈도 사용자의 경우 Zeranoe FFmpeg Builds에서 자신에게 적합한 바이너리를 다운로드한다)
  2. 다운로드한 두 소스를 ffmpeg을 통해 아래와 같은 형식으로 먹싱한다.
    ffmpeg -i $VID_SRC -i $AUD_SRC -c:v copy -c:a copy $DST_PATH
    • $VID_SRC: 영상 소스 경로
    • $AUD_SRC: 음성 소스 경로
    • $DST_PATH: 먹싱된 동영상을 생성할 경로
  3. 감상(하앍//ㅅ//)

ffmpeg을 실행하면서 쓰인 저 스크립트에 대한 설명을 하다보면 이 포스트가 원 의도에서 엄청나게 벗어날 것 같으니 일단은 저렇게 쓴다는 정도로만 이해하도록 하자. 추후 ffmpeg에 대해 다루게 된다면 자세하게 다루어 보겠다(근데 귀찮아...)

2014년 6월 15일 일요일

현존하는 최강의 가성비를 자랑하는 책상 [소프시스] - 위더스 컴퓨터 테이블

처음으로 쓰는 지름신 포스트다. ㅋㅋㅋ

이번 포스트의 제품은 [소프시스] - 위더스 컴퓨터 테이블 (그냥 책상이라 하면 입에 가시가 돋는건가?=_=a;)


거두절미하고 바로 평점 들어가보자.



  • 디자인: ★★★★☆
    • 워낙에 심플한 걸 좋아하는 필자로써 더할나위 없이 깔끔하고 훌륭한 디자인이었다. 하지만 마감 부분에서 언급하겠지만 프레임에 숭숭 뚫린 구멍이 좀...
  • 마감: ★★☆☆☆
    • 우선 자재는 쌈마이 합판이다. 그래서 겉을 나무질감의 경화 시트지를 사용했다. 뭐 여기까지는 좋은데 그 시트지 마감이 조금 아쉽다. 
    • 프레임의 굵기가 생각보다 얇다. 때문에 조립 시 드라이버에 조금만 힘을 줘서 조으다 보면 프레임이 살짝 함몰되는 현상을 볼 수 있다. 하지만 그렇게 심각한 수준은 아니다.
    • 책장 프레임을 좌측이나 우측으로 자유롭게 배치할 수 있도록 프레임에 많은 천공이 있는데 이게 생각보다 신경쓰인다. 값싼 플라스틱 버튼으로 이런 천공들을 메꾸는 방법을 썼더라면 훨씬 완성도가 높았을 것 같다.
  • 배송: ★★★☆☆
    • 그냥 뭐 똑같다. 월요일에 주문했고 수요일에 수령하였다.
  • 가격: ★★★★★ +α
    • 이 가격대에 조그마한 협탁도 못 살걸? ㅋㅋㅋ
  • 내구도: ★★★☆☆
    • 프레임이 생각보다 얇아 일부러 각 프레임들을 반대 방향으로 비틀었을 시 조금씩 흔들리는 것을 확인할 수 있다. 하지만 그렇게 문제가 있는 수준은 아니다.
  • 종합: ★★★★★
    • 가성비 최강!! 내 기준에서 별 다섯개면 진짜 최고인거다. 왠만하면 별 3개 이상 안주는 성격인데 말이다.

그럼 실제 조립 및 사용하고 있는 사진을 보도록 하자.
굉장히 심플하다. 아이 좋아 ㅋㅋ

2014년 5월 12일 월요일

2014년 5월 8일 목요일

libavformat을 이용해 영상의 길이와 프레임레이트를 구해보자 ( How to get the duration and frame rate using libavformat )

거두절미하고 핵심!!

int64_t AVFormatContext::duration
AVRational AVStream::avg_frame_rate
static double av_q2d ( AVRational a )

위에 적힌 저 세개만 알면 끝난다. 뭐 자세한 내용을 나중에 생각하고 아래 소스코드를 보도록 하자.


  1 ////////////////////////////////////////////////////////////////////////////////
  2 //
  3 // File name  : ffinfo.c
  4 // Last edit  : 2014-05-08 crazybird
  5 // Dependency : ffmpeg 2.1.x (libavformat.55.x.x)
  6 //
  7
  8
  9 #include <stdio.h>
 10 #include <stdint.h>
 11 #include <libavformat/avformat.h>
 12
 13
 14 int main(int argc, char** argv)
 15 {
 16     AVFormatContext *fmt_ctx = NULL;    // Format context
 17     int ret = 0;                        // Return value
 18     AVStream *st = NULL;                // Stream
 19     double fps = 0.0;                   // Video fps
 20     int64_t dur = 0;                    // General duration
 21     unsigned int nb_streams = 0;        // A list of all streams in the file
 22     unsigned int idx = 0;               // for(;;) iterator
 23
 24     // Verify arguments
 25     if (argc != 2)
 26     {
 27         printf(
 28             "usage: %s <input_file>\n"
 29             "example program to demonstrate the use of the "
 30             "libavformat metadata API.\n\n"
 31             , argv[0]);
 32
 33         return 1;
 34     }
 35
 36     av_register_all();  // Register URL Protocols.
 37
 38     // Open media file
 39     ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL);
 40     if (ret != 0)
 41     {
 42         return ret;
 43     }
 44
 45     // Find video stream index
 46     nb_streams = fmt_ctx->nb_streams;
 47     if (nb_streams > 0)
 48     {
 49         for (idx = 0; idx < nb_streams; idx++)
 50         {
 51             st = fmt_ctx->streams[idx];
 52             if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
 53             {
 54                 break;
 55             }
 56
 57             st = NULL;
 58         }
 59     }
 60
 61     // Is invalid stream?
 62     if (st == NULL)
 63     {
 64         printf("%s does not exist in the video stream.", argv[1]);
 65
 66         goto END;
 67     }
 68
 69     // IMPORTANT : Get general duration and video frame rate
 70     dur = fmt_ctx->duration;
 71     fps = av_q2d(st->avg_frame_rate);
 72
 73     // Output
 74     printf(
 75         "video fps=%f\n"
 76         "general duration=%lld\n"
 77         , fps, dur);
 78
 79 END:
 80     avformat_close_input(&fmt_ctx);
 81
 82     return 0;
 83 }

 84


보는 것과 같이 매우 간단하다. 더 자세한 내용은 추후에 보충하도록 하겠다.

2014년 4월 15일 화요일

로그를 좀더 이쁘게 박아보자(g++ 기준)

로그를 작성할 때 그냥 막 적어도 상관은 없다만 이왕이면 해당 파일명과 소스라인 위치 함수명까지 알면 더욱 좋을것이다. 언제나 그렇듯이 긴말 말고 아래 매크로 또는 클래스들을 보자.
  • __FILE__ : 소스코드가 포함된 파일 이름을 반환
  • __LINE__ : 소스코드의 줄 번호를 반환
  • __func__ : 소스코드가 포함된 함수 이름을 반환
  • __PRETTY_FUNCTION__ : 소스코드가 포함된 클래스 이름과 함수 이름을 '이쁘게' 반환
  • typeid(T t).name() : 맹글링된 클래스 이름이나 타입 이름을 반환
다시 말하지만 위 매크로들은 g++ 기준이다. MSVC나 clang에서는 이와 같거나 다른 이름을 가지지만 같은 역할을 하는 매크로가 있을 것이다. 아래는 위 매크로를 이용한 간단한 예제이다.


  1 #include <iostream>
  2 #include <typeinfo>
  3
  4
  5 class CPrettyLog
  6 {
  7 public:
  8     void Print(void)
  9     {
 10         std::cout
 11             << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 12             << "__func__ = " << __func__ << std::endl
 13             << "__LINE__ = " << __LINE__ << std::endl
 14             << "__FILE__ = " << __FILE__ << std::endl
 15             << "typeid(this).name() = " << typeid(this).name() << std::endl
 16             << std::endl;
 17     }
 18 };
 19
 20
 21 void Print(void)
 22 {
 23     std::cout
 24         << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 25         << "__func__ = " << __func__ << std::endl
 26         << "__LINE__ = " << __LINE__ << std::endl
 27         << "__FILE__ = " << __FILE__ << std::endl
 28         << std::endl;
 29 }
 30
 31
 32 int main(int argc, char** argv)
 33 {
 34     CPrettyLog pl;
 35     pl.Print();
 36
 37     Print();
 38
 39     std::cout
 40         << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 41         << "__func__ = " << __func__ << std::endl
 42         << "__LINE__ = " << __LINE__ << std::endl
 43         << "__FILE__ = " << __FILE__ << std::endl
 44         << std::endl;
 45
 46     return 0;
 47 }
 48

위 예제를 컴파일 후 실행하면 아래와 같은 결과를 얻을 수 있다.


__PRETTY_FUNCTION__ = void CPrettyLog::Print()
__func__ = Print
__LINE__ = 13
__FILE__ = main.cpp
typeid(this).name() = P10CPrettyLog

__PRETTY_FUNCTION__ = void Print()
__func__ = Print
__LINE__ = 26
__FILE__ = main.cpp

__PRETTY_FUNCTION__ = int main(int, char**)
__func__ = main
__LINE__ = 42
__FILE__ = main.cpp