radikron

package module
v0.7.3 Latest Latest
Warning

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

Go to latest
Published: Nov 26, 2025 License: GPL-3.0 Imports: 27 Imported by: 0

README

radikron

radikron

build status docker status

docker image size godoc codecov go report

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

Disclaimer:

  • Never use this program for commercial purposes.

Features

radikron is a powerful, automated radio program downloader for radiko available in both CLI and GUI versions. Both versions share the same core logic, ensuring consistent behavior and reliability.

🖥️ Dual Interface Support
  • CLI Version: Lightweight command-line interface for servers and automation
  • GUI Version: Modern graphical interface built with Wails v2 for desktop users
  • Shared Core: Both versions use the same engine, ensuring consistent behavior
🎯 Smart Rule-Based Matching

Flexible rules to automatically capture programs using multiple criteria: title/keyword matching, performer filtering, station selection, day-of-week and time-window filters.

📁 Flexible File Organization
  • Rule-based folder organization with configurable download directories
  • Support for AAC (default) and MP3 formats
  • Automatic ID3 tagging with program metadata
🛡️ Intelligent Download Management
  • Duplicate detection and file size validation
  • Automatic retry with exponential backoff for failed downloads
  • Concurrent downloads and automatic cleanup of stale failures
➕ Manual Program Injection

Manually add any program to the download queue (past or future), with persistent storage and automatic retry. GUI users can easily manage injections through the interface.

🌐 Multi-Region & Continuous Monitoring
  • Area-based station filtering with support for extra stations from other regions
  • Continuous background monitoring with scheduled fetching
  • Graceful shutdown that waits for active downloads
🖱️ GUI Features

Configuration management, station browser, program search with filtering, scheduled downloads view, monitoring control, and real-time activity logs.

config-editor program-search

🐳 Docker Support

Pre-built Docker images with all dependencies included, ready for easy deployment.

Requirements

Common Requirements
  • FFmpeg: Required 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.
GUI-Specific Requirements (for building from source)

If you're building the GUI version from source (Linux users), you'll also need:

  • Go 1.20+
  • Node.js and pnpm
  • Wails v2: Install with go install github.com/wailsapp/wails/v2/cmd/wails@latest

Note: macOS and Windows users can download pre-built binaries and don't need these build tools.

Installation

CLI Version

Install the command-line version:

go install github.com/iomz/radikron/cmd/radikron@latest
GUI Version
macOS and Windows Users

Pre-built binaries are available for macOS and Windows. Download the latest release from the Releases page:

  • macOS: Download the .dmg file for your architecture (Intel or Apple Silicon)
  • Windows: Download the .exe file for your architecture (x64 or ARM64)
Linux Users

Linux users need to build from source. See the GUI README for detailed setup instructions.

Quick start for building on Linux:

# Install frontend dependencies
cd cmd/radikron-gui/frontend
pnpm install

# Return to GUI directory
cd ..

# Run in development mode
wails dev

# Or build for production
wails build

Configuration

Create a configuration file (config.yml) to define rules for recording. The configuration supports various options to customize your download behavior:

Configuration Options
  • area-id: Your region code (e.g., JP13 for Tokyo). If unset, defaults to your detected region.
  • file-format: Output audio format - aac (default) or mp3.
  • downloads: Directory path for downloaded files (default: $HOME/Downloads/radiko on all platforms).
  • extra-stations: List of station IDs to include even if they're not in your region.
  • ignore-stations: List of station IDs to exclude from monitoring.
  • minimum-output-size: Minimum file size in MB (default: 1 MB). Files smaller than this are rejected as potentially corrupted.
  • max-downloading-concurrency: Maximum number of concurrent download operations (default: 64). Only included in config if different from default.
  • max-encoding-concurrency: Maximum number of concurrent MP3 encoding operations (default: 2). Set lower than downloading concurrency since encoding is CPU-intensive. Only included in config if different from default.
Rule Configuration

Each rule can use one or more of the following matching criteria (all support partial matching):

  • title: Match programs by title
  • keyword: Match programs containing the keyword in title or description
  • pfm: Match programs by personality/performer name
  • station-id: Filter by specific station (also adds the station to watch list if not in your region)
  • dow: Filter by day of week (e.g., mon, tue, wed, thu, fri, sat, sun)
  • window: Time window filter (e.g., 48h for last 48 hours, 7d for last 7 days)
  • folder: (Optional) Organize downloads for this rule into a subfolder

Rules are evaluated with AND logic - a program must match all specified criteria in a rule.

Important: Rule order matters! When a program matches multiple rules, the first matching rule (in the order they appear in your config file) determines which folder the file is saved to. This allows you to prioritize certain rules by placing them earlier in your configuration.

Example Configuration
area-id: JP13 # if unset, default to "your" region
file-format: aac # audio format: aac or mp3, default is aac
downloads: ~/Downloads/radiko # download directory path, default is "$HOME/Downloads/radiko"
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)
# max-downloading-concurrency: 64  # Maximum concurrent download operations (default: 64)
# max-encoding-concurrency: 2  # Maximum concurrent encoding operations for MP3 conversion (default: 2)
rules:
  midday: # name your rule as you like
    folder: "MIDDAY LOUNGE" # (optional) organize downloads into subfolders
    station-id: FMJ # (optional) the station_id, if not available by default, automatically add this station to the watch list
    title: "MIDDAY LOUNGE" # 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 (i.e., the DJ/MC)
  trad:
    dow: # filter by day of the week (e.g, Mon, tue, WED)
      - wed
      - thu
    station-id: FMT
    title: "THE TRAD"

The base directory for downloads defaults to $HOME/Downloads/radiko (cross-platform: ~/Downloads/radiko on Unix/macOS, %USERPROFILE%\Downloads\radiko on Windows). If the home directory cannot be determined, it falls back to ./radiko in the current working directory.

ID3 Tags

All downloaded audio files (both AAC and MP3) are automatically tagged with ID3v2 metadata:

  • Title: File base name (format: YYYY-MM-DD-HHMM_StationID_ProgramTitle)
  • Artist: Program personality/performer (pfm)
  • Album: Program title
  • Year: Program start year
  • Comment: Program information (info)
  • Album Artist: Rule name (if the program matched a rule)

These tags are embedded in both AAC and MP3 files, making it easy to organize and identify your downloaded programs in music players and media libraries.

Usage

CLI Usage

Basic Usage:

Simply run radikron with your configuration file:

radikron -c config.yml

By default, radikron will use $HOME/Downloads/radiko as the download directory. Temporary files are stored in the system temporary directory.

Note: radikron automatically creates all necessary directories (download directories, subfolders, and temporary directories) when needed. You don't need to create them manually.

The application will:

  • Connect to radiko and authenticate
  • Fetch program schedules for all monitored stations
  • Match programs against your configured rules
  • Download matching programs automatically
  • Tag files with ID3 metadata
  • Continue monitoring and downloading on a schedule

Command-Line Options:

  • -c <file>: Specify the configuration file (default: config.yml)
  • -d: Enable debug mode with detailed logging
  • -v: Print version information

Running as a Service:

radikron is designed to run continuously. It automatically:

  • Schedules the next fetch time based on program availability
  • Waits for downloads to complete before checking again
  • Handles interruptions gracefully (waits for in-progress downloads on shutdown)

For production use, consider running it as a systemd service or using a process manager like supervisord.

GUI Usage

The GUI version provides a visual interface for managing radikron:

  1. Launch the application: Run the built binary or use wails dev for development
  2. Load configuration: Use the configuration panel to load your config.yml file
  3. View stations: Browse available radio stations in your region
  4. Search programs: Use the program search browser to find programs across all stations
  5. Manual injection: Add programs manually to the download queue (useful for one-off downloads or programs that don't match your rules)
  6. View scheduled downloads: See all scheduled downloads, including manual injections (marked with a "Manual" badge)
  7. Start monitoring: Click "Start Monitoring" to begin automatic downloading
  8. Monitor activity: Watch real-time updates in the activity log

Manual Injection Workflow:

  • Search for programs using the program search browser
  • Click on a program to view details
  • Click "Inject" to add it to the download queue
  • Past programs will download immediately; future programs will be scheduled
  • View all scheduled downloads (including manual injections) in the Scheduled Downloads panel
  • Delete manual injections if needed (they will be removed from the queue)

The GUI shares the same configuration format and behavior as the CLI version, so you can use the same config.yml file with both interfaces.

For detailed GUI setup and development instructions, see the GUI README.

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 started off from 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
	// Language for ID3v2 tags
	ID3v2LangJPN = "jpn"
	// LatLng for Japan
	JapanLatLng = 40.0
	// Kilobytes for the metric bytes
	Kilobytes = 1024
	// MaxDownloadingConcurrency limits concurrent download operations
	MaxDownloadingConcurrency = 64
	// MaxEncodingConcurrency limits concurrent encoding operations (MP3 conversion)
	// Set lower than MaxDownloadingConcurrency since encoding is CPU-intensive
	MaxEncodingConcurrency = 2
	// MaxRetryAttempts for BackOffDelay
	MaxRetryAttempts = 8
	// OneDay is 24 hours
	OneDay = 24
	// OutputDatetimeLayout for downloaded files
	OutputDatetimeLayout = "2006-01-02-1504"
	// TZTokyo for time location
	TZTokyo = "Asia/Tokyo"
	// UserIDLength for user-id
	UserIDLength = 16
	// PlaylistM3U8Length parameter for m3u8 playlist requests
	PlaylistM3U8Length = "15"
	// DirPermissions for directory creation (0755 = rwxr-xr-x)
	DirPermissions = 0755

	// 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
	RadikoKeyLengthHeader = "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)

func GetRadikronPath added in v0.7.2

func GetRadikronPath(path string) (string, error)

GetRadikronPath resolves a provided path (or defaults to the user's Downloads/radiko directory). If a relative path is provided, it's resolved relative to the current working directory. If an absolute path is provided, it's used as-is. If no path is provided, it defaults to the user's Downloads/radiko directory, with a fallback to the current working directory/radiko if the home directory cannot be determined.

func InitSemaphores added in v0.6.3

func InitSemaphores(asset *Asset)

InitSemaphores initializes or updates the semaphores based on the asset's concurrency settings. This should be called when configuration is applied to ensure semaphores match the config.

func NewOutputConfig added in v0.7.2

func NewOutputConfig(fileBaseName, fileFormat, downloadDir, folder string) (*radigo.OutputConfig, error)

NewOutputConfig prepares the outputdir

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
	DownloadDir       string // directory name for downloads (default: "radiko")
	Regions           Regions
	Rules             Rules
	Schedules         Schedules
	Stations          Stations
	Versions          Versions
	// MaxDownloadingConcurrency limits concurrent download operations
	MaxDownloadingConcurrency int
	// MaxEncodingConcurrency limits concurrent encoding operations (MP3 conversion)
	MaxEncodingConcurrency int
}

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(ctx context.Context, areaID string) (*Device, error)

NewDevice returns a pointer to a new authorized Device

func (*Asset) RemoveIgnoreStations

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(ctx context.Context, a *Asset, areaID string) error

type Devices

type Devices map[string]*Device

type EventEmitter added in v0.7.0

type EventEmitter interface {
	// EmitDownloadStarted emits when a download starts
	EmitDownloadStarted(stationID, title, startTime, uri string)
	// EmitDownloadCompleted emits when a download completes successfully (file written to disk)
	EmitDownloadCompleted(stationID, title, startTime, filePath string)
	// EmitFileSaved emits when a file is fully saved with metadata tags
	EmitFileSaved(stationID, title, filePath string)
	// EmitDownloadSkipped emits when a download is skipped (duplicate, already exists, etc.)
	EmitDownloadSkipped(reason string, stationID, title, startTime string)
	// EmitEncodingStarted emits when encoding to MP3 starts
	EmitEncodingStarted(filePath string)
	// EmitEncodingCompleted emits when encoding to MP3 completes successfully
	EmitEncodingCompleted(filePath string)
	// EmitLogMessage emits a general log message (for backward compatibility)
	EmitLogMessage(level string, message string)
}

EventEmitter defines the interface for emitting structured events. Implementations can provide structured events to external systems (e.g., GUI).

func GetEventEmitter added in v0.7.0

func GetEventEmitter(ctx context.Context) EventEmitter

GetEventEmitter retrieves the EventEmitter from context, if available. Returns nil if no emitter is set in context (CLI mode).

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
	RuleName          string // name of the rule that matched this program
	RuleFolder        string // folder from the rule that matched this program
	IsManualInjection bool   // true if this program was manually injected
}

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
	Folder    string   `mapstructure:"folder"`     // 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

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

func (*Rule) MatchKeyword

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

func (*Rule) MatchPfm

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

func (*Rule) MatchSilent added in v0.7.0

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

MatchSilent returns true if the rule matches the program without logging

func (*Rule) MatchStationID

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

func (*Rule) MatchTitle

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

func (*Rule) MatchWindow

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

func (*Rule) SetName

func (r *Rule) SetName(name string)

type Rules

type Rules []*Rule

func (Rules) FindMatch added in v0.6.0

func (rs Rules) FindMatch(stationID string, p *Prog) *Rule

FindMatch returns the first matching rule for the given station and program

func (Rules) FindMatchSilent added in v0.7.0

func (rs Rules) FindMatchSilent(stationID string, p *Prog) *Rule

FindMatchSilent returns the first matching rule without logging This is useful when checking for matches on programs that may be skipped

func (Rules) HasMatch

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

func (Rules) HasRuleForStationID

func (rs Rules) HasRuleForStationID(stationID string) bool

func (Rules) HasRuleWithCriteria added in v0.7.2

func (rs Rules) HasRuleWithCriteria() bool

HasRuleWithCriteria returns true if at least one rule has optional criteria (Title, Pfm, or Keyword)

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
radikron command
radikron-gui command
internal

Jump to

Keyboard shortcuts

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