우선 Airsonic에 대해서 설명하자면

Airsonic은 Subsonic에서 파생된 프로그램으로 Subsonic은 간단히 말해 미디어 서버 프로그램이다.

Subsonic은 서버내의 음악파일을 외부 클라이언트로 공유하여 들을 수 있도록 다양한 기능을 제공하는 서버 프로그램으로 버전 6 이전까지는 무료였다가 이후에는 유료로 변경되었다고 한다.

Airsonic은 Subsonic의 유료화에 반발하여 분리된 프로젝트로 Subsonic의 기능과 API 등을 가지고도 무료로 사용할 수 있는 서버 프로그램이다.

때문에 Subsonic과 연동되는 클라이언트 프로그램은 Airsonic에도 연결할 수 있다.

https://github.com/airsonic/airsonic

 

airsonic/airsonic

:satellite: :cloud: :notes:Airsonic, a Free and Open Source community driven media server (fork of Subsonic and Libresonic) - airsonic/airsonic

github.com

Subsonic은 클라이언트에서 사용할 수 있는 다양한 API를 제공하는데

http://www.subsonic.org/pages/api.jsp

 

Subsonic

maxBitRate No (Since 1.13.0) The maximum bit rate (in Kbps) for the user. Audio streams of higher bit rates are automatically downsampled to this bit rate. Legal values: 0 (no limit), 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320.

www.subsonic.org

Airsonic 또한 Subsonic의 API를 대부분 사용할 수 있다.

그 중에서도 이번에 수정해볼 API는 이것이다.

가사를 얻어오는 API인데

이전 포스트에서도 설명했지만 저 API를 실행시키면

Airsonic은 파라미터로 보내진 artist, title 정보를 이용해서

String url = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect?artist=" + artist + "&song=" + song; String xml = executeGetRequest(url); lyrics = parseSearchResult(xml);

이런 위의 URL을 통해 가사를 가져온다.

즉 저 사이트에 가사 정보가 없으면 가사를 가져오지 못한다는 뜻이다.

하지만 Airsonic은 오픈소스 프로젝트이므로 내가 코드를 수정해서 사용하는것이 가능하다.

이번에는 저 코드를 수정해서 Airsonic이 getLyric API를 요청받았을 때

저 URL이 아닌 로컬 환경에서 가사를 가져오도록 수정할 예정이다.

우선 Airsonic을 컴파일할 환경을 구성해야 한다.

환경을 구성하는 방법은 아래 사이트에서 설명해주고 있다.

https://airsonic.github.io/docs/install/source/

 

Airsonic

Airsonic, a Free and Open Source community driven media server, providing ubiquitous access to your music.

airsonic.github.io

계획은 단순한데

Airsonic에서 제공하는 getLyrics API를 실행하면

Airsonic이 자신의 musicDirectory에서 title 파라미터값을 이용해 파일을 검색해 가사 파일을 찾아내는 것이다.

만약 가사파일을 찾으면 가사파일을 읽어서 API를 요청한 클라이언트한테 보내주면 된다.

Airsonic 서버가 로컬 가사파일을 읽어서 클라이언트에게 가사를 전송하도록 수정할 것이다.

나는 윈도우 환경에서 MVN, JDK 버전등을 맞추고 컴파일을 시도해 보았더니

윈도우 환경에서는 컴파일 할 수 없다는 메세지가 출력되어서 Virtualbox 가상머신에 MX 리눅스를 설치했다.

이후 설명 페이지에 나와있는대로 환경을 구성하고 소스코드를 수정하지 않은 상태에서 컴파일하여 .war 파일을 생성하여 테스트 해보았다.

tomcat 9버전대에 올려보니 정상동작하는것을 확인할 수 있었다.

참고로 컴파일에는 한 20분정도 시간이 걸렸는데 실제 컴파일하는데는 5분이면 되는것 같은데

Airsonic 컴파일을 할 때 실행되는 기능 테스트 동작하는데에만 한 15분이 넘게 걸리는 것 같다.

어쨋든 컴파일이 된것을 확인하고 나서는 코드를 수정해 주었다.

참고로 코드 규칙이 정말 까다롭게 되어있는데

탭문자쓰면 탭문자 쓰지 말라고 에러나고 띄어쓰기 한개때문에 에러나고 if , for 문 앞뒤로 한칸씩 띄어쓰라고 에러나고 대괄호 내려썼다고 에러나고 == 쓰지말고 equals 쓰라고 에러나고

얼마나 깐깐하게 만들었는지 알 수 있었다.

그렇게 코드를 수정하고 리눅스 환경에서 Airsonic에 getLyrics API를 날려보니

 

SERVER='http://127.0.0.1:8080/airsonic'
CLIENT='CLI'
USERNAME='admin'
PASSWORD='admin'
SALT="$(openssl rand -hex 20)"
TOKEN="$(echo -n "${PASSWORD}${SALT}" | md5sum | awk '{ print $1 }')"
echo ${SALT}
echo ${TOKEN}
curl "${SERVER}/rest/getLyrics.view?u=${USERNAME}&t=${TOKEN}&s=${SALT}&v=1.15.0&c=${CLIENT}&artist=&title=Anemone"

 

가사가 정상적으로 출력되었다.

안드로이드 클라이언트에서도 가사가 정상적으로 출력되는것을 확인했다.

다만 lrc 파일 특유의 시간표시가 남아있는데

저 시간정보를 이용하려면 서버 프로그램이 아닌 클라이언트 프로그램을 수정해야만 한다.

Subsonic API는 가사정보가 LRC 형식이 아닌 그냥 단순 텍스트로 전송되기때문에

저 시간정보를 이용할 수 있는 클라이언트가 없다.

만약 저걸 이용하려면 클라이언트 기능까지 직접 만들어야된다는건데...

일단은 저 시간정보를 빼고 깔끔하게 보이는 텍스트 가사로 출력하는것에 만족해야겠다.

시간정보를 제거한 가사 출력

이번에 추가한 코드는 아래와 같다.

 

 @RequestMapping("/getLyrics")
    public void getLyrics(HttpServletRequest request, HttpServletResponse response) {
        request = wrapRequest(request);
        String artist = request.getParameter("artist");
        String title = request.getParameter("title");

        Lyrics result = new Lyrics();
        result.setArtist(artist);
        result.setTitle(title);
        result.setContent(getContentMain(title));

        Response res = createResponse();
        res.setLyrics(result);
        jaxbWriter.writeResponse(request, response, res);
    }

    public String getContentMain(String title) {
        for (org.airsonic.player.domain.MusicFolder folder: settingsService.getAllMusicFolders()) {
            File[] fileList = folder.getPath().listFiles();
            for (int i = 0; i < fileList.length; i++) {
                File file = fileList[i];
                if (file.isFile() && file.getName().contains(title) && file.getName().toLowerCase().contains(".lrc")) {
                    String str = "";
                    try {
                        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF8"));
                        br.readLine();
                        br.readLine();
                        br.readLine();
                        String line = br.readLine();
                        while (str != null) {
                            str += line.substring(10) + "\n";
                            line = br.readLine();
                        }
                        br.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                    return str;
                } else if (file.isDirectory()) {
                    String r = getContentSub(file, title);
                    if (!r.isEmpty()) {
                        return r;
                    }
                }
            }
        }
        return "";
    }

    public String getContentSub(File subdir, String title) {
        File[] fileList = subdir.listFiles();
        for (int i = 0; i < fileList.length; i++) {
            File file = fileList[i];
            if (file.isFile() && file.getName().contains(title) && file.getName().toLowerCase().contains(".lrc")) {
                String str = "";
                try {
                    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF8"));
                    br.readLine();
                    br.readLine();
                    br.readLine();
                    String line = br.readLine();
                    while (line != null) {
                        str += line.substring(10) + "\n";
                        line = br.readLine();
                    }
                    br.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                return str;
            } else if (file.isDirectory()) {
                String r = getContentSub(file, title);
                if (!r.isEmpty()) {
                    return r;
                }
            }
        }
        return "";
    }

 

getLyrics 함수를 수정하고 getContentMain, getContentSub 함수를 추가해주었다.

새벽 늦게까지 했지만 기능을 완성하니 뿌듯하고 기분이 좋다.

컴파일 시간이 짧았으면 좀 더 빨리 끝났을것 같은데 테스트를 스킵하는 명령이 있는지 찾아볼걸 그랬다 싶다.

아무튼 이로써 무료 음악 스트리밍 서버를 이용하면서 노래의 가사까지 볼 수 있게 되었다.

집에 음악 스트리밍 서버를 구축하기 위해서 내가 기존에 조사했던 자료이다.

각 프로그램의 전체 기능을 비교한 것이 아닌 음악을 재생 기능만을 비교한 것이다.

대부분이 음악 재생기능이 메인이 아닌 영화 등의 동영상 매체를 메인으로 하다보니 음악재생에 있어서는 부족한 부분이 한두가지씩 있어서 어떤 프로그램을 사용해야 좋을지 한눈에 보기 위해서 정리했던 자료이다.

나는 외부에서 음악을 듣기 위해서 Jellyfin 서버를 설치했는데 막상 사용해보니 조금 문제점이 있었다.

일단 첫번째로 Jellyfin은 가사 출력 기능이 없다.

나는 가사를 출력하기 위해서 음악파일에 대응되는 lrc 파일들을 많이 만들어놨는데 Jellyfin은 온라인 가사 출력기능도, 로컬 가사 출력기능도 없었다.

두번째로는 기본적으로 동영상이 메인이기 때문인지 다음에 재생될 음악 등을 미리 다운로드 하는 기능이 없다.

나같은 경우는 전철 1호선을 타고 갈때 온수 ~ 구로 사이에서 이상하게 LTE 속도가 느려지는 점이 있어서 음악을 미리 다운로드해서 캐시에 넣어놓는 기능이 없으면 다음 파일이 바로 재생되지 않는 문제점이 있었다.

결국 가사 출력이 가능하면서 미리 다운로드하는 기능이 있는 서버 + 클라인트 프로그램을 찾고있었는데

찾기가 생각보다 쉽지 않았다.

그래서 계속 찾아보다보니 Subsonic에 getLrics 라는 기능이 있는것을 알 수 있었다.

딱 봐도 가사를 얻어오는 기능인것 같긴 한데 어떤 동작을 해서 가사를 얻어오는지 알 수가 없었다.

Subsonic과 연결되는 안드로이드 클라이언트를 가능한 한 확인해보았더니

Ultrasonic과 Subsonic에 가사를 받아오는 기능이 있는것을 확인했고 다른 클라이언트 어플리케이션에서는 기능을 찾지 못했다.

Ultrasonic; Subsonic용 안드로이드 클라이언트 어플리케이션

subsonic; Subsonic용 안드로이드 클라이언트 어플리케이션,. 공식 클라이언트인것 같다.

그런데 가사를 받아오는 기능을 사용해도 전혀 가사를 받아올 수 없었다.

그 이유를 알기 위해 Airsonic 코드가 올라와있는 Github에서 관련 코드를 찾아보았다.

https://github.com/airsonic/airsonic

 

airsonic/airsonic

:satellite: :cloud: :notes:Airsonic, a Free and Open Source community driven media server (fork of Subsonic and Libresonic) - airsonic/airsonic

github.com

그렇게 찾은 코드가 글쎄

 

public LyricsInfo getLyrics(String artist, String song) {
        LyricsInfo lyrics = new LyricsInfo();
        try {

            artist = StringUtil.urlEncode(artist);
            song = StringUtil.urlEncode(song);

            String url = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect?artist=" + artist + "&song=" + song;
            String xml = executeGetRequest(url);
            lyrics = parseSearchResult(xml);

        } catch (HttpResponseException x) {
            LOG.warn("Failed to get lyrics for song '{}'. Request failed: {}", song, x.toString());
            if (x.getStatusCode() == 503) {
                lyrics.setTryLater(true);
            }
        } catch (SocketException | ConnectTimeoutException x) {
            LOG.warn("Failed to get lyrics for song '{}': {}", song, x.toString());
            lyrics.setTryLater(true);
        } catch (Exception x) {
            LOG.warn("Failed to get lyrics for song '" + song + "'.", x);
        }
        return lyrics;
    }

 

 

이렇게 되어있었다.

Subsonic 종류의 미디어 서버 프로그램은 코드상에 api.chartlyrics.com 라는 URL을 하드코딩으로 박아넣고

저 사이트에서 가사를 받아오고 있었던 것이다.

그러니 당연히 가사를 찾을수가 없었던 것이다.

하지만 코드가 저렇게 간단하게 되어있으니 수정도 쉬울것이라는 생각이 들었다.

이 다음에는 Airsonic의 코드를 수정해서

로컬환경의 lrc 파일을 읽어 클라이언트로 전송하도록 동작을 바꿔보도록 하겠습니다.

2020. 8. 9. 16:50

 

이번에 핸드폰을 새로 구매했는데 돈을 좀 아껴보겠다고 64GB짜리 작은 용량의 스마트폰을 구매하고 마이크로 SD 카드 128GB 짜리를 하나 주문했다.

그런데 핸드폰 배송이 완료되고 확인해보니 이럴수가 내가 구입한 스마트폰은 마이크로 SD카드를 사용할 수 없는 스마트폰이었던 것이엇따.

그동안 스마트폰에 넣고다니던 음악파일들을 들고다닐 수 없게된 나는 이 참담한 심정을 조금이나마 위로받고싶어 인터넷 커뮤니티에 토로하였으나 돌아온 답변은

 

 

 

요즘 음악 다 스트리밍으로 듣지 어던 찐따가 음악파일 들고다니냐ㅋㅋㅋ 하는 이야기 뿐이었다.

여러가지 고민을 했지만 결국 집에서 사용중인 홈서버용 컴퓨터에 미디어 서버를 설치해서 사용하기로 결정했다.

그 후로 FTP 서버를 깔아서 안드로이드용 FTP 플레이어도 설치해보고 여러가지 서버 프로그램 등도 설치해서 비교해보았는데 그중에 가장 괜찮은게 Jellyfin 미디어 서버였다.

결국 Jellyfin을 설치하였는데 내가 본 Jellyfin의 장점은 우선

1. 설치가 정말 쉬었다.

airsonic을 설치할때는 도커를 깔아서 버추얼박스용 가상머신을 만들어서 네트워크세팅해주고 컨테이너를 다운받고 런한다음에 포트열어주고 외부폴더 연결해주는 등 복잡한 작업을 해주었는데, Jellyfin은 전혀 그런게 없다.

그냥 다운받은다음에 설치해주면 끝이었다.

2. 동영상 파일도 올리고 재생이 가능

subsonic, airsonic처럼 음악 서버가 아니라 미디어서버이다 보니 영화나 애니메이션, 드라마 등의 파일도 넣고 스트리밍으로 재생을 할 수가 있었다.

3. 인터페이스와 디자인이 깔끔하고 각종 설정이 가능

웹페이지와 안드로이드 앱등을 살펴보았을 때, 그동안 설치해본 airsonic이나 FTP 서버와 연결하기 위해 설치한 foobar2000 안드로이드 앱 등과 비교하면 정말 아름답게 보였다.

또한 유저를 추가하여 특정 라이브러리에만 접근 권한을 주거나 접속로그를 보거나 하는 등의 상용 프로그램 못지 않은 기능을 가지고 있다.

 

관리 페이지도 깔끔하다

5. 개발이 활발하다.

Jellyfin 웹페이와 git 페이지등에 찾아가보니 최근에도 개선작업 등이 활발하게 이루어기는것을 볼 수 있었다.

나같은경우도 github 이슈페이지에 안드로이드앱에 캐시기능이 없다고 넣어달라고 글을 올렸더니 2분만에 답장이와서 중복된 이슈가 있다고 내 글을 close 시켜버렸다.

5. 오픈소스 무료 프로젝트다.

무료보다 좋은건 없다.

이런 장점이 있다는것을 느낄 수 있었고

단점도 몇가지 느낄 수 있었는데 단점으로는

1. 파일 탐색 속도가 느리다.

라이브러리를 추가하면 실제로 추가되는데에 생각보다 오랜 시간이 걸렸다. 아무래도 그냥 추가가 되는게 아니라 파일에과 관련된 메타데이터들을 만들고 찾지 못한 메타데이터 등은 인터넷에서 정보를 직접 수집한다음에 집어넣는 그런 방식인것 같다.

이는 라이브러리 추가를 할 때 그런 메타데이터를 얻어올지 말지 등을 설정해 줄 수 있는 것으로 보인다.

2. 안드로이드 앱에 캐시 기능

두가지 안드로이드 앱에서 음악파일을 실행해 봤다. Jellyfin 안드로이드앱과 Gelli 앱인데 Jellyfin 앱에는 캐시기능이 없는것처럼 보이고, Gelli 앱에는 설정에서 캐시 부분을 확인 할 수 있었다.

FTP를 이용하는 음악 플레이어로 테스트를 해봤을 때 음악을 재생하면 그 다음에 재생될 재생목록의 파일 몇가지를 미리 다운로드 하는 등의 나름 편의성있는 기능이 있는것을 보았다.

그런데 이 두가지 앱은 일단 그런 기능도 없었고 캐시가 동작하는지 확인하기 위해

음악1을 재생한 후에 음악2를 재생하고 네트워크를 종료한다음 음악1이 재생되는지에 대해 테스트를 해보았는데 음악1은 재생되지 않았고 심지어는 서버에서 로그아웃까지 된것을 확인할 수 있었다.

Jellyfin앱은 캐시기능이 없는것같으니 그럴 수 있겠지만 Gelli앱은 이런 기능이 되어야하는것 아닌가 싶은데 내가 설정을 따로 해주어야 하는 부분이 있을수도 있을것같다.

Jellyfin 서버는 아래 링크에서 다운받을 수가 있다.

https://jellyfin.org/

 

Jellyfin: The Free Software Media System

Software freedom is important. Jellyfin is Free Software, licensed under the GNU GPL. You can use it, study it, modify it, build it, and distribute it for free, as long as your changes are licensed the same way. The project is community-built, relying enti

jellyfin.org

또한 각종 기능에 대한 설명을 아래 페이지에서 볼 수 있다.

https://jellyfin.org/docs/general/quick-start.html

 

Quick Start | Documentation - Jellyfin Project

 

jellyfin.org

 

이렇게 홈서버나 NAS 등에 Jellyfin 서버를 설치하고 공유기 포트포워딩에서 홈서버 8096 포트에 접속할 수 있도록 설정해 준다음 DDNS 설정을 해주거나 IP로 접속하면 외부에서도 인터넷을 통해서 미디어 서버에 접속을 할 수 있다.

이제 스마트폰 데이터만 걱정 없으면 어디서든 내가 소유한 음악파일을 재생할 수 있게 되었다.

 

참고로 Jellyfin 라이브러리에에 음악을 폴더별로 넣었을때 음악들이 "앨범" 이라는 형태로 들어가는것을 확인할 수 있다.

그런데 확인해보니 이 앨범이라는건 실제 음악의 앨범은 아니고

음악파일이 들어있는 폴더와 그 안에 들어있는 음악파일들이 앨범이라는 형태로 표시되는 것이었다.

앨범의 이름이 그 폴더의 가장 첫 음악파일의 이름으로 되어있었기 때문에 헷갈리게 되는것이다.

앨범의 이름은 아래와 같은 방법으로 바꿀 수 있다.

1. 앨범에서 마우스 오른쪽을 버튼을 누른다.

2. 메타데이터 편집으로 들어가서 제목을 수정한다.

다만 이렇게 수정하고 바로 변경되는것이 아니라 일정 시간이 흐른 후에 변경이 된다.

 

 

 

2020. 7. 10. 2:06

 

이전에 영상 보간 프로그램으로 드미트리를 사용하다가 네트워크 어댑터 추가로 키가 만료되고

무료 영상 보간 플레이어인 Splash를 사용해 보았는데

아무래도 Splash는 일부 코덱을 지원하지 않는데다가 일부 자막형식을 지원하지 않아서 불편한 점이 있었다.

그래서 또다시 방법을 찾아보던 도중 MPV와 SVP 4를 사용하여 영상 보간을 하는 방법을 적은 글을 찾을 수 있었다.

https://quasarzone.com/bbs/qb_free/views/541538

 

프레임 보간 플레이어 필요하신 분들 mpv 한번 써보세요

AMD를 아주 좋아하는데, 이번에 노트북을 구입하며 플루이드 모션을 대체할 방법을 찾아봤습니다.드미트리 렌더…

quasarzone.com

그런데 이 당시에는 SVP 4의 기능을 무료로 사용할 수 있던 시절이었고

현재 SVP는 유료에 30일을 무료로 사용할 수 있다.

MPV를 설치해 보았더니 플레이어 설정까지도 전부 텍스트로 작성해서 실행해야 하는 등의 불편함이 있었다.

 

그러다가 MPV와 같은 갈래에서 파생된 Mplayer 라는 또다른 플레이어를

이용하여 제작된 SMPlayer 라는 플레이어를 발견했다.

기능도 괜찮고 SVP 4와 연동도 간편했다.

 

인터페이스는 정말 깔끔하다.

SMPlayer를 설치하고 SVP 4는 30일동안 무료로 사용할 수 있기 때문에 둘 다 설치해서 연동했더니

Splash처럼 자막의 종류를 가리거나 기능이 동작하지 않는 문제점도 없고

인터페이스나 마우스 버튼 등의 동작을 바꾸는 기능도 있어서 사용하기 좋게 셋팅을 할 수 있었다.

아래는 설치 및 연동 과정입니다.

우선 SMPlayer 64bit버전을 설치한다.

 

https://www.smplayer.info/

 

SMPlayer - Free media player for your PC

SMPlayer is a free media player than can play virtually all audio and video formats. It can even play and download Youtube videos. Other interesting features: find and download subtitles, thumbnail generator, resume playback.

www.smplayer.info

만약 기본 스킨이 마음에 들지 않는다면 다음 방법으로 플레이어 모습을 바꿀 수 있다.

 

그리고 나서 SVP 4를 설치해주었다.

윈도우에서는 SVP 4를 20달러를 내고 구매하여 사용할 수 있지만 설치하면 30일 동안은 무료로 사용이 가능하다.

리눅스에서는 무료라고 하니 나중에 리눅스에서도 설치해보고 싶다.

 

https://www.svp-team.com/get/

 

Get – SVP – SmoothVideo Project

In case the payment was rejected or blocked by your bank, please try the PayPal option instead! We accept Visa and MasterCard bank cards via CloudPayments processing service, please find more information on security and privacy below.

www.svp-team.com

 

설치가 완료되면 이런 창이 뜨는데 나중에 구매를 하겠지만 일단 평가판을 사용하기로 하겠다.

지금 등록하기! 를 선택하면 평가판 기간이 30일 남았다는 메세지가 뜬다.

이제 SMPlayer에서 SVP를 사용해보겠습니다.

사실 너무 간단한데 아래 페이지를 그대로 따라하면 됩니다.

 

https://www.svp-team.com/wiki/SVP:SMPlayer

 

SVP:SMPlayer - SmoothVideo Project (SVP) - frame doubling interpolation

Setting up SMPlayer (Windows, Linux) Installation - Windows Install [VPS_64] mpv video player package via SVP's "Additional programs and features" Install 64-bit version of SMPlayer, Open Preferences, On the General tab set Multimedia engine to Other and b

www.svp-team.com

우선 환경설정으로 들어가서 일반 메뉴의 멀티미디어 엔진의 기타를 선택한다.

 

그러면 파일을 선택하는 다이얼로그가 뜨는데 SVP를 설치한 경로의 MPV 실행파일을 선택한다.

 

내 경우는 기본경로로 설치를 했기 때문에

C:\Program Files (x86)\SVP 4\mpv64

경로에 설치되었다.

다음은 고급 탭으로 이동해서 아래 옵션을 입력해준다.

 

--hr-seek-framedrop=no

확인 버튼을 누르고 나서 영상을 재생해 보면

 

SVP 아이콘이 뜨면서 영상 보간이 동작하는것을 확인할 수 있다.

추가로 SMPlayer에서 OSD를 출력하려면

이 메뉴를 선택하면 된다

영상의 현재 초당 프레임과 각종 정보등을 표시할 수 있다.

 

 

 

 

 

 

 

또한 SMPlayer에서 유튜브 영상을 볼 수 있는데

 

이렇게 주소 메뉴를 클릭한 후 유튜브 주소를 입력하면 재생이 된다.

또는 메뉴에 있는 유튜브 브라우저를 누르고 설치한 뒤

 

 

 

이렇게 검색하여 동영상을 누르면 유튜브 영상이 재생되게 된다.

이렇게 유튜브 영상을 SMPlayer + SVP 환경에서 실행하면

 

이렇게 유튜브 영상또한 보간이 되어 높은 프레임으로 유튜브 영상을 볼 수 있다.

이렇게 볼 일이 있을지는 모르겠지만...

이렇게 해서 동영상을 보간하여 높은 프레임의 영상으로 만들어 부드러운 동영상을 감상할 수 있도록 하였습니다.

2020. 5. 30. 0:32

 

 

이전에 컴퓨터를 새로 맞추고 나서 드리트리 렌더라는 영상 보간 프로그램을 사용하고 있었는데

얼마 전부터 동영상 플레이어를 실행하면 이렇게 오류가 발생하여 드미트리렌더가 동작하지 않고 있다.

컴퓨터 부품을 바꾸거나 한적도 없는데 이런 에러가 발생하고 있는 상황이다.

사실 이렇게 에러가 발생한것은 이번이 두번째인데 이전에는 에러 리포트를 출력해서

제작자에게 보냈더니 키를 새로 보내줘서 다시 등록하여 사용할 수 있었다.

그때 제작자가 혹시 버추얼박스를 쓰냐고 물어보길래 그렇다고 했는데 아무래도 버추얼박스가 드미트리 렌더에 영향을 주는것 같다.

내 생각으로는 버추얼박스가 문제라기 보다는 네트워크 어댑터의 MAC 주소를 받아와서 다른 컴퓨터에서 실행시키는지 확인하는 방식이 아닐까 싶다.

아마 전체 어댑터중에 가장 처음나오는 어댑터의 MAC 주소를 등록해서 검사하는 방식이 아닐까?

실제로 이런식으로 가장 처음에 나오는 MAC 주소를 등록해서 컴퓨터를 구분하는 방식을 쓰는곳을 본적이 있다.

그리고 버추얼박스나 일부 VPN 등을 설치하면 어댑터가 추가되기 때문에 가장 처음 나오는 MAC 주소가 변경되어 컴퓨터가 변경되었다고 인식하는게 아닐까 싶다.

어쨌든 이번이 두번째 발생한거라서 차라리 다른 영상 보간 프로그램으로 갈아탈까 싶은 생각이 있었는데

SVP 프로그램을 살펴보다가 Splash라는 무료 영상 보간 프로그램이 있다는것을 알 수 있었다.

Splash는 다른 보간 프로그램과의 경쟁에서 밀려서 결국 무료로 풀었다는것 같다.

 

 

https://mirillis.com/download-splash-free-hd-video-player

 

Download - Splash 2.0 - The ultimate free HD video player

 

mirillis.com

Splash는 다른 보간 프로그램과는 다르게 동영상 플레이어 형식으로 설치되기 때문에

별다른 설정이 필요없으며 사용해본 결과 60FPS 까지 영상을 보간해 주는것을 확인할 수 있었다.

 

 

드미트리는 모니터의 프레임을 따라가기 때문에 144hz 모니터의 경우 144프레임까지 영상을 보간해주지만

스플래시는 60프레임으로 고정되어 있다.

근데 나같은 경우는 막상 써보니 드미트리와 부드러움의 차이를 별로 느끼지 못했다.

이건 그냥 내 눈의 문제겠지만 좋았던점은 고품질 영상을 드미트리를 사용하여 보간했을 때

내 그래픽카드 점유율이 60%를 넘어가면서 고주파를 내뿜었는데

스플래시의 경우 GPU 사용률이 그렇게 높지 않았다.

단순히 프레임의 차이때문인지 더 좋은 방식을 쓰고있는건지는 모르겠지만 나같은 경우는 단순히 그래픽카드에서 고주파가 안나오니 좋았다.

사용해보니 단점도 몇가지 눈에 띄었는데

우선 가장 큰 문제점이 SMI 자막 형식을 지원하지 않는것 같다. 뭔가 사용할 수 있는 방법이 있는지는 모르겠지만 일단 기본 상태에서는 SMI 자막을 전혀 사용할 수 없었다.

또한 ASS 형식의 자막은 사용 가능했지만 이런 자막의 경우 자막 제작자가 글자를 세로로 세운다거나 여러가지 형태로 자막을 표시할 수 있는데 스플래시 플레이어는 그런 형태로 자막을 출력하지 못하는 것으로 보인다.

또 일부 코덱을 지원하지 않는건지 일부 영상이 깨져서 표시되지 않았다..

 

 

잠깐 사용해 봤을 때 이런 단점들이 있었지만

이런 단점들을 커버할 수 있는 최고의 장점인 무료라는 장점이 있으니

드미트리나 SVP 등의 구매를 고려하고 있다면 그 전에 한번 설치해볼만한 프로그램이라고 생각된다.

 

 

플레이어 세팅은 이쪽 보고 따라했습니다.

 

 

https://quasarzone.co.kr/bbs/qb_tip/views/15501

 

영상 보간 기능이 있는 Splash Video Player

라데온 5700 시리즈부터 플루이드모션 지원이 끊기면서 컴퓨터용 영상 보간 기능 대안을 찾는 분들이 있으실듯…

quasarzone.com

 

'IT > 기타' 카테고리의 다른 글

Virtualbox 네트워크 설정 다했는데 인터넷이 안될때  (0) 2022.01.14
Jellyfin 미디어 서버 소개  (2) 2020.12.24
SMPlayer + SVP 4 설정하기  (0) 2020.12.24

 2020. 4. 30. 17:42

 

 

서버가 실행된 상태에서 HTTP 요청을 받으면

미리 기록해놓은 텍스트 혹은 스크립트 파일을 HTTP 리스폰스 형식으로 되돌려주는 간단한 서버다

제작 도중까지는 더 복잡한걸 생각했는데 생각해보니까 복잡하게 만들어봤자 귀찮고 좋을것도 없을것 같아서 이렇게 됏다.

한번에 여러개의 포트를 열어서 사용할 수 있고

지원하는 스크립트는 파이썬, 쉘, 배치 이렇게 3가지 종류이다.

이런 스크립트 파일이나 지정된 메세지를 보내려면 이 프로그램 자체 스크립트를 작성해야 한다.

자체 스크립트로 할 수 있는것은 포트별로 다른 요청을 주거나 HTTP 리스폰스의 컨텐츠 타입이나

서버가 실행된 상태에서 브라우저를 이용하여 스크립트파일에 요청을 보내면

서버에서 스크립트가 실행된 다음 스크립트에서 리턴한 텍스트 메세지를 클라이언트에게 돌려준다.

또한 리퀘스트로 전달한 GET, POST 파라미터를 스크립트에 전달하는 기능도 있기 때문에

파라미터에 따라 스크립트의 동작을 다르게 하거나 리턴되는 메세지를 다르게 할 수도 있을것이다.

그리고 내가만든 로거 라이브러리를 사용해서 로그도 잘남기게 만들어놨다.

기본 실행하면 실행 로그랑 클라이언트 접속 로그만 남지만

디버그모드로 실행하면 클라이언트가 전송한 메세지, 클라이언트에게 전송한 메세지까지 전부 다 남긴다.

서버 구조도 나름 신경써서

소켓 연결만 하는 네트워크 스레드와

HTTP 메세지를 처리하는 워커스레드로 구성했는데

워커스레드의 개수는 최소 한개 최대 CPU 논리코어수 -1 개가 만들어지도록 했다.

스레드간 통신은 메세지 큐 방식으로 만들었고

코드도 최대한 C++ 표준 코드를 사용하여 만들려고 노력했는데 쉽지는 않아서

결국 리눅스 환경에서 컴파일되는 코드랑 윈도우 환경에서 컴파일되는 코드를 따로 구분하는 방식으로 만들었다.

그래서 리눅스 환경에서도 컴파일이랑 동작이 잘 되는걸 확인했고

아직 개선하야 할 점이 많이 있기는 하지만 틈틈히 만들어서 이정도 만들어진게 기뻐서

1.0 버전으로 릴리즈해서 올렸다

사실 톰캣이랑 비슷한 기능이기도 한데 톰캣과 비교해서 장점은

별다른 설정이 필요 없고 API에 대한 테스트를 해볼 때 별도의 웹 컴포넌트를 작성할 필요가 없다는 장점이 있다.

그리고 이 서버 프로그램으로 현재 바이너리 파일 전송이 안되는데

바이너리 파일 전송기능을 넣을까 하다가 API 테스트용 프로그램에 그런 기능이 필요할까 싶어서 일단 안넣었다.

그래서 브라우저에서 아이콘 파일을 요청할경우 경로상에 아이콘파일이 존재해도 응답을 보낼 수가 없다.

혹시라도 나중에 생각이 바뀌면 넣어야겠음

 

윈도우 콘솔에서의 실행 방법 -g는 디버그모드로 실행하는 명령어다

 

브라우저로 서버에 접속해서 받은 응답
sample 스크립트 파일 형식

 

로그파일이 이렇게 남았다.

 

 

 

 

https://github.com/AhatLi/AhatDummyServer

 

AhatLi/AhatDummyServer

Contribute to AhatLi/AhatDummyServer development by creating an account on GitHub.

github.com

 

 

지금 와서 보니까 개선할점이 많이 보이는것 같다...

나중에 여유가 된다면 개선을 해봐야겠다.

2020. 4. 12. 19:54

 

저는 요즘 소액으로 주식 투자를 해보고 있는 중인데

소소하게 투자를 해서 소소하게 잃고있는 중입니다.

그런데 저번주에는 갑자기 어떤 회사 주식에 꽂혀서 그 주식을 몰빵해서 샀고

가격이 내려도 추가 구입해서 평단가를 맞추는 등의 노력을 했지만

금요일날 결국 그 회사 주식이 폭락하여 3000 초반대까지 떨어져 원금의 10퍼센트를 잃고 손절하고 말았습니다.

그러다가 오늘은 꿈에서 손절한 주식이 8000원까지 올라가는 악몽을 꾸고 말았습니다.

결국 내가 뭐가 부족해서 계속 잃는것인지 생각해보았는데

생각해보면 제가 샀던 주식이 올랐던 적도 꽤나 있었습니다. 그런데도 손해만 보고 있었고요

저에게 부족한건 적절한때 손절 익절을 하는 능력(?)이 부족했던것이 아닐까 싶습니다.

그래서 생각해봤는데 손절 익절을 mts에 모두 맡기면 오히려 잃는 돈이 줄어들지 않을까 싶었고

주식을 구입한 후 +-3%가 되면 자동으로 판매를 하도록 설정을 해놓으면 어떨까 하고 생각을 했습니다.

보기에는 그럴듯 하지만 진짜로 저렇게 한다고 이익을 볼 수 있을까요?

물론 실제로 저런식으로 거래를 해서 이익을 보는 사람도 많이 있겠지만

결국 저런 방식으로 거래를 하는것도 오를 주식을 사야 얻는거고 또 오를 타이밍에 사야 이익을 볼텐데요

오를 주식을 미리 알 수는 없지만 어떤 상황에서 사야 오를지에 대해서 알 수 있을까 싶어서

과거의 자료를 보고 간단하게 차트를 만드는 코드를 만들어보았습니다.

이 코드를 만들기 위해서 우선 주식 프로그램에서

저번주 금요일 거래량이 많았던 종목들의 하루치의 데이터를 다운받았습니다.

 

 

 

 

그리고 데이터를 엑셀 파일로 다운받았습니다.

그리고 파이썬에서 다운받은 엑셀 파일을 읽어들여 제가 원하는 결과를 출력하도록 코드를 작성하겠습니다.

규칙은 간단한데

어떤 시점에서 주식을 구입했을 경우 해당 주식이 +3% 가 되는것이 먼저인지 -3%가 되는것이 먼저인지를

출력하는것 입니다.

데이터는 분단위의 봉차트 데이터로 시가 고가 저가 종가에 다른 데이터도 더 있지만

시가 고가 저가 데이터만을 사용하도록 하겠습니다.

만약 09:00에 시가로 주식을 개당 1000원에 구입했을 경우

09:05분에 고가가 1030이 되고 09:06분에 저가가 970원이 된다면

09:05분에 1030원에 에 익절하는 형식입니다.

아래는 작성한 코드입니다.

 

 

import pandas as pd
import os
import matplotlib.pyplot as plt
from matplotlib import pyplot
import datetime
from matplotlib import dates

path = "./excel/"
file_list = os.listdir(path)

print ("file_list: {}".format(file_list))

plt.rcParams["figure.figsize"] = (14,7)
plt.rcParams['lines.linewidth'] = 2
plt.rcParams['lines.color'] = 'r'
plt.rcParams['axes.grid'] = False 

p = 0.03

for file in file_list:
    if file.find('xlsx') < 0 and file.find('xls') < 0:
        continue
    print(file)
        
    xlsx = pd.read_excel(path + file)
    color = []
    for i in range(len(xlsx['시가'])):
        color.append(0)
    
    for i in reversed(range(0, xlsx.shape[0])):
        for j in reversed(range(0, i-1)):
            if xlsx['시가'][i] + xlsx['시가'][i] * p < xlsx['고가'][j]:
                color[i] = 1
    #            print(xlsx['시간'][i], ",", xlsx['시가'][i], ",", p, ",", "익절" , xlsx['고가'][j] , xlsx['시간'][j], color[i])   
                break
            if xlsx['시가'][i] - xlsx['시가'][i] * p > xlsx['저가'][j]:
                color[i] = 2
    #            print(xlsx['시간'][i], ",", xlsx['시가'][i], ",", p, ",", "손절" , xlsx['저가'][j] , xlsx['시간'][j], color[i])     
                break
    
    fig, ax = plt.subplots()
    converted_dates = list(map(datetime.datetime.strptime, xlsx['시간'], len(xlsx['시간'])*['%m/%d,%H:%M']))
    x_axis = converted_dates
    formatter = dates.DateFormatter('%H:%M')
    
    ax.xaxis.set_major_formatter(formatter)    
    ax.plot([],[])
    ax.scatter(x_axis, xlsx['시가'], c = color)
    
    plt.show()

 

결과를 차트로 출력하는데

위의 규칙을 그대로 사용하여

청록색일때 구입하면 익절

노란색일때 구입하면 손절

보라색일때 구입하면 손절도 익절도 하지 않습니다.

결과를 보면 저번 금요일 시점의 이 주식은 정말 특별한 타이밍이 아니면 이익을 보기 힘들었을거라는 결과입니다.

다른 회사들의 결과를 보겠습니다.

 

 

 

 

돈벌기 참 힘드네요

결과만 보면 주식 가격이 급격하게 내려갔을 때 구입하면 가격이 다시 회복되면서 익절을 하게되는 경우가 많은데

가장 첫번째 그래프처럼 다시 안올라가고 쭉 내려가기만 하는 경우도 있어서 그경우는 무조건 손해만 입겠네요.

결국 타이밍도 중요하지만 좋은 종목을 골라서 거래하는게 더 중요한것 같습니다.

2020. 4. 3. 14:14

 

인터넷에 있는 케라스 이미지 분류 코드를 이용해 이미지 분류 연습을 해 보았습니다.

연습을 위해 이미지셋을 다운받아서 테스트 해보았더니 그럭저럭 90퍼센트 이상의 정확도가 나오는것을 확인할 수 있었습니다.

하지만 이미 있는 이미지로만 해보면 재미가 없으니 새로운 이미지 셋을 만들어 연습해 보았습니다.

 

깔끔하게 잘 정리된 이미지셋들

우선 이미지셋을 만들기 위해 제가만든 이미지 다운로더 프로그램을 사용하여 이미지를 다운로드 하였습니다.

 

이번에 테스트 해볼 이미지셋은 아이돌 마스터의

시마무라 우즈키,

혼다 미오,

시부야 린

이렇게 세명의 캐릭터 이미지로 분류되는 이미지셋입니다.

 

각각 캐릭터 이미지를 약 100개씩 준비하였습니다.

그런데 이미지들이 통일성이 없고 배경색이나 색들도 완전히 각양각색입니다.

이미지마다의 공통점이라고는 해당 캐릭터들의 얼굴이 들어가 있다는점 밖에는 없네요

이런 이미지로 이미지 분류가 가능할지 잘 모르겠습니다.

각각의 이미지를 uzuki, rin, mio 폴더에 집어넣고 이미지셋을 데이터로 만들었습니다.

 

 

from sklearn.model_selection import cross_validate
from sklearn.model_selection import train_test_split
import cv2
from PIL import Image
import os, glob
import numpy as np

caltech_dir = "./lib/img"
categories = ["mio","rin","uzuki"]
nb_classes = len(categories)

image_w = 64 
image_h = 64
pixels = image_w * image_h * 3

X = []
Y = []
for idx, cat in enumerate(categories):
    print(cat)
    label = [0 for i in range(nb_classes)]
    label[idx] = 1
    image_dir = caltech_dir + "/" + cat
    print(image_dir)
    files = glob.glob(image_dir+"/*.jpg")
    for i, f in enumerate(files):
        print(f)
        img = cv2.imread(f)
        img = cv2.resize(img, None, fx=64/img.shape[1], fy=64/img.shape[0])
        X.append(img/256)
        Y.append(label)
        
X = np.array(X)
Y = np.array(Y)
X = np.array(X)
Y = np.array(Y)
X_train, X_test, Y_train, Y_test = train_test_split(X,Y)
xy = (X_train, X_test, Y_train, Y_test)

np.save("./lib/obj.npy", xy)

그 후 이미지 데이터를 이용하여 학습을 진행하였습니다.

 

from keras.models import Sequential
from keras.layers import MaxPooling2D
from keras.layers import Conv2D
from keras.layers import Dropout, Activation, Dense, Flatten
import h5py
import numpy as np
import cv2
import os

categories = ["mio","rin","uzuki"]

nb_classes = len(categories)

X_train, X_test, Y_train, Y_test = np.load('./lib/obj3.npy', allow_pickle=True)
print('Xtrina_shape', X_train.shape)

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=X_train.shape[1:], padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))

model.add(Conv2D(64, (3, 3)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))

model.add(Dense(nb_classes))
model.add(Activation('softmax'))

model.compile(loss='binary_crossentropy', optimizer='Adam', metrics=['accuracy'])
hdf5_file = "./lib/obj-model.hdf5"
if os.path.exists(hdf5_file):
    model.load_weights(hdf5_file)
else:
    model.fit(X_train, Y_train, batch_size=32, epochs=50)
    model.save_weights(hdf5_file)

score = model.evaluate(X_test, Y_test)
print('loss=', score[0])        # loss
print('accuracy=', score[1])    # acc

 

마지막으로 트레이닝 된 데이터를 이용하여 테스트셋의 정확도를 확인해 보겠습니다.

중복되지 않는 이미지를 따로 모아 테스트 하였습니다.

 

 

from keras.models import Sequential
from keras.layers import MaxPooling2D
from keras.layers import Conv2D
from keras.layers import Dropout, Activation, Dense, Flatten
import h5py
import numpy as np
import cv2
import os

categories = ["mio","rin","uzuki"]

nb_classes = len(categories)

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=X_train.shape[1:], padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))

model.add(Conv2D(64, (3, 3)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))

model.add(Dense(nb_classes))
model.add(Activation('softmax'))

model.compile(loss='binary_crossentropy', optimizer='Adam', metrics=['accuracy'])

hdf5_file = "./lib/obj-model.hdf5"
model.load_weights(hdf5_file)

import glob

X = []
Y = []

files = glob.glob("./lib/img/test/*.jpg")
for i, f in enumerate(files):
    print(f)
    img = cv2.imread(f)
    img = cv2.resize(img, None, fx=64/img.shape[1], fy=64/img.shape[0])
    X.append(img/256)

X = np.array(X)
Y = np.array(Y)

Y = model.predict(X)

c1 = 0;
c2 = 0;

for i in range(len(Y)):
    for j in range(len(Y[i])):
        if Y[i][j] == np.max(Y[i]):
            c1 = c1 + 1
            tmp1 = files[i].split('\\')[1].split(' ')[0]
            if(tmp1 == categories[j]):
                c2 = c2 + 1
            break

print(c1)
print(c2)

 

 

 

출력된 결과는

97 62

97개 테스트 중에 62개가 정답입니다. 약 63퍼센트의 정확도네요

절반도 안될줄 알았는데 생각보다 높게 나왔습니다.

물론 실제로 사용하기에는 정확도가 너무 낮지만

랜덤으로 찍어서 맞출 확률보다 약 2배나 높은 확률입니다.

 

다만 테스트 자체의 신뢰성이 조금 의심됩니다...

이미지셋 자체가 너무 적은게 문제인것 같습니다.

정리되지 않은 이미지들로 이정도라면 정리된 데이터로 테스트 해보면 훨씬 높은 정확도가 나올것 같습니다.

예를들면 캐릭터 얼굴만 잘라내서 트레이닝 시키고 테스트하면 90퍼센트 이상의 정확도를 구할 수 있지 않을까요?

그건 나중에 따로 해보도록 하겠습니다.

 

 

 

 

참고

https://hoony-gunputer.tistory.com/entry/keras%EC%83%89-%EC%9E%88%EB%8A%94-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B02

 

 

+ Recent posts