I've been encoding my DVD's lately using HandBrake for Linux. As far as I can tell, there isn't a GUI for the Linux version, though there is a very nice one for OS X. However, you don't need the GUI at all.
Once you have compiled HandBrake from source, you have everything you need. Except that the default options in the CLI program (HBTest) aren't exactly the greatest in the world. So every time you want to encode a movie, you have to type a long string of command line options which aren't strictly necessary, since they're the same every time. (I'm assuming you encode all your movies the same way, because that's what I do ... and who wants to worry about encoding each movie differently?)
I encode the video using the x264 codec at 700 kbps, and the audio using the faac codec at 96 kbps. I find that this gives a high enough quality rip that the artifacts aren't typically very apparent. In most cases, my encoded version is indistinguishable from the MPEG-2 version on the DVD, except for the small size.
Normally, I would have to type:
/home/sean/Desktop/HandBrake-0.7.0/HBTest -e x264 -E faac -2 -b 700 -B 96 -w 512 -i /dev/dvd -o This\ is\ the\ movie\ title.mp4
That's a lot of typing, especially considering the fact that all of it is the same every time except for the -o argument.
To save myself some time (it's a valuable couple of seconds!), I wrote a wrapper script in Python to use my defaults instead of HandBrake's.
Here it is.
Now all I have to type is:
./hbencode.py 'This is the movie title.mp4'
Which of course is a lot faster.
I also decided that I might at some point forget to type a title for the movie, but I'd still want the movie to be ripped and encoded, and I could name it later. So my solution was that if nothing is passed in at the command line, it would get the current timestamp and use that as the filename. That should be unique enough that I'd be able to repeatedly encode things without setting a title and be able to go back and set their names later. It has the added bonus of keeping the files in chronological order, according to when they're ripped.
The next step is to take more command line options than just the movie title, so that the defaults can be changed if necessary. The first addition would be the ability to set the title to rip, in case you're ripping a TV show, or a movie where the main feature is (for whatever dastardly reason) not the first title.
This is a very simple script, but it makes my video encoding life a little simpler, and a little more pleasant. Hopefully it does the same for you.
Programming is a wonderful mix of art and science; source code is both a poem and a math problem. It should be as simple and elegant as it is functional and fast. This blog is about that (along with whatever else I feel like writing about).
Wednesday, December 14, 2005
Tuesday, December 06, 2005
Web Based Remote Control
I spend a lot of time at my computer, but I spend even more time near my computer but not quite at the keyboard. It's a Mac Mini, and most of the time it's playing music in iTunes or a movie in VLC. I wanted a way to control those applications remotely, so I could pause without going all the way over to the computer. Since I have a Nokia 770, I thought the easiest way to do this would be to write a web page that can control these applications, and connect to it from the 770.
The first thing I needed was a way to control the applications. This was quite simple, of course, using Applescript. I made a small script for each action I wanted to perform. For example:
All the scripts I wrote were similar in complexity, but more complicated scripts can of course be written.
The next step was to run these scripts from a web page. PHP handles this quite nicely, using the system() command. However, it won't work right out of the box, because OS X runs the web browser as a different user than the person running iTunes (for obvious security reasons). So I had to add a line to the /etc/sudoers file:
This gives the user running Apache the right to run osascript (which executes Applescript files) without typing in a password. This is probably not the best thing to do, security-wise, so it's pretty important to make sure that your machine is only accessible to computers on your local network.
Next, the PHP. It's quite simple. I only need one page, which takes a script and executes it as if it were on the command line. Here it is (execute.php):
I'm planning on sending requests to this PHP file via XMLHttpRequest, of course. I'm using my standard xmlhttp() function which you can get from a previous blog. The only other Javascript function I actually need is the execute() function, which sends a request to execute.php. Here it is:
This is a pretty simple function, sending a single parameter asyncronously. Since it's only sending the request, and doesn't really need to return anything, I don't even bother checking the return. We just shoot off the request and wait for more input. You'll know if it worked it the music starts playing.
It was important to me that I didn't limit this program to any set of functionality that would be difficult to augment later. So I created an XML file that holds records for each program that can be controlled and the scripts to run in order to control it. Originally, I wanted the interface to be built dynamically via Javascript, so I crafted the XML file such that it had no attributes, only elements, because my JS XML parser doesn't read attributes (I should work on that). Unfortunately, it seems that the browser on the 770 doesn't support dynamically created DOM elements, so this approach didn't work. Plenty of wasted work there (and a lot more Javascript than I've included here).
This was when it dawned on me that I don't actually need to build the interface every time the page loads. It only needs to be done when the XML file is changed. So I wrote a Python program to take the XML file, parse it, and output the same HTML as would have been produced by the Javascript. I recreated the XML file to use attributes instead of just elements, because I find that to be simpler to read, and it's much easier to handle that in Python's xml.sax library. Here's the Python (buildinterface.py):
As you can see, it outputs directly to index.html, which is convenient. Every time you run buildinterface.py, you have a fully updated interface waiting for you to load up in your browser. And in case you were wondering what those span tags are all about, they are to mimic links without the bother of actually putting a link on there. Here's the CSS for that:
.link {
text-decoration: underline;
font-size: 10pt;
color: black;
cursor: pointer;
}
Each link has its onclick event set to call execute() with the script to run. It's a fairly simple scheme, and when I loaded it in my browser to test it out, it worked swimmingly. I could control iTunes, VLC, Quicktime, and the system volume. I pulled out my laptop and connected, and found that it worked from remote systems. Now it was time to try it out on the 770. I fired up the browser and opened my page. This time the interface loaded, which was of course promising. However, none of the links worked. So the remote control program works, and is extensible, but I haven't been able to get it working on my Nokia. It seems that the browser doesn't want to send the XMLHttpRequests, which is weird, because it's Opera, and I tested this in Opera on both OS X and Linux, and it works there. I'll be trying to get it to work on the 770, but until then, here's the working code.
Things that could be added:
1. Get it to work on the Nokia 770.
2. Write plugins for other programs, to build out the capabilities of the remote.
3. Have the PHP return something, so it's possible to tell if the command has been executed or not. This will allow for more interesting possibilities on the front end.
4. Anything else ... ?
You can download the code here. Let me know how it goes, or if there are any problems you find with it. And let's here about those plugins!
The first thing I needed was a way to control the applications. This was quite simple, of course, using Applescript. I made a small script for each action I wanted to perform. For example:
tell application "iTunes"
playpause
end tell
playpause
end tell
All the scripts I wrote were similar in complexity, but more complicated scripts can of course be written.
The next step was to run these scripts from a web page. PHP handles this quite nicely, using the system() command. However, it won't work right out of the box, because OS X runs the web browser as a different user than the person running iTunes (for obvious security reasons). So I had to add a line to the /etc/sudoers file:
www ALL = NOPASSWD; /usr/bin/osascript
This gives the user running Apache the right to run osascript (which executes Applescript files) without typing in a password. This is probably not the best thing to do, security-wise, so it's pretty important to make sure that your machine is only accessible to computers on your local network.
Next, the PHP. It's quite simple. I only need one page, which takes a script and executes it as if it were on the command line. Here it is (execute.php):
<?php
$script = $_REQUEST['script'];
system($script);
?>
$script = $_REQUEST['script'];
system($script);
?>
I'm planning on sending requests to this PHP file via XMLHttpRequest, of course. I'm using my standard xmlhttp() function which you can get from a previous blog. The only other Javascript function I actually need is the execute() function, which sends a request to execute.php. Here it is:
function execute(script) {
var req = xmlhttp();
var param = 'script='+script;
//this should be made async
req.open('POST', 'execute.php', true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.send(param);
}
var req = xmlhttp();
var param = 'script='+script;
//this should be made async
req.open('POST', 'execute.php', true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.send(param);
}
This is a pretty simple function, sending a single parameter asyncronously. Since it's only sending the request, and doesn't really need to return anything, I don't even bother checking the return. We just shoot off the request and wait for more input. You'll know if it worked it the music starts playing.
It was important to me that I didn't limit this program to any set of functionality that would be difficult to augment later. So I created an XML file that holds records for each program that can be controlled and the scripts to run in order to control it. Originally, I wanted the interface to be built dynamically via Javascript, so I crafted the XML file such that it had no attributes, only elements, because my JS XML parser doesn't read attributes (I should work on that). Unfortunately, it seems that the browser on the 770 doesn't support dynamically created DOM elements, so this approach didn't work. Plenty of wasted work there (and a lot more Javascript than I've included here).
This was when it dawned on me that I don't actually need to build the interface every time the page loads. It only needs to be done when the XML file is changed. So I wrote a Python program to take the XML file, parse it, and output the same HTML as would have been produced by the Javascript. I recreated the XML file to use attributes instead of just elements, because I find that to be simpler to read, and it's much easier to handle that in Python's xml.sax library. Here's the Python (buildinterface.py):
import os, sys
from xml.sax import saxutils, handler, make_parser
#set up the environment variables
config_file = "server2.xml"
html_file = "index.html"
output_file = file(html_file, 'w')
#globals programs dictionary
programs = []
class Program:
def __init__(self, name):
self.name = name
self.commands = []
programs.append(self)
def addCommand(self, cmd):
self.commands.append(cmd)
class Command:
def __init__(self, name, script):
self.name = name
self.script = script
class ConfParser(handler.ContentHandler):
def __init__(self):
handler.ContentHandler.__init__(self)
def startElement(self, name, attrs):
global temp_prog
if name == "program":
temp_prog = Program(attrs['name'])
elif name == "command":
temp_prog.addCommand(Command(attrs['name'], attrs['script']))
def write_html(out = sys.stdout):
out.write("<html><head><title>Remote Control</title>")
out.write("<link rel='stylesheet' type='text/css' href='remote.css' />")
out.write("<script type='text/javascript' src='remote.js'></script>")
out.write("</head><body>")
for prog in programs:
out.write("<fieldset><legend>" + prog.name + "</legend>")
out.write("<div id='" + prog.name + "_div'>")
for cmd in prog.commands:
out.write("<span class='link' onclick='execute(\"" + cmd.script + "\");'>" + cmd.name + "</span>")
out.write("<br />")
out.write("</div></fieldset>")
out.write("</body></html>")
parser = make_parser()
parser.setContentHandler(ConfParser())
parser.parse(config_file)
write_html(output_file)
output_file.close()
from xml.sax import saxutils, handler, make_parser
#set up the environment variables
config_file = "server2.xml"
html_file = "index.html"
output_file = file(html_file, 'w')
#globals programs dictionary
programs = []
class Program:
def __init__(self, name):
self.name = name
self.commands = []
programs.append(self)
def addCommand(self, cmd):
self.commands.append(cmd)
class Command:
def __init__(self, name, script):
self.name = name
self.script = script
class ConfParser(handler.ContentHandler):
def __init__(self):
handler.ContentHandler.__init__(self)
def startElement(self, name, attrs):
global temp_prog
if name == "program":
temp_prog = Program(attrs['name'])
elif name == "command":
temp_prog.addCommand(Command(attrs['name'], attrs['script']))
def write_html(out = sys.stdout):
out.write("<html><head><title>Remote Control</title>")
out.write("<link rel='stylesheet' type='text/css' href='remote.css' />")
out.write("<script type='text/javascript' src='remote.js'></script>")
out.write("</head><body>")
for prog in programs:
out.write("<fieldset><legend>" + prog.name + "</legend>")
out.write("<div id='" + prog.name + "_div'>")
for cmd in prog.commands:
out.write("<span class='link' onclick='execute(\"" + cmd.script + "\");'>" + cmd.name + "</span>")
out.write("<br />")
out.write("</div></fieldset>")
out.write("</body></html>")
parser = make_parser()
parser.setContentHandler(ConfParser())
parser.parse(config_file)
write_html(output_file)
output_file.close()
As you can see, it outputs directly to index.html, which is convenient. Every time you run buildinterface.py, you have a fully updated interface waiting for you to load up in your browser. And in case you were wondering what those span tags are all about, they are to mimic links without the bother of actually putting a link on there. Here's the CSS for that:
.link {
text-decoration: underline;
font-size: 10pt;
color: black;
cursor: pointer;
}
Each link has its onclick event set to call execute() with the script to run. It's a fairly simple scheme, and when I loaded it in my browser to test it out, it worked swimmingly. I could control iTunes, VLC, Quicktime, and the system volume. I pulled out my laptop and connected, and found that it worked from remote systems. Now it was time to try it out on the 770. I fired up the browser and opened my page. This time the interface loaded, which was of course promising. However, none of the links worked. So the remote control program works, and is extensible, but I haven't been able to get it working on my Nokia. It seems that the browser doesn't want to send the XMLHttpRequests, which is weird, because it's Opera, and I tested this in Opera on both OS X and Linux, and it works there. I'll be trying to get it to work on the 770, but until then, here's the working code.
Things that could be added:
1. Get it to work on the Nokia 770.
2. Write plugins for other programs, to build out the capabilities of the remote.
3. Have the PHP return something, so it's possible to tell if the command has been executed or not. This will allow for more interesting possibilities on the front end.
4. Anything else ... ?
You can download the code here. Let me know how it goes, or if there are any problems you find with it. And let's here about those plugins!
Subscribe to:
Posts (Atom)