Newer
Older
radiko_rec_server / rec_radiko_live.sh
@kuro kuro on 28 Oct 2022 6 KB version 1.0
#!/bin/sh
#
# Radiko Live recorder
# Copyright (C) 2017-2019 uru (https://twitter.com/uru_2)
# License is MIT (see LICENSE file)
set -u

radiko_session=""

# Define authorize key value (from http://radiko.jp/apps/js/playerCommon.js)
readonly AUTHKEY_VALUE="bcd151073c03b352e1ef2fd66c32209da9ca0afa"

#######################################
# Show usage
# Arguments:
#   None
# Returns:
#   None
#######################################
show_usage() {
  cat << _EOT_
Usage: $(basename "$0") [options]
Options:
  -s STATION      Station ID (see http://radiko.jp/v3/station/region/full.xml)
  -d MINUTE       Record minute
  -m ADDRESS      Radiko premium mail address
  -p PASSWORD     Radiko premium password
  -o FILEPATH     Output file path
_EOT_
}

#######################################
# Radiko Premium Login
# Arguments:
#   Mail address
#   Password
# Returns:
#   0: Success
#   1: Failed
#######################################
login() {
  mail=$1
  password=$2

  # Login
  login_json=$(curl \
      --silent \
      --request POST \
      --data-urlencode "mail=${mail}" \
      --data-urlencode "pass=${password}" \
      --output - \
      "https://radiko.jp/v4/api/member/login" \
    | tr -d "\r" \
    | tr -d "\n")

  # Extract login result
  radiko_session=$(echo "${login_json}" | extract_login_value "radiko_session")
  areafree=$(echo "${login_json}" | extract_login_value "areafree")

  # Check login
  if [ -z "${radiko_session}" ] || [ "${areafree}" != "1" ]; then
    return 1
  fi

  return 0
}

#######################################
# Extract login JSON value
# Arguments:
#   (pipe)Login result JSON
#   Key
# Returns:
#   None
#######################################
extract_login_value() {
  name=$1

  # for gawk
  #value=$(cat - | gawk -v "name=${name}" 'BEGIN { FS = "\n"; } { regex = "\""name"\"[ ]*:[ ]*(\"[0-9a-zA-Z]+\"|[0-9]*)"; if (!match($0, regex, v)) { exit 0; } val=v[1]; if (match(val, /\"([0-9a-zA-Z]*)\"/, v)) { val=v[1]; } print val; }')

  value=$(cat - \
    | awk -v "name=${name}" '
      BEGIN {
        FS = "\n";
      }
      {
        # Extract key and value
        regex = "\""name"\"[ ]*:[ ]*(\"[0-9a-zA-Z]+\"|[0-9]*)";
        if (!match($1, regex)) {
          exit 0;
        }
        str = substr($0, RSTART, RLENGTH);

        # Extract value
        regex = "\""name"\"[ ]*:[ ]*";
        match(str, regex);
        str = substr(str, RSTART + RLENGTH);

        # String value
        if (match(str, /^\"[0-9a-zA-Z]+\"/)) {
          print substr(str, RSTART + 1, RLENGTH - 2);
          exit 0;
        }

        # Numeric value
        if (match(str, /^[0-9]*/)) {
          print substr(str, RSTART, RLENGTH);
          exit 0;
        }
      }')

  echo "${value}"
  return 0
}

#######################################
# Radiko Premium Logout
# Arguments:
#   None
# Returns:
#   None
#######################################
logout() {
  # Logout
  curl \
    --silent \
    --request POST \
    --data-urlencode "radiko_session=${radiko_session}" \
    --output /dev/null \
    "https://radiko.jp/v4/api/member/logout"
  radiko_session=""
  return 0
}

#######################################
# Finalize program
# Arguments:
#   None
# Returns:
#   None
#######################################
finalize() {
  if [ -n "${radiko_session}" ]; then
    logout
  fi
  return 0
}

#######################################
# Format time text
# Arguments:
#   Time minute
# Returns:
#   None
#######################################
format_time() {
  minute=$1

  hour=$((minute / 60))
  minute=$((minute % 60))

  printf "%02d:%02d:%02d" "${hour}" "${minute}" "0"
}

# Define argument values
station_id=
duration=
mail=
password=
output=

# Argument none?
if [ $# -lt 1 ]; then
  show_usage
  finalize
  exit 1
fi

# Parse argument
while getopts s:d:m:p:o: option; do
  case "${option}" in
    s)
      station_id="${OPTARG}"
      ;;
    d)
      duration="${OPTARG}"
      ;;
    m)
      mail="${OPTARG}"
      ;;
    p)
      password="${OPTARG}"
      ;;
    o)
      output="${OPTARG}"
      ;;
    \?)
      show_usage
      finalize
      exit 1
      ;;
  esac
done

# Check argument parameter
if [ -z "${station_id}" ]; then
  # -s value is empty
  echo "Require \"Station ID\"" >&2
  finalize
  exit 1
fi
if [ -z "$(echo "${duration}" | awk '/^[0-9]+$/ {print $0}')" ]; then
  # -d value is invalid
  echo "Invalid \"Record minute\"" >&2
  finalize
  exit 1
fi

# Login premium
if [ -n "${mail}" ]; then
  login "${mail}" "${password}"
  ret=$?

  if [ ${ret} -ne 0 ]; then
    echo "Cannot login Radiko premium" >&2
    finalize
    exit 1
  fi
fi

# Authorize 1
auth1_res=$(curl \
    --silent \
    --header "X-Radiko-App: pc_html5" \
    --header "X-Radiko-App-Version: 0.0.1" \
    --header "X-Radiko-Device: pc" \
    --header "X-Radiko-User: dummy_user" \
    --dump-header - \
    --output /dev/null \
    "https://radiko.jp/v2/api/auth1")

# Get partial key
authtoken=$(echo "${auth1_res}" | awk 'tolower($0) ~/^x-radiko-authtoken: / {print substr($0,21,length($0)-21)}')
keyoffset=$(echo "${auth1_res}" | awk 'tolower($0) ~/^x-radiko-keyoffset: / {print substr($0,21,length($0)-21)}')
keylength=$(echo "${auth1_res}" | awk 'tolower($0) ~/^x-radiko-keylength: / {print substr($0,21,length($0)-21)}')

if [ -z "${authtoken}" ] || [ -z "${keyoffset}" ] || [ -z "${keylength}" ]; then
  echo "auth1 failed" >&2
  finalize
  exit 1
fi

partialkey=$(echo "${AUTHKEY_VALUE}" | dd bs=1 "skip=${keyoffset}" "count=${keylength}" 2> /dev/null | base64)

# Authorize 2
auth2_url_param=""
if [ -n "${radiko_session}" ]; then
  auth2_url_param="?radiko_session=${radiko_session}"
fi
curl \
    --silent \
    --header "X-Radiko-Device: pc" \
    --header "X-Radiko-User: dummy_user" \
    --header "X-Radiko-AuthToken: ${authtoken}" \
    --header "X-Radiko-PartialKey: ${partialkey}" \
    --output /dev/null \
    "https://radiko.jp/v2/api/auth2${auth2_url_param}"
ret=$?

if [ ${ret} -ne 0 ]; then
  echo "auth2 failed" >&2
  finalize
  exit 1
fi

# Get playlist URL
areafree="0"
if [ -n "${radiko_session}" ]; then
  areafree="1"
fi
playlist=$(curl \
    --silent \
    "http://radiko.jp/v2/station/stream_smh_multi/${station_id}.xml" \
  | xmllint --xpath "/urls/url[@areafree='${areafree}'][1]/playlist_create_url/text()" -)

if [ -z "${playlist}" ]; then
  echo "Cannot get playlist URL" >&2
  finalize
  exit 1
fi

# Generate default file path
if [ -z "${output}" ]; then
  output="${station_id}_$(date +%Y%m%d%H%M%S).m4a"
else
  # Fix file path extension
  echo "${output}" | grep -q "\\.m4a$"
  ret=$?

  if [ ${ret} -ne 0 ]; then
    # Add .m4a
    output="${output}_$(date +%Y%m%d%H%M%S).m4a"
  fi
fi

# Record
ffmpeg \
    -loglevel error \
    -fflags +discardcorrupt \
    -headers "X-Radiko-Authtoken: ${authtoken}" \
    -i "${playlist}" \
    -acodec copy \
    -vn \
    -bsf:a aac_adtstoasc \
    -y \
    -t "$(format_time "${duration}")" \
    "${output}"

# Finish
finalize
exit 0