speedyrazor

Q: Applescript to extract and export individual Audio Tracks

Hi, I have been searching for a solution to an Applescript to take a Quicktime file and extract and export out all the individual audio tracks out to seperate wavs. So, for example, I have a Quicktime mov with multichannel audio, 6 mono channels and 1 stereo, which currently I go into 'Show Movie Properties', select each audio track in turn, click on the 'Extract' button then export that out matching the original audios spec, so in this case it was mono, 24bit, wav.

So I am looking for an Applescript which would batch this.

 

Kind regards.

OS X Mavericks (10.9.2)

Posted on Apr 4, 2014 2:12 AM

Close

Q: Applescript to extract and export individual Audio Tracks

  • All replies
  • Helpful answers

  • by Hiroto,

    Hiroto Hiroto Apr 7, 2014 6:41 PM in response to speedyrazor
    Level 5 (7,281 points)
    Apr 7, 2014 6:41 PM in response to speedyrazor

    Hello

     

    I took me a while to get the knack of QTKit. The AppleScript script listed below, which is indeed a simple wrapper of rubycocoa script, will extract sound tracks of specified files silently without using QuickTime Player 7.

     

    It will let you choose movie file(s) and output directory and then extract each sound track from chosen files and save it as movie file in the specified directory. Currently it does NOT export sound track as wav file, for I'm yet to understand how to define export setting in QTKit. If you're familiar with python, you'd be able to do the same thing using pyobjc.

     

    Please see comments in script for the current naming convention of extracted sound tracks. A log file is created in the output directory.

     

    I'm going to try to revise the code so that it can export sound track as wav file of desired specifications.

     

    Hope this may help somehow,

    H

     

     

    on run
        open (choose file with prompt ("Choose movie file(s)") with multiple selections allowed)
    end run
    
    on open aa
        set outdir to (choose folder with prompt "Choose output folder")'s POSIX path
        repeat with a in aa
            set a's contents to a's POSIX path
        end repeat
        extract_sound_tracks(aa, outdir)
    end open
    
    on extract_sound_tracks(infiles, outdir)
        (*
            list infiles : list of POSIX path of input movie files
            string outdir : POSIX path of output directory
        *)
        set args to ""
        repeat with a in {outdir} & infiles
            set args to args & a's quoted form & space
        end repeat
        do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby <<'EOF' - " & args & "
    require 'FileUtils'
    require 'osx/cocoa'
    OSX.require_framework 'QTKit'
    include OSX
    
    def log(logf, s)
        File.open(logf, 'a') do |a|
            a.puts '%-26s%s' % [Time.now.strftime('%F %T%z'), s]
        end
    end
    
    def extract_sound_tracks(infile, outdir)
        # 
        #     string infile : POSIX path of input movie file
        #     string outdir : POSIX path of output directory where extracted sound files are saved
        # 
        #     * this method extracts each sound track of infile and saves it as mov file in outdir with name 
        #     source file name & '_[' & sound track index & ']_' & sound track name & '.mov'.
        #     
        #     E.g., given source file name = a.mov, its 3rd sound track named 'Center' will be saved in
        #     outdir/a.mov_[3]_Center.mov
        #
        #     * outdir is created if not present
        #     
        outdir = File.readlink(outdir) if File.symlink?(outdir)
        FileUtils.mkdir_p outdir unless File.exists?(outdir)
        raise RuntimeError, %Q[#{outdir}: Not a directory.] unless File.directory?(outdir)
        logf = %Q[#{outdir}/#{Time.now.strftime('%F_extract_sound_tracks_log.txt')}]
    
        err = OCObject.new
        mov = QTMovie.objc_send(
            :movieWithAttributes, {
                    QTMovieFileNameAttribute => infile,
                    QTMovieOpenAsyncOKAttribute => NSNumber.numberWithBool(false),
                },
            :error, err)
        unless mov
            log(logf, %Q[# Failed to load movie: %s] % [infile])
            log(logf, err ? err.to_s : 'err = nil')
            return
        end
        
        mov_name = mov.attributeForKey(QTMovieDisplayNameAttribute)
        mov.tracksOfMediaType(QTMediaTypeSound).each_with_index do |trk, i|
            trk_id = trk.attributeForKey(QTTrackIDAttribute)
            trk_name = trk.attributeForKey(QTTrackDisplayNameAttribute)
            trk_range = trk.attributeForKey(QTTrackRangeAttribute)
            
            mov1 = QTMovie.movie    # new movie
            mov1.objc_send(:setAttribute, NSNumber.numberWithBool(true), :forKey, QTMovieEditableAttribute)
            mov1.objc_send(
                :insertSegmentOfTrack, trk,
                :fromRange, trk_range.QTTimeRangeValue,
                :scaledToRange, trk_range.QTTimeRangeValue)
                
            outfile = outdir + '/' + ('%s_[%d]_%s.mov' % [mov_name, i + 1, trk_name])
            err = OCObject.new
            r = mov1.objc_send(
                :writeToFile, outfile,
                :withAttributes, {
                        QTMovieFlatten => NSNumber.numberWithBool(true),
                    },
                :error, err)
            if r
                log(logf, %Q[Extracted track %d (%s) of %s => %s] % [trk_id, trk_name, infile, outfile])
            else
                log(logf, %Q[# Failed to save track %d (%s) of %s in %s] % [trk_id, trk_name, infile, outfile])
                log(logf, err ? err.to_s : 'err = nil')
            end
        end
    end
    
    raise ArgumentError, %Q[Usage: #{File.basename($0)} outdir infile [infile ...]] unless ARGV.length > 1
    outdir = ARGV.shift
    ARGV.each { |f| extract_sound_tracks(f, outdir) }
    EOF"
    end extract_sound_tracks
    
  • by Hiroto,

    Hiroto Hiroto Apr 11, 2014 10:08 PM in response to speedyrazor
    Level 5 (7,281 points)
    Apr 11, 2014 10:08 PM in response to speedyrazor

    Hello again.

     

    It turned out to be more involved than I thought to define export settings programmatically. So, for now, here's a very simple version to export to wav file using default settings that QuickTime sees fit. In my brief experiments, I found limitations as follows:

     

    a) It does not export to multi-channel wave file other than mono and stereo (2.0). If original sound track is of multi-channel > 2.0, audio channels will be mixed down to stereo 2.0.

     

    b) If original sound track does not use lossless codec, such as AAC, AC3 etc, resulting LPCM sample sise (bit depth) will be 16-bit whereas sample rate (frequency) will (or may) be preserved as the original. If original sound track uses lossless codec, such as ALAC, resulting LPCM will preserve the original sample size and sample rate.

     

    If you're fine with these limitations, the following script might help.

     

    Tested with QuickTime 7.6.6 (1800) under 10.6.8.

     

    Regards,

    H

     

     

    (*
        Export each sound track of movie file to wav file
    *)
    on run
        open (choose file with prompt ("Choose movie file(s)") with multiple selections allowed)
    end run
    
    on open aa
        set outdir to (choose folder with prompt "Choose output folder")'s POSIX path
        repeat with a in aa
            set a's contents to a's POSIX path
        end repeat
        extract_sound_tracks(aa, outdir)
    end open
    
    on extract_sound_tracks(infiles, outdir) -- export as wav
        (*
            list infiles : list of POSIX path of input movie files
            string outdir : POSIX path of output directory
        *)
        set args to ""
        repeat with a in {outdir} & infiles
            set args to args & a's quoted form & space
        end repeat
        do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby <<'EOF' - " & args & "
    require 'FileUtils'
    require 'osx/cocoa'
    OSX.require_framework 'QTKit'
    include OSX
    
    def log(logf, s)
        File.open(logf, 'a') do |a|
            a.puts '%-26s%s' % [Time.now.strftime('%F %T%z'), s]
        end
    end
    
    def extract_sound_tracks(infile, outdir)
        # 
        #     string infile : POSIX path of input movie file
        #     string outdir : POSIX path of output directory where extracted sound files are saved
        # 
        #     * this method extracts each sound track of infile and saves it as wav file in outdir with name 
        #     source file name & '_[' & sound track index & ']_' & sound track name & '.wav'.
        #     
        #     E.g., given source file name = a.mov, its 3rd sound track named 'Center' will be saved in
        #     outdir/a.mov_[3]_Center.wav
        #
        #     * outdir is created if not present
        #     
        outdir = File.readlink(outdir) if File.symlink?(outdir)
        FileUtils.mkdir_p outdir unless File.exists?(outdir)
        raise RuntimeError, %Q[#{outdir}: Not a directory.] unless File.directory?(outdir)
        logf = %Q[#{outdir}/#{Time.now.strftime('%F_extract_sound_tracks_log.txt')}]
    
        err = OCObject.new
        mov = QTMovie.objc_send(
            :movieWithAttributes, {
                    QTMovieFileNameAttribute => infile,
                    QTMovieOpenAsyncOKAttribute => NSNumber.numberWithBool(false),
                },
            :error, err)
        unless mov
            log(logf, '# Failed to load movie: %s' % [infile])
            log(logf, err ? err.to_s : 'err = nil')
            return
        end
        
        mov_name = mov.attributeForKey(QTMovieDisplayNameAttribute)
        mov.tracksOfMediaType(QTMediaTypeSound).each_with_index do |trk, i|
            trk_id = trk.attributeForKey(QTTrackIDAttribute)
            trk_name = trk.attributeForKey(QTTrackDisplayNameAttribute)
            trk_range = trk.attributeForKey(QTTrackRangeAttribute)
            
            mov1 = QTMovie.movie    # new movie
            mov1.objc_send(:setAttribute, NSNumber.numberWithBool(true), :forKey, QTMovieEditableAttribute)
            mov1.objc_send(
                :insertSegmentOfTrack, trk,
                :fromRange, trk_range.QTTimeRangeValue,
                :scaledToRange, trk_range.QTTimeRangeValue)
                
            outfile = outdir + '/' + ('%s_[%d]_%s.wav' % [mov_name, i + 1, trk_name])
            err = OCObject.new
            r = mov1.objc_send(
                :writeToFile, outfile,
                :withAttributes, {
                        QTMovieExport => NSNumber.numberWithBool(true),
                        QTMovieExportType => NSNumber.numberWithLong(KQTFileTypeWave),
                    },
                :error, err)
            if r
                log(logf, 'Extracted track %d (%s) of %s => %s' % [trk_id, trk_name, infile, outfile])
            else
                log(logf, '# Failed to save track %d (%s) of %s in %s' % [trk_id, trk_name, infile, outfile])
                log(logf, err ? err.to_s : 'err = nil')
            end
        end
    end
    
    raise ArgumentError, %Q[Usage: #{File.basename($0)} outdir infile [infile ...]] unless ARGV.length > 1
    outdir = ARGV.shift
    ARGV.each { |f| extract_sound_tracks(f, outdir) }
    EOF"
    end extract_sound_tracks
    
  • by speedyrazor,

    speedyrazor speedyrazor Apr 14, 2014 11:20 PM in response to Hiroto
    Level 1 (0 points)
    Apr 14, 2014 11:20 PM in response to Hiroto

    Hi Hiroto, many thanks for taking the time to write these applescripts, although the restriction of not being able to handle multichannel is the killer for me. I have since developed a Python GUI application that takes cae of this using ffmpeg. Happy to share if anyone wants it.

     

    Kind regards.