반응형

문서를 듣자!! 문서를 읽는 것은 집중력을 높여 이해도를 높여 주기도 하지만, 많은 문서를 볼 때는 오히려 음성으로 듣는 것이 낫기도 하다. 여기서는 Google Cloud TTS를 이용하여 Google Docs에 있는 내용을 음성으로 들어 본다. Google Cloud TTS의 예제이자, Google Docs 문서를 액세스하는 예제이기도 하다.

 

Permission

 

Service Account 생성 및 Key 생성 및 다운로드

  • tts-api라는 이름의 Service Account 생성 및 별 다른 Permission을 주지 않음

    • 생성된 Account: tts-api@api-project-249965614499.iam.gserviceaccount.com

  • 아래와 같이 JSON Key 생성

 

  • 다음 받은 JSON 파일을 현재 디렉토리로 복사

$ mv ~/Downloads/api-project-249965614499-33c4f9f41469.json .



액세스할 Google Docs 또는 Google Drive 폴더에 Service Account의 Read 권한 추가



필요한 구글 Python 라이브러리 패키지 설치

pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
pip install oauth2client
pip install google-cloud
pip install argparse



Google Docs를 읽어 JSON으로 출력하는 프로그램 작성

  • 일단 테스트로 Google Docs의 JSON 구조를 확인하 위한 프로그램

  • dic.json 파일 생성

  • DOCUMENT_ID에 Google Docs의 ID (참고로 본 문서 사용)

from googleapiclient import discovery
from google.oauth2 import service_account
import json
import os

DOCUMENT_ID='1FCGcY00_8_DK8edLP1ws5NQamdmB6agCZpfXXXXXXXXXXXXX'

SCOPES = [
#    'https://www.googleapis.com/auth/drive',
    'https://www.googleapis.com/auth/documents.readonly'
]
# DISCOVERY_DOC = 'https://docs.googleapis.com/$discovery/rest?version=v1'
DISCOVERY_DOC = 'https://docs.googleapis.com/$discovery/rest?version=v1'

SERVICE_ACCOUNT_FILE = 'api-project-249965614499-33c4f9f41469.json'
credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)

service = discovery.build('docs', 'v1', discoveryServiceUrl=DISCOVERY_DOC, credentials=credentials)

# Do a document "get" request and print the results as formatted JSON
f = open("dic.json", "w")
result = service.documents().get(documentId=DOCUMENT_ID).execute()
jsonStr = json.dumps(result, indent=4, sort_keys=True)
f.write(jsonStr)
f.close()

print("dic.json has been created")

 

생성된 JSON 파일의 구조 확인

  • body/content 배열에 있는 paragraph가 각각의 문단

  • content에 텍스트 내용이 들어가고 한글은 Unicode로 escaping 됨

  • namedStyleType에 테스트의 Style

{
    "body": {
        "content": [
            {
                "endIndex": 1,
                "sectionBreak": {
                    "sectionStyle": {
                        "columnSeparatorStyle": "NONE",
                        "contentDirection": "LEFT_TO_RIGHT",
                        "sectionType": "CONTINUOUS"
                    }
                }
            },
            {
                "endIndex": 38,
                "paragraph": {
                    "elements": [
                        {
                            "endIndex": 38,
                            "startIndex": 1,
                            "textRun": {
                                "content": "Google Cloud TTS to read Google Docs\n",
                                "textStyle": {}
                            }
                        }
                    ],
                    "paragraphStyle": {
                        "direction": "LEFT_TO_RIGHT",
                        "headingId": "h.swe0qm2wob2",
                        "namedStyleType": "TITLE"
                    }
                },
                "startIndex": 1
            },
...
            {
                "endIndex": 42,
                "paragraph": {
                    "elements": [
                        {
                            "endIndex": 42,
                            "startIndex": 39,
                            "textRun": {
                                "content": "\ucc38\uace0\n",
                                "textStyle": {}
                            }
                        }
                    ],
                    "paragraphStyle": {
                        "direction": "LEFT_TO_RIGHT",
                        "headingId": "h.1o1ly2f6sob",
                        "namedStyleType": "HEADING_2"
                    }
                },
                "startIndex": 39
            },

 

JSON에서 문단을 추출하는 프로그램

  • dic.json을 참고해서 다음과 같이 문단 추출 부분을 작성

...


service = discovery.build('docs', 'v1', discoveryServiceUrl=DISCOVERY_DOC, credentials=credentials)


result = service.documents().get(documentId=DOCUMENT_ID).execute()

for sentence in result['body']['content']:
if 'paragraph' in sentence and 'textRun' in sentence['paragraph']['elements'][0]:
print(sentence['endIndex'])
# print("**************************************************************************")
# print(json.dumps(sentence['paragraph'], indent=4, sort_keys=True))
text = sentence['paragraph']['elements'][0]['textRun']['content']
style = sentence['paragraph']['paragraphStyle']['namedStyleType']
print(style, text)

 

  • 테이블 처리를 추가

...


for sentence in result['body']['content']:
if 'paragraph' in sentence and 'textRun' in sentence['paragraph']['elements'][0]:
print(sentence['endIndex'])
# print("**************************************************************************")
# print(json.dumps(sentence['paragraph'], indent=4, sort_keys=True))
text = sentence['paragraph']['elements'][0]['textRun']['content']
style = sentence['paragraph']['paragraphStyle']['namedStyleType']
print(style, text)
elif 'table' in sentence:
print(sentence['endIndex'])
for tableRow in sentence['table']['tableRows']:
for tableCell in tableRow['tableCells']:
text = ''
for tableContents in tableCell['content']:
print(tableContents['endIndex'])
for tableSentence in tableContents['paragraph']['elements']:
text = text + tableSentence['textRun']['content']
text = text + '\u000b'
for line in text.split('\u000b'):
print('[TABLE]', line)

 

  • 테이블 테스트

컬럼1

컬럼2

컬럼3

로우2-1

로우2-2

로우2-3

로구3-1

로우3-2

로우3-3

문장인 경우에는 어떻게 하는 지 궁금합니다. 이어진 문장.

떨어진 문장.

   




Flask Python 프로그램 작성

  • 일단 Flask를 통해서 화면에 시각화 하는 프로그램. Google Docs 문서 ID를 URL 파라미터로 보내면 해당 결과를 HTML로 렌더링 해 준다

from flask import Flask, request, Response
from googleapiclient import discovery
from google.oauth2 import service_account
import json
import os

app = Flask(__name__)

@app.route("/<docid>")
def hello(docid):
styles = {
'TITLE': '<h1>%s%s</h1>',
'HEADING_1': '<h2>%s%s</h2>',
'HEADING_2': '<h3>%s%s</h3>',
'HEADING_3': '<h4>%s%s</h4>',
'NORMAL_TEXT': '<p>%s%s</p>'
}

DOCUMENT_ID=docid

SCOPES = [
#    'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/documents.readonly'
]
# DISCOVERY_DOC = 'https://docs.googleapis.com/$discovery/rest?version=v1'
DISCOVERY_DOC = 'https://docs.googleapis.com/$discovery/rest?version=v1'

SERVICE_ACCOUNT_FILE = 'api-project-249965614499-33c4f9f41469.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)

service = discovery.build('docs', 'v1', discoveryServiceUrl=DISCOVERY_DOC, credentials=credentials)

result = service.documents().get(documentId=DOCUMENT_ID).execute()

f = open("dic.json", "w")
jsonStr = json.dumps(result, indent=4, sort_keys=True)
f.write(jsonStr)
f.close()

html = ''
for sentence in result['body']['content']:
if 'paragraph' in sentence and 'textRun' in sentence['paragraph']['elements'][0]:
print(sentence['endIndex'])
# print("**************************************************************************")
# print(json.dumps(sentence['paragraph'], indent=4, sort_keys=True))
bullet = ''
if 'bullet' in sentence['paragraph']:
bullet = '&#8226;'
text = ''
for t in sentence['paragraph']['elements']:
text = text + t['textRun']['content']
style = sentence['paragraph']['paragraphStyle']['namedStyleType']
print(style, text)
html = html + (styles[style] % (bullet, text)) + "\n"
elif 'table' in sentence:
print(sentence['endIndex'])
html = html + "<table border='1'>\n"
for tableRow in sentence['table']['tableRows']:
html = html + "<tr>\n"
for tableCell in tableRow['tableCells']:
html = html + "<td>\n"
text = ''
for tableContents in tableCell['content']:
print(tableContents['endIndex'])
for tableSentence in tableContents['paragraph']['elements']:
text = text + tableSentence['textRun']['content']
text = text + '\u000b'
for line in text.split('\u000b'):
print('[TABLE]', line)
html = html + ("<p>%s</p>\n" % (line))
html = html + "</td>\n"
html = html + "</tr>\n"
return html, 200

if __name__ == "__main__":
app.run(host='0.0.0.0', port=5001, debug=False)

 

  • 나중에 JavaScript에서 제어를 편하게 하기 위해서는 <h1> 등의 태그를 직접 사용하지 말고 <div>에 고유한 class 명을 넣어서 제어

styles = {
'TITLE': '<div class="jerry_tts"><h1>%s%s</h1></div>',
'HEADING_1': '<div class="jerry_tts"><h2>%s%s</h2></div>',
'HEADING_2': '<div class="jerry_tts"><h3>%s%s</h3></div>',
'HEADING_3': '<div class="jerry_tts"><h4>%s%s</h4></div>',
'NORMAL_TEXT': '<div class="jerry_tts"><p>%s%s</p></div>'
}



Text To Speech API를 호출해서 Text를 읽기

  • Text를 음성으로 만들어 출력하는 부분은 브라우저에서 이루어져야 함

  • Google Cloud Text To Speech API 설치

$ pip install google-cloud-texttospeech

 

  • Text를 보내면 TTS API 호출

@app.route("/speak/<encoding>")
def speak(encoding):
text = request.args.get('text', '')

# Instantiates a client
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = SERVICE_ACCOUNT_FILE
client = texttospeech.TextToSpeechClient()

# Set the text input to be synthesized
synthesis_input = texttospeech.SynthesisInput(text=text)

# Build the voice request, select the language code ("en-US") and the ssml
# voice gender ("neutral")
voice = texttospeech.VoiceSelectionParams(
language_code=encoding, ssml_gender=texttospeech.SsmlVoiceGender.NEUTRAL
)

# Select the type of audio file you want returned
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.MP3
)

# Perform the text-to-speech request on the text input with the selected
# voice parameters and audio file type
response = client.synthesize_speech(
input=synthesis_input, voice=voice, audio_config=audio_config
)

# The response's audio_content is binary.
with open("output.mp3", "wb") as out:
# Write the response to the output file.
out.write(response.audio_content)

return Response(response=response.audio_content, mimetype='audio/mp3')

 

JavaScript 에서 음성 읽기를 키보드로 제어

  • Ctrl+> 키나 Ctrl+< 키를 통해 전진, 후진

  • Ctrl+? 키로 음성 읽기 중단

scripts = '''
<script>
idx = -1;
lastIdx = -1;
var audio = null;

window.onload = function() {
document.addEventListener('keydown', keyEvent);
setTimeout(forward, 200);
}

function play() {
sentences = document.querySelectorAll(".jerry_tts");
sentence = sentences[idx];
if(sentence.nodeName == "DIV" && sentence.textContent) {
if(lastIdx >= 0) {
sentences[lastIdx].style.backgroundColor='transparent';
}
sentence.style.backgroundColor='yellow';
lastIdx = idx;
sentence.scrollIntoView(true);
window.scrollTo(0, window.scrollY - 100);
encoding = document.querySelector("body > select:nth-child(1)").value
speed = document.querySelector("body > select:nth-child(2)").value
if(audio) {
audio.pause();
}
audio = new Audio("/speak/" + encoding + "?text=" + sentence.textContent);
audio.load();
audio.playbackRate = speed;
audio.play()
.then(() => {
// Playing
})
.catch(error => {
console.log("empty voice");
setTimeout(forward, 100);
});
audio.addEventListener('ended', forward);
}
}

function forward() {
sentences = document.querySelectorAll(".jerry_tts");
if(idx - 1 < sentences.length) {
idx++;
}

play();
}

function backward() {
if(idx > 0) {
idx--;
}

play();
}

function pause() {
if(audio) {
if(audio.paused) {
audio.play();
}
else {
audio.pause();
}
}
}

function keyEvent(e) {
    e = e || window.event;
if(e.ctrlKey && e.key == '.') {
forward();
}
else if(e.ctrlKey && e.key == ',') {
backward();
}
else if(e.ctrlKey && e.key == '/') {
pause();
}
}
</script>
'''



HTML Body 생성

  • 인코딩, 재생 속도 등을 제어할 수 있는 컨트롤 배치

  html = '<html>\n'
  html = html + scripts
  html = html + '''
      <body>
      <select name='encoidng' onChange='play()'>
          <option value='en-US'>en-US</option>
          <option value='ko-KR'>ko-KR</option>
      </select>
      <select name='speed' onChange='play()'>
          <option value='2'>2배</option>
          <option value='1.5'>1.5배</option>
          <option value='1.3'>1.3배</option>
          <option value='1.2'>1.2배</option>
          <option value='1.1'>1.1배</option>
          <option value='1' selected='selected'>1배</option>
          <option value='0.9'>0.9배</option>
          <option value='0.8'>0.8배</option>
          <option value='0.7'>0.7배</option>
          <option value='0.5'>0.5배</option>
      </select>
      <input type='button' value='Pause/Resume' onClick='pause()'>
      <div class='jerry_tts_body'>
  '''

 



최종 소스



Cloud Run으로 실행

  • 다음과 같이 스크립트를 만들어 실행

  • 먼저 로컬에서 다음과 같은 스크립트를 이용하여 테스트 

  • Dockerfile (5001 포트를 사용)

$ cat Dockerfile
FROM python:3.7-slim

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install Flask gunicorn
RUN pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
RUN pip install oauth2client
RUN pip install google-cloud
RUN pip install google-cloud-texttospeech
RUN pip install argparse

EXPOSE 5001

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :5001 --workers 1 --threads 8 --timeout 0 tts-read-google-docs5:app



$ cat 02.test_local.sh
if [ "$1" == "build" ];
then

mkdir -p build
cd build
cp ../tts-read-google-docs5.py .
cp ../api-project-249965614499-33c4f9f41469.json .
cp ../Dockerfile .
docker build -t "google-docs-tts" .

fi

docker run --name jerry -d -p 5001:5001 google-docs-tts

sleep 1
docker logs -f jerry &

sleep 1
echo Enter to stop...
read a

docker stop jerry
docker rm jerry

 

  • 다음과 같이 Cloud Build 실행 (프로젝트 ID 등은 각자)

$ cat 01.build.sh
mkdir -p build
cd build
cp ../tts-read-google-docs5.py .
cp ../api-project-249965614499-33c4f9f41469.json .
cp ../Dockerfile .
cp ../favicon.ico .
gcloud builds submit --project api-project-249965614499 --tag gcr.io/api-project-249965614499/google-docs-tts

 

  • 다음과 같이 Cloud Run으로 실행

$ cat 03.deploy.sh
gcloud run deploy --project api-project-249965614499 --image gcr.io/api-project-249965614499/google-docs-tts --platform managed --region asia-northeast1 --port 5001 --allow-unauthenticated google-docs-tts














Posted by Hey Jerry
,
반응형

Window에서 SSH 서버를 실행하는 방법은 여러가지가 있지면, 여기서는 cygwin을 이용한 데몬(윈도우 서비스) 사용 방법을 알아 본다. 먼저 관리자 권한으로 cygwin 실행해서 아래의 설치 절차를 진행한다.

 

다른 블로그 글을 참고해서 설치한 절차

  • cygwin을 관리자 모드로 실행

  • ssh-host-config 명령 실행해서 모두 yes로 답

  • Privileges는 no로 답 (굳이 특수 권한이 필요하지 않다면)

  • 서비스로 설치

 

작업 절차

  1. 일단, Cygwin의 setup.exe 에서 openssh를 선택해서 설치한다.

 

  1. Cygwin 셸(관리자 모드 - 윈도우 서비스 설치를 위해)을 실행하고서 ssh-host-config명령을 실행한다. 그리고 모든 질문에 yes 로 대답한다. 마지막에 CYGWIN 옵션을 물을 때는 "ntsec"을 지정한다. 버전마다 조금씩 달라질 수 있지만 디폴트 옵션을 계속 선택하면 된다.

 

$ ssh-host-config
Generating /etc/ssh_host_key
Generating /etc/ssh_host_rsa_key
Generating /etc/ssh_host_dsa_key
Generating /etc/ssh_config file
Privilege separation is set to yes by default since OpenSSH 3.3.
However, this requires a non-privileged account called 'sshd'.
For more info on privilege separation read /usr/share/doc/openssh/README.privsep.

Should privilege separation be used? (yes/no) yes
Warning: The following function requires administrator privileges!
Should this script create a local user 'sshd' on this machine? (yes/no) yes
Generating /etc/sshd_config file
Added ssh to C:WINDOWSsystem32driversetcservices


Warning: The following functions require administrator privileges!

Do you want to install sshd as service?
(Say "no" if it's already installed as service) (yes/no) yes

Which value should the environment variable CYGWIN have when
sshd starts? It's recommended to set at least "ntsec" to be
able to change user context without password.
Default is "ntsec". CYGWIN=ntsec

The service has been installed under LocalSystem account.
To start the service, call `net start sshd' or `cygrunsrv -S sshd'.

Host configuration finished. Have fun!

 

  1. 제어판의 서비스에서 Cygwin sshd를 끄고 켤 수 있으며, Cygwin 셸에서는 cygrunsrv -S sshd 명령이나, 윈도우 명령창에서 net start sshd으로 마찬가지로 sshd를 켤 수 있다.

 

  1. 외부에서 접속이 안될 때는 윈도우의 방화벽을 확인하고, 22포트를 열어준다. (윈도우 방화벽에서)

 

사용/접속 방법

 

  • 윈도우 서비스 메뉴를 보면 openssh도 서비스 모드가 존재하는데 여기서는 cygwin 사용한다. (직접 작업을 할 필요 없고 아래와 같이 확인 가능하다는 의미)

 

  • SSH 접속을 하고 싶은 계정의 home 디렉토리에서 가서 .ssh 디렉토리 설정

  • SSH Key (id_rsa, id_rsa.pub) 생성: ssh-config -t rsa 명령으로

  • authorized_keys 파일에 접속을 원하는 클라이언트의 Public Key를 등록해 준다.

 

  • ps 명령으로 확인 가능

 

 

삭제 방법

 

# Remove sshd service
cygrunsrv --stop sshd
cygrunsrv --remove sshd

# Delete any sshd or related users (such as cyg_server) from /etc/passwd
#   (use your favorite editor)

# Delete any sshd or related users (such as cyg_server) from the system
net user sshd /delete
net user cyg_server /delete





참고 자료

 

http://kwon37xi.egloos.com/2496478

 

http://www.howtogeek.com/howto/41560/how-to-get-ssh-command-line-access-to-windows-7-using-cygwin/ 문서를 참조한다.

 

http://old.nabble.com/openssh-5.1p1-3---ssh-host-config-dependency-and-permission-issues-p19865924.html





Posted by Hey Jerry
,
반응형

구글 클라우드(GCP)의 Cloud Run은 컨테이너를 빠르게 생성, 배포, 관리할 수 있는 솔루션이다. Cloud Run 데모를 고민하다가, 실제로 업무에 활용할 수 있는 데모를 준비해 봤다. Google Sheets를 액세스하는 부분도 있어, 그것을 활용하여 간단한 구성이나 데이터 저장소로 활용하는데도 도움이 될 수 있다. 최근에는 브라우저 기반의 업무들이 많아지고 있다. 그래서 브라우저 활용을 효율화 하는 것이 업무 효율 향상에도 도움이 많이 된다.

 

URL Shortcut 만들기

브라우저를 통해서 여러 웹 사이트를 브라우징 하다보면, 너무 많은 페이지와 정보에 당황하고 그것들을 관리하기 어려워 하는 경우가 많다. 사실 대부분의 경우 북마크를 활용한다. 북마크의 장점은 마우스 클릭을 통해 빠르게 원하는 페이지를 URL을 기억하지 않고 접근할 수 있다는 장점이 있다. 필자 같은 경우는 아래와 같이 북마크 바를 크롬에 배치하여 잘 사용하고 있다.

 

 

다만, 문제는 북마크의 개수가 많아지면 그것 자체를 브라우징 하는데 시간이 소요되거나 잊어 먹고 중복해서 계속 북마크를 만드는 문제도 있다. 혹시 북마크와 함께 우리가 키보드로 입력하는 URL 주소 창에서 빠르게 원하는 페이지를 찾아가는 방법은 없을까?

 

 

Custom Search Engine 활용하기

크롬브라우저의 설정 ⇒ 검색엔진 ⇒ 검색엔진 관리 메뉴에서 빠른 사이트 액세스를 가능하게 한다. 다음은 네이버맵과 구글맵 검색을 최적화한 예이다. 아래와 같이 설정을 하게 되면

 

 

브라우저 창에서 다음과 같이 빠르게 구글맵이나 네이버맵을 사용할 수 있다. 키워드(예: nmap)를 치고 스페이스를 누르면, 

 

 

다음과 같이 네이버맵 웹사이트에 들어가지 않고도 바로 검색을 할 수 있다.

 

 

키워드를 통해서 웹 사이트 액세스하기

 

Custom Search Engine을 조금 더 확장해서, 특정 키워드를 특정 URL과 맵핑해서 사용할 수는 없을까? 하는 생각에 이르게 되었다. 다음과 같이 활용하는 것이다. 예를 들어 필자 같은 경우는 ‘jl’ 또는 ‘jlink’ 라는 키워드를 prefix로 활용하고 있는데 다음은 그 활용 방법이다.

 

 

스페이스 누르고 사전에 정한 키워드 memo 를 입력하면

 

 

거기에 링크된, Google Docs(또는 특정 웹사이트)가 열린다.

 

즉, 브라우저 기반으로 업무를 수행해서 사용해야 할 컨텐츠에 대한 고유한 링크들이 많다면 예를 들어 아래와 같이 키워드를 등록하면 그것을 빠르게 액세스 할 수 있다.

 

memo - 개인 메모

dev - 개발 관련 페이지

famacc - 개인 회계 페이지

cloudrun - Cloud Run 관련 기술 문서

 

이런 기능을 Cloud Run을 통해서 만들어 보자.

 

Cloud Run 이용한 개발

여기 문서를 통해서 Cloud Run에 대한 이해가 많아진다.

 

프로그램은 다음과 같이 생각해 본다. 브라우저에서 Custom Search Engine 기능을 통해서 Cloud Run을 실행하면, Cloud Run에서는 URL 파라미터를 받아 Google Sheets에 저장된 맵핑 테이블(키워드 - URL)을 조회한다. 그리고 조회된 URL을 Redirect 할 수 있게 응답으로 보낸다.

 

 

먼저 Cloud Run 프로그램은 Python 3 기반으로 다음과 같이 작성하였다.

 

import os
from flask import redirect
from flask import Flask
from flask import request
from apiclient import discovery
from oauth2client import client
import urllib.parse


app = Flask(__name__)

@app.route('/<id>', defaults={'param': None})
@app.route('/<id>/<param>')
def keyword_routing(id, param):

    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'api-project-249965614499-2749b23186fb.json'

    credentials = client.GoogleCredentials.get_application_default()
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?version=v4')
    service = discovery.build('sheets', 'v4', credentials=credentials,
    discoveryServiceUrl=discoveryUrl)

    spreadsheetId = '<Google Sheets 문서 ID>'
    rangeName = 'Sheet1!A3:B200'

    result = service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=rangeName).execute()
    values = result.get('values', [])

    links = {}
    for entry in values:
        for x in entry[0].split(','):
            links[x.strip()] = entry[1]

    print(links)
   
    if id in links:
        if param is None:
          return redirect(links[id])
        else:
          return redirect(links[id] % (urllib.parse.quote(param)))
    else:
        return redirect(links['list'])

if __name__ == "__main__":
    app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))

 

몇 가지 눈여겨 봐야할 할 부분이 있는데, 먼저 이 프로그램에서는 Google Sheets를 액세스해야 하기 때문에 Service Account를 사용했다. JSON 기반의 Key 파일을 만들어 내려 받아, 프로그램 내에서 다음과 같이 해당 Key를 환경변수로 지정한다.

 

 

    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'api-project-249965614499-2749b23186fb.json'

 

키워드 맵핑 테이블이 들어갈 문서에는 Google Sheets에 다음과 같이 해당 문서에 대한 액세스 권한을 주어야 한다.

 

 

Service Account를 이용해서 Google Sheets를 이용하려면 아래와 같이 Sheets API를 이용한다.

 

    credentials = client.GoogleCredentials.get_application_default()
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?version=v4')
    service = discovery.build('sheets', 'v4', credentials=credentials,
    discoveryServiceUrl=discoveryUrl)

 

Google Sheets에서 데이터를 읽어오려면 아래와 같이 코딩한다. 

 

    spreadsheetId = '<Google Sheets 문서 ID>'
    rangeName = 'Sheet1!A3:B200'

    result = service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=rangeName).execute()
    values = result.get('values', [])

 

여기서 문서 ID는 문서를 열었을 때 보이는 아래 고유한 Key 부분이다. 슬래쉬 사이의 문자열만 사용하면 된다. 여기서는 쉬트 ‘Sheet1’의 A3 ~ B200 사이의 영역을 읽어온다. 따라서 만약 키워드 맵핑이 많아지면 이 부분을 수정해야 한다.

 

 

이 프로그램에서는 키워드 컬럼에 컴머로 구분해서 두 개 이상의 키워드를 지정할 수 있고, %s 를 이용하여 추가 파라미터를 맵핑하는 기능도 들어 있다.

 

Google Sheets에 키워드 - URL 맵핑은 다음과 같이 한다. 예를 들어 필자 같은 경우 jl list 라고 하면 본 맵핑 테이블 Sheets가 열리게 했고, jl jlink 라고 입력하면 이 코드를 관리하는 문서가 열리게 했다. jl deploy 라고 입력하면 GCP 상에서 이 Cloud Run 프로그램 관리 콘솔이 열리게 했다.

 

 

그리고 다음과 같이 컴머 기반으로 두 개 이상의 키워드를 하나의 URL에 맵핑할 수 있고, 키워드는 한글도 가능하다.

 

 

Cloud Run 빌드/배포 방법

소스는 다음과 같이 src에 넣었다.

[userid@hostname cloudrun]$ ls -lR
.:
total 16
-rwxr-xr-x. 1 userid gke-69fccc16664a9606b68f  75 May  3  2020 01.build.sh
-rwxr-xr-x. 1 userid gke-69fccc16664a9606b68f 375 May  3  2020 02.test_local.sh
-rwxr-xr-x. 1 userid gke-69fccc16664a9606b68f 121 May  3  2020 03.deploy.sh
-rw-rw-r--. 1 userid gke-69fccc16664a9606b68f 386 Oct 18 09:08 README.md
drwxrwxr-x. 2 userid gke-69fccc16664a9606b68f 111 Jan 21 17:52 src

./src:
total 12
-rw-r--r--. 1 userid gke-69fccc16664a9606b68f 2345 May  3  2020 api-project-249965614499-2749b23186fb.json
-rw-rw-r--. 1 userid gke-69fccc16664a9606b68f  607 May  3  2020 Dockerfile
-rw-rw-r--. 1 userid gke-69fccc16664a9606b68f 1406 Jan 21 17:39 hello.py

 

각 빌드, 로컬테스트, 배포 스크립트를 각각 아래와 같이 만들었다. Cloud Build를 통해서 빌드를 하고 결과를 Container Registry로 올리는 스크립트이다.

$ cat 01.build.sh
cd src
gcloud builds submit --tag gcr.io/api-project-249965/cloudrun

 

일본 리전에 cloudrun이라는 이름으로 배포를 했다.

$ cat 03.deploy.sh
gcloud run deploy --image gcr.io/api-project-249965/cloudrun --platform managed --region asia-northeast1 cloudrun

 

배포를 실행하면 아래와 같이 고유한 URL이 만들어진다.

$ ./03.deploy.sh
Deploying container to Cloud Run service [cloudrun] in project [api-project-249965] region [asia-northeast1]
✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
Done.
Service [cloudrun] revision [cloudrun-00012-jam] has been deployed and is serving 100 percent of traffic at https://cloudrun-n3tlhzi66a-an.a.run.app

 

Custom Search Engine 설정

예를 들어 mk 라는 단어를 Prefix로 사용하려면 아래와 같이 설정한다. Cloud Run Endpoint에 %s를 붙여서 URL 항목에 입력한다.

 

그러면 브라우저 주소 창에 아래와 같이 입력하면 Google Sheets에 해당 키워드에 맵핑된 URL로 이동이 된다.

 

mk doc1

mk doc2




Posted by Hey Jerry
,
반응형

왜 브라우저 자동화가 필요할까?

 

크롬 브라우저를 통해서 웹사이트 작업을 할 때, 자동화를 하고 싶다면?

예를 들어, 나는 네이버 영어 사전의 단어장을 정말 많이 사용한다. 현재 영어 단어장에 저장된 단어의 수가

 

 

영어 공부에 정도는 없는 것 같다. 안해 본 것이 없다. 학원도 1:1 학원도 작문학원도 다녔고 전화 영어도 일주일 30분짜리부터 일주일에 5시간(매일 했다는 얘기)도 해 봤다. 그래서 여러 가지가 발전했지만 문제는 외국에 살지 않는 이상 네이티브처럼 얘기하기는 어렵다. 내가 찾은 최선은 관용어를 포함한 모든 단어를 저장해서 주기적으로 외우는 것이다. 네이버 단어장은 그런 면에서 매우 유용하다. 단, 인터페이스가 약간 구리다~~~ ㅠㅠ

 

하나의 단어장에 대략 100~200개 내외의 단어가 들어 있다. 문제는 단어장에 들어가 암기 반복을 할 때 페이지간 브라우징이 매우 불편하다.

 

긴 페이지를 스크롤 다운해서 아래 ‘다음' 버튼을 눌러야 한다. 그러면 7페이지에 있는 단어로 가려면 무지하게 마우스를 움직여야 해야 한다. 단축키로 할 수는 없을까?

 




Run Javascript 익스텐션

 

클라이언트 UI를 조금 개발해 본 사람이면 브라우저에서 보는 웹페이지에 바로 JavaScript를 실행할 수 있다는 것을 안다. 브라우저 Address 창에 다음과 같이 넣고 엔터를 눌러 봐라. 페이지는 아마 브라우저 Back 버튼을 누른 것과 같은 결과를 볼 것이다. 즉, 아래 JavaScript 코드가 실행된 것이다.

 

 

문제는 이것을 어떻게 키보드와 맵핑하고 그리고 자동화를 할 것인가? 희소식이 있다. Run Javascript 익스텐션이 있다. 아래 링크를 눌러 설치하자.

 

크롬 익스텐션 설치

 

그리고 나서 해당 익스텐션 아이콘을 클릭하면 스크립트를 실행할 수 있는 환경이 된다. 즉, 주소 창에 쳐 넣을 필요가 없는 것이다. 그리고 Enable on 체크박스를 이용해서 특정 페이지가 열리면 자동으로 실행할 수 있게 된다.

 

 

네이버 단어 사전 자동화

 

자 이제 마지막 남은 것은 약간의 JavaScript 지식으로 키보드 클릭(여기서는 Ctrl+’[‘ 버튼과 Ctrl+’]’ 버튼을 활용하겠다)을 감지해서 웹페이지 상에서 원하는 버튼을 눌러야 한다.

 

먼저 웹 페이지에서 원하는 버튼을 찾아가는 방법은 여기를 참고한다.

 

Selenium을 이용한 웹사이트 크롤링 

 

마지막에 개발자 도구에서 다음과 같이 해당 부분을 복사하면 된다.

 

 

그러면 다음과 같이 해당 컴포넌트를 선택하는 스크립트가 생성된다.

 

document.querySelector("#page_area > div > button.btn.btn_prev._prev_page_btn")

 

여기서는 해당 버튼을 클릭하고 싶은 것이므로 .click() 메소드만 추가한다. 다음은 전체 코드이다.

 

document.addEventListener('keydown', function(e) {
    e = e || window.event;
    if(e.ctrlKey && e.key == '[') {
        document.querySelector("#page_area > div > button.btn.btn_prev._prev_page_btn").click();
    }
    else if(e.ctrlKey && e.key == ']') {
        document.querySelector("#page_area > div > button.btn.btn_next._next_page_btn").click();
    }
});

 

잊지말고 아래 체크박스를 활성화 하면 매번 사전이 열릴 때마다 실행 된다.

 

크롬브라우저 동기화를 걸어 놓으면 이 구성(스크립트)은 다른 장비의 브라우저에서도 동일하게 동작한다.

 

Make your browser life easy !!!



모바일에서는?

 

모바일에서 할 방법은 없을까?

모바일에서는 주소창을 활용하는 것이 최선일 것 같다. Bookmark에 javascript: 구문을 넣는 것이다. 다만, 어떻게 이벤트를 발생시킬까 고민이 좀 된다.

https://stackoverflow.com/questions/38425321/injecting-css-or-js-into-chrome-on-android







Posted by Hey Jerry
,
반응형

Selenium을 이용한 웹사이트 크롤링

 

참고

 

설치

 

pip install virtualenv

 

  • 이유를 모르겠지만 Python 3 윈도우 설치 버전에서는 virtualenv.exe가 만들어지지 않아

  • 다음과 같이 스크립트 생성

  • cmd.exe를 관리자 권한으로 실행 (또는 PATH에 걸려 있는 아무 디렉토리에서)

 

cd C:\Program Files\Python37\Scripts
copy con virtualenv.cmd
@echo off
python -m venv %*
^Z     (입력이 아니라 Ctrl+Z를 누르라는 의미)

 

  • virtualenv로 환경을 생성

 

cd selenium
virtualenv .
Scripts\activate.bat

(selenium) C:\Users\javalove93\selenium>



  • Selenium 설치

 

pip install selenium

 

 

테스트

  • 다운 받은 크롬 브라우저 Web Driver를 Python의 Scripts(예: C:\Program Files\Python37\Scripts)로 복사

  • 다음과 같은 테스트 프로그램 실행

 

import selenium
from selenium import webdriver
from selenium.webdriver import ActionChains

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait

URL = 'https://www.naver.com'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.get(url=URL)

 

python selenium_test.py

 

  • 다음과 같이 브라우저가 기동 된다.

 

  • 네이버 로그인이 필요하면 다음과 같이 실행한다.

 

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import pyperclip
import time


#클립보드에 input을 복사한 뒤
#해당 내용을 actionChain을 이용해 로그인 폼에 붙여넣기
def copy_input(xpath, input):
    pyperclip.copy(input)
    driver.find_element_by_xpath(xpath).click()
    ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
    time.sleep(1)


id = '<ID>'
pw = '<PWD>'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.implicitly_wait(3)

driver.get('https://nid.naver.com/nidlogin.login?mode=form&url=https%3A%2F%2Fwww.naver.com')

copy_input('//*[@id="id"]', id)
time.sleep(1)
copy_input('//*[@id="pw"]', pw)
time.sleep(1)
driver.find_element_by_xpath('//*[@id="frmNIDLogin"]/fieldset/input').click()

 

브라우저를 백그라운드로 띄우려면

  • 다음과 같이 headless 옵션을 준다

 

# 옵션 생성
options = webdriver.ChromeOptions()
# 창 숨기는 옵션 추가
options.add_argument("headless")

driver = webdriver.Chrome(executable_path='chromedriver', options = options)



본격적으로 브라우저를 제어해 보자

  • 여기서 시나리오는 네이버 단어장의 모든 단어를 읽어 오는 것이다

  • 다음과 같이 네이버 사전으로 이동한다.

 

time.sleep(2)
driver.get('https://learn.dict.naver.com/wordbook/enkodict/#/my/main')

 

  • 다음과 같은 브라우저 화면이 있다고 가정할 때 ‘전체'에 해당하는 부분을 클릭하고자 한다.

 

  • 브라우저의 개발자 도구를 연다

  • Elements에서 태그들을 확장하다 보면 아래와 같이 해당 부분이 하이라이트 되는 부분을 찾을 수 있다.

  • 오른쪽 버튼을 누른 후 Copy ⇒ Copy Xpath 를 선택한다.

 

 

  • 그리고 해당 부분을 이용해서 다음과 같이 클릭 하도록 한다.

 

time.sleep(1)
driver.find_element_by_xpath('//*[@id="content"]/div[1]/ul/li[1]/span').click()

 

  • 만약 목록(여기서는 ‘단어장 목록'을 제어해야 한다면, 다음과 같이 배열과 for 문을 사용한다.

  • 다음 구문은 단어장에 해당하는 main_folder를 구한 후에 그 안에 있는 <ul>, <li> 태그를 구한다.

  • 실제 Python의 for 문은 0부터 시작하지만, xpath를 통한 태그 indexing은 1부터 시작하므로 i+1을 했다.

  • 여기서는 단어장 클릭을 통해 페이지를 변경했다가, 다시 이전 페이지로 돌아오는 작업을 하기 때문에 for 문 안에서 매번 다시 driver.find_xxx 로 태그를 구하고 있다.

  • find_element_xxx 는 하나의 태그를, find_elements_xxx 는 해당 조건에 해당하는 여러 개의 태그들을 배열로 가져오는 역할을 한다.

 

list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folders = list.find_elements_by_xpath('li')
num_of_folders = len(folders)
for i in range(num_of_folders):
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folder = list.find_element_by_xpath('li[%d]/a' % (i+1))
title = folder.find_element_by_xpath('div/span[2]').text
print("=============================================================")
print("제목 %s" % (title))
folder.click()
time.sleep(1)

 

찾으려는 태그가 존재하지 않으면

  • 브라우저의 컨텐츠에 따라 원하는 태그가 존재하지 않을 수도 있다.

  • 그러면 에러가 발생하므로, 아래와 같이 존재하는지 체크해서 작업을 하게 할 수도 있다.

 

if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

 

부모 엘리먼트 선택

  • 아래에서 span을 기준으로 엘리먼트를 선택한 후에 그것의 부모를 선택

 

# 단어장 이름
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
f = list.find_element_by_xpath('//span[contains(text(), "A01")]')
print(f.text)

# 단어장 클릭
f.find_element_by_xpath('../..').click()
time.sleep(2)




네이버 단어장의 모든 단어를 파일로 저장

  • 궁극적으로 셀레니움을 테스트했던 목적으로 완성된 코드이다.

 

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import pyperclip
import time


#클립보드에 input을 복사한 뒤
#해당 내용을 actionChain을 이용해 로그인 폼에 붙여넣기
def copy_input(xpath, input):
    pyperclip.copy(input)
    driver.find_element_by_xpath(xpath).click()
    ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
    time.sleep(1)


id = '<ID>'
pw = '<PWD>'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.implicitly_wait(3)

driver.get('https://nid.naver.com/nidlogin.login?mode=form&url=https%3A%2F%2Fwww.naver.com')

copy_input('//*[@id="id"]', id)
time.sleep(1)
copy_input('//*[@id="pw"]', pw)
time.sleep(1)
driver.find_element_by_xpath('//*[@id="frmNIDLogin"]/fieldset/input').click()
time.sleep(2)

# 사전으로 이동
driver.get('https://learn.dict.naver.com/wordbook/enkodict/#/my/main')
time.sleep(2)

# 전체 보기 버튼 클릭
if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

f = open('naver_dict.txt', 'w', encoding='utf8')

# 단어장 이름 배열 작성
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folders = list.find_elements_by_xpath('li/a/div/span[2]')
titles = []
for t in folders:
titles.append(t.text)

for i in range(0, len(folders)):
# 단어장 제목 출력
title = titles[i]
print("\n\n\n=============================================================")
f.write("\n\n\n=============================================================\n")
print('%d 번째 단어장' % (i))
f.write('%d 번째 단어장\n' % (i))
print("제목 %s\n\n" % (title))
f.write("제목 %s\n\n\n" % (title))

# 특정 단어장 클릭
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folder = list.find_element_by_xpath('//span[contains(text(), "%s")]' % (title))
folder.find_element_by_xpath('../..').click()
time.sleep(1)

# 각 단어장에서
while True:
time.sleep(1)
if len(driver.find_elements_by_xpath('//*[@id="page_area"]/div/div/span[1]')) == 0:
break
current_page = driver.find_element_by_xpath('//*[@id="page_area"]/div/div/span[1]')
total_page = driver.find_element_by_xpath('//*[@id="page_area"]/div/div/span[3]')
print('\n%s 페이지' % (current_page.text))
f.write('\n%s 페이지\n' % (current_page.text))
print('**********\n')
f.write('**********\n\n')

words = driver.find_elements_by_class_name('card_word')
for w in words:
word = w.find_element_by_xpath('div[1]/div[1]/div/div/a').text
# word = word.replace('·', '').replace('|', '').replace('\u21c4', '').replace('\u30fb', '').replace('\xe1', 'a').replace('\xe0', 'a').replace('\xe9', 'e')
word = word.replace('·', '').replace('|', '').replace('\u21c4', '').replace('\u30fb', '')
print(word)
f.write(word + '\n')

mean = w.find_elements_by_class_name('list_mean')
for m in mean:
mtext = m.text
print(mtext)
f.write(mtext + '\n')
print('------------------------------------------')
f.write('------------------------------------------\n')

f.flush()

if int(current_page.text) >= int(total_page.text):
break
else:
driver.find_element_by_xpath('//*[@id="page_area"]/div/button[2]').click()

# 전체 단어장으로 돌아가기
driver.find_element_by_xpath('//*[@id="title"]').click()
time.sleep(1)

# 전체 보기 버튼 클릭
if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

f.close()
driver.close()

 

결과 및 평가

  • headless는 완벽하게 돌지 않아, 백그라운드 작업이 얼마나 가능할지는 봐야 할 듯

  • 모든 작업이 실제 돌려봐야 알기 때문에, trial & error 디버깅이 쉽지 않아 프로그램 작성 시간이 오래 걸림

  • 그럼에도 웹브라우저 작업을 자동화 할 수 있다는 장점














Posted by Hey Jerry
,
반응형
Ecliipse에서 plugin으로 설치하여 사용할 수 있는 PyDev Python 개발툴 사용법이다.
 
(Eclipse Market Place에서 설치하면 간단)
 
설치하고 아면 Python 프로젝트 또는 Python 모듈을 생성할 수 있다.
 
 

여기에 기존에 사용하던 VirtualEnv 환경을 적용하려면
 

 

Pointing to /venv/env1/bin/python and /venv/env2/bin/python2 should do the trick...
 
VirtualEnv로 만들어진 가상 디렉토리를 직접 Python 환경으로 지정하면 된다.
  • Eclipse Interpreter
 
  • Project 세팅
 
 
 
 
 
 
 
Posted by Hey Jerry
,

Maven Profile + Exec Java

기술 2019. 8. 7. 14:32
반응형
 
참고 웹싸이트
 
샘플 - Profile 샘플
    <!--DetectIntentAudio-->
    <profile>
      <id>DetectIntentAudio</id>
      <activation>
        <property>
          <name>DetectIntentAudio</name>
        </property>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.6.0</version>
            <executions>
              <execution>
                <goals>
                  <goal>java</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <mainClass>com.example.dialogflow.DetectIntentAudio</mainClass>
              <cleanupDaemonThreads>false</cleanupDaemonThreads>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
 
위와 같은 경우 아래와 같이 Eclipse에서 실행 가능
 
 
 
 
 
Posted by Hey Jerry
,
반응형
 
Dialogflow 샘플코드 작성 중... gRPC 인터페이스의 메시지 포맷을 보고 싶어서(IDL 없이 gRPC 기반 API만 받은 경우)
다음과 같은 코드를 작성해 보았다. Dialogflow에 있는 InputAudioConfig 라는 객체를 분해해서 파라미터 포맷을 출력한다.
 
      Iterator<FieldDescriptor> it = InputAudioConfig.getDescriptor().getFields().iterator();
      while(it.hasNext()) {
          FieldDescriptor fd = it.next();
          System.out.println(fd);
      }
      
      it = InputAudioConfig.getDescriptor().getExtensions().iterator();
      while(it.hasNext()) {
          FieldDescriptor fd = it.next();
          System.out.println(fd);
      }
      
      Map<FieldDescriptor, Object> allOptions = InputAudioConfig.getDescriptor().getOptions().getAllFields();
      it = allOptions.keySet().iterator();
      while(it.hasNext()) {
          FieldDescriptor fd = it.next();
          System.out.println(fd);
      }
      
      Iterator<OneofDescriptor> it2 = InputAudioConfig.getDescriptor().getOneofs().iterator();
      while(it2.hasNext()) {
          OneofDescriptor fd = it2.next();
          System.out.println(fd);
      }
 
      it = QueryInput.getDescriptor().getFields().iterator();
      while(it.hasNext()) {
          FieldDescriptor fd = it.next();
          System.out.println(fd);
      }
      
      // Instructs the speech recognizer how to process the audio content.
      InputAudioConfig inputAudioConfig = InputAudioConfig.newBuilder()
          .setAudioEncoding(audioEncoding) // audioEncoding = AudioEncoding.AUDIO_ENCODING_LINEAR_16
          .setLanguageCode(languageCode) // languageCode = "en-US"
          .setSampleRateHertz(sampleRateHertz) // sampleRateHertz = 16000
          .setModel("phone_call")
          .setModelVariant(SpeechModelVariant.USE_ENHANCED)
//          .setModelVariantValue(SpeechModelVariant.USE_ENHANCED_VALUE)
//          .setField(field, value)
          .build();
      
      OutputAudioEncoding outputAudioEncoding = OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_LINEAR_16;
      int outputSampleRateHertz = 16000;
      OutputAudioConfig outputAudioConfig =
          OutputAudioConfig.newBuilder()
              .setAudioEncoding(outputAudioEncoding)
              .setSampleRateHertz(outputSampleRateHertz)
              .build();
      // Build the query with the InputAudioConfig
      QueryInput queryInput = QueryInput.newBuilder().setAudioConfig(inputAudioConfig).build();
 

'기술' 카테고리의 다른 글

Python PyDev IDE 개발툴  (0) 2019.08.07
Maven Profile + Exec Java  (0) 2019.08.07
Window에서 taskmgr(task manager) 대치  (0) 2019.08.07
OpenStack All-In-One 설치 과정  (0) 2019.08.07
TCP 다이어그램과 CLOSE_WAIT + FIN_WAIT  (0) 2019.08.07
Posted by Hey Jerry
,
반응형
 
다음 위치에서
 
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\taskmgr.exe
 
다음과 같이 변경하면 됨
 
이 때 파라미터로 원래 taskmgr.exe 실행 경로가 오므로 주의
 
"C:\Windows\system32\taskmgr.exe"
 
procexp.exe는 파라미터가 있으면 에러가 나므로 batch 파일을 만들어 피해가게 했음
 
 
 
 
 

'기술' 카테고리의 다른 글

Maven Profile + Exec Java  (0) 2019.08.07
gRPC Protobuf 메시지 포맷/필드 보는 샘플 코드  (0) 2019.08.07
OpenStack All-In-One 설치 과정  (0) 2019.08.07
TCP 다이어그램과 CLOSE_WAIT + FIN_WAIT  (0) 2019.08.07
CentOS 7 KVM 설치  (0) 2019.08.07
Posted by Hey Jerry
,
반응형
==> 내부 Lion 서버에 openstack1 이라는 이름으로 설치
 
*** 새로운 설치 (기존 방법 포기하고... 각자 KVM에서 VM 인스턴스를 만들어 설치)
 
1. Baremetal 서버의 Bridge Network 설정
 
먼저 bridge 모듈 설치
yum install bridge-utils
 
KVM Host 서버의 외부 접속 Ethernet을 Bridge로 변경
(10.0.0.100이 할당된 것)
 
[root@Tiger ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth7
DEVICE=eth7
HWADDR=A0:D3:C1:EF:F6:30
TYPE=Ethernet
UUID=ceebc5a7-338d-456b-b752-98cae0d7e700
ONBOOT=yes
BOOTPROTO=static
BRIDGE=br7
 
==> 기존 Ethernet의 IP를 아래 Bridge로 옮김
 
[root@Tiger ~]# cat /etc/sysconfig/network-scripts/ifcfg-br7
DEVICE=br7
TYPE=Bridge
ONBOOT=yes
BOOTPROTO=static
DELAY=0
IPADDR=192.168.0.240
NETMASK=255.255.255.0
GATEWAY=192.168.0.1
 
2. CentOs 7 VM 이미지 생성
  http://www.centos.org/download/ 에서 Minimal Install Image 선택
  
 
  ==> openstack1으로 생성 (마스터 VM 이미지)
3. 생성된 CentOS VM의 NIC 중 하나를 위에 생성된 br7으로 변경
 
virt-manager의 VM 설정 화면에서...
NIC를 선택하고 Device에 'Specify shared device name'
Bridge name에 'br7' (위에서 br7으로 생성)
 
4. CentOS VM 에 연결할 수 있는 VNC 할당
 
다음과 같이 VNC 포트 수정. IP는 KVM Host(Baremetal)의 IP
virsh # edit openstack1
 
    <graphics type='vnc' port='6901' autoport='no' listen='10.0.0.100'>
      <listen type='address' address='10.0.0.100'/>
    </graphics>
그러면 다음과 같이 VNC Listening 하고 접속 가능
 
virsh # vncdisplay openstack1
10.0.0.100:1001
 
[root@Lion ~]# netstat -an | grep 690
tcp        0      0 10.0.0.100:6901              0.0.0.0:*                   LISTEN
 
 
 
파이어월에 의해 69XX 포트가 접속이 안되는 경우가 있으므로 다음과 같이 완전히 OFF 하거나 예외를 추가해야 함
 
파이어월 OFF
systemctl stop firewalld
69XX 포트 예외 추가
[root@server ~]# firewall-cmd --permanent --zone=public --add-port=69XX/tcp
[root@server ~]# firewall-cmd --reload
 
 
5. CentOS VM을 여러 개 복제 생성
 
다음과 같이 virt-clone 툴 설치
 
yum install virt-install.noarch
virt-clone --original openstack1 --name openstack2 --file /var/lib/libvirt/images/openstack2.img
 
4번 과정을 참고해서 각 VM에 맞게 VNC 포트 변경
 
 

6. 각자 VM에서 ifcfg-eth0의 IP 및 MAC addr 변경
 
미리 ip a 또는 ifconfig 명령으로 eth0에 새로 할당된 MAC Addr 확인 후
아래 정보를 각자 환경에 맞게 수정
 
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0
HWADDR="52:54:00:AB:AF:48"
TYPE="Ethernet"
BOOTPROTO="static"
IPADDR=192.168.0.61
GATEWAY=192.168.0.1
NAME="eth0"
UUID="fed9319b-ef74-4be9-a1bd-9044b6d5641a"
ONBOOT="yes"
 
DNS 서버를 세팅
 
[root@localhost ~]# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 10.0.0.2
nameserver 210.94.0.73
 
 
7. PackStack으로 OpenStack 설치
 
root로 로그인 해서
 
ELEP 패키지 Repository 설정
 
OpenStack IceHouse 버전 Repository 설정
 
PackStack 설치
yum install openstack-packstack
 
 
8. Answer 파일 준비
 
다음 파일을 각자 IP에 맞게 수정
 
vi에서 :%s/192.168.25.13/<각 서버 IP>/g
 
 
9. PackStack에 있는 버그 때문에 MongoDB(Ceilometer의 스토리지로) 설치 중 에러
 
Workaround로 다음과 같이 작업
 
cd /var/run/mongodb
ln -s mongodb.pid mongod.pid 
 
 
9. 설치 작업
 
packstack --answer-file=my_answers.txt
 
==> 약 30분 정도 작업
 
대략 다음과 같은 화면이 나오면 All In One 설치 완료
 
 
10. 접속
 
대시보드 접속 (horizon): http://10.0.0.51/
  ==> ID/PW는 다음과 같이 keystonerc_admin 파일에 들어 있음
[root@localhost ~]# cat keystonerc_admin
export OS_USERNAME=admin
export OS_TENANT_NAME=admin
export OS_PASSWORD=780a485e394b4013

export OS_AUTH_URL=http://192.168.0.61:5000/v2.0/
export PS1='[\u@\h \W(keystone_admin)]\$ '
 
 
Nagios 접속
  ==> ID/PW는 다음과 같이 설치 확인 메시지 참고
 * To use Nagios, browse to http://192.168.0.61/nagios username: nagiosadmin, password: 84093c56484344de
 
 
이 패스워드들은 my_answers.txt 파일에 저장이 되어 있으므로 8번 과정에서 미리 Text 파일을 수정해서 원하는 패스워드를 사용할 수도 있음
 
[root@localhost ~]# ls -l
합계 40
-rw-r--r--. 1 root root   196 12월 16 21:37 a.txt
-rw-------. 1 root root   936 12월 16 20:34 anaconda-ks.cfg
-rw-r--r--. 1 root root   120 12월 16 21:36 b.txt
-rw-------. 1 root root   179 12월 16 22:41 keystonerc_admin
-rw-------. 1 root root   176 12월 16 22:41 keystonerc_demo
-rw-r--r--. 1 root root 17628 12월 16 22:02 my_answers.txt

 
11. OpenStack 설치 및 동작 상태 확인
 
[root@localhost ~]# source keystonerc_admin
==> Admin 유저 환경 변수 세팅
 
[root@localhost ~(keystone_admin)]# openstack-status
== Nova services ==
openstack-nova-api:                     active
openstack-nova-cert:                    active
openstack-nova-compute:                 active
openstack-nova-network:                 inactive  (disabled on boot)
openstack-nova-scheduler:               active
openstack-nova-volume:                  inactive  (disabled on boot)
openstack-nova-conductor:               active
== Glance services ==
openstack-glance-api:                   active
openstack-glance-registry:              active
== Keystone service ==
openstack-keystone:                     active
== Horizon service ==
openstack-dashboard:                    active
== neutron services ==
neutron-server:                         active
neutron-dhcp-agent:                     active
neutron-l3-agent:                       active
neutron-metadata-agent:                 active
neutron-lbaas-agent:                    active
neutron-openvswitch-agent:              active
neutron-linuxbridge-agent:              inactive  (disabled on boot)
neutron-ryu-agent:                      inactive  (disabled on boot)
neutron-nec-agent:                      inactive  (disabled on boot)
neutron-mlnx-agent:                     inactive  (disabled on boot)
== Swift services ==
openstack-swift-proxy:                  active
openstack-swift-account:                active
openstack-swift-container:              active
openstack-swift-object:                 active
== Cinder services ==
openstack-cinder-api:                   active
openstack-cinder-scheduler:             active
openstack-cinder-volume:                active
openstack-cinder-backup:                active
== Ceilometer services ==
openstack-ceilometer-api:               active
openstack-ceilometer-central:           active
openstack-ceilometer-compute:           active
openstack-ceilometer-collector:         active
openstack-ceilometer-alarm-notifier:    active
openstack-ceilometer-alarm-evaluator:   active
== Heat services ==
openstack-heat-api:                     active
openstack-heat-api-cfn:                 active
openstack-heat-api-cloudwatch:          inactive  (disabled on boot)
openstack-heat-engine:                  active
== Support services ==
libvirtd:                               active
openvswitch:                            active
dbus:                                   active
tgtd:                                   inactive  (disabled on boot)
rabbitmq-server:                        active
memcached:                              active
== Keystone users ==
+----------------------------------+------------+---------+----------------------+
|                id                |    name    | enabled |        email         |
+----------------------------------+------------+---------+----------------------+
| d0628dbf8841486d92550a1a411ea6aa |   admin    |   True  |    root@localhost    |
| 6ae674f8ba5047a98a427f043bfac5b4 | ceilometer |   True  | ceilometer@localhost |
| 33ce7a39194244e49b965368d5c77722 |   cinder   |   True  |   cinder@localhost   |
| d1f5c5d4810e4731ad8f335f6dd237bb |    demo    |   True  |                      |
| 5dcd12aceafc4bf8adaf34a657612490 |   glance   |   True  |   glance@localhost   |
| 3e3a0c38357e44fa8f2439b46e3d02b4 |    heat    |   True  |    heat@localhost    |
| bd5cde239e874dc9bbae326981434035 |  heat-cfn  |   True  |  heat-cfn@localhost  |
| dbeb4a46c31d4fa29130b1e19e781f86 | heat_admin |   True  |                      |
| 8884d2eab5b54ceb94deab9c3e45d3db |  neutron   |   True  |  neutron@localhost   |
| 6878401b2b0a43659eec2880317c119e |    nova    |   True  |    nova@localhost    |
| 140ae099f0e341c182d033992c37c9c0 |   swift    |   True  |   swift@localhost    |
+----------------------------------+------------+---------+----------------------+
== Glance images ==
+--------------------------------------+--------+-------------+------------------+----------+--------+
| ID                                   | Name   | Disk Format | Container Format | Size     | Status |
+--------------------------------------+--------+-------------+------------------+----------+--------+
| c874b9cd-d74d-43d2-8acb-703a2a70324d | cirros | qcow2       | bare             | 13147648 | active |
+--------------------------------------+--------+-------------+------------------+----------+--------+
== Nova managed services ==
+------------------+-----------------------+----------+---------+-------+----------------------------+-----------------+
| Binary           | Host                  | Zone     | Status  | State | Updated_at                 | Disabled Reason |
+------------------+-----------------------+----------+---------+-------+----------------------------+-----------------+
| nova-consoleauth | localhost.localdomain | internal | enabled | up    | 2014-12-17T12:23:41.000000 | -               |
| nova-scheduler   | localhost.localdomain | internal | enabled | up    | 2014-12-17T12:23:44.000000 | -               |
| nova-conductor   | localhost.localdomain | internal | enabled | up    | 2014-12-17T12:23:45.000000 | -               |
| nova-compute     | localhost.localdomain | nova     | enabled | up    | 2014-12-17T12:23:43.000000 | -               |
| nova-cert        | localhost.localdomain | internal | enabled | up    | 2014-12-17T12:23:46.000000 | -               |
+------------------+-----------------------+----------+---------+-------+----------------------------+-----------------+
== Nova networks ==
+--------------------------------------+---------+------+
| ID                                   | Label   | Cidr |
+--------------------------------------+---------+------+
| 4ace4285-e254-4c20-b5c2-e07c3cf7939d | private | -    |
| b0e95e38-9762-4774-9946-fd221ca54a48 | public  | -    |
+--------------------------------------+---------+------+
== Nova instance flavors ==
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| ID | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| 1  | m1.tiny   | 512       | 1    | 0         |      | 1     | 1.0         | True      |
| 2  | m1.small  | 2048      | 20   | 0         |      | 1     | 1.0         | True      |
| 3  | m1.medium | 4096      | 40   | 0         |      | 2     | 1.0         | True      |
| 4  | m1.large  | 8192      | 80   | 0         |      | 4     | 1.0         | True      |
| 5  | m1.xlarge | 16384     | 160  | 0         |      | 8     | 1.0         | True      |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
== Nova instances ==
+----+------+--------+------------+-------------+----------+
| ID | Name | Status | Task State | Power State | Networks |
+----+------+--------+------------+-------------+----------+
+----+------+--------+------------+-------------+----------+
 
 
 
 
Posted by Hey Jerry
,