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

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

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

나는 외부에서 음악을 듣기 위해서 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. 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