Category Archives: Code

Sometimes I code, sometimes I don’t

Library tools for RadioDJ

Important note about compatibility

This plug-in is outdated. It hasn’t been updated since June 2016 (version and will only function in RadioDJ up to version 1.8.2. This is mostly due to how the PluginInterface was implemented by Marius and because there’s no publicly available documentation or source code for potential plug-in developers.

RadioDJ v2.0.0.x and newer is not supported and I don’t have any plans to resume development of plug-ins for RadioDJ.

RadioDJ Library Tools plug-in provides an overview of available tracks in each category and is intended as a guide for building rotations and troubleshooting track quantity issues.

The plug-in started as an SQL query for RadioDJ library overview. It all began on this RadioDJ forum thread.
After initial release *life happened* and I completely neglected plug-in development. I realised that that original forum thread is the only place this plug-in was published. Many RadioDJ users may be completely oblivious to plug-in’s existence so I’m posting it here.

RadioDJ Library Tools 1.0
 RadioDJ Library Tools 1.0

Version 1.0
Initial release

ENHANCEMENT: Option to enter a minimum number of tracks.
ENHANCEMENT: Added keyboard shortcuts for refreshing the table (F5 or Ctrl+R)
ENHANCEMENT: Option to view SQL script used by the plugin in Help > The SQL
ENHANCEMENT: Option to save selected cells in table to CSV or HTML file

ENHANCEMENT: Added second tab to view data for genres. Grouping by subcategory is done using DataGridViewGrouper and that’s why rendering is slow. I may remove the grouping if there are enough complaints.

Version 2016-06-27 @ 23:44 GMT
BUGFIX: Exception is thrown when RadioDJ is closing
BUGFIX: Exception is thrown when collapsing groups in genres grid
ENHANCEMENT: Low track count highlighting in genres grid

Grab it here:

I hope that some of you will find the plug-in useful.

Folder Loader plugin for RadioDJ

Important note about compatibility

This plug-in is outdated. It hasn’t been updated since April 2015 (version when RadioDJ v1.8.0 was released and will only function in RadioDJ up to version 1.8.2. This is mostly due to how the PluginInterface was implemented by Marius and because there’s no publicly available documentation or source code for potential plug-in developers.
RadioDJ v2.0.0.x and up is not supported and I don’t have any plans to resume development of plug-ins RadioDJ.

Unfortunately, I’ve left the mostly welcoming and friendly RadioDJ community. In some part it was because I had left the radio station and then there were some personal issues I had to sort out.

This is my first RadioDJ plugin and also happens to be my first C# project.

There is a dedicated topic on forums which contains detailed changelog and may be more up to date than this post.

The idea came about after reading a post by PHAZE FM on RadioDJ forums. He needed to clear a category and then import all files from a directory into that category. Unfortunately, RadioDJ does not have that kind of functionality. That is until I decided to create a RadioDJ plugin which can do exactly that.

Folder Loader plugin adds two event actions for use in scheduled events:

  1. Clear Subcategory – deletes songs from a subcategory specified by category and subcategory name or ID as argument. It will delete song records from database, so it’s up to you to decide if it suits your needs.
  2. Load Files From Folder – imports all supported file formats from a directory, placing them in a subcategory, assigning genre and track type specified by name or ID as arguments.

Example action for importing tracks from “C:News” into main category “News”, subcategory “Latest News”,  assign “News” genre and set type to “News”:

PluginAction|Folder Loader|Load Files From Folder|C:News;cat=News&subcat=Latest News&genre=News&type=News

There is just one option – Recurse subfolders. It directs the plugin to scan for files in all subdirectories of specified directory.


If genre is set to AutoGenre, plugin will try to read genre from ID3 tags. This may slow down import process.
Cue point data can be imported from file ID3 tags (if saved previously) using ID3cue=yes.
Do not add the ID3cue parameter, since plugin checks for its presence, not its value. Reading cue data from ID3 tags may slow down the import process.
e.g. to import tracks with cue points and genres from ID3 tags, action would be

PluginAction|Folder Loader|Load Files From Folder|C:News;cat=News&subcat=Latest News&type=News&genre=AutoGenre&ID3cue=yes

Get it here: Plugin_FolderLoader

To start using it, unzip downloaded file to Plugins directory and restart RadioDJ.

Zip files with PHP and upload to Dropbox

Important note: Dropbox API v1 is deprecated and will be disabled on June 28, 2017.
I’ll update this post as soon as I have time.

I needed a quick and dirty cheap backup solution for ever growing directory of MySQL backups.

Some were already backed up on Dropbox. So, I decided to throw together a PHP script which could upload those sql files without having to rely on Dropbox client. Then use cron to launch the script after daily MySQL dump. Few web searches later I found DropboxUploader on GitHub but I didn’t like its approach. Official API seems more elegant as it uses OAuth 2 authentication.


First of all, take a look at introduction on using Dropbox API with PHP, then download Dropbox SDK for PHP.

Register an app on Dropbox App Console to get API key and secret. Paste values into config.json.


Include Dropbox API autolaoder and define dbx alias for \Dropbox namespace:

# Include the Dropbox SDK libraries
require_once __DIR__."/dropbox-sdk/lib/Dropbox/autoload.php";
use \Dropbox as dbx;

I didn’t bother with the authentication part because I will use the “app” only for this script. I just generated access token in App Console and hardcoded it in my script.

$accessToken = 'NotARealTokenXQAAAAAAAAABmKadnX7wFdbUyIllvKphLHSXPqBNotARealToken';

createZip function

To make things easier, I wrapped the compression code in a function.

 * Add array of $files to $output_file archive and deletes the
 * files after successful archiving
 * @param $files array Array of files to zip
 * @param $output_file string Archive path
 * @return string|boolean Path of zip file or FALSE if anything failed
function createZip($files, $output_file){

	$zip = new ZipArchive();

	if ($zip->open($output_file, ZipArchive::CREATE)!==TRUE) {
		exit("cannot open '$output_file'".PHP_EOL);
	$to_delete = array();
	foreach($files as $file){
		if(!file_exists($file) || !is_readable($file)){
		if($zip->addFile($file, basename($file))){
			$to_delete[] = $file;
	$status = $zip->status;
	$success = $zip->close();

	// Delete files after ZipArchive::close() because ZipArchive locks files
	foreach($to_delete as $file){

	return $status == 0 && $success ? $output_file : FALSE;

Load Dropbox client and set access token

$appInfo = dbx\AppInfo::loadFromJsonFile(__DIR__."/config.json");
$dbxClient = new dbx\Client($accessToken, "MySQLbumps-BACKUP/1.0");

And the main part

Zipping and uploading

// define some required paths
define('TEMP_DIR', sys_get_temp_dir());
// Local backup location. Change path as needed
define('BACKUPS_DIR', '/var/backup/mysql/');

// Find latest file and upload so that there is at least one fresh backup
$latest = glob(BACKUPS_DIR.'*_'.date('Y-m-d').'*.sql');
$latest_file = reset($latest);

if($latest_file && file_exists($latest_file)){
	echo "Found latest backup: '".basename($latest_file)."'".PHP_EOL;
	$temp_zip = TEMP_DIR.'';
	// delete old zip file if it exists otherwise the file will be added to that zip
	$upload_file = createZip(array($latest_file), $temp_zip);
	$f = fopen($temp_zip, "rb");
	$result = $dbxClient->uploadFile('/MySQL backups/', dbx\WriteMode::force(), $f);
		echo "Uploaded ".basename($temp_zip).PHP_EOL;

// Check if there are any files from past month
$date_part = date('Y-m', strtotime('last month'));
$search = BACKUPS_DIR.'*_'.$date_part.'*.sql';
$files = glob($search);
	echo "Nothing to do: no files match '$search'".PHP_EOL;

$zip_file = 'Backup_'.$date_part.'.zip';
echo("Creating $zip_file file for MySQL dumps from last month".PHP_EOL);
$upload_file = createZip($files, BACKUPS_DIR.$zip_file);
if(!$upload_file || !file_exists($upload_file)){
	$msg = 'Could not create zip archive';

echo("Uploading $zip_file to Dropbox\n");
$f = fopen($upload_file, "rb");
$result = $dbxClient->uploadFile('/MySQL backups/'.$zip_file, dbx\WriteMode::force(), $f);

I have set the script to run right after nightly MySQL dump. Now I can sleep in peace knowing that I’ll have all backups from previous months and one from last night if anything goes wrong.

Have any suggestions? Add them in comments sections.

Migrate SAM Broadcaster data to RadioDJ

As expected, I’m getting sick of all the bugs in SAM Broadcaster and lack of support from Spacial. I’ve decidet to swith Radio H2O to RadioDJ The idea had been on my mind for some time, but after reading a RadioDJ forum post by NounosSon I decided to start working on it. After all – how hard can it be; It is only data after all.

This solution generates a valid MySQL SQL script from SAMBC Firebird or MySQL database if WORK_MODE in config.php is set to WORK_MODE_FILE or inserts data from SAMBC database directly into RDJ MySQL database if WORK_MODE is set to WORK_MODE_INSERT. In latter case you have to make sure the database and its tables exist. I highly suggest to try WORK_MODE_FILE first, so you don’t mess up you RDJ database. While exporting data it is possible to convert between character sets if SAM_CHARSET and TARGET_CHARSET are set to correct values.

Note: Charset conversion can be disable by setting SAM_CHARSET and CHARESET to same value.

To run the script, shut down SAMBC. You can even completely uninstall SAMBC, because all we need is the data from database and your data belongs to you, not Spacial.

If you use direct import option – WORK_MODE_INSERT, you have to shut down RadioDJ and please back up your data.

Version 0.1

Exports only historylist data from SAMBC. Few fields are missing from resulting data:

  1. id_subcat (sub category reference);
  2. disk_no (no disc numbers in SAMDB);
  3. original_artist;
  4. copyright;

I had to fiddle a bit with genre data, but got it working after all. If the genre from SAMBC songlist matches a genre in RDJ genre table the matching genre_id will be exported. Comparison is done case-insensitive, so it should be ok if case is different from song to song.

Imortant! Make sure you’ve set RDJ “Keep History For” under “Stream Titles” settings tab to a higher value before starting migration, otherwise the history data will be lost on next RDJ startup.

Version 0.2

Can export historylist and songlist data from SAMBC. For now song data will be exported without category data. The problem is that RadioDJ does not have multilevel categories like SAMBC does. I had a conversation with Marius about this and he promised to look if and how he could implement that in RDJ. All SAMBC xfade settings will be translated to RDJ cue_times, but all songs will have to be placed in correct subcategories.


  • Esport SAMBC categories as RadioDJ playlists. If only RDJ rotations allowed to select songs from playlists, I’d be ready to migrate.
  • Create RDJ MySQL tables if needed
  • Check songlist entries for moved/removed files before import

The source is available on GitHub.

SAM Broadcaster Firebird to MySQL migration

While managing SAM Broadcaster for Radio H2O I got sick of all the bugs and charset incompatibilities between filesystem and Firebird DB dealing with other SAMBC quirks. So, I decided to migrate SAMBC data from FirebirdSQL to MySQL to see if it could help.  In short; it didn’t solve main problem, because SAMBC messes up everything by using Windows default code page for its internal workings. Never the less, it was a nice exercise in coding for two databases and charset conversion.

The script requires a recent PHP version and PDO extension. It can be run from CLI as well as web script in Apache.

Fork or download it from GitHub.