#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import threading
import os
import signal
import re
from subprocess import *
import gtk
import pygtk
import gobject

import ConfigParser
import gettext

gettext.install('recorder')
gobject.threads_init()
gtk.gdk.threads_init()

class Recorder:
    dev, temp, growisofs, cdrecord, save = '', '', '', '', False
    context_id = 0
    
    ui = '''<ui>
        <menubar name='MenuBar'>
	    <menu action='File'>
		<menuitem action='Add file'/>
		<menuitem action='Add directory'/>
		<menuitem action='Remove'/>
		<separator/>
		<menuitem action='Preferences'/>
		<separator/>
		<menuitem action='Quit'/>
	    </menu>
	    <menu action='Disc'>
		<menuitem action='Erase CDRW'/>
		<menuitem action='Rip disc'/>
	    </menu>
	    <menu action='Session'>
		<menuitem action='CD Data'/>
		<menuitem action='CD Audio'/>
		<menuitem action='Video CD'/>
		<menuitem action='SuperVideo CD'/>
		<menuitem action='DVD Data'/>
		<menuitem action='DVD Video'/>
		<menuitem action='ISO Image'/>
		<separator/>
		<menuitem action='Burn'/>
	    </menu>
	    <menu action='Help'>
		<menuitem action='About'/>
	    </menu>
        </menubar>
	<toolbar name='ToolBar'>
	    <toolitem action='Add file'/>
	    <toolitem action='Add directory'/>
	    <toolitem action='Remove'/>
	    <separator/>
	    <toolitem action='Burn'/>
	</toolbar>
    </ui>'''

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
	self.window.connect('destroy', gtk.main_quit)
	self.window.set_title('Recorder')
	self.window.set_default_size(400, 400)
	self.uimanager = gtk.UIManager()
	accelgroup = self.uimanager.get_accel_group()
	self.window.add_accel_group(accelgroup)
	actiongroup = gtk.ActionGroup('Recorder')
        actiongroup.add_radio_actions([('CD Data', None, _('_CD Data'), None, None, 0),
	    ('CD Audio', None, _('CD _Audio'), None, None, 1),
	    ('Video CD', None, _('V_ideo CD'), None, None, 2),
	    ('SuperVideo CD', None, _('_SuperVideo CD'), None, None, 3),
	    ('DVD Data', None, _('_DVD Data'), None, None, 4),
	    ('DVD Video', None, _('DVD _Video'), None, None, 5),
	    ('ISO Image', None, _('_ISO Image'), None, None, 6)])
	actiongroup.add_actions([('File', None, _('_File')),
	    ('Quit', gtk.STOCK_QUIT, _('_Quit'), None, None, gtk.main_quit),
	    ('Erase CDRW', gtk.STOCK_CLEAR, _('_Erase CDRW'), None, None, self.erase_cd),
	    ('Add file', gtk.STOCK_FILE, _('Add _file'), '<Control>f', _('Add file'), self.add_files),
	    ('Add directory', gtk.STOCK_DIRECTORY, _('Add _directory'), '<Control>d', _('Add directory'), self.add_dirs),
	    ('Remove', gtk.STOCK_DELETE, _('_Remove'), 'Delete', _('Remove'), self.remove_files),
	    ('Session', None, _('_Session')),
	    ('Preferences', gtk.STOCK_PREFERENCES , _('_Preferences'), None , _('Options'), self.mod_config),
	    ('Burn', gtk.STOCK_MEDIA_RECORD, _('_Burn'), '<Control>b', _('Burn'), self.start),
	    ('Rip disc', gtk.STOCK_CDROM , _('_Rip disc'), None, None, self.rip_disc),
	    ('Disc', None, _('_Disc')),
	    ('Help', None, _('_Help')),
	    ('About', gtk.STOCK_ABOUT, _('_About'), None, None, self.about)])
	self.uimanager.insert_action_group(actiongroup, 0)
	self.uimanager.add_ui_from_string(self.ui)

	menubar = self.uimanager.get_widget('/MenuBar')	
	toolbar = self.uimanager.get_widget('/ToolBar')
	self.liststore = gtk.ListStore(str, str)
	self.treeview = gtk.TreeView(self.liststore)
	self.treeselection = self.treeview.get_selection()
	self.treeselection.set_mode(gtk.SELECTION_MULTIPLE)
	column = gtk.TreeViewColumn(_('File'))
	column.set_expand(True)
	cellrenderer = gtk.CellRendererText()
	column.pack_start(cellrenderer, True)
	column.add_attribute(cellrenderer, 'text', 0)
	self.treeview.append_column(column)
	column = gtk.TreeViewColumn(_('Size'))
	cellrenderer = gtk.CellRendererText()
	column.pack_start(cellrenderer, True)
	column.add_attribute(cellrenderer, 'text', 1)
	self.treeview.append_column(column)
	liststore_scroll = gtk.ScrolledWindow()
	liststore_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
	liststore_scroll.add(self.treeview)
	self.statusbar = gtk.Statusbar()
	self.context_id = self.statusbar.get_context_id('Total Size')
	main_box = gtk.VBox(False)
	main_box.pack_start(menubar, False)
	main_box.pack_start(toolbar, False)
	main_box.pack_start(liststore_scroll, True)
	main_box.pack_start(self.statusbar, False)

	self.textbuffer = gtk.TextBuffer()
	self.textbuffer.create_mark('tail', self.textbuffer.get_end_iter(), False)
	self.output = gtk.TextView(self.textbuffer)
	self.output.set_editable(False)
	output_scroll = gtk.ScrolledWindow()
	output_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
	output_scroll.add(self.output)
	close_button = gtk.Button(_('Close'))
	close_button.connect('clicked', self.close)
	tab2 = gtk.VBox(False, 5)	
	tab2.set_border_width(5)
	tab2.pack_start(output_scroll,True)
	tab2.pack_start(close_button,False)

	self.notebook = gtk.Notebook()
	self.notebook.append_page(main_box)
	self.notebook.append_page(tab2)
	self.notebook.set_show_tabs(False)
	self.notebook.set_show_border(False)

	self.window.add(self.notebook)
	self.window.show_all()

#################################
# READ USER INPUT

    def read_variables(self):
	if len(sys.argv) > 2:
            if sys.argv[1] == 'cd-data':
		self.uimanager.get_widget('/MenuBar/Session/CD Data').set_active(True)
	    elif sys.argv[1] == 'cd-audio':
		self.uimanager.get_widget('/MenuBar/Session/CD Audio').set_active(True)
	    elif sys.argv[1] == 'cd-video':
		self.uimanager.get_widget('/MenuBar/Session/Video CD').set_active(True)
	    elif sys.argv[1] == 'cd-svideo':
		self.uimanager.get_widget('/MenuBar/Session/SuperVideo CD').set_active(True)
	    elif sys.argv[1] == 'dvd-data':
		self.uimanager.get_widget('/MenuBar/Session/DVD Data').set_active(True)
	    elif sys.argv[1] == 'dvd-video':
		self.uimanager.get_widget('/MenuBar/Session/DVD Video').set_active(True)
	    elif sys.argv[1] == 'iso':
		self.uimanager.get_widget('/MenuBar/Session/ISO Image').set_active(True)
	    for filename in sys.argv[2:]:
	    	self.add_to_tree(filename,os.path.getsize(filename))

#################################

#################################
# DIALOGS

# OPTIONS

    def mod_config(self, data):
	preferences_dialog = gtk.Dialog(_('Preferences'), None, gtk.DIALOG_NO_SEPARATOR,(gtk.STOCK_OK, gtk.RESPONSE_OK))
	preferences_dialog.set_border_width(5)

	dlabel = gtk.Label()
	dlabel.set_markup('<b>Optical drive</b>')
	dlabel.set_alignment(0,0.5)
	dev_hbox = gtk.HBox(spacing=12)
	dev_label = gtk.Label(_('Path to your drive: '))
	dev_label.set_alignment(0,0.5)
	dev_entry = gtk.Entry()
	dev_entry.set_text(self.dev)
	dev_hbox.pack_start(dev_label, False)
	dev_hbox.pack_start(dev_entry, False)

	tlabel = gtk.Label()
	tlabel.set_markup('<b>Temporary files</b>')
	tlabel.set_alignment(0,0.5)
	temp_hbox = gtk.HBox(spacing=12)
	temp_label = gtk.Label(_('Directory to save files: '))
	temp_label.set_alignment(0,0.5)
	direntry = gtk.FileChooserButton(_('Select a directory'))
	direntry.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
	if self.temp == '':
	    if not os.path.exists(os.path.expanduser('~/.config/recorder/')): 
	    	os.makedirs(os.path.expanduser('~/.config/recorder/'))
	    if not os.path.exists(os.path.expanduser('~/.config/recorder/temp')): 
	    	os.makedirs(os.path.expanduser('~/.config/recorder/temp'))
 	    direntry.set_current_folder(os.path.expanduser('~/.config/recorder/temp'))
	else:
	    direntry.set_current_folder(self.temp)
	temp_hbox.pack_start(temp_label, False)
	temp_hbox.pack_start(direntry, False)

	clabel = gtk.Label()
	clabel.set_markup('<b>Command line</b>')
	clabel.set_alignment(0,0.5)
	cdr_label = gtk.Label(_('Options for cdrecord: '))
	cdr_label.set_alignment(0,0.5)
	cdr_entry = gtk.Entry()
	cdr_entry.set_text(self.cdrecord);
	grow_label = gtk.Label(_('Options for growisofs: '))
	grow_label.set_alignment(0,0.5)
	grow_entry = gtk.Entry()
	grow_entry.set_text(self.growisofs);
  	save_button = gtk.CheckButton(_("Save options on exit"))
	if self.save == True:
	    save_button.set_active(True)
	
	widgets = (dlabel, dev_hbox, tlabel, temp_hbox, clabel, cdr_label, cdr_entry, grow_label, grow_entry, save_button)
	table = gtk.Table(len(widgets), 1, False)
	for i, widget in enumerate(widgets):
	    if i in (0,2,4):
		table.attach(widget, 0, 1, i, i+1, gtk.FILL|gtk.EXPAND, gtk.FILL,0,8)
	    else:
		table.attach(widget, 0, 1, i, i+1, gtk.FILL|gtk.EXPAND, gtk.FILL,20,1)
	table.set_row_spacing(1,6)
	table.set_row_spacing(3,6)

	preferences_dialog.vbox.pack_start(table)
	preferences_dialog.show_all()

	if preferences_dialog.run() == gtk.RESPONSE_OK:
	    self.dev = dev_entry.get_text()
	    self.temp = direntry.get_current_folder()
	    self.cdrecord = cdr_entry.get_text()
	    self.growisofs = grow_entry.get_text()
	    config = ConfigParser.ConfigParser()
	    config.add_section('paths')
	    config.set('paths', 'dev', self.dev)
	    config.set('paths', 'temp', self.temp)
	    if save_button.get_active() == True:
		self.save = True
	    	config.add_section('options')
	    	config.set('options', 'cdrecord', self.cdrecord)
	    	config.set('options', 'growisofs', self.growisofs)
	    else:
	    	self.save = False
    	    config.write(file(os.path.expanduser('~/.config/recorder/recorderrc'), 'w'))

	preferences_dialog.destroy()

    def read_config(self):
	config = ConfigParser.ConfigParser()
	if os.path.isfile(os.path.expanduser('~/.config/recorder/recorderrc')):
	    config.read(os.path.expanduser('~/.config/recorder/recorderrc'))
	    if config.has_option('paths', 'dev'): 
		self.dev = config.get('paths', 'dev')
	    if config.has_option('paths', 'temp'): 
		self.temp = config.get('paths', 'temp')
	    if config.has_option('options', 'cdrecord'): 
		self.cdrecord = config.get('options', 'cdrecord')
		self.save = True
	    if config.has_option('options', 'growisofs'): 
		self.growisofs = config.get('options', 'growisofs')
		self.save = True
	else:
	    self.mod_config(None)

# CHOOSER

    def chooser(self, type, name):
	filenames = []
	filechooser = gtk.FileChooserDialog(name, action=type, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
	if type != gtk.FILE_CHOOSER_ACTION_SAVE: 
	    filechooser.set_select_multiple(True)
	if filechooser.run() == gtk.RESPONSE_OK: 
	    for i in filechooser.get_filenames(): 
		filenames.append(i) 
	filechooser.destroy()
	return filenames

#################################


#################################
# FILES

# ADD

    def add_files(self, data):
	for filename in self.chooser(gtk.FILE_CHOOSER_ACTION_OPEN, _('Add file')): 
	    self.add_to_tree(filename,os.path.getsize(filename))
	
    def add_dirs(self, data):
	for directoryname in self.chooser(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, _('Add folder')): 
	    self.add_to_tree(directoryname,(int)(os.popen('du -s "' + directoryname +'"').readlines()[0].split('\t')[0]) * 1024)

    def add_to_tree(self, name, size):
	if size < 1024: 
	    dot_position = (str)((float)(size)).find('.')
	    formatted_size = (str)((float)(size))[:dot_position+3] + ' B'
	elif size < 1048576:
	    dot_position = (str)((float)(size)/1024).find('.')
	    formatted_size= (str)((float)(size)/1024)[:dot_position+3] + ' KB'
	else: 
	    dot_position = (str)((float)(size)/1048576).find('.')
	    formatted_size= (str)((float)(size)/1048576)[:dot_position+3] + ' MB'	
	self.liststore.append((name,formatted_size))
	self.update_size()

# REMOVE
	
    def remove_files(self, data):
	iters = []
	sorted_model, selected = self.treeselection.get_selected_rows()
	for path in selected: 
	    iters.append(sorted_model.get_iter(path))
	for iter in iters: 
	    sorted_model.remove(iter)
	self.update_size()

# CLEAN

    def clean_temp(self):
	if os.path.isdir(self.temp):
	    for root, dirs, files in os.walk(self.temp, False):
		for name in files: 
		    os.remove(os.path.join(root, name))
		for name in dirs: 
		    os.remove(os.path.join(root, name))

# UPDATE TOTAL SIZE

    def update_size(self):
	size_list = [i[1].strip() for i in self.treeview.get_model()]
	total_size = 0.0
	for size in size_list:
	    if size.endswith(' B'): 
		total_size += (float)(size.split(' ')[0])
	    elif size.endswith(' KB'): 
		total_size += (float)(size.split(' ')[0])*1024
	    else: 
		total_size += (float)(size.split(' ')[0])*1048576
	if total_size < 1024:
	    x = (str)(total_size).find('.')
	    c =  _('Total size: ')+(str)(total_size)[:x+3] + ' B'
	elif total_size < 1048576:
	    x = (str)(total_size/1024).find('.')
	    c =  _('Total size: ')+(str)(total_size/1024)[:x+3] + ' KB'
	else: 
	    x = (str)(total_size/1048576).find('.')
	    c = _('Total size: ')+(str)(total_size/1048576)[:x+3] + ' MB'
	self.statusbar.push(self.context_id, c)

#################################

#################################
# ABOUT

    def about(self, data):
	dialog = gtk.AboutDialog()
	dialog.set_name('Recorder 1.4.5')
	dialog.set_comments(_('A frontend application for cdrecord, mkisofs and growisofs.\n Based on PyBurn 0.1 by Judd Vinet.'))
	dialog.set_copyright('Copyright ©2008 Alex Ciurana')
	dialog.set_license('''Copyright ©2008 Alex Ciurana <icrave@gmail.com>

This is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with program; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.''')
	dialog.run()
	dialog.destroy()

#################################

#################################
# BURN FUNCTIONS

    def start(self, data):
	self.filelist = [i[0].strip() for i in self.treeview.get_model()]
	if len(self.filelist) == 0: 
	    return
	self.clean_temp()
	if self.uimanager.get_widget('/MenuBar/Session/CD Audio').get_active():
	    self.burn_cdaudio()
	elif self.uimanager.get_widget('/MenuBar/Session/DVD Video').get_active():
	    self.burn_dvdvideo()
	elif self.uimanager.get_widget('/MenuBar/Session/Video CD').get_active():
	    self.burn_videocd()
	elif self.uimanager.get_widget('/MenuBar/Session/SuperVideo CD').get_active():
	    self.burn_supervideocd()
	else:
	    for src in self.filelist: 
		os.symlink(src, self.temp + '/' + src.split('/')[-1])
	    if self.uimanager.get_widget('/MenuBar/Session/CD Data').get_active():
		if len(self.filelist) == 1 and (self.filelist[0].endswith('.iso') or self.filelist[0].endswith('.ISO')):
			self.burn_image()
		else:
			self.burn_cddata()
	    elif self.uimanager.get_widget('/MenuBar/Session/DVD Data').get_active():
		if len(self.filelist) == 1 and (self.filelist[0].endswith('.iso') or self.filelist[0].endswith('.ISO')):
			self.burn_image()
		else:
			self.burn_dvddata()
	    elif self.uimanager.get_widget('/MenuBar/Session/ISO Image').get_active():
		fn = self.chooser(gtk.FILE_CHOOSER_ACTION_SAVE, _('Save file'))
                if len(fn) > 0:
			self.make_isoimage(fn[0])

# DATA

    def burn_cddata(self):
	cmd = []
	cmd.append('mkisofs -pad -J -r -o "'+self.temp+'/imagen.iso" -f "'+self.temp+'/"')
	cmd.append('cdrecord -v -eject dev='+self.dev+' '+self.cdrecord+' -data "'+self.temp+'/imagen.iso"')
	self.create_process(cmd)

    def burn_dvddata(self):
	cmd = []
	cmd.append('mkisofs -pad -J -r -o "'+self.temp+'/imagen.iso" -f "'+self.temp+'/"')
	cmd.append('growisofs -dvd-compat -Z '+self.growisofs+' '+self.dev+'="'+self.temp+'/imagen.iso"')
	self.create_process(cmd)

# IMAGE

    def make_isoimage(self, data):
	cmd = []
	cmd.append('mkisofs -pad -J -r -o "'+data+'" -f "'+self.temp+'/"')
	self.create_process(cmd)

    def burn_image(self):
	cmd = []
	if self.uimanager.get_widget('/MenuBar/Session/CD Data').get_active():
            cmd.append('cdrecord -v -eject dev='+self.dev+' '+self.cdrecord+' -data "'+self.filelist[0]+'"')
        elif self.uimanager.get_widget('/MenuBar/Session/DVD Data').get_active():
            cmd.append('growisofs -dvd-compat -Z '+self.growisofs+' '+self.dev+'="'+self.filelist[0]+'"')
	self.create_process(cmd)

# DISC

    def rip_disc(self, data):
	fn = self.chooser(gtk.FILE_CHOOSER_ACTION_SAVE, _('Save file'))
	if len(fn) > 0:
	    count = os.popen('isoinfo -d -i '+self.dev+' | grep "^Volume size is:" | cut -d " " -f 4').readlines()[0].split('\n')[0]
	    block = os.popen('isoinfo -d -i '+self.dev+' | grep "^Logical block size is:" | cut -d " " -f 5').readlines()[0].split('\n')[0]
	    cmd = []
	    cmd.append('dd if='+self.dev+' of='+fn[0]+' bs='+block+' count='+count)
	    self.create_process(cmd)

    def erase_cd(self, data):
	cmd = []
	cmd.append('cdrecord -v -eject dev='+self.dev+' blank=fast')	
	self.create_process(cmd)

# AUDIO

    def burn_cdaudio(self):
	cmd = []
	for src in self.filelist:
	   dest = self.temp + '/' + src.split('/')[-1]
	   dest = dest[:-4]+'.wav'
	   if src.endswith('.wav') or src.endswith('.WAV'): 
		cmd.append('ln -s "'+src+'" "'+dest+'"')
	   elif src.endswith('.mp3') or src.endswith('.MP3'): 
		cmd.append('mpg123 -w "'+dest+'" "'+src+'"')
	   elif src.endswith('.ogg') or src.endswith('.OGG'): 
		cmd.append('oggdec -o "'+dest+'" "'+src+'"')
	   else:
		return
	cmd2 = 'cdrecord -v -eject dev='+self.dev+' '+self.cdrecord+' -pad -audio'
	for src in self.filelist:
 	   dest = self.temp + '/' + src.split('/')[-1]
	   dest = dest[:-4]+'.wav'
	   cmd2 = cmd2+' "'+dest+'"'
	cmd.append(cmd2)
	self.create_process(cmd)

# VIDEO

    def burn_dvdvideo(self):
	cmd = []
	cmd.append('growisofs -dvd-video -Z '+self.growisofs+' '+self.dev+' "'+self.filelist[0]+'/"')	
	self.create_process(cmd)

    def burn_videocd(self):
	cmd = []
	cmd2 = 'vcdimager -t vcd2 -l VCD -c "'+self.temp+'/vcd.cue" -b "'+self.temp+'/vcd.bin"'
	for src in self.filelist:
	   cmd2 = cmd2+' '+src
	cmd.append(cmd2)
	cmd.append('cdrdao write --device '+self.dev+' "'+self.temp+'/vcd.cue"')
	self.create_process(cmd)

    def burn_supervideocd(self):
	cmd = []
	cmd2 = 'vcdimager -t svcd -l SVCD -c "'+self.temp+'/svcd.cue" -b "'+self.temp+'/svcd.bin"'
	for src in self.filelist:
	   cmd2 = cmd2+' '+src
	cmd.append(cmd2)
	cmd.append('cdrdao write --device '+self.dev+' "'+self.temp+'/svcd.cue"')
	self.create_process(cmd)

#################################

#################################
# PROCESS

    def start_thread(self, view, bufer, command):
	self.thr = threading.Thread(target=self.read_output, args=(view, bufer, command))
	self.thr.start()

    def read_output(self, view, bufer, command):
	p = Popen(command[0], shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
	stdin, stdouterr = p.stdin, p.stdout
	self.pid = p.pid
	while 1:
	    line = stdouterr.readline()
	    if not line:
		if len(command) > 1:
		    self.start_thread(self.output, self.textbuffer, command[1:])
		break
	    gtk.gdk.threads_enter()
	    iter = bufer.get_end_iter()
	    bufer.place_cursor(iter)
	    bufer.insert(iter, line)
	    view.scroll_to_mark(bufer.get_insert(), 0.1)
	    gtk.gdk.threads_leave()

    def create_process(self, command):
	self.textbuffer.delete(self.textbuffer.get_start_iter(),self.textbuffer.get_end_iter())
	self.notebook.set_current_page(1)
	self.start_thread(self.output, self.textbuffer, command)

# CLOSE ALL

    def close(self, data):
	close_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _('Do you want to close?'))
	if close_dialog.run() == gtk.RESPONSE_YES:
	    os.system('kill -9 '+str(self.pid))
	    self.clean_temp()
	    self.notebook.set_current_page(0)
	close_dialog.destroy()

#################################

    def main(self):
	gtk.main()
		
		
if __name__ == '__main__':
    b = Recorder()
    b.read_config()
    b.read_variables()
    b.main()
