node.js

[node.js] EBS 외국어 라디오 녹음 with 편성표

이의있소 2019. 10. 10. 13:43


관련지식
node.js, javascript, atd, crontab, request, moment

이전에 만들었던 EBS 외국어 라디오 편성표 데이터를 이용하여 자동으로 녹음하는 기능을 만들어보겠습니다. 이번에는 준비가 좀 필요합니다.

  1. 라디오 녹음을 위한 shell script 준비
    https://velvio.tistory.com/219 를 참고하여 녹음을 하는 shell을 하나 만드셔야 합니다. 블로그 내용은 Debian chroot 를 기준으로 되어있지만 docker 에 ubuntu 를 설치해서 사용해도 되고, 그냥 리눅스에서도 됩니다. 가장 중요한것은 ffmpeg 가 설치되어있어야 하는것입니다.
  2. atd 설치
    보통 crontab은 익숙하지만 atd는 생소하실수도 있겠습니다. crontab는 주기적으로 실행하는 스케쥴 관리라면, atd는 단 한번만 수행하는 예약 실행 개념입니다. 만약 설치가 안되신 분은 사용가능하도록 해두셔야 합니다.
    1. sudo apt-get install at

변수 정의

먼저 변수가 필요하겠죠. url 엔 EBS 편성표 데이터를 JSON으로 가공한 주소를 넣으면 됩니다. 각자의 환경에 맞게 입력하시면 되고, 혹시 제가 사용하는 URL이 필요하신 분은 연락주시면 알려드리겠습니다.

  1. let url = ''; //EBS 편송표를 가공한 데이터URL
  2. let record_program = ['EBS 착!붙는 중국어', '박나리 미카미의 문화탐방 여행 일본어']; //녹음하고 싶은 방송의 이름
  3. let shell_path = '/root/radio_record/ebs_record.sh'; //EBS 녹음 스크립트
  4. let map = [];
  5. let now = new Date().getTime();
  6. let yyyymmdd = moment(now).format('YYYYMMDD');
  7. for(let i = 0; i < record_program.length; i++) {
  8. let program = record_program[i];
  9. map[program] = {};
  10. }

record_program 에는 녹음하고 싶은 방송의 이름을 넣으면 됩니다. 이때의 방송명은 데이터에 있는 방송 이름과 완전히 동일해야 합니다. shell_path는 ffmpeg를 이용하여 녹음하는 쉘 스크립트의 경로입니다. 제가 참고했던 블로그의 쉘스크립트를 조금 변형해서 사용하고 있기 때문에, 제가 쓰는 쉘 스크립트는 밑에서 별도로 공개하겠습니다.

변수 map은 자료구조의 Hash와 같은 기능으로 사용할 예정입니다. 방송명이 key가 됩니다.

로직

먼저 JSON으로 가공된 EBS 편성표 데이터를 request 패키지로 조회합니다.

  1. request.get(url, async function(err, resp, body) {
  2. let schedule_list = JSON.parse(body);
  3. for(let i = 0; i < schedule_list.length; i++) {
  4. let program = schedule_list[i];
  5. }
  6. });

EBS 외국어 라디오는 하루에 2,3 번씩 동일한 방송을 틀어줍니다. 당연히 모든 방송을 녹음할 필요는 없겠죠. 현재 시간을 기준으로 가장 빠른 방송만 녹음할 것입니다.

  1. //현재 시간 이후 방송중에서 녹음하고 싶은 방송인지 확인. 이미 가장 빠른시간 하나만 저장한다.
  2. if(now < program.timestamp && map[program.title] && !map[program.title].timestamp) {
  3. map[program.title] = program;
  4. }

골라낸 방송을 linux 명령어로 실행하여 예약 실행되도록 할것입니다. 실행될 리눅스 명령어는 아래의 형태가 될 것입니다.

  1. echo '녹음쉘스크립트 "방송제목 - 방송내용" 녹음길이' | at 시:분 년-월-일
  1. let title = `${program.title} - ${yyyymmdd} ${program.part}`;
  2. title = title.replace(/\//g, ',');
  3. let atTime = moment(program.timestamp).format('HH:mm YYYY-MM-DD');
  4. let cmd = `echo '${shell_path} "${title}" ${program.running}' | at ${atTime}`;
  5. console.log(cmd);
  6. let { stdout, stderr } = await exec(cmd);
  7. if(stdout)
  8. console.log(stdout);
  9. if(stderr)
  10. console.error(stderr);

만약 쉘스크립트의 파라미터가 저와 다르다면 파라미터에 맞춰서 위 소스를 수정하면 됩니다. 제 경우엔 아래와 같이 실행될것입니다.

  1. echo '/root/radio_record/ebs_record.sh "EBS 착!붙는 중국어 - 20191001 2강 중국어 발음1-2" 1500' | at 20:00 2019-10-01

실행 확인

node schedule로 실행한 후 at -l 또는 atq 명령어를 실행하여 등록된 작업이 있는지 확인합니다. 변수 record_program 에 지정한 방송 갯수만큼 등록되어야 합니다.

  1. root@chinese:~# at -l
  2. 28 Tue Oct 1 17:00:00 2019 a root
  3. 29 Tue Oct 1 20:00:00 2019 a root

만약 등록되는 내역이 없다면 방송제목은 동일하게 입력했는지, 현재 시간 이후에 방송 예정이 있는지, 쉘 스크립트는 정상 실행되었는지 확인이 필요합니다.

정상 동작하는것을 확인했다면 crontab, 부팅쉘 등 원하시는곳에 등록해서 사용하시면 됩니다.

저는 월요일부터 토요일까지 아침 10시에 실행되도록 crontab에 등록하였습니다.

  1. 00 10 * * 1-6 node /root/radio_record/schedule >> /root/cron.log

최종소스

ebs_record.sh)

  1. #!/bin/sh
  2. PROGRAM_NAME=$1
  3. RECORD_SECS=$2
  4. RADIO_ADDR="rtsp://new_iradio.ebs.co.kr:554/iradio/iradiolive_m4a"
  5. TEMP_PATH="/tmp/`date +%H%M%S%N`.m4a"
  6. DEST_PATH="/chinese_record/$PROGRAM_NAME.m4a"
  7. echo "run `date`" >> ~/record.log
  8. ffmpeg -rtsp_transport tcp -i $RADIO_ADDR -t $RECORD_SECS -codec:a copy -vn -metadata title="$PROGRAM_NAME" -metadata date=`date +%F` $TEMP_PATH
  9. mv "$TEMP_PATH" "$DEST_PATH"

schedule.sh)

  1. const request = require('request');
  2. const moment = require('moment');
  3. const util = require('util');
  4. const exec = util.promisify(require('child_process').exec);
  5. let url = ''; //EBS 편송표를 가공한 데이터URL
  6. let record_program = ['EBS 착!붙는 중국어', '박나리 미카미의 문화탐방 여행 일본어']; //녹음하고 싶은 방송의 이름
  7. // let record_program = ['박나리 미카미의 문화탐방 여행 일본어']; //녹음하고 싶은 방송의 이름
  8. let shell_path = '/root/radio_record/ebs_record.sh'; //EBS 녹음 스크립트
  9. let map = [];
  10. let now = new Date().getTime();
  11. let yyyymmdd = moment(now).format('YYYYMMDD');
  12. for(let i = 0; i < record_program.length; i++) {
  13. let program = record_program[i];
  14. map[program] = {};
  15. }
  16. request.get(url, async function(err, resp, body) {
  17. let schedule_list = JSON.parse(body);
  18. for(let i = 0; i < schedule_list.length; i++) {
  19. let program = schedule_list[i];
  20. //현재 시간 이후 방송중에서 녹음하고 싶은 방송인지 확인. 이미 가장 빠른시간 하나만 저장한다.
  21. if(now < program.timestamp && map[program.title] && !map[program.title].timestamp) {
  22. map[program.title] = program;
  23. let title = `${program.title} - ${yyyymmdd} ${program.part}`;
  24. title = title.replace(/\//g, ',');
  25. let atTime = moment(program.timestamp).format('HH:mm YYYY-MM-DD');
  26. let cmd = `echo '${shell_path} "${title}" ${program.running}' | at ${atTime}`;
  27. console.log(cmd);
  28. let { stdout, stderr } = await exec(cmd);
  29. if(stdout)
  30. console.log(stdout);
  31. if(stderr)
  32. console.error(stderr);
  33. }
  34. }
  35. });