#!/usr/bin/ruby # --- MP3 Control --- # # Author: Magnus Engström # Email: magnus@gisab.se # File: mp3controld # # Description # ----------- # An application which manages # mp3 songs on the poor users # computer. # ------------------------- # --- External files --- # Change directory to find all functions #Dir::chdir("/usr/lib/mp3control") load "extras/config_file" # Handles the mp3controld.conf configuration file load "extras/cmdline_parse" # Provides the Commandline::parse which returns an hash of the commandline load "extras/showHelp" # Hmmm... guess what? This function shows the help message =) load "extras/errorMsg" # Outputs error messages to the admin load "extras/playlist" # Core playlist functionality # --- MP3Control - the main class --- class MP3Control # --- Core functions --- # initialize() reads the config files and daemonizes! def initialize() # Parse the commandline $commandline = Commandline::parse() # Display help if the commandline sucks if($commandline == -1) showHelp() Kernel::exit(-1) end # Make this class instance a background daemon daemonize() if($commandline["--daemon"] != 'no' && $commandline["-D"] != 'no') # Read the configuration file configfile = ( $commandline["--configfile"] ? $commandline["--configfile"] : 'configuration/mp3controld.conf' ) # Use the --configfile if supplied, else use the default /etc/mp3controld/mp3controld.conf $configFile = ConfigFile::parse(configfile) # Create an new config file object errorMsg("Configuration file #{configfile} isn't readable or doesn't exist!", 137) if($configFile == -1) # Find the default playlist (or use the user supplied one from the commandline) defaultPlaylist = $configFile.getParam("programConfig", "defaultPlaylist") defaultPlaylist = $commandline["--playlist"] if($commandline["--playlist"]) if( $configFile.getParam(defaultPlaylist, 'listFile').type == String) # Make sure the listFile contains a value if(FileTest.readable_real?( $configFile.getParam(defaultPlaylist, 'listFile') )) # Make sure this file is readable # Cool. Everything looks alright. Lets create a playlist! errorMsg("Loading playlist '#{defaultPlaylist}' from #{$configFile.getParam(defaultPlaylist, 'listFile')}...") if($commandline['--verbose'] == 'on') $current_playlist = Playlist::create_from_file($configFile.getParam(defaultPlaylist, 'listFile')) $current_playlist_name = defaultPlaylist # Errorhandling errorMsg("Playlist creation from #{$current_playlist.filename} failed.", 1) if($current_playlist == -1) # Quit and complain if it didn't work errorMsg("Playlist #{defaultPlaylist} (#{$current_playlist.filename}) doesn't contain any entries.", 1) if($current_playlist.length() < 1) errorMsg("Playlist '#{defaultPlaylist}' loaded successfully.") if($commandline["--verbose"] == 'on') # Fix wrap/repeat and shuffle the playlist if the configfile says so $current_playlist.wrap = true if($configFile.getParam(defaultPlaylist, 'repeat') == 'true') $current_playlist.shuffle() if($configFile.getParam(defaultPlaylist, 'random') == 'true') $current_playlist.goto(0) # Rewind the playlist else errorMsg("The default playlist doesn't appear to exist, or has the wrong permissions. That's not good.", 1) end else # listFile returns other than string, that's baaaaad errorMsg("the listFile value for '#{defaultPlaylist}' doesn't appear to exist in your #{$configFile.filename}. That's not good.", 1) end # Check for the existence of our mpg123 instance errorMsg("Trying to find the mp3 player...") if($commandline["--verbose"] == 'on') @mpg123 = $configFile.getParam('programConfig', 'application') # Get the application name, path, and possible arguments mpg123_application = @mpg123.scan(/^\S+/) if(mpg123_application.type == Array) mpg123_application = mpg123_application[0] else errorMsg("Please check your ProgramConfig/application setting in the configuration file. It seems weird.", 1) end # Do some nice errorhandling if( !FileTest::executable_real?(mpg123_application) ) errorMsg("#{mpg123_application} doesn't appear to exist. Please do something about it (no, not \"touch #{mpg123_application}\"! Stupid!).", 1) else errorMsg("Found #{mpg123_application}!") if($commandline["--verbose"] == 'on') end # Open an instance of mpg123, our high-performance mp3 player @@mpg123 = IO::popen("#{@mpg123} -R -", 'r+') # Start all plugins load_plugins() # Start the mp3 player mpg123_control() end # mpg123_control() gets the input from the mpg123 process and handles it hopefully correctly :) def mpg123_control() # Initialize class variables @@stopped = false # Start playing if the configfile says so if( $configFile.getParam('programConfig', 'playOnStartup') == 'true' ) MP3Control::play() else puts("Dont want to play!") end # The infinite 5 second loop =) while(true) begin input = @@mpg123.readline.chop() rescue EOFError errorMsg("mpg123 error! Probably just a file that didn't exist (#{$current_playlist.song_info["filename"]}).") @@mpg123 = IO::popen("#{@mpg123} -R -", 'r+') # Just start it again... MP3Control::next_song() # Next song, so it doesn't die again... end # Song stopped MP3Control::next_song() if(input == '@P 0' && @@stopped != true) # Change to the next song if current song stops and the user didn't told it to @@stopped = false if(input == '@P 0' && @@stopped == true) # Reset the stop-flag if the user ordered the stop # Time left if(input[0,2] == '@F') input =~ /^@F\s+\S+\s+\S+\s+(\S+)\s+(\S+)$/ @@time = $1 @@timeleft = $2 end end end # start() creates a new instance. Just like .new, but a nicer name. def MP3Control::start() MP3Control::new() end # daemonize() daemonizes this process. Cool =) def daemonize() if(child_pid = fork) # Parent code puts("PID #{child_pid}") Kernel::exit(0) # We dont want this parent process any more. Too bad. end # Child code. And child wants to be free from parent Process.setsid() end # load_plugins() loads all plugins that can be found in the plugins directory def load_plugins() Dir::entries('extras/plugins/').each { |plugin| # Try to load every plugin except all files that begins with a dot if(plugin[0].chr() != '.') retval = 0 # Do some errorhandling begin load("extras/plugins/#{plugin}") # Load the files errorMsg("Loaded plugin '#{plugin}'") if($commandline['--verbose'] == 'no') rescue SyntaxError errorMsg("Plugin '#{plugin}' has syntax errors.") end # Run the plugin in a separate thread # Try to start the class with the same name as the plugin eval(" Thread::new() { start_again = true while(start_again) puts(\"Plugin starting...\") begin retval = #{plugin}::start() rescue error = $! puts(error) puts(error.backtrace) puts(\"Error in plugin #{plugin}! Waiting for 5 seconds before attempting to start again...\") sleep(5) end if(retval == -1) errorMsg(\"Plugin 'plugins/#{plugin}' failed to load...\") start_again = false end end } ") end } end # exit_mpg123() terminates the current mpg123 session def MP3Control::exit_mpg123() #system("kill `pidof mpg123`") # Yes, I know this isn't a nice way to do it in, please fix this and send me a patch :) end # mpg123_command() sends a command to mpg123 def MP3Control::mpg123_command(command) errorMsg("mpg123 command: '#{command}'") if($commandline['--verbose'] == 'on') @@mpg123.write("#{command}\n") @@mpg123.flush() end # --- play functions --- # play() plays the current index of the current playlist def MP3Control::play() @@stopped = false mpg123_command("LOAD #{$current_playlist.song_info["filename"]}") end # stop() stops playing def MP3Control::stop() mpg123_command("PAUSE") @@stopped = true # Set the stop-status to true end # pause() pauses playing def MP3Control::pause() @@stopped = false mpg123_command("PAUSE") end # next_song() loads the next song. Yay... def MP3Control::next_song() retval = $current_playlist.next() if(retval == -1) errorMsg("Error while changing to the next song.") # Tell him/her else MP3Control::play() if(@@stopped == false) end end # prev_song() loads the previous song. Yay... def MP3Control::prev_song() retval = $current_playlist.prev() if(retval == -1) errorMsg("Error while changing to the previous song.") # Tell him/her else MP3Control::play() if(@@stopped == false) end end # forward(sec) jumps sec seconds forward def MP3Control::forward(sec) mpg123_command("JUMP +#{sec*38}") end # backward(sec) jumps sec seconds backward def MP3Control::backward(sec) mpg123_command("JUMP -#{sec*38}") end # --- Misc functions --- # getTime() - Get the current song time information. getTime(true) for time left def MP3Control::getTime(whichTime = 'elapsed') case whichTime when 'elapsed' parsed = Time::at(@@time.to_i) when 'left' parsed = Time::at(@@timeleft.to_i) when 'total' parsed = Time::at( (@@time.to_f + @@timeleft.to_f).to_i ) end hour = (parsed.hour - 1).to_s min = parsed.min.to_s sec = parsed.sec.to_s hour = "0#{hour}" if(hour.length == 1) min = "0#{min}" if(min.length == 1) sec = "0#{sec}" if(sec.length == 1) hour = (hour == '00' ? '' : "#{hour}:") # Return the now formatted time "#{hour}#{min}:#{sec}" end end # Create an instance of the MP3Control class and start the application MP3Control::start()