Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

A Python script to back up DVDs to MP4 files Apps
This is a script that came from my desire to easily back up dvds that I don't have the time to watch now.

HandBrake is an awesome program, but it requires user intervention. If I want to use it on a headless Mac, I want to automate it. This script is a wrapper around HandBrakeCLI, the command-line version of Handbrake.. First it locates the DVD, by scanning /Volumes. Next, it scans the titles for the longest feature on the disc (the movie). Finally, it rips the DVD to an mp4 file. I have hard-coded all the options (2048 kbps, 192kbps AAC, pixel aspect ratio). If these settings don't work for you, look in the code and change them.

To use:
./rip.py [destination dir]
Dependencies:
  • python 2.4+ (Mac OS X ships with 2.3): go to python.org
  • HandBrakeCLI (place in /usr/local/bin, and make sure your path includes this directory, or change the HANDBRAKE variable in the rip.py source file): go to HandBrake
  • chmod +x rip.py
    (to make the script executable)
I think that's it. This method won't appeal to everyone, but it's simple to run, once you get it set up. Here's the script:



#!/usr/bin/python

import os
import sys
import re
from subprocess import Popen
import time

HANDBRAKE = '/usr/local/bin/HandBrakeCLI'

def usage():
    print 'rip.py [dvd_name output_dir]'

def get_dvd_file():
    vols = os.listdir('/Volumes')
    candidates = list()
    for vol in vols:
        if vol.startswith('.'):
            continue
        candidates.append(vol)
    if len(candidates) > 0:
        for vol in candidates:
            dirs = os.listdir('/Volumes/' + vol)
            if 'VIDEO_TS' in dirs:
                return vol
    else:
        sys.exit()

def prettify_filename(filename):
    """
    LITTLE_MISS_SUNSHINE => Little Miss Sunshine
    """
    pretty = ''
    l = None
    for i in xrange(len(filename)):
        c = filename[i]
        if not l or l == '_':
            pretty += c.upper()
        else:
            pretty += c.lower()
        l = c
    return pretty

start = time.time()
dvd_file = None
out_dir = None

#really ugly comand line parsing, should you opt
if len(sys.argv) > 3:
    print sys.argv
    usage()
    sys.exit()
if len(sys.argv) == 3:
    dvd_file, out_dir = sys.argv[1:]
elif len(sys.argv) == 2:
    dvd_file = get_dvd_file()
    out_dir = sys.argv[1]
else:
    dvd_file = get_dvd_file()
    out_dir = '.'
if out_dir.endswith('/'):
    out_dir = out_dir[:-1]

#input and output options for handbrake
infile = '/Volumes/%s' % (dvd_file)
dvd_file = prettify_filename(dvd_file)
outfile = '%s/%s.m4v' % (out_dir, dvd_file)

#lets verify the paths
if not os.path.exists(infile) or not os.path.exists(out_dir):
    print 'bad paths: input file (%s) output directory (%s)' % (infile, out_dir)
    sys.exit()

#scan the dvd for the titles
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t0'), stderr=tmp) 
while po.poll() == None:
    time.sleep(1)
    sys.stdout.write('.')
    sys.stdout.flush()
tmp.seek(0)
sys.stdout.write('n')

#read the output from the scan to get all the titles and their length
title = re.compile('title (d+):')
duration = re.compile('duration: (d+):(d+):(d+)')
chapters = list()
t = d = None
for line in tmp.readlines():
    tm = title.search(line)
    dm = duration.search(line)
    if tm != None:
        t = tm.groups()[0]
    elif dm != None:
        d = dm.groups()
        d = [ int(x) for x in d ]
        h, m, s = d
        secs = h * 3600 + m * 60 + s
        chapters.append((t, secs))
        t = d = None

#determine the longest title
max_secs = title = 0
for chapter in chapters:
    if chapter[1] > max_secs:
        title = chapter[0]
        max_secs = chapter[1]

print 'ripping: chapter %s (%ds)' % (title, max_secs)

#start the ripping process, redirect stderr to tmp
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t%s' % (title), '-o%s' % (outfile), 
            '-effmpeg', '-m', '-b2048', '-p',         #video options
            '-B256', '-R48', '-66ch') , stderr=tmp)   #audio options
while po.poll() == None:
    time.sleep(60)
    sys.stdout.write('.')
    sys.stdout.flush()
sys.stdout.write('n')

#get total time
secs = time.time() - start
hrs = int(secs) / 3600
min = int(secs) % 3600 / 60
sec = int(secs) % 60

print 'you totally ripped! (%d:%d:%d total time)' % (hrs, min, sec)

# notify via growl
os.popen('growlnotify -m "Completed encoding %s" -p 2' % (dvd_file))



kirkmc adds: I haven't tested this.
    •    
  • Currently 2.80 / 5
  You rated: 3 / 5 (5 votes cast)
 
[26,050 views]  

A Python script to back up DVDs to MP4 files | 17 comments | Create New Account
Click here to return to the 'A Python script to back up DVDs to MP4 files' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
A Python script to back up DVDs to MP4 files
Authored by: enoch111 on May 30, '07 10:39:53AM

Does this run automatically when the DVD is inserted?

Also, would it be possible to have it eject the DVD once the rip is finished? I have no idea about programming anything, so if that's a stupid question as to the capabilities of python, I apologize. :)

jason



[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: berndtj on May 30, '07 07:00:22PM

There is a way to eject afterward using diskutil, but I'm not sure what the command is.

I have the script hooked up to sofa control to run, but you could install a folder action to have it run everytime you insert a DVD. If you figure it out, add it as a comment.



[ Reply to This | # ]
Script Problem
Authored by: amaloney on May 30, '07 11:03:22AM

When I tried to run the script I got

Traceback (most recent call last):
File "./rip.py", line 6, in ?
from subprocess import Popen
ImportError: No module named subprocess

I am a newbie with scripting.

I am running Mac OS X 10.4.9
I placed HandBrakeCLI in /usr/local/bin

I ran the OS X installer for Python 2.5.1.
It created /Applications/MacPython 2.5 which contains Python Launcher.app

I copied the script to TextWrangler 2.2.1 and saved it as rip.py in a directory /Applications/A_Rip_Stoff

In Terminal I ran chmod +x rip.py

I then ran ./rip.py and got the above msg.

What am I doing wrong?

Al Maloney



[ Reply to This | # ]
Script Problem
Authored by: simonpie on May 30, '07 11:32:05AM

Well you need to install the subprocess module, it is not installed by default.



[ Reply to This | # ]
Script Problem
Authored by: amaloney on May 30, '07 12:12:27PM

Install subprocess module ???

I guess I am out of my league here.
I'll put myself on hold for a while.
I'm afraid I might screw up things in my ignorance.

Thanks anyway
Al



[ Reply to This | # ]
Script Problem
Authored by: simonpie on May 30, '07 06:46:59PM
Script Problem
Authored by: berndtj on May 30, '07 06:54:20PM

The issue is that the script is still running python 2.3. The subprocess module is part of python 2.4+, hence the dependence.

After you install python 2.4 or 2.5, you will need to change the link to python in /usr/bin

from the terminal type "ls -la | grep python", you will see what I mean.

To fix this just do the following:

sudo mv python python.old

sudo ln -s /Library/Frameworks/Python.framework/Versions/Current/bin/python python

sudo rm python.old

Once you do this, the script should work. If you don't want to muck with the links in /usr/bin, you can replace the first line of the script from:
#!/usr/bin/python
to:
#!/Library/Frameworks/Python.framework/Versions/Current/bin/python

Hope that helps.



[ Reply to This | # ]
Script Problem
Authored by: robgmann on May 30, '07 10:13:18PM

I was able to get the script to start by making sure #!/usr/bin/python was on the first line of the script. (When I pasted it from the hint, it wasn't the first line).

Then it reads the DVD for a minute and outputs:
........................................nripping: chapter 0 (0s)
.nyou totally ripped! (0:1:40 total time)

No output file is created, i.e. nothing appears to have happened.
Is there a simple way to see diagnostics of this script?
So far, it seems no one has had success making it run.




[ Reply to This | # ]
Script Problem
Authored by: berndtj on May 30, '07 10:30:24PM

Hmmm.

Well you can run HandBrakeCLI stand alone, and see if it works. From the output you show, it appears that the scanning did not return anything.

Try:

HandBrakeCLI /Volumes/your_dvd -t 0

You should see a bunch of output. The script parses this output to return the longest title.



[ Reply to This | # ]
Script Problem
Authored by: robgmann on Jun 01, '07 08:13:29AM
Still not getting it... here is my command and output:
HandBrakeCLI -i /Volumes/WW_S3_D4/ -t0

HandBrake 0.8.5b1 (2007042001) - http://handbrake.m0k.org/
1 CPU detected
Opening /Volumes/WW_S3_D4/...
Scanning title 1 of 13...
Scanning title 2 of 13...
Scanning title 3 of 13...
Scanning title 7 of 13...
Scanning title 8 of 13...
Scanning title 9 of 13...
Scanning title 10 of 13...
Scanning title 13 of 13...
No title found.
HandBrake has exited.

And when I run the script, I still just get
./scripts/rip.py /Volumes/SHARED/shared/Ripped/
.........nripping: chapter 0 (0s)
.nyou totally ripped! (0:1:9 total time)
sh: line 1: growlnotify: command not found



[ Reply to This | # ]
Script Problem
Authored by: berndtj on Jun 01, '07 08:18:56AM

"No title found" sounds like HandBrake is having trouble reading the DVD properly. Try it with a different DVD.



[ Reply to This | # ]
subprocess module
Authored by: tatilsever on May 30, '07 03:53:35PM

I gotta say "subprocess module" sounds like it came straight out of StarTrek. :)



[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: bhammond on Jun 05, '07 07:40:54AM
just add this to the end of the file:

os.popen("diskutil eject '%s'" % (infile))


[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: bhammond on Jun 05, '07 07:44:01AM

oops, that was supposed to be in response to the user that wanted the disk ejected after ripping successfully finished.



[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: trpeterson1984 on Jun 30, '07 09:22:10PM
I'm having a problem with the script. After I run it, I get:
Traceback (most recent call last):
  File "/library/scripts/rip.py", line 66, in <module>
    dvd_file = prettify_filename(dvd_file)
  File "/library/scripts/rip.py", line 35, in prettify_filename
    for i in xrange(len(filename)):
TypeError: object of type 'NoneType' has no len()
I don't know Python so I don't know what len() means. Any help in the right direction would be appreciated.

[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: feedcorn on Sep 22, '12 02:11:24AM

This thread may be old, but the script provided here still stands the test of time and works (albeit with a few modifications) on Lion - awesome. I had to change the regex that looks for title number and duration for each title, or I'd get an "encoding chapter 0 (0s)" message. For some reason on my machine I also had to use the "x264" encoding instead of "ffmpeg." Here's the modified version of the script that is working for me:


#!/usr/bin/python

import os
import sys
import re
from subprocess import Popen
import time

HANDBRAKE = '/usr/local/bin/HandBrakeCLI'

def usage():
    print 'rip.py [dvd_name output_dir]'

def get_dvd_file():
    vols = os.listdir('/Volumes')
    candidates = list()
    for vol in vols:
        if vol.startswith('.'):
            continue
        candidates.append(vol)
    if len(candidates) > 0:
        for vol in candidates:
            dirs = os.listdir('/Volumes/' + vol)
            if 'VIDEO_TS' in dirs:
                return vol
    else:
        sys.exit()

def prettify_filename(filename):
    """
    LITTLE_MISS_SUNSHINE => Little Miss Sunshine
    """
    pretty = ''
    l = None
    for i in xrange(len(filename)):
        c = filename[i]
        if not l or l == '_':
            pretty += c.upper()
        else:
            pretty += c.lower()
        l = c
    return pretty

start = time.time()
dvd_file = None
out_dir = None

#really ugly comand line parsing, should you opt
if len(sys.argv) > 3:
    print sys.argv
    usage()
    sys.exit()
if len(sys.argv) == 3:
    dvd_file, out_dir = sys.argv[1:]
elif len(sys.argv) == 2:
    dvd_file = get_dvd_file()
    out_dir = sys.argv[1]
else:
    dvd_file = get_dvd_file()
    out_dir = '.'
if out_dir.endswith('/'):
    out_dir = out_dir[:-1]

#input and output options for handbrake
infile = '/Volumes/%s' % (dvd_file)
dvd_file = prettify_filename(dvd_file)
outfile = '%s/%s.m4v' % (out_dir, dvd_file)

#lets verify the paths
if not os.path.exists(infile) or not os.path.exists(out_dir):
    print 'bad paths: input file (%s) output directory (%s)' % (infile, out_dir)
    sys.exit()

#scan the dvd for the titles
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t0'), stderr=tmp) 
while po.poll() == None:
    time.sleep(1)
    sys.stdout.write('.')
    sys.stdout.flush()
tmp.seek(0)
sys.stdout.write('n')

#read the output from the scan to get all the titles and their length
title = re.compile('title ([0-9]+):')
duration = re.compile('duration: ([0-9]+):([0-9]+):([0-9]+)')

chapters = list()
t = d = None
for line in tmp.readlines():
    tm = title.search(line)
    dm = duration.search(line)
    if tm != None:
        t = tm.groups()[0]
    elif dm != None:
        d = dm.groups()
        d = [ int(x) for x in d ]
        h, m, s = d
        secs = h * 3600 + m * 60 + s
        chapters.append((t, secs))
        t = d = None

#determine the longest title
max_secs = title = 0
for chapter in chapters:
    if chapter[1] > max_secs:
        title = chapter[0]
        max_secs = chapter[1]

print 'ripping: chapter %s (%ds)' % (title, max_secs)

#start the ripping process, redirect stderr to tmp
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t%s' % (title), '-o%s' % (outfile), 
            '-ex264', '-m', '-b2048',        #video options
            '-B256', '-R48', '-66ch') , stderr=tmp)   #audio options
while po.poll() == None:
    time.sleep(60)
    sys.stdout.write('.')
    sys.stdout.flush()
sys.stdout.write('n')

#get total time
secs = time.time() - start
hrs = int(secs) / 3600
min = int(secs) % 3600 / 60
sec = int(secs) % 60

print 'you totally ripped! (%d:%d:%d total time)' % (hrs, min, sec)

# notify via growl
os.popen('growlnotify -m "Completed encoding %s" -p 2' % (dvd_file))
# os.popen("diskutil eject '%s'" % (infile))
Good night and good luck.

[ Reply to This | # ]
A Python script to back up DVDs to MP4 files
Authored by: macmadness86 on May 17, '14 03:13:29AM

I've been using this script on my Linux Mint machine and I noticed a few things. It works great unless the script gets interrupted for some reason. If this happens, the next time the script is run, the DVD drive seems to go through the motions for a short time, then the script says that it has finished successfully. There was no file output, however, because it did not really encode anything. I am not sure what is causing this, but I bet it is related to other peoples' problems of the script not giving an output and ending with 'you totally ripped! (%d:%d:%d total time)' % (hrs, min, sec)

I modified the script to include all audio tracks and subtitles:


#!/usr/bin/python

import os
import sys
import re
from subprocess import Popen, PIPE
import time

HANDBRAKE = '/usr/bin/HandBrakeCLI'

def usage():
print 'rip.py [dvd_name output_dir]'

def get_dvd_file():
vols = os.listdir('/media')
candidates = list()
for vol in vols:
if vol.startswith('.'):
continue
candidates.append(vol)
if len(candidates) > 0:
for vol in candidates:
dirs = os.listdir('/media/' + vol)
if 'VIDEO_TS' in dirs:
return vol
else:
sys.exit()

def prettify_filename(filename):
"""
LITTLE_MISS_SUNSHINE => Little Miss Sunshine
"""
pretty = ''
l = None
for i in xrange(len(filename)):
c = filename[i]
if not l or l == '_':
pretty += c.upper()
else:
pretty += c.lower()
l = c
return pretty

start = time.time()
dvd_file = None
out_dir = None
out_name = None

#really ugly comand line parsing, should you opt
if len(sys.argv) > 3:
print sys.argv
usage()
sys.exit()
if len(sys.argv) == 3:
out_name, out_dir = sys.argv[1:]
dvd_file = get_dvd_file()
elif len(sys.argv) == 2:
dvd_file = get_dvd_file()
out_dir = sys.argv[1]
else:
dvd_file = get_dvd_file()
out_dir = '.'
if out_dir.endswith('/'):
out_dir = out_dir[:-1]

#input and output options for handbrake
infile = '/media/%s' % (dvd_file)
dvd_file = prettify_filename(dvd_file)
outfile = '%s/%s.m4v' % (out_dir, out_name)

#lets verify the paths
if not os.path.exists(infile) or not os.path.exists(out_dir):
print 'bad paths: input file (%s) output directory (%s)' % (infile, out_dir)
sys.exit()

#scan the dvd for the titles
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t0'), stderr=tmp)
while po.poll() == None:
time.sleep(1)
sys.stdout.write('.')
sys.stdout.flush()
tmp.seek(0)
sys.stdout.write('n')

#read the output from the scan to get all the titles and their length
title = re.compile('title ([0-9]+):')
duration = re.compile('duration: ([0-9]+):([0-9]+):([0-9]+)')

chapters = list()
t = d = None
for line in tmp.readlines():
tm = title.search(line)
dm = duration.search(line)
if tm != None:
t = tm.groups()[0]
elif dm != None:
d = dm.groups()
d = [ int(x) for x in d ]
h, m, s = d
secs = h * 3600 + m * 60 + s
chapters.append((t, secs))
t = d = None

#determine the longest title
max_secs = title = 0
for chapter in chapters:
if chapter[1] > max_secs:
title = chapter[0]
max_secs = chapter[1]

#determine number of audio tracks
#audioCount = check_output([HANDBRAKE, '--scan -i%s' % (infile), '-t%s' % (title), '2>&1 | grep "scan: audio 0x" | wc -l'
audioCMD = HANDBRAKE + ' --scan -i%s -t%s 2>&1 | grep "scan: audio 0x" | wc -l' % (infile, title)
audioTrackCount = Popen(audioCMD,shell=True,stdout=PIPE,stderr=None).communicate()[0]
audioCMD2 = "seq -s, 1 %s" % (audioTrackCount)
audio = Popen(audioCMD2,shell=True,stdout=PIPE,stderr=None).communicate()[0]
subtitles = "scan,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"

print 'ripping: chapter %s (%ds)' % (title, max_secs)

#start the ripping process, redirect stderr to tmp
tmp = os.tmpfile()
po = Popen((HANDBRAKE, '-i%s' % (infile), '-t%s' % (title), '-o%s' % (outfile),
'-e x264', '-m', '-d', #video options
'-a%s' % (audio), '-s%s' % (subtitles), #audio tracks and subtitles
'-B256', '-R48', '-66ch') , stderr=tmp) #audio options

while po.poll() == None:
time.sleep(60)
sys.stdout.write('.')
sys.stdout.flush()
sys.stdout.write('n')

#get total time
secs = time.time() - start
hrs = int(secs) / 3600
min = int(secs) % 3600 / 60
sec = int(secs) % 60

print 'you totally ripped! (%d:%d:%d total time)' % (hrs, min, sec)

# notify via growl
#os.popen('growlnotify -m "Completed encoding %s" -p 2' % (dvd_file))
# os.popen("diskutil eject '%s'" % (infile))




[ Reply to This | # ]