radicron

package module
v0.2.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2023 License: GPL-3.0 Imports: 25 Imported by: 0

README

radicron

build status docker status

docker image size godoc codecov go report

Sometimes we miss our favorite shows on radiko and they get vanished from http://radiko.jp/#!/timeshift – let's just keep them automatically saved locally, from AoE.

Disclaimer:

  • Never use this program for commercial purposes.

Requirements

radicron requires FFmpeg to combine m3u8 chunks to a single aac file (or convert to mp3).

Make sure ffmpeg exists in your $PATH.

The docker image already contains all the requirements including ffmpeg.

Installation

go install github.com/iomz/radicron/cmd/radicron@latest

Configuration

Create a configuration file (config.yml) to define rules for recording:

area-id: JP13 # if unset, default to "your" region
extra-stations:
  - ALPHA-STATION # include stations not in your region
ignore-stations:
  - JOAK # ignore stations from search
minimum-output-size: 2 # do not save an audio below this size (in MB), default is 1 (MB)
rules:
  airship: # name your rule as you like
    station-id: FMT # (optional) the staion_id, if not available by default, automatically add this station to the watch list
    title: "GOODYEAR MUSIC AIRSHIP~シティポップ レイディオ~" # this can be a partial match
  citypop:
    keyword: "シティポップ" # search by keyword (also a partial match)
    window: 48h # only within the past window from the current time
  hiccorohee:
    pfm: "ヒコロヒー" # search by pfm
  trad:
    dow: # filter by day of the week (e.g, Mon, tue, WED)
      - wed
      - thu
    station-id: FMT
    title: "THE TRAD"

In addition, set ${RADICRON_HOME} to set the download directory.

Usage

mkdir -p ./radiko/{downloads,tmp} && RADICRON_HOME=./radiko radicron -c config.yml

Try with Docker

By default, it mounts ./config.yml and ./radiko to the container.

docker compose up

Build the image yourself

In case the image is not available for your platform:

docker compose build

Credit

This project is heavily based on yyoshiki41/go-radiko and yyoshiki41/radigo, and therefore follows the GPLv3 License.

Documentation

Index

Constants

View Source
const (
	// BufferMinutes for fetching the playlist.m3u8 chunks
	BufferMinutes = 5
	// DatetimeLayout for time strings from radiko
	DatetimeLayout = "20060102150405"
	// DefaultArea for radiko are
	DefaultArea = "JP13"
	// RetryDelaySecond for initial delay
	DefaultInitialDelaySeconds = 60
	// DefaultInterval to fetch the programs
	DefaultInterval = "168h"
	// DefaultMinimumOutputSize
	DefaultMinimumOutputSize = 1
	// Environment Variable for RADICRON_HOME
	EnvRadicronHome = "RADICRON_HOME"
	// Language for ID3v2 tags
	ID3v2LangJPN = "jpn"
	// Kilobytes for the metric bytes
	Kilobytes = 1024
	// DefaultMaxConcurrents
	MaxConcurrency = 64
	// MaxRetryAttempts for BackOffDelay
	MaxRetryAttempts = 8
	// OneDay is 24 hours
	OneDay = 24
	// OutputDatetimeLayout for downloaded files
	OutputDatetimeLayout = "200601021504"
	// TZTokyo for time location
	TZTokyo = "Asia/Tokyo"
	// UserIDLength for user-id
	UserIDLength = 16

	// API endpoints
	// region full
	APIRegionFull    = "https://radiko.jp/v3/station/region/full.xml"
	APIPlaylistM3U8  = "https://radiko.jp/v2/api/ts/playlist.m3u8"
	APIWeeklyProgram = "https://radiko.jp/v3/program/station/weekly/%s.xml"

	// HTTP Headers
	// auth1 req
	UserAgentHeader        = "User-Agent"
	RadikoAreaIDHeader     = "X-Radiko-AreaId"
	RadikoAppHeader        = "X-Radiko-App"
	RadikoAppVersionHeader = "X-Radiko-App-Version"
	RadikoDeviceHeader     = "X-Radiko-Device"
	RadikoUserHeader       = "X-Radiko-User"
	// auth1 res
	RadikoAuthTokenHeader = "X-Radiko-AuthToken" //nolint:gosec
	RadikoKeyLentghHeader = "X-Radiko-KeyLength"
	RadikoKeyOffsetHeader = "X-Radiko-KeyOffset"
	// auth2 req
	RadikoConnectionHeader = "X-Radiko-Connection"
	RadikoLocationHeader   = "X-Radiko-Location"
	RadikoPartialKeyHeader = "X-Radiko-Partialkey"
)

Variables

View Source
var (
	// Base64FullKey holds the /assets/flutter_assets/assets/key/android.jpg in the v8 APK
	//go:embed assets/base64-full.key
	Base64FullKey embed.FS
	// CoordinatesJSON is a JSON contains the base GPS locations
	//go:embed assets/coordinates.json
	CoordinatesJSON embed.FS
	// RegionsJSON is a JSON contains the region mapping
	//go:embed assets/regions.json
	RegionsJSON embed.FS
	// VersionsJSON is a JSON contains the valid SDK versions
	//go:embed assets/versions.json
	VersionsJSON embed.FS
)
View Source
var (
	CurrentTime time.Time
	Location    *time.Location
)

Functions

func Download

func Download(
	ctx context.Context,
	wg *sync.WaitGroup,
	prog *Prog,
) (err error)

Types

type Area

type Area struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

type Asset

type Asset struct {
	AvailableStations []string
	AreaDevices       Devices
	Base64Key         string
	Coordinates       Coordinates
	DefaultClient     *radiko.Client
	// MinimumOutputSize in bytes for the downloaded audio
	MinimumOutputSize int64
	NextFetchTime     *time.Time
	OutputFormat      string
	Regions           Regions
	Rules             Rules
	Schedules         Schedules
	Stations          Stations
	Versions          Versions
}

func GetAsset

func GetAsset(ctx context.Context) *Asset

func NewAsset

func NewAsset(client *radiko.Client) (*Asset, error)

func (*Asset) AddExtraStations

func (a *Asset) AddExtraStations(es []string)

AddExtraStations appends stations to AvailableStations

func (*Asset) GenerateGPSForAreaID

func (a *Asset) GenerateGPSForAreaID(areaID string) string

GenerateGPS returns the RadikoLocationHeader GPS string e.g., "35.689492,139.691701,gps"

func (*Asset) GetAreaIDByStationID

func (a *Asset) GetAreaIDByStationID(stationID string) string

GetAreaIDByStationID returns the first AreaID for the station

func (*Asset) GetPartialKey

func (a *Asset) GetPartialKey(offset, length int64) (string, error)

GetPartialKey returns the partial key for auth2 API

func (*Asset) GetStationIDsByAreaID

func (a *Asset) GetStationIDsByAreaID(areaID string) []string

GetStationIDsByAreaID returns a slice of StationIDs

func (*Asset) LoadAvailableStations

func (a *Asset) LoadAvailableStations(areaID string)

LoadAvailableStations loads up the avaialable stations

func (*Asset) NewDevice

func (a *Asset) NewDevice(areaID string) (*Device, error)

NewDevice returns a pointer to a new authorized Device

func (*Asset) RemoveIgnoreStations added in v0.2.11

func (a *Asset) RemoveIgnoreStations(is []string)

RemoveIgnoreStations remove stations from AvailableStations

func (*Asset) UnmarshalJSON

func (a *Asset) UnmarshalJSON(b []byte) error

UnmarshalJSON loads up Coordinates with Regions

type ContextKey

type ContextKey string

type Coordinate

type Coordinate struct {
	Lat float64
	Lng float64
}

type Coordinates

type Coordinates map[string]*Coordinate

type Device

type Device struct {
	AppName    string
	AppVersion string
	AuthToken  string
	Connection string
	Name       string
	UserAgent  string
	UserID     string
}

func (*Device) Auth

func (d *Device) Auth(a *Asset, areaID string) error

type Devices

type Devices map[string]*Device

type Prog

type Prog struct {
	ID        string
	StationID string
	Ft        string
	To        string
	Title     string
	Desc      string
	Info      string
	Pfm       string
	Tags      []string
	Genre     ProgGenre
	M3U8      string
}

Prog contains the solicited program metadata

type ProgGenre

type ProgGenre struct {
	Personality string
	Program     string
}

type Progs

type Progs []*Prog

Progs is a slice of Prog.

func FetchWeeklyPrograms

func FetchWeeklyPrograms(stationID string) (Progs, error)

FetchWeeklyPrograms returns the weekly programs.

func (*Progs) UnmarshalXML

func (ps *Progs) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error

type Regions

type Regions map[string][]Area

type Rule

type Rule struct {
	Name      string   `mapstructure:"name"`       // required
	Title     string   `mapstructure:"title"`      // required if pfm and keyword are unset
	DoW       []string `mapstructure:"dow"`        // optional
	Keyword   string   `mapstructure:"keyword"`    // optional
	Pfm       string   `mapstructure:"pfm"`        // optional
	StationID string   `mapstructure:"station-id"` // optional
	Window    string   `mapstructure:"window"`     // optional
}

func (*Rule) HasDoW

func (r *Rule) HasDoW() bool

func (*Rule) HasKeyword

func (r *Rule) HasKeyword() bool

func (*Rule) HasPfm

func (r *Rule) HasPfm() bool

func (*Rule) HasStationID

func (r *Rule) HasStationID() bool

func (*Rule) HasTitle

func (r *Rule) HasTitle() bool

func (*Rule) HasWindow

func (r *Rule) HasWindow() bool

func (*Rule) Match

func (r *Rule) Match(stationID string, p *Prog) bool

Match returns true if the rule matches the program 1. check the Window filter 2. check the DoW filter 3. check the StationID 4. match the criteria

func (*Rule) MatchDoW added in v0.2.9

func (r *Rule) MatchDoW(ft string) bool

func (*Rule) MatchKeyword added in v0.2.9

func (r *Rule) MatchKeyword(p *Prog) bool

func (*Rule) MatchPfm added in v0.2.9

func (r *Rule) MatchPfm(pfm string) bool

func (*Rule) MatchStationID added in v0.2.9

func (r *Rule) MatchStationID(stationID string) bool

func (*Rule) MatchTitle added in v0.2.9

func (r *Rule) MatchTitle(title string) bool

func (*Rule) MatchWindow added in v0.2.10

func (r *Rule) MatchWindow(ft string) bool

func (*Rule) SetName

func (r *Rule) SetName(name string)

type Rules

type Rules []*Rule

func (Rules) HasMatch

func (rs Rules) HasMatch(stationID string, p *Prog) bool

func (Rules) HasRuleForStationID

func (rs Rules) HasRuleForStationID(stationID string) bool

func (Rules) HasRuleWithoutStationID

func (rs Rules) HasRuleWithoutStationID() bool

type SDK

type SDK struct {
	ID     string   `json:"sdk"`
	Builds []string `json:"builds"`
}

type Schedules

type Schedules []*Prog

func (Schedules) HasDuplicate

func (ss Schedules) HasDuplicate(prog *Prog) bool

type Station

type Station struct {
	Areas []string
	Name  string
	Ruby  string
}

type Stations

type Stations map[string]*Station

type Versions

type Versions struct {
	Apps   []string        `json:"apps"`
	Models []string        `json:"models"`
	SDKs   map[string]*SDK `json:"sdks"`
}

type XMLProg

type XMLProg struct {
	ID    string `xml:"id,attr"`
	Ft    string `xml:"ft,attr"`
	To    string `xml:"to,attr"`
	Title string `xml:"title"`
	Desc  string `xml:"desc"`
	Info  string `xml:"info"`
	Pfm   string `xml:"pfm"`
	Tag   struct {
		Item []XMLProgItem `xml:"item"`
	} `xml:"tag"`
	Genre struct {
		Personality XMLProgItem `xml:"personality"`
		Program     XMLProgItem `xml:"program"`
	} `xml:"genre"`
}

XMLProg contains the raw program metadata

type XMLProgItem

type XMLProgItem struct {
	ID   string `xml:"id,attr,omitempty"`
	Name string `xml:"name"`
}

type XMLProgs

type XMLProgs struct {
	Date string     `xml:"date"`
	Prog []*XMLProg `xml:"prog"`
}

type XMLRegion

type XMLRegion struct {
	Region []XMLRegionStations `xml:"stations"`
}

func FetchXMLRegion

func FetchXMLRegion() (XMLRegion, error)

type XMLRegionStation

type XMLRegionStation struct {
	ID     string `xml:"id"`
	Name   string `xml:"name"`
	AreaID string `xml:"area_id"`
	Ruby   string `xml:"ruby"`
}

type XMLRegionStations

type XMLRegionStations struct {
	Stations   []XMLRegionStation `xml:"station"`
	RegionID   string             `xml:"region_id,attr"`
	RegionName string             `xml:"region_name,attr"`
}

type XMLWeekly

type XMLWeekly struct {
	XMLName     xml.Name `xml:"radiko"`
	XMLStations struct {
		XMLName xml.Name           `xml:"stations"`
		Station []XMLWeeklyStation `xml:"station"`
	} `xml:"stations"`
}

type XMLWeeklyStation

type XMLWeeklyStation struct {
	StationID string   `xml:"id,attr"`
	Name      string   `xml:"name"`
	Progs     XMLProgs `xml:"progs"`
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL