FLAC to MP3 Conversion
Here is my custom Python script to convert FLAC files to MP3 files.
In addition to the obligatory audio conversion, it also handles tag conversion. Because of this, the external eyeD3 module is required.
Take a look:
#!/usr/bin/python import subprocess import eyed3 import os import os.path import re import struct import getopt temp_tags = r"/tmp/___TAGS___.utf8" temp_cover = r"/tmp/___COVER___.jpg" def apic_from_unicode_to_latin1(filename): data = open(filename, "rb").read() match = re.search('APIC(......)\x01image/jpeg\x00\x03\xff\xfe\x00\x00', data) if match: header = match.group(1) # Just inject FIX as picture description to keep same frame size! old = 'APIC' + header + '\x01image/jpeg\x00\x03\xff\xfe\x00\x00' new = 'APIC' + header + '\x00image/jpeg\x00\x03FIX\x00' data = data.replace(old, new) fh = open(filename, "wb") fh.write(data) fh.close() class TagData(object): def __init__(self): self.title = None self.album = None self.track_num = None self.artist = None self.genre = None self.date = None self.album_artist = None def read_flac_tags(self, filename): subprocess.call(["metaflac", "--no-utf8-convert", "--export-tags-to=" + temp_tags, filename]) fh = open(temp_tags, "rb") for line in fh: match = re.match(r'^TITLE=(.*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.title = match.group(1) match = re.match(r'^ALBUM=(.*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.album = match.group(1) match = re.match(r'^TRACKNUMBER=(\d*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.track_num = int(match.group(1)) match = re.match(r'^ARTIST=(.*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.artist = match.group(1) match = re.match(r'^GENRE=(.*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.genre = match.group(1) match = re.match(r'^DATE=(\d*)', line.decode('UTF-8'), re.IGNORECASE) if match: self.date = int(match.group(1)) fh.close() def write_mp3_tags(self, filename): audiofile = eyed3.load(filename) audiofile.tag = eyed3.id3.Tag() # Create NEW! if self.title: audiofile.tag.title = self.title if self.album: audiofile.tag.album = self.album if self.track_num: audiofile.tag.track_num = self.track_num if self.artist: audiofile.tag.artist = self.artist if self.genre: audiofile.tag.genre = self.genre if self.date: audiofile.tag.recording_date = self.date # Works if version 2.3 if self.album_artist: audiofile.tag.album_artist = self.album_artist audiofile.tag.save(filename, eyed3.id3.ID3_V2_3) def print_usage(progname): print "Usage: %s " % (progname) print "-f <FLAC File>" print "-a [AlbumArtist]" print "-c [Cover Image Path]" print "-n [Album Name Override]" if __name__ == "__main__": import sys tagdata = TagData() # NOTE: Need to filter out empty strings from sys.argv. # If not, the parser thinks parsing finishes early. try: opts, args = getopt.getopt(filter(None, sys.argv[1:]), "hf:a:c:n:", ["help", "flacfile=", "albumartist=", "cover=", "albumname="]) except getopt.GetoptError as err: print str(err) print_usage(sys.argv[0]) sys.exit(1) print "Opts:", opts print "Args:", args filename = None album_artist = None cover_override = None album_name_override = None for o, a in opts: if o in ("-h", "--help"): print_usage(sys.argv[0]) sys.exit(1) elif o in ("-f", "--flacfile"): filename = a print "FLAC File:", filename elif o in ("-a", "--albumartist"): album_artist = a print "AlbumArtist:", album_artist elif o in ("-c", "--cover"): cover_override = a print "Cover Image Path:", cover_override elif o in ("-n", "--albumname"): album_name_override = a print "Album Name Override:", album_name_override if filename == None: print_usage(sys.argv[0]) sys.exit(1) # Set optional AlbumArtist. if album_artist: tagdata.album_artist = unicode(album_artist) # Read old tags from FLAC. tagdata.read_flac_tags(filename) # Set optional album name override, AFTER reading default tags. if album_name_override: tagdata.album = unicode(album_name_override) # Decode FLAC, encode MP3. subprocess.call(["flac", "-d", filename, "-o", filename + ".wav"]) subprocess.call(["lame", "-b", "320", "-h", filename + ".wav", filename + ".mp3"]) # Write new tags to MP3. tagdata.write_mp3_tags(filename + ".mp3") # Get cover image from argument, or... if cover_override: cover_file = cover_override else: # ...attempt to extract cover image from FLAC file... subprocess.call(["metaflac", "--export-picture-to=" + temp_cover, filename]) cover_file = temp_cover # ...then apply it. if os.path.isfile(cover_file): imagedata = open(cover_file, "rb").read() if imagedata.startswith("\xff\xd8\xff"): audiofile = eyed3.load(filename + ".mp3") audiofile.tag.images.set(3, imagedata, "image/jpeg") audiofile.tag.save() else: print "Warning: Image data is not JPEG." # iTunes APIC fix, encoding must be changed to latin1. apic_from_unicode_to_latin1(filename + ".mp3") # Remove ".flac" part of final MP3 file. if os.path.isfile(filename + ".mp3"): os.rename(filename + ".mp3", filename.replace(".flac", "") + ".mp3") # Remove temporary files. if os.path.isfile(filename + ".wav"): os.remove(filename + ".wav") if os.path.isfile(temp_tags): os.remove(temp_tags) if os.path.isfile(temp_cover): os.remove(temp_cover)
Rename by Reference
Another specialized tool for a specialized task, this Python script renames files in a directory based on names from another directory. The pattern matching is only based on matching substrings. If a filename fits as a substring within another filename from the reference directory, then that reference filename is selected as a possible choice. Filename extensions are excluded automatically.
Take a look:
#!/usr/bin/python import os import glob def rename_by_reference(target_dir, ref_dir): ref = dict() for f in glob.glob(os.path.join(ref_dir, "*")): if os.path.isdir(f): (name, ext) = (os.path.basename(f), "") else: (name, ext) = os.path.splitext(os.path.basename(f)) ref[name] = ext for f in glob.glob(os.path.join(target_dir, "*")): if os.path.isdir(f): (name, ext) = (os.path.basename(f), "") else: (name, ext) = os.path.splitext(os.path.basename(f)) for (ref_name, ref_ext) in ref.iteritems(): if name == ref_name: continue # Equal, not interesting... if name in ref_name: print name + ext, "--->", ref_name + ext answer = raw_input("Rename? ").lower() if answer.startswith("q"): return elif answer.startswith("y"): src = f dst = os.path.join(os.path.dirname(f), ref_name + ext) os.rename(src, dst) break if __name__ == "__main__": import sys if len(sys.argv) < 3: print "Usage: %s <target dir> <reference dir>" % (sys.argv[0]) sys.exit(1) rename_by_reference(sys.argv[1], sys.argv[2]) sys.exit(0)
AVR Alarm Clock
Here is an implementation of an alarm clock running on an Atmel AVR (ATMega8) microcontroller. It uses a 8-digit LCD display for output, a 3x4 button keypad for input and a buzzer to give an alarm.
Here is a circuit diagram of the whole setup:

Get a higher resolution schematic here.
The software on the microcontroller is written entirely in AVR Assembly language. It features a menu to view/set the clock and view/set/enable the alarm and the buzzer. The clock itself is updated by a timer interrupt in the microcontroller, and is not very accurate when running on the internal clock unfortunately. Perhaps an external crystal would improve it, but I never got around to trying it due to lack of hardware.
I used the AVRA assembler and AVRDUDE utility against a Atmel STK500 board for development. Easy with this Makefile:
clock.hex clock.eep.hex: clock.asm avra clock.asm -o clock.hex # Needs to be in the correct order, Flash first, then EEPROM second. .PHONY: install install: clock.hex clock.eep.hex avrdude -c stk500v2 -p m8 -P /dev/ttyS0 -U flash:w:clock.hex:i avrdude -c stk500v2 -p m8 -P /dev/ttyS0 -U eeprom:w:clock.eep.hex:i .PHONY: clean clean: rm clock.hex clock.eep.hex clock.obj clock.cof
Here is the code (clock.asm):
; ***************************************************************************** ; AVR ALARM CLOCK ; ***************************************************************************** .NOLIST .INCLUDE "m8def.inc" ; ATMega8 .LIST ; ============================================================================= ; REGISTER DEFINITIONS ; ============================================================================= .DEF second = r0 ; Clock second. .DEF minute = r1 ; Clock minute. .DEF hour = r2 ; Clock hour. .DEF alarm_active = r3 ; Alarm active/enable flag. .DEF alarm_minute = r4 ; Alarm minute. .DEF alarm_hour = r5 ; Alarm hour. .DEF var1 = r16 ; Local use in routines. .DEF var2 = r17 ; Local use in routines. .DEF arg1 = r18 ; Argument (or return value) between routines. .DEF arg2 = r19 ; Argument (or return value) between routines. .DEF intvar = r20 ; Used only in interrupt routines. ; ============================================================================= ; VALUE LABELS ; ============================================================================= .EQU MENU_ENTRY_CLOCK_SHOW = 0 .EQU MENU_ENTRY_CLOCK_SET = 1 .EQU MENU_ENTRY_ALARM_SET = 2 .EQU MENU_ENTRY_ALARM_ENABLE = 3 .EQU MENU_ENTRY_BUZZER_STATUS = 4 .EQU MENU_ENTRY_END = 5 .EQU CLOCK_SET_HOUR_MSD = 0 .EQU CLOCK_SET_HOUR_LSD = 1 .EQU CLOCK_SET_MINUTE_MSD = 2 .EQU CLOCK_SET_MINUTE_LSD = 3 .EQU CLOCK_SET_SECOND_MSD = 4 .EQU CLOCK_SET_SECOND_LSD = 5 .EQU CLOCK_SET_END = 6 .EQU ALARM_SET_HOUR_MSD = 0 .EQU ALARM_SET_HOUR_LSD = 1 .EQU ALARM_SET_MINUTE_MSD = 2 .EQU ALARM_SET_MINUTE_LSD = 3 .EQU ALARM_SET_END = 4 ; ============================================================================= ; DATA DEFINITIONS ; ============================================================================= .DSEG .ORG 0x0060 _keypad_key_previous: .BYTE 1 ; ============================================================================= ; EEPROM DATA ; ============================================================================= .ESEG .ORG 0x0000 clock_set_menu_text: .db "ClockSet" alarm_set_menu_text: .db "AT:" alarm_enable_menu_text: .db "AE:" buzzer_status_menu_text: .db "Buzzer:" ; ============================================================================= ; RESET AND INTERRUPT VECTORS ; ============================================================================= .CSEG .ORG 0x0000 ; Reset vector. rjmp main_init .ORG 0x0006 ; Timer1 compare 'a' interrupt. rjmp timer1_interrupt ; ============================================================================= ; MAIN INITIALIZATION ; ============================================================================= .ORG 0x0010 main_init: ; Setup MCU control register. in var1, MCUCR ori var1, 0x80 ; Enable sleep in idle mode. out MCUCR, var1 ; Define SRAM as stack. (In order to use subroutines.) ldi var1, HIGH(RAMEND) out SPH, var1 ldi var1, LOW(RAMEND) out SPL, var1 ; Clear registers. clr second clr minute clr hour clr alarm_active clr alarm_minute clr alarm_hour ; Clear data. ldi var1, 0 sts _keypad_key_previous, var1 ; Set parts of Port-B (Connected to LCD) as output. ldi var1, 0b00111111 out DDRB, var1 ; Set parts of Port D (Connected to Keypad and buzzer) as output. ldi var1, 0b10010101 out DDRD, var1 ; Enable compare match 'a' interrupt for Timer1. ldi var1, 0b00010000 out TIMSK, var1 ; Set compare value 'a' for Timer1. ; (Tuned to one second when using internal clock.) ldi var1, 0b00111100 out OCR1AH, var1 ldi var1, 0b10000000 out OCR1AL, var1 ; Clear Timer1 counter. clr var1 out TCNT1H, var1 out TCNT1L, var1 ; Enable interrupts globally. sei ; Start Timer1 in CTC mode and use internal clock with clk/64 prescaler. clr var1 out TCCR1A, var1 ldi var1, 0b00001011 out TCCR1B, var1 ; Initialize LCD. rcall lcd_init ; Start main program loop. rjmp menu_loop_init ; ============================================================================= ; MAIN MENU LOOP ; ============================================================================= menu_loop_init: clr var1 ; Holds "current entry". What to show and what action to take. menu_loop: cpi var1, MENU_ENTRY_CLOCK_SHOW brne _menu_loop_display_clock_set ; Display clock. rcall lcd_first_position mov arg1, hour rcall split_msd_lsd rcall lcd_number ldi arg1, ':' rcall lcd_character mov arg1, minute rcall split_msd_lsd rcall lcd_number ldi arg1, ':' rcall lcd_character mov arg1, second rcall split_msd_lsd rcall lcd_number ; ----------------------------------------------------------------------------- _menu_loop_display_clock_set: cpi var1, MENU_ENTRY_CLOCK_SET brne _menu_loop_display_alarm_set ; Display "ClockSet". rcall lcd_first_position ldi arg1, LOW(clock_set_menu_text) ldi arg2, 8 rcall lcd_eeprom_string rjmp _menu_loop_keypad_poll ; ----------------------------------------------------------------------------- _menu_loop_display_alarm_set: cpi var1, MENU_ENTRY_ALARM_SET brne _menu_loop_display_alarm_enable ; Display "AT:" and time of alarm. rcall lcd_first_position ldi arg1, LOW(alarm_set_menu_text) ldi arg2, 3 rcall lcd_eeprom_string mov arg1, alarm_hour rcall split_msd_lsd rcall lcd_number ldi arg1, ':' rcall lcd_character mov arg1, alarm_minute rcall split_msd_lsd rcall lcd_number rjmp _menu_loop_keypad_poll ; ----------------------------------------------------------------------------- _menu_loop_display_alarm_enable: cpi var1, MENU_ENTRY_ALARM_ENABLE brne _menu_loop_display_buzzer_status ; Display "AE:" and 1 if active, or 0 if not active. rcall lcd_first_position ldi arg1, LOW(alarm_enable_menu_text) ldi arg2, 3 rcall lcd_eeprom_string tst alarm_active breq _menu_loop_display_alarm_not_active ldi arg1, '1' rcall lcd_character ldi arg1, ' ' rcall lcd_character rcall lcd_character rcall lcd_character rcall lcd_character rjmp _menu_loop_keypad_poll _menu_loop_display_alarm_not_active: ldi arg1, '0' rcall lcd_character ldi arg1, ' ' rcall lcd_character rcall lcd_character rcall lcd_character rcall lcd_character rjmp _menu_loop_keypad_poll ; ----------------------------------------------------------------------------- _menu_loop_display_buzzer_status: cpi var1, MENU_ENTRY_BUZZER_STATUS brne _menu_loop_keypad_poll ; Display "Buzzer:" and 1 if active, or 0 if not active. rcall lcd_first_position ldi arg1, LOW(buzzer_status_menu_text) ldi arg2, 7 rcall lcd_eeprom_string rcall buzzer_is_active tst arg1 breq _menu_loop_display_buzzer_status_not_active ldi arg1, '1' rcall lcd_character rjmp _menu_loop_keypad_poll _menu_loop_display_buzzer_status_not_active: ldi arg1, '0' rcall lcd_character rjmp _menu_loop_keypad_poll ; ----------------------------------------------------------------------------- _menu_loop_keypad_poll: rcall keypad_poll cpi arg1, '*' brne _menu_loop_keypad_poll_action inc var1 cpi var1, MENU_ENTRY_END brge _menu_loop_wrap rjmp menu_loop _menu_loop_wrap: clr var1 rjmp menu_loop _menu_loop_keypad_poll_action: cpi arg1, '#' breq _menu_loop_action_clock_set rjmp menu_loop _menu_loop_action_clock_set: cpi var1, MENU_ENTRY_CLOCK_SET brne _menu_loop_action_alarm_set rcall clock_set_action rjmp menu_loop _menu_loop_action_alarm_set: cpi var1, MENU_ENTRY_ALARM_SET brne _menu_loop_action_alarm_enable rcall alarm_set_action rjmp menu_loop _menu_loop_action_alarm_enable: cpi var1, MENU_ENTRY_ALARM_ENABLE brne _menu_loop_action_buzzer_status rcall alarm_enable_action rjmp menu_loop _menu_loop_action_buzzer_status: cpi var1, MENU_ENTRY_BUZZER_STATUS brne _menu_loop_end rcall buzzer_status_action rjmp menu_loop _menu_loop_end: rjmp menu_loop ; ============================================================================= ; CLOCK SET SUB-MENU ; ============================================================================= ; clock_set_action -- Adjust time of the clock. ; IN: N/A ; OUT: N/A clock_set_action: push var1 cli ; Freeze time while adjusting clock. clr var1 ; Holds current digit to be adjusted (0 to 5). _clock_set_loop: mov arg1, var1 rcall clock_set_display rcall keypad_poll ; Check if action should be aborted. cpi arg1, '#' breq _clock_set_end cpi arg1, '*' breq _clock_set_end ; Check a key between '0' and '9' is pressed, only then perform adjust. cpi arg1, 0x30 brlt _clock_set_loop cpi arg1, 0x40 brge _clock_set_loop mov arg2, var1 rcall clock_set_update_digit inc var1 cpi var1, CLOCK_SET_END breq _clock_set_end rjmp _clock_set_loop _clock_set_end: sei ; Un-freeze time. pop var1 ret ; ----------------------------------------------------------------------------- ; clock_set_update_digit -- Update individual digit on clock manually. ; IN: arg1 -> ASCII value of digit to update. ; arg2 -> Digit location from 0 to 5. ; OUT: N/A clock_set_update_digit: subi arg1, 0x30 ; Convert from ASCII to actual integer value. cpi arg2, CLOCK_SET_HOUR_MSD brne _clock_set_update_hour_lsd rcall convert_to_msd mov hour, arg1 ret _clock_set_update_hour_lsd: cpi arg2, CLOCK_SET_HOUR_LSD brne _clock_set_update_minute_msd add hour, arg1 ret _clock_set_update_minute_msd: cpi arg2, CLOCK_SET_MINUTE_MSD brne _clock_set_update_minute_lsd rcall convert_to_msd mov minute, arg1 ret _clock_set_update_minute_lsd: cpi arg2, CLOCK_SET_MINUTE_LSD brne _clock_set_update_second_msd add minute, arg1 ret _clock_set_update_second_msd: cpi arg2, CLOCK_SET_SECOND_MSD brne _clock_set_update_second_lsd rcall convert_to_msd mov second, arg1 ret _clock_set_update_second_lsd: cpi arg2, CLOCK_SET_SECOND_LSD brne _clock_set_update_digit_end add second, arg1 ret _clock_set_update_digit_end: ret ; ----------------------------------------------------------------------------- ; clock_set_display -- Display "update in progress" clock. ; IN: arg1 -> Digit location from 0 to 5. (What will be updated next.) ; OUT: N/A clock_set_display: push var1 mov var1, arg1 rcall lcd_first_position mov arg1, hour rcall split_msd_lsd subi arg1, -0x30 subi arg2, -0x30 cpi var1, CLOCK_SET_HOUR_MSD brne _clock_set_display_hour_msd ldi arg1, '_' ; Use underscore to display "current" number to update. _clock_set_display_hour_msd: rcall lcd_character mov arg1, arg2 cpi var1, CLOCK_SET_HOUR_LSD brne _clock_set_display_hour_lsd ldi arg1, '_' _clock_set_display_hour_lsd: rcall lcd_character ldi arg1, ':' rcall lcd_character mov arg1, minute rcall split_msd_lsd subi arg1, -0x30 subi arg2, -0x30 cpi var1, CLOCK_SET_MINUTE_MSD brne _clock_set_display_minute_msd ldi arg1, '_' _clock_set_display_minute_msd: rcall lcd_character mov arg1, arg2 cpi var1, CLOCK_SET_MINUTE_LSD brne _clock_set_display_minute_lsd ldi arg1, '_' _clock_set_display_minute_lsd: rcall lcd_character ldi arg1, ':' rcall lcd_character mov arg1, second rcall split_msd_lsd subi arg1, -0x30 subi arg2, -0x30 cpi var1, CLOCK_SET_SECOND_MSD brne _clock_set_display_second_msd ldi arg1, '_' _clock_set_display_second_msd: rcall lcd_character mov arg1, arg2 cpi var1, CLOCK_SET_SECOND_LSD brne _clock_set_display_second_lsd ldi arg1, '_' _clock_set_display_second_lsd: rcall lcd_character pop var1 ret ; ============================================================================= ; ALARM SET SUB-MENU ; ============================================================================= ; alarm_set_action -- Adjust the time of the alarm. ; IN: N/A ; OUT: N/A alarm_set_action: push var1 clr var1 ; Holds current digit to be adjusted (0 to 5). _alarm_set_loop: mov arg1, var1 rcall alarm_set_display rcall keypad_poll ; Check if action should be aborted. cpi arg1, '#' breq _alarm_set_end cpi arg1, '*' breq _alarm_set_end ; Check a key between '0' and '9' is pressed, only then perform adjust. cpi arg1, 0x30 brlt _alarm_set_loop cpi arg1, 0x40 brge _alarm_set_loop mov arg2, var1 rcall alarm_set_update_digit inc var1 cpi var1, ALARM_SET_END breq _alarm_set_end rjmp _alarm_set_loop _alarm_set_end: pop var1 ret ; ----------------------------------------------------------------------------- ; alarm_set_update_digit -- Update individual digit on alarm. ; IN: arg1 -> ASCII value of digit to update. ; arg2 -> Digit location from 0 to 5. ; OUT: N/A alarm_set_update_digit: subi arg1, 0x30 ; Convert from ASCII to actual integer value. cpi arg2, ALARM_SET_HOUR_MSD brne _alarm_set_update_hour_lsd rcall convert_to_msd mov alarm_hour, arg1 ret _alarm_set_update_hour_lsd: cpi arg2, ALARM_SET_HOUR_LSD brne _alarm_set_update_minute_msd add alarm_hour, arg1 ret _alarm_set_update_minute_msd: cpi arg2, ALARM_SET_MINUTE_MSD brne _alarm_set_update_minute_lsd rcall convert_to_msd mov alarm_minute, arg1 ret _alarm_set_update_minute_lsd: cpi arg2, ALARM_SET_MINUTE_LSD brne _alarm_set_update_digit_end add alarm_minute, arg1 ret _alarm_set_update_digit_end: ret ; ----------------------------------------------------------------------------- ; alarm_set_display -- Display "update in progress" for alarm. ; IN: arg1 -> Digit location from 0 to 5. (What will be updated next.) ; OUT: N/A alarm_set_display: push var1 mov var1, arg1 rcall lcd_first_position mov arg1, alarm_hour rcall split_msd_lsd subi arg1, -0x30 subi arg2, -0x30 cpi var1, ALARM_SET_HOUR_MSD brne _alarm_set_display_hour_msd ldi arg1, '_' ; Use underscore to display "current" number to update. _alarm_set_display_hour_msd: rcall lcd_character mov arg1, arg2 cpi var1, ALARM_SET_HOUR_LSD brne _alarm_set_display_hour_lsd ldi arg1, '_' _alarm_set_display_hour_lsd: rcall lcd_character ldi arg1, ':' rcall lcd_character mov arg1, alarm_minute rcall split_msd_lsd subi arg1, -0x30 subi arg2, -0x30 cpi var1, ALARM_SET_MINUTE_MSD brne _alarm_set_display_minute_msd ldi arg1, '_' _alarm_set_display_minute_msd: rcall lcd_character mov arg1, arg2 cpi var1, ALARM_SET_MINUTE_LSD brne _alarm_set_display_minute_lsd ldi arg1, '_' _alarm_set_display_minute_lsd: rcall lcd_character ldi arg1, ' ' rcall lcd_character rcall lcd_character rcall lcd_character pop var1 ret ; ============================================================================= ; ALARM STATUS SUB-MENU ; ============================================================================= ; alarm_enable_action -- Toggle "alarm_active" flag. ; IN: N/A ; OUT: N/A alarm_enable_action: push var1 tst alarm_active breq _alarm_enable_action_not_active clr var1 mov alarm_active, var1 pop var1 ret _alarm_enable_action_not_active: ser var1 mov alarm_active, var1 pop var1 ret ; ============================================================================= ; BUZZER STATUS SUB-MENU ; ============================================================================= ; buzzer_status_action -- Toggle buzzer. ; IN: N/A ; OUT: N/A buzzer_status_action: rcall buzzer_is_active tst arg1 breq _buzzer_status_action_not_active rcall buzzer_deactivate ret _buzzer_status_action_not_active: rcall buzzer_activate ret ; ============================================================================= ; LCD DISPLAY INTERFACE ; ============================================================================= ; lcd_init -- Initialize LCD display. ; IN: N/A ; OUT: N/A lcd_init: push var1 ; Wait a little after MC startup. rcall long_delay ; Set 4-bit data mode. (Directly, since interfaces does not work yet.) ldi var1, 0b00000010 out PORTB, var1 sbi PORTB, 4 ; Set enable. rcall short_delay cbi PORTB, 4 ; Clear enable. rcall long_delay ; Trick to keep to keep brightness correct. ldi arg1, 0b10001000 rcall lcd_command ldi arg1, '!' rcall lcd_character ; Set 1-line mode. ldi arg1, 0b00100000 rcall lcd_command ; Turn on LCD. (Without blinking cursor.) ldi arg1, 0b00001100 rcall lcd_command ; Clear whole display. ldi arg1, 0b00000001 rcall lcd_command rcall long_delay ; Set cursor to move left after write operation. ldi arg1, 0b00000110 rcall lcd_command pop var1 ret ; ----------------------------------------------------------------------------- ; lcd_character -- Print single character (extended ASCII) to LCD display. ; IN: arg1 -> character ; OUT: N/A lcd_character: push var1 ; Extract high nibble from byte. mov var1, arg1 swap var1 ; Swap nibbles. andi var1, 0xf ; Clear high nibble. out PORTB, var1 sbi PORTB, 5 ; Set RS to 1 to use character register. ; Toggle enable to send the first 4 bits of data. sbi PORTB, 4 rcall short_delay cbi PORTB, 4 rcall short_delay ; Extract lower nibble from byte. mov var1, arg1 andi var1, 0xf out PORTB, var1 sbi PORTB, 5 ; Toggle enable to send the second 4 bits of data. sbi PORTB, 4 rcall short_delay cbi PORTB, 4 rcall short_delay pop var1 ret ; ----------------------------------------------------------------------------- ; lcd_command -- Send control command to LCD display. ; IN: arg1 -> command number ; OUT: N/A lcd_command: push var1 ; Extract high nibble from byte. mov var1, arg1 swap var1 ; Swap nibbles. andi var1, 0xf ; Clear high nibble. out PORTB, var1 ; RS remains 0 to use command register. ; Toggle enable to send the first 4 bits of data. sbi PORTB, 4 rcall short_delay cbi PORTB, 4 rcall short_delay ; Extract lower nibble from byte. mov var1, arg1 andi var1, 0xf out PORTB, var1 ; Toggle enable to send the second 4 bits of data. sbi PORTB, 4 rcall short_delay cbi PORTB, 4 rcall short_delay pop var1 ret ; ----------------------------------------------------------------------------- ; lcd_first_position -- Set LCD address to first position (0). ; IN: N/A ; OUT: N/A lcd_first_position: ldi arg1, 0b10000000 ; Set DDRAM address to first display position. rcall lcd_command ret ; ----------------------------------------------------------------------------- ; lcd_number -- Print two digit number to LCD display. ; IN: arg1 -> MSD of two digit number. ; arg2 -> LSD of two digit number. ; OUT: N/A lcd_number: ; Add ASCII compensator. subi arg1, -0x30 subi arg2, -0x30 rcall lcd_character mov arg1, arg2 rcall lcd_character ret ; ----------------------------------------------------------------------------- ; lcd_eeprom_string -- Print string from EEPROM to LCD display. ; IN: arg1 -> Start of EEPROM address. ; arg2 -> Amount of characters in string. ; OUT: N/A lcd_eeprom_string: push var1 push var2 mov var1, arg1 mov var2, arg2 _lcd_eeprom_string_loop: mov arg1, var1 rcall eeprom_read rcall lcd_character inc var1 ; Address incremented for every cycle. dec var2 ; Will be decremented until zero. tst var2 brne _lcd_eeprom_string_loop pop var2 pop var1 ret ; ============================================================================= ; KEYPAD INTERFACE ; ============================================================================= ; keypad_poll -- Retrive next keypress from keypad. ; IN: N/A ; OUT: arg1 -> Pressed key in ASCII, or 0 if no new key has been pressed. keypad_poll: push var1 clr arg1 ; ldi var1, 0b00000100 ; K1 ; out PORTD, var1 sbi PORTD, 2 ; K1 nop ; This NOP is required for correct timing. sbic PIND, 1 ; R1 ldi arg1, '1' sbic PIND, 6 ; R2 ldi arg1, '4' sbic PIND, 5 ; R3 ldi arg1, '7' sbic PIND, 3 ; R4 ldi arg1, '*' cbi PORTD, 2 ; K1 ; ldi var1, 0b00000001 ; K2 ; out PORTD, var1 sbi PORTD, 0 ; K2 nop sbic PIND, 1 ; R1 ldi arg1, '2' sbic PIND, 6 ; R2 ldi arg1, '5' sbic PIND, 5 ; R3 ldi arg1, '8' sbic PIND, 3 ; R4 ldi arg1, '0' cbi PORTD, 0 ; K2 ; ldi var1, 0b00010000 ; K3 ; out PORTD, var1 sbi PORTD, 4 ; K3 nop sbic PIND, 1 ; R1 ldi arg1, '3' sbic PIND, 6 ; R2 ldi arg1, '6' sbic PIND, 5 ; R3 ldi arg1, '9' sbic PIND, 3 ; R4 ldi arg1, '#' cbi PORTD, 4 ; K3 lds var1, _keypad_key_previous cp arg1, var1 brne _keypad_return_new_key clr arg1 pop var1 ret ; Return 0 to indicate NO new key. _keypad_return_new_key: sts _keypad_key_previous, arg1 pop var1 ret ; ============================================================================= ; BUZZER INTERFACE ; ============================================================================= ; buzzer_activate -- Start buzzer. ; IN: N/A ; OUT: N/A buzzer_activate: sbi PORTD, 7 ret ; ----------------------------------------------------------------------------- ; buzzer_deactivate -- Stop buzzer. ; IN: N/A ; OUT: N/A buzzer_deactivate: cbi PORTD, 7 ret ; ----------------------------------------------------------------------------- ; buzzer_is_active -- Fetch current status of buzzer (output). ; IN: N/A ; OUT: arg1 -> 1 if active, or 0 if deactivated. buzzer_is_active: ldi arg1, 0 sbic PORTD, 7 ldi arg1, 1 ret ; ============================================================================= ; COMMON ROUTINES ; ============================================================================= ; eeprom_read -- Read from EEPROM. ; IN: arg1 -> EEPROM address ; OUT: arg1 -> value eeprom_read: sbic EECR, 1 ; Check if EEPROM is busy. rjmp eeprom_read out EEARL, arg1 ; Select EEPROM address register. ldi arg1, 0 out EEARH, arg1 sbi EECR, EERE ; Activate "Read Enable". in arg1, EEDR ; Read from data register. ret ; ----------------------------------------------------------------------------- ; long_delay -- Waste CPU cycles to cause a "long" delay. ; IN: N/A ; OUT: N/A long_delay: push var1 push var2 _long_delay_loop: dec var1 brne _long_delay_loop dec var2 brne _long_delay_loop pop var2 pop var1 ret ; ----------------------------------------------------------------------------- ; short_delay -- Waste CPU cycles to cause a "short" delay. ; IN: N/A ; OUT: N/A short_delay: push var1 _short_delay_loop: dec var1 brne _short_delay_loop pop var1 ret ; ----------------------------------------------------------------------------- ; split_msd_lsd -- Split two digit number into MSD and LSD. ; IN: arg1 -> Two digit number. ; OUT: arg1 -> MSD ; arg2 -> LSD split_msd_lsd: mov arg2, arg1 ldi arg1, -1 _split_msd_lsd_positive: inc arg1 subi arg2, 10 brpl _split_msd_lsd_positive subi arg2, -10 ; Actually same as "addi arg1, 10", which does not exist. ret ; ----------------------------------------------------------------------------- ; convert_to_lsd -- Convert LSD to MSD. ; IN: arg1 -> LSD ; OUT: arg1 -> MSD convert_to_msd: push var1 mov var1, arg1 _convert_to_msd_loop: cpi var1, 0 breq _convert_to_msd_done subi arg1, -9 dec var1 rjmp _convert_to_msd_loop _convert_to_msd_done: pop var1 ret ; ============================================================================= ; INTERRUPT ROUTINES ; ============================================================================= ; timer1_interrupt -- Updates clock (second/minute/hour) counters. ; IN: N/A ; OUT: N/A timer1_interrupt: inc second ldi intvar, 60 cp second, intvar brlt _timer1_check_alarm clr second inc minute cp minute, intvar brlt _timer1_check_alarm clr minute inc hour ldi intvar, 24 cp hour, intvar brlt _timer1_check_alarm clr hour _timer1_check_alarm: tst alarm_active breq _timer1_done cp alarm_hour, hour brne _timer1_done cp alarm_minute, minute brne _timer1_done ; Clear alarm active bit, to prevent triggering again. clr intvar mov alarm_active, intvar ; Set port directly, since buzzer_activate cannot be called from interrupt. sbi PORTD, 7 _timer1_done: reti
Steam on Slackware 14.2
Slackware 14.2 was recently released, so I decided to attempt a Steam installation on it, which succeeded in the end.
Here are the steps I followed:
1) Download and install the 64-bit version of Slackware 14.2. You can get it here: http://www.slackware.com/getslack/torrents.php
2) Setup "multilib", so the system is able to run 32-bit programs. Use this guide: http://www.slackware.com/~alien/multilib/.
3) Install OpenAL, which is a dependency for Steam. I recommend to use SlackBuilds: https://slackbuilds.org/repository/14.2/libraries/OpenAL/
4) Install Steam. Also easy with SlackBuilds: https://slackbuilds.org/repository/14.2/games/steam/
5) Install a proprietary graphics driver, for better OpenGL performance.
It is important to install a 64-bit base system because some of the games on Steam are in fact only 64-bit. However, Steam itself and certain other games are only 32-bit, hence the need for multilib. I use an nVidia graphics card, and this had to be installed AFTER multilib had been set up, so I recommend doing the same.
I have done only minor testing so far, but I am still a bit sceptical about the OpenAL dependency. It seems that some games bundle their own OpenAL libraries, so they aren't always needed. It is also possible that a 32-bit version of the OpenAL library needs to be installed for certain stuff to work.
SDL-Man for SDL2
Eight years ago I released a Pac-Man clone called SDL-Man. Now someone has expressed interest and asked me to update the code to use the newer SDL2 library instead of the older SDL1 library. And so I did. The changes are mostly related to the initialization and handling of video and graphics, which has changed a lot with SDL2.
The source code is still released under GPL3 and can be found here. As before, the main SDL2 and SDL2_mixer libraries are required.
D64 PRG Dumper
Maybe such a program already exists, but I do not know of any, so I made my own. This program scans a D64 file, which is a disk image typically used by Commodore 64 emulators, and attempts to extract all programs (PRG files) from it.
Take a look:
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <limits.h> #include <errno.h> #include <ctype.h> #define SECTOR_SIZE 256 #define FIRST_DIR_TRACK 18 #define FIRST_DIR_SECTOR 1 #define DIR_ENTRIES_PER_SECTOR 8 #define FILE_NAME_SIZE 16 #pragma pack(1) typedef struct dir_entry_s { uint8_t next_dir_track; uint8_t next_dir_sector; uint8_t file_type; uint8_t first_file_track; uint8_t first_file_sector; uint8_t petscii_file_name[FILE_NAME_SIZE]; uint8_t extra[11]; /* Unused by this program. */ } dir_entry_t; #pragma pack() static int byte_offset(int track_no, int sector_no) { int offset = 0; while (track_no-- > 0) { if (track_no >= 31) { offset += 17; } else if (track_no >= 25) { offset += 18; } else if (track_no >= 18) { offset += 19; } else if (track_no >= 1) { offset += 21; } } return (offset + sector_no) * SECTOR_SIZE; } static void dump_program(FILE *d64, char *name, int first_track, int first_sector) { uint8_t file_track, file_sector; uint8_t data[254]; int size, saved = 0; char path[PATH_MAX]; FILE *prg; snprintf(path, PATH_MAX, "%s.prg", name); prg = fopen(path, "wbx"); if (prg == NULL) { fprintf(stderr, "Warning: fopen(%s) failed: %s\n", path, strerror(errno)); return; } file_track = first_track; file_sector = first_sector; do { if (fseek(d64, byte_offset(file_track, file_sector), SEEK_SET) == -1) { fprintf(stderr, "Warning: fseek(%d,%d) failed: %s\n", file_track, file_sector, strerror(errno)); fclose(prg); return; } size = fread(&file_track, sizeof(uint8_t), 1, d64); size += fread(&file_sector, sizeof(uint8_t), 1, d64); size += fread(&data, sizeof(uint8_t), 254, d64); if (size != SECTOR_SIZE) { fprintf(stderr, "Warning: fread() read less than a sector for program: \"%s\"\n", name); fclose(prg); return; } saved += fwrite(&data, sizeof(uint8_t), 254, prg); /* NOTE: No protection against circular references. */ } while (file_track != 0); printf("Extracted: \"%s\" (%d bytes)\n", name, saved); fclose(prg); } int main(int argc, char *argv[]) { FILE *d64; int i, n, size; uint8_t dir_track, dir_sector, petscii; char ascii_name[FILE_NAME_SIZE + 1]; dir_entry_t de[DIR_ENTRIES_PER_SECTOR]; if (argc != 2) { fprintf(stderr, "Usage: %s <d64 image>\n", argv[0]); return 1; } d64 = fopen(argv[1], "rb"); if (d64 == NULL) { fprintf(stderr, "Error: fopen(%s) failed: %s\n", argv[1], strerror(errno)); return 1; } /* Traverse directory entries: */ dir_track = FIRST_DIR_TRACK; dir_sector = FIRST_DIR_SECTOR; do { if (fseek(d64, byte_offset(dir_track, dir_sector), SEEK_SET) == -1) { fprintf(stderr, "Error: fseek(%d,%d) failed: %s\n", dir_track, dir_sector, strerror(errno)); return 1; } size = fread(&de[0], sizeof(dir_entry_t), DIR_ENTRIES_PER_SECTOR, d64); if (size != DIR_ENTRIES_PER_SECTOR) { fprintf(stderr, "Error: fread() read less than a sector for directory entry\n"); return 1; } for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++) { /* Only extract if PRG type, meaning bit 2 is set: */ if ((de[i].file_type & 0x2) == 0) continue; /* Convert file name from PETSCII to ASCII: */ memset(ascii_name, '\0', FILE_NAME_SIZE + 1); for (n = 0; n < FILE_NAME_SIZE; n++) { petscii = de[i].petscii_file_name[n]; if (petscii == 0xA0) /* Padding, end of name. */ break; if (isalnum(petscii)) { ascii_name[n] = tolower(petscii); } else if (petscii == ' ') { ascii_name[n] = '_'; } else { ascii_name[n] = '.'; } } /* Dump the program unless 0: */ if (de[i].first_file_track == 0) continue; dump_program(d64, ascii_name, de[i].first_file_track, de[i].first_file_sector); } dir_track = de[0].next_dir_track; dir_sector = de[0].next_dir_sector; /* NOTE: No protection against circular references. */ } while (dir_track != 0); fclose(d64); return 0; }
LUKS Hidden by FAT32
Here is a trick to hide the existence of an encrypted LUKS partition on a disk, by offsetting it into the "data region" of a FAT32 partition. When the disk is inserted on Windows, it will simply show up as an regular empty drive. Putting data on it when mounted as a FAT32 (e.g. on Windows) will most likely destroy the encrypted information.
To find the data region of a FAT32 partition, I have developed this tool based on earlier work on VBRs:
#include <stdio.h> #include <stdint.h> #pragma pack(1) typedef struct vbr_s { uint8_t jump_code[3]; char oem_id[8]; uint16_t bytes_per_sector; uint8_t sectors_per_cluster; uint16_t reserved_sectors; uint8_t no_of_fats; uint16_t root_entries; uint16_t small_sectors; uint8_t media_descriptor; uint16_t sectors_per_fat; uint16_t sectors_per_track; uint16_t heads; uint32_t hidden_sectors; uint32_t large_sectors; uint8_t physical_drive_number; uint8_t current_head; uint8_t signature; uint32_t id; char volume_label[11]; char system_id[8]; uint8_t boot_code[448]; uint16_t boot_record_signature; } vbr_t; #pragma pack() int main(int argc, char *argv[]) { FILE *fh; int bytes; uint8_t sector[512]; vbr_t *vbr; int root_dir_sectors; int fat_part; int fat_size; int first_data_sector; if (argc != 2) { fprintf(stderr, "Usage: %s <device>\n", argv[0]); return 1; } fh = fopen(argv[1], "rb"); if (fh == NULL) { fprintf(stderr, "Error: Unable to open device for reading.\n"); return 1; } bytes = fread(§or, sizeof(uint8_t), 512, fh); fclose(fh); if (bytes < 512) { fprintf(stderr, "Error: File is less than 512 bytes.\n"); return 1; } vbr = (vbr_t *)§or[0]; if (vbr->bytes_per_sector == 0 || vbr->sectors_per_cluster == 0 || vbr->no_of_fats == 0) { fprintf(stderr, "Error: Invalid boot record.\n"); return 1; } root_dir_sectors = ((vbr->root_entries * 32) + (vbr->bytes_per_sector - 1)) / vbr->bytes_per_sector; fat_part = ((256 * vbr->sectors_per_cluster) + vbr->no_of_fats) / 2; fat_size = ((vbr->large_sectors - (vbr->reserved_sectors + root_dir_sectors)) + (fat_part - 1)) / fat_part; first_data_sector = vbr->reserved_sectors + (vbr->no_of_fats * fat_size) + root_dir_sectors; printf("%d\n", first_data_sector * vbr->bytes_per_sector); return 0; }
Here are step by step instructions on creating it all:
(Assuming the tool mentioned above is compiled as "fds" and available.)
fdisk /dev/sdd # Create one single large FAT32 partition (0xb) as /dev/sdd1. mkfs.vfat /dev/sdd1 losetup -o `./fds /dev/sdd1` /dev/loop0 /dev/sdd1 cryptsetup -v luksFormat /dev/loop0 cryptsetup luksOpen /dev/loop0 luks mkfs.ext2 /dev/mapper/luks cryptsetup luksClose luks losetup -d /dev/loop0
Mounting the hidden LUKS partition later on:
losetup -o `./fds /dev/sdd1` /dev/loop0 /dev/sdd1 cryptsetup luksOpen /dev/loop0 luks mount /dev/mapper/luks /mnt/luks
And unmounting it:
umount /mnt/luks cryptsetup luksClose luks losetup -d /dev/loop0
It may be possible to further hide the existence on the LUKS partition by also placing the LUKS header elsewhere, as is allegedly supported in newer versions of the "cryptsetup" tool.
Directory Tree Diff Front End
You may recall the Directory Tree Diff program I presented earlier. This is a new and improved version of that program. Instead of just dumping the differences, they are now displayed through a curses-based interface, which can be navigated. The program can also start the regular "diff" program on any different files found.
Have a look:

The source code is released under the MIT License here.
Monitoring File Open in a Directory
Here is part of an experiment I have been working on. I did not find any use for this (yet), but the method is worth considering. The following program uses the Linux kernel's "inotify" mechanism to print a message whenever a file is opened in a specific directory.
Have a look:
#include <dirent.h> #include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/inotify.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #define WATCH_FILES_MAX 256 typedef struct watch_s { int wd; char file[PATH_MAX]; } watch_t; static watch_t watch[WATCH_FILES_MAX]; static int watch_n = 0; static int ifd; static int watch_files(char *dir) { struct dirent *entry; struct stat st; DIR *dh; char path[PATH_MAX]; ifd = inotify_init(); if (ifd == -1) { fprintf(stderr, "inotify_init() failed: %s\n", strerror(errno)); return -1; } dh = opendir(dir); if (dh == NULL) { fprintf(stderr, "opendir(%s) failed: %s\n", dir, strerror(errno)); return -1; } watch_n = 0; while ((entry = readdir(dh))) { if (entry->d_name[0] == '.') continue; snprintf(path, PATH_MAX, "%s/%s", dir, entry->d_name); if (stat(path, &st) == -1) { fprintf(stderr, "stat(%s) failed: %s\n", path, strerror(errno)); closedir(dh); return -1; } if (S_ISREG(st.st_mode)) { watch[watch_n].wd = inotify_add_watch(ifd, path, IN_OPEN); if (watch[watch_n].wd == -1) { fprintf(stderr, "inotify_add_watch(%s) failed: %s\n", path, strerror(errno)); closedir(dh); return -1; } strncpy(watch[watch_n].file, entry->d_name, PATH_MAX); fprintf(stderr, "Watching: %s\n", entry->d_name); watch_n++; if (watch_n >= WATCH_FILES_MAX) { fprintf(stderr, "Max files reached, skipping the rest!\n"); break; } } } closedir(dh); return 0; } int main(int argc, char *argv[]) { struct inotify_event iev; time_t now; int i; if (argc != 2) { fprintf(stderr, "Usage: %s <watch directory>\n", argv[0]); return 1; } if (watch_files(argv[1]) != 0) { return 1; } setlinebuf(stdout); while (1) { if (read(ifd, &iev, sizeof(struct inotify_event)) > 0) { for (i = 0; i < watch_n; i++) { if (iev.wd == watch[i].wd) { time(&now); fprintf(stdout, "%s @ %s", watch[i].file, ctime(&now)); } } } } /* NOTE: No file descriptors explicitly closed. */ return 0; }