Saturday, June 29, 2013

Building a python media player: Part 3

Got the player to a fairly polished state now! Piamp is a fairly powerful media player finally. Here is the project folder, for anyone interested: LINK

Also, to demonstrate the functionality of the player, I made a short video for you:




The great thing about this player is that it should work across platform. The only windows-specific code in the program is a specification of initial directory for Tkinter's file dialog, but even if Tkinter throws an error when trying to find "F://Media//Music//" on a linux operating system, it really isn't a big deal to me to change a line of code to fix that.

We'll see how it runs across platform when I get the touchscreen in and compile the modified debian image (with touchscreen support) for the Pi early next week. Stay tuned!




Wednesday, June 26, 2013

Building a python media player: Part 2

I've been working heavily on the code for Piamp, and I'm getting closer to a stable enough point where I can go through the code step by step. There are just a few features left to implement before I can get there. In the meantime, here is an updated screenshot of the player:


The most notable difference here is clearly the volume slider, but under the hood I've rewritten just about the whole thing. Here is the current piamp code:

#---------------------------------------------------------------------
#        piamp.py
#  
#      A Python Media Player
#
#      By: Josh Archer
#---------------------------------------------------------------------

import os
import pyglet
import Tkinter, tkFileDialog

dir = pyglet.resource.get_settings_path('Piamp')
if not os.path.exists(dir):
 os.makedirs(dir)
configFileName = os.path.join(dir, 'settings.cfg')

pyglet.resource.path = ['resources']
pyglet.resource.reindex()
batch = pyglet.graphics.Batch()

class Piamp(pyglet.window.Window):
 def __init__(self):
  super(Piamp, self).__init__()
  self.set_size(800, 480)
  #self.set_fullscreen(True)
  
  configFile = open(configFileName, 'wt')
  
  self.load_graphics()
  self.canPlay = True
  self.canMute = True
  self.mainMenuVisible = True
  self.frameCount = 0
  
  self.selectedFile = "null"
  
  self.mediaType = 'Audio'
 
  self.currentVolume = 0.5
  
  self.refresh_player()
  
 def load_graphics(self):
  #Background:
  self.img_background = pyglet.resource.image('background.png')
  self.img_background.anchor_x = self.img_background.width/2
  self.img_background.anchor_y = self.img_background.height/2
  
  #Main Menu Buttons:
  self.img_movies = pyglet.resource.image('movies.png')
  self.img_movies.anchor_x = self.img_movies.width/2
  self.img_movies.anchor_y = self.img_movies.height/2
  
  self.img_music = pyglet.resource.image('music.png')
  self.img_music.anchor_x = self.img_music.width/2
  self.img_music.anchor_y = self.img_music.height/2
  
  self.img_shutdown = pyglet.resource.image('shutdown.png')
  self.img_shutdown.anchor_x = self.img_shutdown.width/2
  self.img_shutdown.anchor_y = self.img_shutdown.height/2
  
  self.img_reboot = pyglet.resource.image('reboot.png')
  self.img_reboot.anchor_x = self.img_reboot.width/2
  self.img_reboot.anchor_y = self.img_reboot.height/2
  
  self.img_eq = pyglet.resource.image('equalizer.png')
  self.img_eq.anchor_x = self.img_eq.width/2
  self.img_eq.anchor_y = self.img_eq.height/2
  
  
  #Player Control Buttons:
  self.img_mute = pyglet.resource.image('mute.png')
  self.img_mute.anchor_x = self.img_mute.width/2
  self.img_mute.anchor_y = self.img_mute.height/2
  
  self.img_unmute = pyglet.resource.image('unmute.png')
  self.img_unmute.anchor_x = self.img_unmute.width/2
  self.img_unmute.anchor_y = self.img_unmute.height/2
  
  self.img_prev = pyglet.resource.image('previous.png')
  self.img_prev.anchor_x = self.img_prev.width/2
  self.img_prev.anchor_y = self.img_prev.height/2
  
  self.img_rewind = pyglet.resource.image('rewind.png')
  self.img_rewind.anchor_x = self.img_rewind.width/2
  self.img_rewind.anchor_y = self.img_rewind.height/2
  
  self.img_play = pyglet.resource.image('play.png')
  self.img_play.anchor_x = self.img_play.width/2
  self.img_play.anchor_y = self.img_play.height/2
  
  self.img_pause = pyglet.resource.image('pause.png')
  self.img_pause.anchor_x = self.img_pause.width/2
  self.img_pause.anchor_y = self.img_pause.height/2
  
  self.img_ff = pyglet.resource.image('ff.png')
  self.img_ff.anchor_x = self.img_ff.width/2
  self.img_ff.anchor_y = self.img_ff.height/2
  
  self.img_next = pyglet.resource.image('next.png')
  self.img_next.anchor_x = self.img_next.width/2
  self.img_next.anchor_y = self.img_next.height/2
  
  #Volume Control Graphics
  self.img_volume = pyglet.resource.image('volume.png')
  self.img_volume.anchor_y = self.img_volume.height/2
  
  self.img_volume_fill = pyglet.resource.image('volume_fill.png')
  #self.img_volume_fill.anchor_y = self.img_volume_fill.height/2
  
  #self.maxFill = self.img_volume_fill.width
  
 def update_sprites(self):
  #print("Entering update_sprites function...")
  
  self.backgroundSprite = pyglet.sprite.Sprite(self.img_background, x=400, y=240, batch=batch)
  
  if(self.mainMenuVisible == True):
   self.mainMenuSprites = [pyglet.sprite.Sprite(self.img_movies, x=280, y=315, batch=batch),
         pyglet.sprite.Sprite(self.img_music, x=525, y=315, batch=batch),
         pyglet.sprite.Sprite(self.img_shutdown, x=60, y=425, batch=batch),
         pyglet.sprite.Sprite(self.img_reboot, x=60, y=305, batch=batch),
         pyglet.sprite.Sprite(self.img_eq, x=60, y=185, batch=batch)]
         
  if(self.canPlay == True and self.canMute == True):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=108, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_play, x=242, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
       
  if(self.canPlay == False and self.canMute == True):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=108, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_pause, x=242, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
       
  if(self.canPlay == False and self.canMute == False):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=108, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_pause, x=242, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
       
  if(self.canPlay == True and self.canMute == False):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=108, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_play, x=242, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
       
  
  self.volumeSprites = [pyglet.sprite.Sprite(self.img_volume, x=498, y=35, batch=batch),
       pyglet.sprite.Sprite(self.currentFill, x=498, y=3, batch=batch)]
       
  
       
 def update_volume_fill(self):
  self.currentFill = self.img_volume_fill.get_region(x=0, y=0, width=int(self.img_volume_fill.width * self.currentVolume), height=64)
 
 def update_volume(self):
  self.player.volume = self.currentVolume
  
 def on_draw(self):
  self.clear()
  
  if(self.frameCount%10 == 0):
   #print("Resetting frame count")
   self.frameCount = 0
   
   self.update_volume_fill()
   
   self.update_volume()
   
   self.update_sprites()
  batch.draw()
  
  self.frameCount += 1
 
 def open_file(self):
  
  self.root = Tkinter.Tk()
  self.root.withdraw()
  
  if(self.mediaType == 'Audio'):
   f = tkFileDialog.askopenfilename(parent = self.root, title = 'Please select an audio file:', filetypes = [('MP3 Files', '.mp3'), ('WAV Files', '.wav'), ('All Files', '.*')], initialdir = 'F:\\Media\\Music\\')
   if(f):
    file = f
    self.root.destroy()
    return file
   else:
    self.root.destroy()
    return None
    
  else:
   f = tkFileDialog.askopenfilename(parent = self.root, title = 'Please select a video file:', filetypes = [('AVI Files', '.avi'), ('MPEG Files', '.mpeg'), ('MKV Files', '.mkv'), ('All Files', '.*')], initialdir = 'F:\\Media\\Video\\')
   if(f):
    file = f
    self.root.destroy()
    return file
   else:
    self.root.destroy()
    return None
  
  
 def refresh_player(self):
 
  self.player = pyglet.media.Player()
  
  @self.player.event('on_eos')
  def auto_next():
   self.next()
   
  self.player.volume = self.currentVolume
 
 def previous(self):
  #Go back to the previous file in the current directory
  
  self.player.canPlay = True
  self.player.pause()
  if(self.player.source != 'null'):
   
   #Find the index of the first / from the end of our source filepath
   sourceIndex = self.selectedFile.rfind('/')
   
   #Now we can get the name of the directory (including path)
   directory = self.selectedFile[:sourceIndex + 1]
   
   previousFile = 'null'
   if(self.mediaType == 'Audio'):
    for file in os.listdir(directory):
     if file.endswith('.MP3') or file.endswith('.mp3'):
      print('Previous Button: Checking ' + file)
      if(self.selectedFile == (directory + file)):
       print('Previous Button: Match!')
       #Found the current file, now we need to check if there is a file before this in the directory, and play that file if there is
       if(previousFile != 'null'):
        print('Previous Button: Previous file is ' + previousFile)
        
        self.refresh_player()
        
        media = pyglet.media.load(directory + previousFile)
        self.player.queue(media)
        
        self.selectedFile = directory + previousFile
        
        self.player.play()
        self.player.canPlay = False
       else:
        print('Previous Button: Current file first in directory!')
        
       return
      else:
       #Assign the current file to previousFile so that we can refer back to it if the next file is the current source
       previousFile = file
       
     if file.endswith('.WAV') or file.endswith('.wav') or file.endswith('.Wav') or file.endswith('.WAv') or file.endswith('.waV') or file.endswith('.wAV'):
      print('Previous Button: Checking ' + file)
      if(self.selectedFile == (directory + file)):
       print('Previous Button: Match!')
       #Found the current file, now we need to check if there is a file before this in the directory, and play that file if there is
       if(previousFile != 'null'):
        print('Previous Button: Previous file is ' + previousFile)
        
        self.refresh_player()
        
        media = pyglet.media.load(directory + previousFile)
        self.player.queue(media)
        
        self.selectedFile = directory + previousFile
        
        self.player.play()
        self.player.canPlay = False
       else:
        print('Previous Button: Current file first in directory!')
        
       return
      else:
       #Assign the current file to previousFile so that we can refer back to it if the next file is the current source
       previousFile = file
       
  else:
   print('Previous Button: Source is null!')
        
 def next(self):
  #Go to the next file in the current directory
  
  self.player.canPlay = True
  self.player.pause()
  if(self.player.source != 'null'):
   
   #Find the index of the first / from the end of our source filepath
   sourceIndex = self.selectedFile.rfind('/')
   
   #Now we can get the name of the directory (including path)
   directory = self.selectedFile[:sourceIndex + 1]
   
   foundCurrent = False
   isLastFile = True
   
   if(self.mediaType == 'Audio'):
    for file in os.listdir(directory):
     if file.endswith('.MP3') or file.endswith('.mp3') or file.endswith('.mP3') or file.endswith('.Mp3'):
      if(foundCurrent):
       print('Next Button: Next file is ' + file)
       
       self.refresh_player()
       
       media = pyglet.media.load(directory + file)
       self.player.queue(media)
       
       self.selectedFile = (directory + file)
       
       self.player.play()
       self.player.canPlay = False
       
       #Use this to tell program we successfully loaded the next file
       isLastFile = False
       
       return
       
      if(self.selectedFile == (directory + file)):
      
       #Found the current file, so change foundCurrent to True
       foundCurrent = True
       
     if file.endswith('.WAV') or file.endswith('.wav') or file.endswith('.Wav') or file.endswith('.WAv') or file.endswith('.waV') or file.endswith('.wAV'):
      if(foundCurrent):
       print('Next Button: Next file is ' + file)
       
       self.refresh_player()
       
       media = pyglet.media.load(directory + file)
       self.player.queue(media)
       
       self.selectedFile = (directory + file)
       
       self.player.play()
       self.player.canPlay = False
       
       #Use this to tell program we successfully loaded the next file
       isLastFile = False
       
       return
       
      if(self.selectedFile == (directory + file)):
      
       #Found the current file, so change foundCurrent to True
       foundCurrent = True
        
   if(isLastFile):
    print('Next Button: Current file is last in the directory!')
      
   
 def check_for_button(self, x, y):
  #Check all button coordinates against x and y, return True if a button was hit or False if not
  
  if( (525 - (self.img_music.width / 2)) < x < (525 + (self.img_music.width / 2)) and ( (315 - (self.img_music.height / 2)) < y < (315 + (self.img_music.height / 2)) ) ):
   #coordinate lies inside the music button
   if(self.player.playing):
    self.player.canPlay = True
    self.player.pause()
    wasPlaying = True
   else:
    wasPlaying = False
   
   self.mediaType = 'Audio'
   filename = self.open_file()
   
   if(filename):
    self.selectedFile = filename
    print('Opening ' + self.selectedFile + ' ...')
    
    #Drop the current queue so we can build a new one by overwriting our player with a fresh one
    self.refresh_player()
    
    media = pyglet.media.load(filename)
    self.player.queue(media)
    
  
    #Start playback from the selected file
    self.canPlay = False
    self.player.play()
   else:
    print('Dialog cancelled')
    
    if(wasPlaying):
     self.player.play()
   
  if( (280 - (self.img_movies.width / 2)) < x < (280 + (self.img_movies.width / 2)) and ( (315 - (self.img_movies.height /2)) < y < (315 + (self.img_movies.height /2)) ) ):
   #coordinate lies inside the movies button
   if(self.player.playing):
    self.player.canPlay = True
    self.player.pause()
    wasPlaying = True
   else:
    wasPlaying = False
   
   self.mediaType = 'Video'
   filename = self.open_file()
   
   if(filename):
    self.selectedFile = filename
    print('Opening ' + self.selectedFile + ' ...')
    
    #Drop the current queue so we can build a new one by overwriting our player with a fresh one
    self.refresh_player()
    
    media = pyglet.media.load(filename)
    self.player.queue(media)
  
    
    #Start playback from the selected file
    self.canPlay = False
    self.player.play()
   else:
    print('Dialog cancelled')
    
    if(wasPlaying):
     self.player.play()
    
  if( (242 - (self.img_play.width / 2)) < x < (242 + (self.img_play.width / 2)) and ( (35 - (self.img_play.height / 2)) < y < (35 + (self.img_play.height / 2)) ) ):
   #coordinate lies inside the play/pause button
   if(self.canPlay):
    print('Play Button: Playing ' + self.selectedFile)
    self.player.play()
    
    self.canPlay = False
   else:    
    print('Pause Button: Pausing ' + self.selectedFile)
    self.player.pause()
    
    self.canPlay = True
   
  if( (434 - (self.img_next.width / 2)) < x < (434 + (self.img_next.width / 2)) and ( (35 - (self.img_next.height / 2)) < y < (35 + (self.img_next.height / 2)) ) ):
   #coordinate lies inside the next button
   print('Clicked next button...')
   
   self.next()
   
  if( (50 - (self.img_prev.width / 2)) < x < (50 + (self.img_prev.width / 2)) and ( (35 - (self.img_prev.height / 2)) < y < (35 + (self.img_prev.height / 2)) ) ):
   #coordinate lies inside the previous button
   print('Clicked previous button...')
   
   self.previous()
   
  if( (50 - (self.img_mute.width / 2)) < x < (50 + (self.img_mute.width / 2)) and ( (108 - (self.img_mute.height / 2)) < y < (108 + (self.img_mute.height / 2)) ) ):
   #coordinate lies inside the mute/unmute button
   if(self.canMute):
    print('Mute Button: Audio Muted!')
    self.player.volume = 0
    self.canMute = False
   else:
    print('Mute Button: Audio Unmuted!')
    self.player.volume = self.currentVolume
    self.canMute = True
    
  if( (146 - (self.img_rewind.width / 2)) < x < (146 + (self.img_rewind.width / 2)) and ( (35 - (self.img_rewind.height / 2)) < y < (35 + (self.img_rewind.height / 2)) ) ):
   #coordinate lies inside the rewind button
   if(self.player.playing or self.canPlay):
    print('Rewind Button: Seeking backwards 10 seconds.')
    newPosition = self.player.time - 10.0
    
    if(newPosition < 0.0):
     print('Rewind Button: New time is negative, set to 0 instead.')
     newPosition = 0.0
     
    self.player.seek(newPosition)
    
    if(self.canPlay):
     pass
    else:
     self.player.play()
    
  if( (338 - (self.img_ff.width / 2)) < x < (338 + (self.img_ff.width / 2)) and ( (35 - (self.img_ff.height / 2)) < y < (35 + (self.img_ff.height / 2)) ) ):
   #coordinate lies inside the fast forward button
   if(self.player.playing or self.canPlay):
    print('FF Button: Seeking forwards 10 seconds.')
    newPosition = self.player.time + 10.0
    
    #Note: We don't need to check if the time is past the end of the track because the Player() class will automatically clamp this to the end of the source
    
    self.player.seek(newPosition)
    if(self.canPlay):
     pass
    else:
     self.player.play()
     
     
  self.volumeSprites = [pyglet.sprite.Sprite(self.img_volume, x=498, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_volume_fill, x=498, y=35, batch=batch)]
       
  if( (498 < x < (498 + self.img_volume.width)) and ( (35 - (self.img_volume.height / 2)) < y < (35 + (self.img_volume.height / 2)) ) ):
   #coordinate lies inside the volume control
   
   #First, we need to convert the image's width to a percentage scale
   ratio = 100.0 / self.img_volume.width / 100.0
   
   #To make more sense of this, here is an example:
   #100 / 256 = 0.390625
   #If our touch/mouse location was 128 (right in the middle of the image), we want to set our volume to 50%, or 0.5
   #Since 128 * 0.390625 = 50, we still need to divide that by 100 to get the percentage (or 0 to 1 decimal) that we want to use.
   #Therefore, instead of 100 / 256 = 0.390625, we need a ratio of 100 / 256 / 100 = 0.00390625
   
   #Now we need to find our x position in relation to the image, instead of the screen like it currently is
   local_x = x - 498
   
   
   #Let's use our new conversion factor to set the volume appropriately
   self.currentVolume = local_x * ratio
   
   
   
 
 def on_mouse_press(self, x, y, button, modifiers):
  self.check_for_button(x, y)
  
  
if __name__ == '__main__':
 window = Piamp()
 #window.set_mouse_visible(False)
 pyglet.app.run()


I've decided to utilize Tkinter and tkFileDialog for the file opening window, which looks like this:

This dialog will look different depending on the operating system that runs it. Tkinter works on a variety of operating systems, so it was an easy choice for this functionality.

To give you an idea on the state of the player:

Working:

  • Volume Slider
  • Previous Button
  • Rewind
  • Play/Pause
  • Fast Forward
  • Next Button
  • Mute/Unmute
  • Music Button (Open audio file)
  • Movies Button (Open video file)
Not Yet Implemented:
  • Show current selected file in text box
  • Shutdown button
  • Reboot button
  • Equalizer button
  • Video Playback - Currently player only plays audio for video files
In case you want to play around with the player (or the code) yourself, here is a zip file of the entire project:


Stay tuned - I'll be finishing up as much as possible tomorrow, and then I'm transferring all of this to the Pi and getting it installed in the car!

Saturday, June 15, 2013

Building a python media player: Part 1

After testing out several different libraries for python, I've decided upon pyglet. Getting an mp3 file to play was as simple as this:

import pyglet

player_window = pyglet.window.Window(800, 480)
player = pyglet.media.Player()

song=pyglet.media.load('thesong.mp3')
player.queue(song)
player.play()
pyglet.app.run()

Pyglet's player class has a lot of built-in functionality that should streamline the development of this media player (which is good because I have to do it in 2 weeks!). Currently my prototype has the graphics positioned and updating. Here is a screenshot:



Here is the full code so far:

#---------------------------------------------------------------------
# piamp prototype
#---------------------------------------------------------------------

import os
import pyglet

dir = pyglet.resource.get_settings_path('Piamp')
if not os.path.exists(dir):
 os.makedirs(dir)
configFileName = os.path.join(dir, 'settings.cfg')

pyglet.resource.path = ['resources']
pyglet.resource.reindex()
batch = pyglet.graphics.Batch()

class Piamp(pyglet.window.Window):
 def __init__(self):
  super(Piamp, self).__init__()
  self.set_size(800, 480)
  
  configFile = open(configFileName, 'wt')
  
  self.load_graphics()
  self.canPlay = True
  self.canMute = True
  self.mainMenuVisible = True
  self.frameCount = 0
  
  player = pyglet.media.Player()
  player.volume = 0.5
  
 def load_graphics(self):
  #Background:
  self.img_background = pyglet.resource.image('background.png')
  self.img_background.anchor_x = self.img_background.width/2
  self.img_background.anchor_y = self.img_background.height/2
  
  #Main Menu Buttons:
  self.img_movies = pyglet.resource.image('movies.png')
  self.img_movies.anchor_x = self.img_movies.width/2
  self.img_movies.anchor_y = self.img_movies.height/2
  
  self.img_music = pyglet.resource.image('music.png')
  self.img_music.anchor_x = self.img_music.width/2
  self.img_music.anchor_y = self.img_music.height/2
  
  self.img_shutdown = pyglet.resource.image('shutdown.png')
  self.img_shutdown.anchor_x = self.img_shutdown.width/2
  self.img_shutdown.anchor_y = self.img_shutdown.height/2
  
  self.img_reboot = pyglet.resource.image('reboot.png')
  self.img_reboot.anchor_x = self.img_reboot.width/2
  self.img_reboot.anchor_y = self.img_reboot.height/2
  
  self.img_eq = pyglet.resource.image('equalizer.png')
  self.img_eq.anchor_x = self.img_eq.width/2
  self.img_eq.anchor_y = self.img_eq.height/2
  
  
  #Player Control Buttons:
  self.img_mute = pyglet.resource.image('mute.png')
  self.img_mute.anchor_x = self.img_mute.width/2
  self.img_mute.anchor_y = self.img_mute.height/2
  
  self.img_unmute = pyglet.resource.image('unmute.png')
  self.img_unmute.anchor_x = self.img_unmute.width/2
  self.img_unmute.anchor_y = self.img_unmute.height/2
  
  self.img_prev = pyglet.resource.image('previous.png')
  self.img_prev.anchor_x = self.img_prev.width/2
  self.img_prev.anchor_y = self.img_prev.height/2
  
  self.img_rewind = pyglet.resource.image('rewind.png')
  self.img_rewind.anchor_x = self.img_rewind.width/2
  self.img_rewind.anchor_y = self.img_rewind.height/2
  
  self.img_play = pyglet.resource.image('play.png')
  self.img_play.anchor_x = self.img_play.width/2
  self.img_play.anchor_y = self.img_play.height/2
  
  self.img_pause = pyglet.resource.image('pause.png')
  self.img_pause.anchor_x = self.img_pause.width/2
  self.img_pause.anchor_y = self.img_pause.height/2
  
  self.img_ff = pyglet.resource.image('ff.png')
  self.img_ff.anchor_x = self.img_ff.width/2
  self.img_ff.anchor_y = self.img_ff.height/2
  
  self.img_next = pyglet.resource.image('next.png')
  self.img_next.anchor_x = self.img_next.width/2
  self.img_next.anchor_y = self.img_next.height/2
 
 def update_sprites(self):
  #print("Entering update_sprites function...")
  
  self.backgroundSprite = pyglet.sprite.Sprite(self.img_background, x=400, y=240, batch=batch)
  
  if(self.mainMenuVisible == True):
   self.mainMenuSprites = [pyglet.sprite.Sprite(self.img_movies, x=280, y=315, batch=batch),
         pyglet.sprite.Sprite(self.img_music, x=525, y=315, batch=batch),
         pyglet.sprite.Sprite(self.img_shutdown, x=60, y=425, batch=batch),
         pyglet.sprite.Sprite(self.img_reboot, x=60, y=305, batch=batch),
         pyglet.sprite.Sprite(self.img_eq, x=60, y=185, batch=batch)]
         
  if(self.canPlay == True and self.canMute == True):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=208, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=304, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_play, x=400, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=496, y=35, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=592, y=35, batch=batch)]
       
  if(self.canPlay == False and self.canMute == True):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=208, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=304, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_pause, x=400, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=496, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=592, y=360, batch=batch)]
       
  if(self.canPlay == False and self.canMute == False):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=208, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=304, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_pause, x=400, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=496, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=592, y=360, batch=batch)]
       
  if(self.canPlay == True and self.canMute == False):
   self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_prev, x=208, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_rewind, x=304, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_play, x=400, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_ff, x=496, y=360, batch=batch),
       pyglet.sprite.Sprite(self.img_next, x=592, y=360, batch=batch)]
 def on_draw(self):
  self.clear()
  
  if(self.frameCount%10 == 0):
   #print("Resetting frame count")
   self.frameCount = 0
   self.update_sprites()
  batch.draw()
  
  self.frameCount += 1
 
 def check_for_button(self, x, y):
  #Check all button coordinates against x and y, return True if a button was hit or False if not
  pass
 
 def on_mouse_press(x, y, button, modifiers):
  if (check_for_button(x, y)):
   pass
  pass
 
 def on_mouse_release(x, y, button, modifiers):
  pass
  
 def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
  pass
  
if __name__ == '__main__':
 window = Piamp()
 #window.set_mouse_visible(False)
 pyglet.app.run()

Keep in mind this code is still quite raw. After I've got the media player in a fairly stable setting, I'll walk through the entire code file step by step and explain what is going on.

Sunday, June 9, 2013

Speech issues; changing course again

Sorry it's been awhile since my last post. I've been swimming in the deep end of Linux speech recognition, and unfortunately I haven't had much luck. I was able to get CMU sphinx compiled and "working," but the accuracy of the recognition was extremely low, so I had to find an alternative. I attempted to follow the voxforge guide for setting up Julius and HTK, but I ran into a lot of trouble there as well. HTK would not compile correctly with the Pi's gcc 4.6, and after several hours of searching I was not able to find a gcc version under 4 that would work with the Pi.

Due to time constraints, I've decided to abandon speech recognition for pi-amp. Instead, I'm going to build a custom media player in python controlled by touch. For the touch aspect, I've ordered this kit from ebay that comes with a 7" monitor, a 7" touchscreen panel to fit the monitor, and a controller board that converts the touch input to usb.


Here is a rough rendition of the interface:

















The top-left button will send a "sudo halt" to the Pi (shutdown command), so that I can safely turn the Pi off in the vehicle.

The middle-left button may or may not be in the final rendition, but I decided to create it at least for the testing phases of the application. This button will reboot the Pi.

The lower-left button will open a mixer/equalizer menu to adjust bass & treble.

The music button will allow the user to browse the contents of the music library, as well as select a song to play. The primary display area will be replaced with a file browser. Once a file is selected, the user will be returned to the main interface.

The video button will allow the user to browse the contents of the video library, as well as select a video to play. The primary display area will be replaced with a file browser. Once a file is selected, the primary display area will be replaced with the chosen video.

The central info-box will display information about the currently playing file.

As to the player controls, they are fairly self explanatory. From left to right:

  • mute/unmute toggle button (will switch graphics when toggled)
  • previous
  • rewind
  • play/pause toggle button (will switch graphics when toggled)
  • stop
  • fast forward
  • next

The last button on the right is a placeholder graphic - I'm going to come up with something unique to the project. This button will call the main menu to the primary display area. If playing a video, the user can press this main menu button again to re-hide the main menu.


I still need to find a good place for a volume control, and possibly a horizontal seek slider so the user can jump to different parts of the file easier.


This next week should have several posts - I have a lot to get done. First, I'm going to get a more final draft of the interface done, and then I'll be starting work on the Python code. Stay tuned!


Wednesday, May 29, 2013


 After visiting my local Radio Shack and Staples without finding the USB sound adapter or USB microphone that would work with the Pi, I decided to check one of the local box stores. After searching through their computer accessories for awhile I happened upon this Logitech C110 webcam with microphone, which reminded me of a blog post I was reading about voice control, here. After a few Google searches on my smartphone, I discovered that someone else had already used this same webcam and was able to stream audio from it successfully (here). At a purchase price of $19, this was cheaper than most standalone USB microphones, so I decided to go with the C110 webcam to act as my microphone. Maybe I'll add gesture-control to the Pi-amp later on!















Here is a close up view of the camera.






















I also picked up a 3' audio cable for the audio out on the Pi.












And I figured I'd probably need these zip ties for the cable routing once I move everything to the vehicle.











Stay tuned - I'll be getting the microphone set up soon!



Sunday, May 26, 2013

Obstacles; time to take a step back

The lap-dock turned out to take up a bit more of the view in my front windshield than I was comfortable with, so I've decided to modify my project slightly. I'm going to be getting the Pi set up to operate headless and be completely voice controlled. I spent the majority of the day attempting to get my Pi to recognize my Turtle Beach bluetooth earpiece (to no avail). Setting up bluetooth to work on the Pi is much more difficult than I had anticipated. I may look at getting a simple USB microphone that I can mount somewhere in the vehicle instead.


These obstacles that I seem to be running into have made me realize that my project planning needs a bit more.... well, planning. To that end, I've decided to come up with an official project plan:


Project Summary

The goal of "pi-amp" is to create a headless (no video display) jukebox for the vehicle which is controlled entirely through voice. Pi-amp will log in automatically after boot, after which it will play a sound file to the stereo and begin listening for voice commands. The vehicle operator will then be able to search for either artist, song title, or album name using a simple command structure.

Pi-amp will play a sound to confirm that the command was recognized. If more than one match is found, Pi-amp will inform the operator. The operator may then either say "filter <criteria>" to include only those files which match <criteria>, or say "list choices" to tell Pi-amp to read off the choices. Pi-amp will number the choices as it reads them for easy selection. The operator may say "cancel" at any time to tell Pi-amp to stop any action it is currently performing. After choices are read, the operater may say "select <number>" to indicate to Pi-amp which file should be played.

Timeline

Week 1 (May 6-12):

Successfully boot Raspberry Pi.

Week 2 (May 13-19):

Acquire all hardware necessary for headless in-vehicle operation.

Week 3 (May 20-26):

Successfully record an audio file using an attached microphone.

Week 4 (May 27-June 2):

Install Julius continuous speech decoder OR pocketsphinx.

Week 5 (June 3-9):

Able to play audio files from attached USB hard drive using python script.

Week 6 (June 10-16):

Script to search sound files completed.

Week 7 (June 17-23):

Speech control scripts finalized and pi-amp installed and working in vehicle.

Scope

Hardware Requirements:

  • Raspberry Pi
  • Powered USB Hub
  • USB Microphone
  • USB Hard Drive
  • 150 Watt 3-Outlet Power Inverter with USB
  • 5V 1.2A DC to USB charging adapter with two USB ports

Software Requirements:

  • Debian "Wheezy"
  • Julius large vocabulary continuous speech recognition decoder OR pocketsphinx


I've already completed the milestones for weeks 1 and 2, but I've yet to complete the milestone for week 3 (successfully record from microphone). As a result, I'm going to have to work a bit of over time in week 4 to finish up week 3's milestone in addition to week 4's. It should be a busy week!



Saturday, May 18, 2013

Project components arrive, one minor disappointment:


Motorola Bionic Lapdock

I received my Motorola Bionic Lapdock today, and I was greeted by a small disappointment, which you can see pictured below. There are numerous gouges on the bottom of the lapdock, as well as one major puncture mark (shiny silver spot mid-right). I haven't received all of my adapter cables to hook the Pi up to the lapdock yet, so I can't test the functionality of the screen, but I was able to confirm that the lapdock's battery is charging while plugged into the wall.




On the bright side, the top of the lapdock looks much better, and it fits almost perfectly in the dash compartment of my Ford Fusion that I was planning to use as a mounting platform for the lapdock. All I need to do is remove the compartment cover, and then I will be able to open the lapdock's screen fully. The lapdock will also sit about 1.5 inches further back without the compartment cover in place, so it won't be hanging over the vents like it is in the pictures below.



















Raspberry Pi!


I also received my Raspberry Pi! The Pi's tiny size is simply incredible when you see it first-hand. The pictures really don't do it justice! I promptly took a trip to town to get an SD card in order to get a test environment up and running on the Pi.




After loading the Raspbian "Wheezy" image onto my newly acquired 8GB SD card using Win32DiskImager, I promptly assembled all the necessary cables for a test boot of the Pi. The top micro-usb cable is providing power to the Pi, while the left HDMI cable is sending the Pi's video output to an HDTV. The bottom USB cables are the keyboard and mouse, while the bottom-left yellow cable is providing a network connection to the Pi.













This first boot of "Wheezy" opened the raspi-config utility from the terminal, and from there I was able to set Pi to boot to the Raspbian desktop by default. After choosing this option the Pi rebooted itself, and then I was greeted with this lovely desktop:


I'm looking forward to testing through the lapdock instead of my HDTV, as well as starting work on the voice-control portion of this project, which will be utilizing pocketsphinx and custom python code for controlling the Pi with voice commands.