반응형

redux 사용전 컴포넌트의 상태 변화의 전달 과정

redux를 사용 후 컴포넌트 외부에서 상태관리 과정

Redux를 사용하는 이유

 

리액트 프로젝트의 경우 대부분의 작업시 부모 컴포넌트를 통해 하위 컴포넌트의 데이터를 업데이트 한다.

(데이터의 연관이 있는 컴포넌트끼리 ref를 사용하여 전달할 수 있으나 코드가 꼬이는 문제로 인해 지양한다)

 

컴포넌트의 갯수가 적을때는 문제가 되지 않지만, 점점 늘어날수록 유지보수의 어려움이 발생한다.

예를 들어 변수명의 변경을 하면 연관된 컴포넌트 파일 모두에 수정을 거쳐야된다.

그러나 리덕스를 사용하게 되면 데이터 상태를 컴포넌트 외부에서 관리하기 때문에 과정을 단순화 시킬 수 있게 된다.

 

- Reducer : 업데이트 로직을 정의하는 함수

 

< Redux 흐름 >

subscribe(상태 변화 감지요청) >> action(상태 변화) >> dispatch(상태업데이트, store에 action 전달)

>> Store(state 갱신) >> listener(상태 변화 알림) >> 컴포넌트 리렌더링

 

 

- 여러 컴포넌트를 거칠 필요없이 부모 컴포넌트에서 다이렉트로 받는거처럼 리덕스 스토어에서 원하는 상태값을 전달한다.

(단방향 데이터 흐름으로 데이터 구조의 단순화)

 

< Redux 의 3 원칙 >

1. Single source of truth
    애플리케이션 내에 Store는 반드시 1개 뿐. Store는 반드시 1개만 존재한다.

2. State is read-only
    state를 직접 변경해서는 안.된.다.
    state를 변화시키는 유일한 방법은 Action을 Reducer에 dispatch(송신, 전달)하는 방법 뿐이다.
    즉, state의 변경은Reducer만 할 수 있다. Reducer 이외의 공간에서는 state는 읽기 전용인 것이다.

 

3. Changes are made with pure functions
    Reducer는 순수 함수여야만 한다.
    Reducer 함수는 parameter로 기존의 state와 Action을 받는데, 

    Reducer 함수는 기존의 state를 직접 변경하지 않고, 새.로.운 state object(객체)를 작성해서 return해야한다.


useReducer

useReducer와 context api 로 redux의 기능을 대부분 구현할 수 있다

(그러나 프로젝트의 규모가 크다면 useReducer와 context api 조합의 한계로 비동기적인 작업시 불편하다)

 

import React, { useState, useReducer } from 'react';

const initialState = { //state 정의
	winner: '',
    turn : 'O',
    tableData : [['','',''], ['','',''], ['','','']],
};

const reducer = (state, action) => { //state 변화 정의
	
};
const [state, dispatch] = useReducer(reducer, initialState);

 

- 여러개의 state를 하나의 state로 만들어 state의 갯수를 줄여준다

 

< Redux와 useReducer 차이 >

 

1. 리덕스는 dispatch를 통해 state가 동기적으로 변경, useReducer는 비동기적으로 변경

 

table 최적화 하기

 

useEffect를 사용하여 렌더링 될 때 console.log로 두번째인자(바뀌는 값)을 넣어서 어떠한 값의 변화로 인한 리렌더링인지 확인할 수 있다.

최종 하위 컴포넌트(td) 값의 변화로 리렌더링 될때 부모 컴포넌트까지 렌더링되지 않도록 memo를 사용하여 props의 값을 기억해준다.

 

 

** React.memo : 기억해둔 props의 값이 같다면 기억해둔 값을 재사용, 다르다면 리렌더링

 

** useCallback :  함수 자체를 메모이징해서 재사용한다. 불필요한 렌더링을 줄일수 있다.


//부모 컴포넌트
import React, { useReducer, createContext } from 'react';

const Context = createContext({initial});
const value = useMemo(() => ({tableData : state.tableData, dispatch}), [state.tableData]);

<Context.Provider value={ value }>
	<Form />
    <Table />
</Context.Provider>


//하위 컴포넌트
import React, { useContext } from 'react';
import { Context } form '부모컴포넌트';

const Table = () => {
	const { tableData } = useContext(Context);
    ...
};

export default Table;

Context Api

context api를 제외하고 useReducer만 사용할 경우, dispatch 와 reducer를 통해 변경할 데이터(action)을 props로 최종 변경할 컴포넌트까지 전달해주어야한다.

- 자식 컴포넌트 뿐 아니라 해당 데이터를 사용할 컴포넌트 모두에게 다이렉트로 전달할 수 있다.

 

1. createContext(기본값) 으로 Context 객체를 생성

2. Context.Provider : 하위 컴포넌트에 접근 가능하도록 명시

    - Context.Provider 의 value 속성으로 데이터 전달

    - value 속성에 객체로 명시해주면 렌더링시 계속 새로운 객체를 만들기 때문에 최적화에 문제가 발생, 

        >> useMemo를 사용(캐싱)하여 메모이징하여 해결한다. dispatch는 항상 같으므로 변경될 값에 기재하지 않아도된다.

 

3. useContext : Context(부모 컴포넌트에서 전달될 props)를 자식컴포넌트에서 불러오도록 해준다.

 

< Context API 최적화 >

 

memo, useMemo를 적절히 사용하여 캐싱을 통해 최적화

 

 

 

출처: https://recoderr.tistory.com/50

반응형
반응형

버튼 태그에 setCount 메소드를 onClick 이벤트에 등록해주는데,

setCount의 인자로 함수를 보냅니다.

+ 버튼을 클릭하면 이전상태로부터 1을 증가시키는 함수가 setCount의 인자로 들어가게 됩니다.

 

간단한 경우에는 useState를 쓰는 것이 더 좋다고 생각하지만

현재의 상태에 액션을 받아서 새로운 상태로 갱신하는(reducer) 형태인 경우 useReducer를 사용해야 합니다.

  1. 다수의 하윗값을 포함하는 정적 로직을 만드는 경우
  2. 다음 state가 이전 state에 의존적인 경우

이러한 경우에 useState보다 useReducer가 선호됩니다.

 

useReducer는 첫 번째 인자로 reducer 함수를 받고, 두 번째 인자로는 initialState 객체를 받습니다.

import React, { useReducer } from 'react';

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      Count : {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
};

export default Counter;

 

useReducer는 state와 dispatch를 반환하기 때문에 dispatch로 액션을 보내는 함수를 onClick 이벤트에 등록해 준 것을 알 수 있습니다. 그러면 RESET 버튼은 어떻게 구현할까요?

import React, { useReducer } from 'react';

const init = (initialCount) => {
  return { count: initialCount };
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
};

const Counter = ({ initialCount }) => {
  const [state, dispatch] = useReducer(reducer, initialCount, init);

  return (
    <>
      Count : {state.count}
      <button
        onClick={() => dispatch({ type: 'reset', payload: initialCount })}
      >
        RESET
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
};

export default Counter;

init 함수를 만들어 useReducer의 세 번째 인자로 전달해 주면 됩니다.

 

잘 동작합니다!

출처: https://clearwater92.tistory.com/26

반응형
반응형

IMO: Converting video to gif is a terrible hack of convenience

요즘에는 통신망의 발달과 디바이스 성능의 개선으로 인해 어딜 가나 다양한 형식의 미디어를 즐길 수 있게 되었습니다.

 

GIF is everywhere (Image Source)

그 중 GIF는 우리가 흔히 볼 수 있는 이미지 포맷 중 하나인데, 보통 “움직이는 짤방" 혹은 줄여서 “짤방" 으로 표현하기도 합니다. 요즘에는 어딜 가나 GIF를 볼 수 있고, 아예 모바일 디바이스의 키보드에서 GIF를 바로 첨부할 수 있도록 그 인기는 날이 갈수록 치솟아가고 있죠.

하지만, 우리는 이제 움직이는 GIF (Animated GIF)를 쓰지 말아야 합니다. 움직이는 GIF는 올해로 30살이 된 오래된 규격이며, 비효율적입니다.

도대체 얼마나 비효율적이냐구요? 한번 알아봅시다.

Compression Efficiency

First frame comparison between two file formats.

여기 각각 다른 포맷에서 동일한 프레임을 캡쳐한 두 이미지가 있습니다. 여러분들은 시각적으로 “두 포맷간 품질의 차이"를 느끼실 수 있으신가요?

글쎄요, 저는 잘 모르겠네요.

놀랍게도 두 포맷의 품질 차이는 거의 느낄 수 없지만, 파일 크기는 무려 10배 이상 차이가 납니다.

File size comparison between two animated image files

이렇게 큰 파일 크기 차이가 발생하는걸 보면, GIF는 정말 “거품” 입니다. 서비스를 운영하는 쪽 뿐만 아니라, 서비스를 사용하는 유저에게도 손해입니다.

GIF는 불필요하게 대역폭을 낭비해 엄청나게 많은 전송량을 발생시킵니다.

특히 대역폭이나 사용량이 제한되어 있는 모바일 네트워크에서는 그 손해가 더 크게 느껴질 수 밖에 없습니다.

자, 아래 동영상은 대역폭을 제한한 상태에서 각 두 포맷을 재생한 것입니다. 한번 보시죠. (두 비디오 모두 MP4 Video -> GIF 순입니다)

 

Single Animated Image on 3Mbps Mobile Network

 

Multiple Animated Image on 10Mbps Wi-Fi Network

H.264 MP4의 경우 대부분 재생이 원활하고 데이터를 적게 소모한 반면, GIF는 재생이 원할하지도 않고 데이터 소모도 엄청납니다! 특히 4개의 Animated Image를 보여주는 페이지에서는 총 GIF 파일의 크기가 100MByte를 넘어서는것을 확인할 수 있습니다. 엄청 크죠?

그 이유는, 각 포맷의 특성에 있습니다.

GIF의 경우 256색을 표현할 수 있는 컬러 팔레트와, 각 프레임의 모든 픽셀에 대한 정보를 무손실 압축 데이터로 담고 있으며 , H.264를 포함한 비디오 포맷들은 기본적으로 손실 압축이고, GIF와는 비교가 안될 정도로 다양한 최적화 기술들이 들어가있습니다.

예를 들면 아래와 같은 기술들이 있습니다:

그래서 발전된 기술들이 많이 쌓인 비디오 포맷들이 GIF보다 더 나은 압축 효율을 보여줄 수 있는 것입니다.

자, 우리는 이제 GIF가 효율이 정말 좋지 않다는걸 직접 확인했습니다. 근데 그것말고도 GIF를 쓰지 말아야 할 이유가 아직 더 남아있습니다.

Decoding Performance

이번에 이야기 할 주제는, 디코딩 성능입니다.

2000년 초까지만 해도 압축된 비디오 데이터를 해석하는 것 (디코딩)은 온전히 CPU의 몫이었습니다. 2005년 즈음부터 MPEG-TS를 시작으로 GPU를 통해 하드웨어 디코딩이 가능하기 시작했죠. 물론 요즘 우리가 쓰는 스마트폰에 탑재된 프로세서도 하드웨어 디코딩을 지원합니다.

하드웨어 디코딩이 중요한 이유는, GPU를 통해 디코딩을 처리하는게 훨씬 전력 효율이 좋기 때문입니다. 아무리 CPU의 연산 성능이 좋아졌다곤 하지만, GPU는 아예 디코딩을 위한 블럭들이 칩에 박혀있기 때문에 그 효율을 따라갈 수 없습니다. 만약 GPU를 통한 하드웨어 디코딩이 가능하지 않았더라면, 스마트폰에서 Youtube 비디오를 2시간 넘게 즐기는건 불가능했었을겁니다.

제가 여기서 “하드웨어 디코딩" 을 이야기 하는 이유는, 바로 GIF 이미지를 디코딩하는것은 GPU에서 처리하지 못하기 때문입니다. H.264/AVC 비디오의 경우 대부분의 디바이스에서 하드웨어 디코딩이 가능합니다. 라즈베리파이에서도 되거든요.

이번에도 비디오를 가져왔습니다. 먼저, 데스크탑 브라우저에서의 디코딩 성능 차이를 보시죠: (MP4 Video -> GIF 순입니다)

 

Instrumented macOS WebKit Processes

처음이 H.264 MP4 비디오 재생, 두번째가 GIF 이미지 재생입니다. Instrument App의 CPU Activity Graph와 Live Processes Table를 보시면 확실히 GIF 이미지를 재생할 때 더 많은 CPU 자원을 소모하는 것을 확인할 수 있습니다.

여기서 흥미로운 점이 있다면, GIF 이미지의 경우 Paint가 계속 일어나는것으로 찍히는 반면, H.264 MP4 Video의 경우 Paint가 찍히지 않습니다.

Chrome에서도 비슷하게 나타납니다. 아마도 GIF의 경우 이미지 디코딩을 CPU에서 직접 처리하고, Video의 경우 GPU에서 직접 처리되어서 이에 따른 차이가 발생하는 것으로 추측됩니다.
(혹시라도 이 부분에 대해 자세히 알고 계신 분이 계시다면 댓글 남겨주시면 감사하겠습니다 :))

(MP4 Video -> GIF 순입니다)

Chrome Raster Thread metrics

각 포맷을 로드할때의 CPU Usage 차이가 보이시나요? 게다가 비디오가 훨씬 더 균일한 FPS를 보여줍니다.

이번에는 iPhone X의 iOS Safari에서 테스트 한 영상입니다. (MP4 Video -> GIF 순입니다)

 

Instrumented macOS WebKit Processes

여기서는 몇가지 지표를 더 확인할 수 있는데, H.264 MP4 비디오를 재생할 때 Graphics Activity가 더 높은걸 확인할 수 있습니다. 그리고 역시 GIF 이미지를 재생할때 더 높은 CPU 사용량을 보입니다.

더 높은 CPU 사용량을 보인다는 것은, 더 많은 배터리를 소모한다는 이야기이기도 합니다. 또한, 저사양 디바이스에서 원할한 재생이 어려울 수도 있겠죠.

그 이외에도 GIF를 피해야 하는 이유는 많습니다.
컬러 팔레트에서 최대 사용가능한 색상의 수가 256색으로 제한되어 있어 색 표현에 제약이 있으며,
일부 브라우저에서 재생 가능한 FPS (Frame rate speed)가 제한 되어 있기도 합니다.

GIF is not designed for Animation

The Graphics Interchange Format is not intended as a platform for animation, even though it can be done in a limited way. — GIF89a Specification

마지막으로, GIF89a 규격을 보면 “GIF는 애니메이션을 위해 디자인 되지 않음”이 명시되어 있습니다.

Platform Compatibility

이정도면 이제 GIF보다 비디오를 쓰는게 더 낫다는건 충분히 이해하실겁니다. 그런데 “호환성"이 걱정되신다고요? 그러실 줄 알고 미리 준비했습니다.

H.264 비디오는 대부분의 플랫폼에서 호환이 되므로, 걱정할 필요가 없습니다. 그냥 쓰세요!

제가 개인적으로 권장하고, Vingle에서 사용하고 있는 Animated Image (GIF)용 비디오 규격은 다음과 같습니다:

  • MP4 Container
  • H.264/AVC, Baseline Profile, Level 3.1
    Recommended encoder: x264
  • Maximum resolution: 540p (960 * 540)
  • CRF rate control mode
    Target CRF: 22 ~ 24 / Maximum Bitrate: 1600K / Buffer size: 2400K)
  • YUV420 Color space/Chroma Subsampling
  • No Audio

위 규격은 Vingle이 지원하는 모든플랫폼 (IE 10+, Android 4.1.4+, iOS 9+) 에서 재생이 가능합니다.

How to play video?

비디오를 클라이언트에서 재생하기 위해서는, 몇가지 변경사항이 필요합니다. 플랫폼별로 필요한 변경사항들을 나열해보면 다음과 같습니다:

Web

 

저게 전부입니다! 웹은 정말 간단하죠?

참고로 iOS Safari의 경우, iOS 10 부터 새로운 비디오 정책이 적용되어 autoplay에 제약이 생겼습니다만, 예외적으로 muted attribute를 준 경우 autoplay가 허용됩니다.
Source: https://webkit.org/blog/6784/new-video-policies-for-ios/

또한, playsinline attribute를 주지 않으면 비디오가 “전체 화면" 으로 재생되니 주의하세요!

Android

Android의 경우 보통 이미지 뷰어로 Facebook에서 만든 Presco를 사용하는데, 비디오를 재생하려면 Presco 대신 별도의 비디오 플레이어 라이브러리를 사용해야 합니다. 저는 Vingle Android App에서 사용중인 ExoPlayer를 추천합니다.

iOS

iOS의 경우 iOS팀의 현수님이 쓰신 글로 대신합니다 :)

How to convert GIF to Video?

비디오를 재생하려면 GIF로 비디오로 변환하는 작업 또한 필요합니다.

여기서는, 크게 두가지 경우로 나누어 설명하겠습니다.

일반 사용자 혹은 플랫폼 내에서 비디오 변환이 꼭 필요하지 않은 경우

더이상은 naver…

위와 같이 클리앙과 같은 온라인 커뮤니티에 업로드 할 목적이거나, 일부 컨텐츠에 대해서만 GIF 대신 비디오로 보여줄 목적이라면, 별도 프로그램 없이도 변환할 방법이 있습니다. Giphy 같은 GIF 호스팅 서비스를 사용하는 방법입니다.

Giphy

Giphy에 변환하고자 하는 GIF를 업로드하면, Giphy가 GIF를 알아서 비디오로 변환해줍니다! 우리는 그 비디오를 그냥 다시 다운받기만 하면 되는거죠.

플랫폼 내에서 비디오 변환이 필요한 경우

플랫폼 내에 GIF 업로드 기능이 이미 있는 상태에서 비디오 변환을 추가하려면, 별도로 서버에서 비디오 변환을 처리해야합니다.

비디오 변환을 서버에서 직접 처리하는 경우, 저는 개인적으로 FFmpeg를 추천합니다. 위에서 언급한 권장 규격으로 출력하는 경우 ffmpeg 명령은 다음과 같습니다:

$ ffmpeg -i GIF_IMAGE_INPUT.gif -c:v libx264 -profile:v baseline -level 3.1 -crf 24 -maxrate 1600K -bufsize 2400K -pix_fmt yuv420p -an -movflags +faststart EFFICIENT_VIDEO_OUTPUT.mp4

GIF를 직접 비디오로 변환하는 경우, 주의해야 할 점이 몇가지 있습니다:

출력 비디오의 해상도가 반드시 2의 배수여야 합니다.
4:2:0 Chroma subsampling의 경우 해상도가 2의 배수여야 합니다.
만약 GIF 이미지의 가로 또는 세로 크기가 홀수면, 아래와 같은 에러를 출력하고 비디오 변환에 실패합니다.

[libx264 @ 0xa3b85a0] height not divisible by 2 (520x369)

이 경우, scale이나 crop 같은 video filter를 사용해 출력 해상도를 제어하시면 됩니다.

아래는 scale filter를 사용하는 예 입니다:

-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"

MP4 container의 moov atom을 앞쪽으로 옮겨야 합니다
(다소 주제가 어려울 수 있겠지만, 이 내용을 최대한 쉽게 설명드리겠습니다.)
MP4 컨테이너의 경우 여러개의 청크로 구성되어 있습니다. 그리고 청크를 atom 이라고 부릅니다.

atom 은 비디오 데이터, 오디오 데이터, 챕터 정보와 같은 메타데이터 등 여러 타입이 있는데, 그 중 moov atom이 있습니다. moov atom은 일종의 목차 같은 역할을 합니다. “재생을 시작할 비디오/오디오 데이터의 위치" 를 담고있는거죠.

Example atom tree

여러분들이 MP4 비디오를 재생하면, 플레이어들은 비디오를 재생하기 위해 내부적으로 이 moov atom을 찾게됩니다. 하지만 정말 불행하게도, atom들의 순서는 정해져있지 않습니다. 즉, 만약 moov atom이 맨 끝의 atom 이라면, 파일을 다 읽어들이기 전까지는 재생을 시작하지 못합니다. 비디오 데이터가 파일의 어디에 있는지 moovatom을 읽기 전까지는 특정할 수 없으니까요.

MP4 비디오가 로컬에 있는 경우에는 디스크에서 탐색을 수행하기 때문에 그래도 괜찮지만, 문제는 네트워크를 통해 MP4 비디오를 스트리밍 하는 경우입니다. moov atom을 찾기 전까지는 비디오 재생이 아예 불가능하기 때문입니다.

요즘 대부분의 모던 브라우저들은 Range Request Header를 사용해 MP4 파일을 탐색 (seek) 하도록 구현이 되어 있지만, 여러 요청을 주고 받기 때문에 네트워크 지연으로 인해 재생을 시작하기까지 시간이 다소 소요될 수 있습니다.

Multiple MP4 Requests explaination

최악의 상황은, 만약 웹서버가 Range 헤더를 읽어 Partial Content Response를 지원하지 않는 경우입니다. 이 경우 비디오 파일을 모두 다운로드 받기 전까지는 재생을 시작할 수 없습니다.

아래 비디오를 통해 Partial Content Response 지원 유무에 따른 그 차이를 확인해보세요:

 

Comparison of Partial Content Response support

moov atom을 맨 앞으로 옮긴 경우, 이러한 seeking이 필요가 없기 때문에 단일 요청을 보내고 응답을 받자마자 즉시 재생을 시작할 수 있습니다. 또한, moov atom을 맨 앞으로 옮겨주었기 때문에 웹서버가 Partial Content response를 지원하지 않아도 걱정할 필요가 없습니다.

위에서 소개한 ffmpeg 명령에서도 moov atom을 맨 앞으로 옮기기 위해 -movflags +faststart 옵션을 사용합니다.

다음 이미지는 moov atom을 맨 앞으로 옮김으로서 단일 요청만으로 재생을 시작하는 것을 보여줍니다:

moov optimization can skip additional seeking steps

그 이외에도, 다양한 오픈소스 프로젝트들이 있으니 참고해보세요:

Vingle 에서 Video Pipeline을 구축한 관련 경험에 대해 궁금하시다면, 아래 슬라이드를 참고해주세요:

 

AWSKRUG Architecture Meetup — Serverless Media Workflow

또한, Google의 Addy Osmani가 작성한 Image Optimization Guide도 한번 읽어보시는걸 강력하게 추천합니다:

만약 컨버팅 프로세스를 직접 만들고 싶지 않다면, SaaS 제품을 사용하시는것도 괜찮은 방법입니다. 개인적으로 추천하는 플랫폼은 다음과 같습니다:

Why we don’t use WebP/WebM

아마 이런 생각을 하시는 분도 계실겁니다.

WebP/WebM을 대안으로 사용할 수 있지 않나요?

네. WebP나 WebM을 사용할 수도 있습니다. 하지만 저희는 다음과 같은 이유로 WebP와 WebM을 선택하지 않았습니다:

  • WebP/WebM에 대한 플랫폼 지원이 아직까지는 많이 모자랍니다.
    비 구글 제품에 대해 지원이 아직까지는 너무나도 빈약합니다. Web, Android, iOS에서 모두 사용할 수 있는 범용성이 필요했습니다.
  • Adoption Rate이 아직 낮다고 판단했습니다.
    H.264/AVC는 사실상의 산업 표준 (industry standard) 인 반면에, WebP/WebM은 사용하기에 적절할 정도로 널리 보급되지 않았습니다.
  • 하드웨어 가속 디코딩에 대한 지원이 부족합니다.
  • 낮은 Bitrate에서는 꽤 괜찮은 결과를 보여주지만, H.264/AVC 만큼 좋지는 않습니다.

또한, HEIC/HEIF나 HEVC와 같은 차세대 포맷을 생각한다면, 더욱 WebP나 WebM을 선택할 이유가 없었습니다. 그 이유는 다음과 같습니다:

HEIC/HEIF는 이미지를 위한 새로운 규격으로, MP4와 비슷한 일종의 컨테이너입니다. 내부적으로는 HEIC나 AVC로 이미지를 인코딩해서 쓸 수 있으며, 다양한 사용 사례들 (e.g. 스틸 이미지, 이미지 컬렉션, 이미지 시퀀스 — iOS의 Live Photo- 등)을 염두하고 디자인 된 규격이기 때문에 상당이 유연하고,

HEVC는 H.264/AVC 를 이어갈 코덱 (그래서 H.265/HEVC 라고 부르기도 합니다) 으로 H.264/AVC를 ‘뛰어넘는' 압축 효율을 가지고 있습니다.

게다가 두 포맷 모두 메이저 플랫폼 뿐만 아니라(iOS 11 / macOS High Sierra / Windows 10), GPU Vendor들(Intel, NVIDIA, AMD, Qualcomm) 이 지원을 시작했습니다.

HEIC/HEIF의 경우 아래 링크를 통해 다양한 사용 사례들을 확인하실 수 있으니 한번 참고해보세요!

 

Wrapping up

이야기 했던 내용들을 간단히 요약하면 다음과 같습니다.

  1. GIF는 거품 덩어리이며, 모두에게 손해다
    H.264 Video로 변환하는 경우 10배 이상 크기를 줄일 수 있으며, 기기의 배터리에도 도움이 된다.
  2. GIF를 Video로 변환해서 제공하자
    giphy, ffmpeg, cloudinary, imgix등 다양한 선택지가 존재하니 상황에 맞게 선택해라
 

GIF 대신 Video를 쓰기로 결정하셨다면 축하의 박수를! :)

빙글에서도 유저들이 올리는 말도안되는 사이즈의 GIF 때문에 아주 오랫동안고통을 받았는데요. (100MByte를 넘어가는 GIF 이미지들, 눈물 없이는 볼 수 없는 CDN 요금, 빙글 앱만 켜면 휴대폰이 손난로가 된다는 피드백… 😭)

다음 글에서는 저희가 어떻게 GIF -> Video Conversion process를 완벽히 serverless 구조로 만들었는지 이야기하고, Lambda와 S3를 사용해 직접 변환 프로세스를 구현하는 방법을 다루어보겠습니다.

다음에 또 만나요!

 

출처 : https://medium.com/vingle-tech-blog/stop-using-gif-as-animation-3c6d223fd35a

반응형
반응형

<< 문제 정의 >>

부동산 매매데이터를 KB부동산 사이트 를 통해서 활용하는 경우가 많습니다.

그러나, 일일이 아파트를 검색하고, 매매거래가를 다운로드 받아서 활용해야 하는 불편함이 있습니다. '크롤링을 통해서 프로그램화 하면 되지 않아?' 라고 생각할 수 있지만, KB부동산의 경우 크롤링에 필요한 html 코드를 볼 수 없게 막아두었답니다.ㅠㅠ

 

다행히도 공공데이터(www.data.go.kr)에서는 OpenAPI를 제공해주고 있습니다. 이를 이용하여 부동산 실거래 데이터를 수집해보시죠!

<< 사전 준비 사항 >>

  • 공공데이터 가입 (www.data.go.kr)
  • 사용할 API: 공공데이터 포털(국토교통부 - 아파트매매 실거래데이터)
  • 언어 & 환경(IDE) : Python3.6 & Jupter notebook

<< 순서 >>

Step 1 아파트매매 실거래 데이터 OpenAPI 신청하기
※  OpenAPI 사용시 제공기관 연계서버와 일정시간을 주기로 동기화되며 동기화에 시간이 소요될 수 있다. 
Step 2 OpenAPI 테스트하기
Step 3 OpenAPI 파라메터 이해하기
Step 4 법정동 코드 다운로드
Step 5 구현
1) 아파트 실거래 OpenAPI 호출
2) 하드코딩한 법정동 코드값 변경하기
3) 하드코딩한 날짜값 변경하기
4) 특정 '구'를 요청 날짜만큼 실거래데이터 저장하기

 

Step1) 아파트매매 실거래자료 OpenAPI 신청하기

신청 순서 : www.data.go.kr > [부동산거래정보] 클릭 > [공공데이터보기] 클릭 > [오픈API] 클릭 > [국토교통부 실거래가 정보] 클릭 > [아파트매매 실거래자료 - 활용신청] 클릭 > [신청] 클릭 

[부동산거래정보] 클릭 > [공공데이터보기] 클릭 

 

[오픈API] 클릭 > [국토교통부 실거래가 정보] 클릭

 

[아파트매매 실거래자료 - 활용신청] 클릭 > [신청] 클릭 

신청완료!

Step2) OpenAPI 테스트하기

2-1) 일반 인증키 란이 비어 있다면? [일반 인증키 받기]

 

2-2) 테스트

※  OpenAPI 사용시 제공기관 연계서버와 일정시간을 주기로 동기화되며 동기화에 시간이 소요될 수 있습니다.

(좌) 미리보기 / (우) 결과

참고) "아파트매매 실거래 상세 자료" API의 경우는 빨간색 박스가 추가됨. 

Step3) OpenAPI 파라메터 이해하기

- "아파트매매 신고정보 조회 기술문서.hwp" 파일 다운로드 > 파라메터 확인

 

법정동 코드가 필요함을 알 수 있다.

Step4) 법정동 코드 다운로드

www.code.go.kr > [법정동] 선택 > [법정동 코드 전체자료] 클릭 > 법정동코드 다운로드 > 파일 포맷을 utf-8 로 변경

www.code.go.kr 

한글이 깨지는 경우가 있어 메모장으로 파일을 열어 "UTF-8" 포맷으로 변경하여 재저장 합니다.

utf-8로 인코딩 변경하여 재저장

Step5) 구현

Step 5-1) 아파트 실거래 OpenAPI 호출

호출시 전달해야 할 파라메터는 다음과 같습니다.

정상동작 확인을 위해서 "지역코드"와 "계약월"은 하드코딩해서 진행하겠습니다.

step5-2부터는 지역코드/계약월을 필요에 따라 설정하도록 변경하겠습니다.

# 1-1. 데이터 가져오기
import requests
import datetime

url ="http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?"
service_key = "<인증키를 입력하세요> "
base_date = "202001" 
gu_code = '11215' ## 법정동 코드 5자리라면, 구 단위로 데이터를 확보하는 것. 11215 = 광진구

payload = "LAWD_CD=" + gu_code + "&" + \
          "DEAL_YMD=" + base_date + "&" + \
          "serviceKey=" + service_key + "&" 
          
res = requests.get(url + payload)
print(res)

200이 나오면 성공입니다.

전달된 데이터가 xml형태이므로, 라이브러리를 사용해서 parsing합니다.

결과 데이터를 dataframe 형태로 관리하도록 하겠습니다.

import xml.etree.ElementTree as ET

def get_items(response):
    root = ET.fromstring(response.content)
    item_list = []
    for child in root.find('body').find('items'):
        elements = child.findall('*')
        data = {}
        for element in elements:
            tag = element.tag.strip()
            text = element.text.strip()
            # print tag, text
            data[tag] = text
        item_list.append(data)  
    return item_list
    
items_list = get_items(res)
items = pd.DataFrame(items_list) 
items.head()

[결과 확인] 

 

이제부터는 하드코딩한 내용을 제거하고, 파일로 저장하는 것 까지를 구현해 보시죠!!

Step5-2) 하드코딩한 법정동 코드값 변경하기

1. 다운로드 받아둔 "법정동코드 전체자료.txt" 파일 읽기

법정동 코드의 경우 tab으로 분리가 되어 있음을 알 수 있습니다.

code_file = "법정동코드 전체자료/법정동코드 전체자료.txt"
code = pd.read_csv(code_file, sep='\t')

 

2. 데이터 클린징 

 

2-1. 컬럼명이 한글로 되어 있으면 코딩하기 번거로우므로 한글로 변경

code.columns = ['code', 'name', 'is_exist']

 

2-2. 폐지된 법정동 코드는 사용할 필요 없으므로 삭제

code = code [code['is_exist'] == '존재']

 

2-3. 법정동 코드가 int로 되어 있는데, string으로 변경

print(code['code'][0])
print(type(code['code'][0])) ## int64타입

## string으로 변경
code['code'] = code['code'].apply(str) 

 

Step5-3) 하드코딩한 날짜값 변경하기

2015년 01월 ~ 2019년 12월 까지 데이터를 받아보도록 하겠습니다.

year = [str("%02d" %(y)) for y in range(2015, 2020)]
month = [str("%02d" %(m)) for m in range(1, 13)]
base_date_list = ["%s%s" %(y, m) for y in year for m in month ]

 

Step5-4) 특정 '구'를 요청 날짜만큼 실거래데이터 저장하기

1. "광진구"의 법정동 코드 5자리 추출

gu = "광진구"
gu_code = code[ (code['name'].str.contains(gu) )]
gu_code = gu_code['code'].reset_index(drop=True)
gu_code = str(gu_code[0])[0:5]
print(gu_code)

 

2. step5-1)에서 수행했던 소스코드를 함수형태로 만들어 사용

import requests
import datetime

def get_data(gu_code, base_date):
    url ="http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?"
    service_key = "<인증키를 입력하세요>"    
    payload = "LAWD_CD=" + gu_code + "&" + \
              "DEAL_YMD=" + base_date + "&" + \
              "serviceKey=" + service_key + "&"

    res = requests.get(url + payload)
    
    return res

 

3. step5-3) 에서 준비해둔 날짜만큼 반복하며 실거래 데이터 얻기

items_list = []
for base_date in base_date_list:
    res = get_data(gu_code, base_date)
    items_list += get_items(res)
    
len(items_list)

총, 10,073건의 데이터를 얻었네요^^

 

4. dataframe 형태로 변경하여 csv파일로 저장

파일명은 [구이름]_[시작년도]_[끝년도].csv로 저장하겠습니다.

items = pd.DataFrame(items_list) 
items.head()
items.to_csv(os.path.join("%s_%s~%s.csv" %(gu, year[0], year[-1])), index=False,encoding="euc-kr") 

[결과 확인]

저장된 csv 파일명

 

이것으로 실거래가 데이터를 가지고 오는것은 완성! 하였습니다.

다른 지역의 데이터를 가져와 저장하고 싶다면, gu = "광진구" 만 변경하면 됩니다~!!

< 참고 >

- 국토부에서 제공하는 (OpenAPI) 데이터는 전용면적을 기준으로 데이터를 제공

- 네이버 부동산, KB, 호갱노노 모두 공급면적 기준으로 데이터를 제공

 

출처: https://ai-creator.tistory.com/24

반응형

+ Recent posts