2 # manincure.rb version 0.66
4 # This file is part of the HandBrake source code.
5 # Homepage: <http://handbrake.m0k.org/>.
6 # It may be used under the terms of the GNU General Public License.
8 # This script parses HandBrake's Mac presets into hashes, which can
9 # be displayed in various formats for use by the CLI and its wrappers.
11 # For handling command line arguments to the script
15 # CLI options: (code based on http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/index.html )
18 # --[no-]cli-raw, -r gives raw CLI for wiki
19 # --cli-parse, -p gives CLI strings for wrappers
20 # --api, -a gives preset code for test.c
21 # --api-list, -A gives CLI strings for --preset-list display
22 # --[no-]header, -h turns off banner display
23 options = OpenStruct.new
24 options.cliraw = false
25 options.cliparse = false
27 options.apilist = false
28 options.header = false
30 opts = OptionParser.new do |opts|
31 opts.banner = "Usage: manicure.rb [options]"
34 opts.separator "Options:"
36 opts.on("-r", "--cli-raw", "Gives example strings for the HB wiki") do |raw|
41 opts.on("-p", "--cli-parse", "Gives presets as wrapper-parseable CLI", " option strings") do |par|
42 options.cliparse = par
45 opts.on("-a", "--api", "Gives preset code for test.c") do |api|
49 opts.on("-A", "--api-list", "Gives code for test.c's --preset-list", " options") do |alist|
50 options.apilist = alist
53 opts.on("-H", "--Header", "Display a banner before each preset") do |head|
57 opts.on_tail("-h", "--help", "Show this message") do
67 # These arrays contain all the other presets and hashes that are going to be used.
68 # Yeah, they're global variables. In an object-oriented scripting language.
71 # This class parses the user's presets .plist into an array of hashes
74 attr_reader :hashMasterList
76 # Running initialization runs everything.
77 # Calling it will also call the parser
81 # Grab input from the user's presets .plist
82 rawPresets = readPresetPlist
84 # Store all the presets in here
87 # Each item in the array is one line from the .plist
88 presetStew = rawPresets.split("\n")
90 # Now get rid of white space
91 presetStew = cleanStew(presetStew)
93 # This stores the offsets between presets.
94 presetBreaks = findPresetBreaks(presetStew)
96 # Now it's time to use that info to store each
97 # preset individually, in the master list.
98 @presetMasterList = []
100 while i <= presetBreaks.size
101 if i == 0 #first preset
102 # Grab the stew, up to the 1st offset.
103 @presetMasterList[i] = presetStew.slice(0..presetBreaks[i].to_i)
104 elsif i < presetBreaks.size #middle presets
105 # Grab the stew from the last offset to the current..
106 @presetMasterList[i] = presetStew.slice(presetBreaks[i-1].to_i..presetBreaks[i].to_i)
108 # Grab the stew, starting at the last offset, all the way to the end.
109 @presetMasterList[i] = presetStew.slice(presetBreaks[i-1].to_i..presetStew.length)
114 # Parse the presets into hashes
121 def readPresetPlist # Grab the .plist and store it in presets
123 # Grab the user's home path
124 homeLocation = `echo $HOME`.chomp
126 # Use that to build a path to the presets .plist
127 inputFile = homeLocation+'/Library/Application\ Support/HandBrake/UserPresets.plist'
129 # Builds a command that inputs the .plist, but not before stripping all the XML gobbledygook.
130 parseCommand = 'cat '+inputFile+' | sed -e \'s/<[a-z]*>//\' -e \'s/<\/[a-z]*>//\' -e \'/<[?!]/d\' '
134 # Run the command, return the raw presets
135 rawPresets = `#{parseCommand}`
138 def cleanStew(presetStew) #remove tabbed white space
139 presetStew.each do |oneline|
144 def findPresetBreaks(presetStew) #figure out where each preset starts and ends
148 presetStew.each do |presetLine|
149 if presetLine =~ /AudioBitRate/ # This is the first line of a new preset.
150 presetBreaks[j] = i-1 # So mark down how long the last one was.
158 def buildPresetHash #fill up @hashMasterList with hashes of all key/value pairs
161 # Iterate through all presets, treating each in turn as singleServing
162 @presetMasterList.each do |singleServing|
164 # Initialize the hash for preset j (aka singleServing)
165 @hashMasterList[j] = Hash.new
167 # Each key and value are on sequential lines.
168 # Iterating through by twos, use that to build a hash.
169 # Each key, on line i, paired with its value, on line i+1
171 while i < singleServing.length
172 @hashMasterList[j].store( singleServing[i], singleServing[i+1] )
182 # This class displays the presets to stdout in various formats.
186 def initialize(hashMasterList, options)
188 @hashMasterList = hashMasterList
191 # A width of 40 gives nice, compact output.
195 displayCommandStrings
199 def displayCommandStrings # prints everything to screen
201 # Iterate through the hashes.
202 @hashMasterList.each do |hash|
204 # Check to make there are valid contents
205 if hash.key?("PresetName")
207 if @options.header == true
208 # First throw up a header to make each preset distinct
212 if @options.cliraw == true
213 # Show the preset's full CLI string equivalent
214 generateCLIString(hash)
217 if @options.cliparse == true
218 generateCLIParse(hash)
221 if @options.api == true
222 # Show the preset as code for test/test.c, HandBrakeCLI
223 generateAPIcalls(hash)
226 if @options.apilist == true
227 # Show the preset as print statements, for CLI wrappers to parse.
228 generateAPIList(hash)
234 def displayHeader(hash) # A distinct banner to separate each preset
236 # Print a line of asterisks
237 puts "*" * @columnWidth
239 # Print the name, centered
240 puts '* '+hash["PresetName"].to_s.center(@columnWidth-4)+' *'
242 # Print a line of dashes
243 puts '~' * @columnWidth
245 # Print the description, centered and word-wrapped
246 puts hash["PresetDescription"].to_s.center(@columnWidth).gsub(/\n/," ").scan(/\S.{0,#{@columnWidth-2}}\S(?=\s|$)|\S+/)
248 # Print another line of dashes
249 puts '~' * @columnWidth
251 # Print the formats the preset uses
252 puts "#{hash["FileCodecs"]}".center(@columnWidth)
254 # Note if the preset isn't built-in
255 if hash["Type"].to_i == 1
256 puts "Custom Preset".center(@columnWidth)
259 # Note if the preset is marked as default.
260 if hash["Default"].to_i == 1
261 puts "This is your default preset.".center(@columnWidth)
264 # End with a line of tildes.
265 puts "~" * @columnWidth
269 def generateCLIString(hash) # Makes a full CLI equivalent of a preset
271 commandString << './HandBrakeCLI -i DVD -o ~/Movies/movie.'
274 case hash["FileFormat"]
276 commandString << "mp4 "
278 commandString << "avi "
280 commandString << "ogm "
282 commandString << "mkv "
286 if hash["VideoEncoder"] != "FFmpeg"
287 commandString << " -e "
288 commandString << hash["VideoEncoder"].to_s.downcase
292 case hash["VideoQualityType"].to_i
294 commandString << " -S " << hash["VideoTargetSize"]
296 commandString << " -b " << hash["VideoAvgBitrate"]
298 commandString << " -q " << hash["VideoQualitySlider"]
302 if hash["VideoFramerate"] != "Same as source"
303 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
304 commandString << " -r " << "23.976"
305 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
306 commandString << " -r " << "29.97"
308 commandString << " -r " << hash["VideoFramerate"]
312 #Audio encoder (only specifiy bitrate and samplerate when not doing AC-3 pass-thru)
313 commandString << " -E "
314 case hash["FileCodecs"]
316 commandString << "ac3"
318 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
320 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
322 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
326 commandString << " -f "
327 case hash["FileFormat"]
329 commandString << "mp4"
331 commandString << "avi"
333 commandString << "ogm"
335 commandString << "mkv"
339 if hash["Mp4iPodCompatible"].to_i == 1
340 commandString << " -I"
344 if !hash["PictureAutoCrop"].to_i
345 commandString << " --crop "
346 commandString << hash["PictureTopCrop"]
348 commandString << hash["PictureBottomCrop"]
350 commandString << hash["PictureLeftCrop"]
352 commandString << hash["PictureRightCrop"]
356 if hash["PictureWidth"].to_i != 0
357 commandString << " -w "
358 commandString << hash["PictureWidth"]
360 if hash["PictureHeight"].to_i != 0
361 commandString << " -l "
362 commandString << hash["PictureHeight"]
366 if hash["Subtitles"] != "None"
367 commandString << " -s "
368 commandString << hash["Subtitles"]
372 if hash["UsesPictureFilters"].to_i == 1
374 case hash["PictureDeinterlace"].to_i
376 commandString << " --deinterlace=\"fast\""
378 commandString << " --deinterlace=\slow\""
380 commandString << " --deinterlace=\"slower\""
382 commandString << " --deinterlace=\"slowest\""
385 case hash["PictureDenoise"].to_i
387 commandString << " --denoise=\"weak\""
389 commandString << " --denoise=\"medium\""
391 commandString << " --denoise=\"strong\""
394 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
395 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
396 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
400 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
401 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
402 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
403 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
404 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
407 if hash["x264Option"] != ""
408 commandString << " -x "
409 commandString << hash["x264Option"]
412 # That's it, print to screen now
415 #puts "*" * @columnWidth
420 def generateCLIParse(hash) # Makes a CLI equivalent of all user presets, for wrappers to parse
422 commandString << '+ ' << hash["PresetName"] << ":"
425 if hash["VideoEncoder"] != "FFmpeg"
426 commandString << " -e "
427 commandString << hash["VideoEncoder"].to_s.downcase
431 case hash["VideoQualityType"].to_i
433 commandString << " -S " << hash["VideoTargetSize"]
435 commandString << " -b " << hash["VideoAvgBitrate"]
437 commandString << " -q " << hash["VideoQualitySlider"]
441 if hash["VideoFramerate"] != "Same as source"
442 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
443 commandString << " -r " << "23.976"
444 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
445 commandString << " -r " << "29.97"
447 commandString << " -r " << hash["VideoFramerate"]
451 #Audio encoder (only include bitrate and samplerate when not doing AC3 passthru)
452 commandString << " -E "
453 case hash["FileCodecs"]
455 commandString << "ac3"
457 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
459 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
461 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
465 commandString << " -f "
466 case hash["FileFormat"]
468 commandString << "mp4"
470 commandString << "avi"
472 commandString << "ogm"
474 commandString << "mkv"
478 if hash["Mp4iPodCompatible"].to_i == 1
479 commandString << " -I"
483 if !hash["PictureAutoCrop"].to_i
484 commandString << " --crop "
485 commandString << hash["PictureTopCrop"]
487 commandString << hash["PictureBottomCrop"]
489 commandString << hash["PictureLeftCrop"]
491 commandString << hash["PictureRightCrop"]
495 if hash["PictureWidth"].to_i != 0
496 commandString << " -w "
497 commandString << hash["PictureWidth"]
499 if hash["PictureHeight"].to_i != 0
500 commandString << " -l "
501 commandString << hash["PictureHeight"]
505 if hash["Subtitles"] != "None"
506 commandString << " -s "
507 commandString << hash["Subtitles"]
511 if hash["UsesPictureFilters"].to_i == 1
513 case hash["PictureDeinterlace"].to_i
515 commandString << " --deinterlace=\"fast\""
517 commandString << " --deinterlace=\slow\""
519 commandString << " --deinterlace=\"slower\""
521 commandString << " --deinterlace=\"slowest\""
524 case hash["PictureDenoise"].to_i
526 commandString << " --denoise=\"weak\""
528 commandString << " --denoise=\"medium\""
530 commandString << " --denoise=\"strong\""
533 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
534 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
535 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
539 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
540 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
541 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
542 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
543 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
546 if hash["x264Option"] != ""
547 commandString << " -x "
548 commandString << hash["x264Option"]
551 # That's it, print to screen now
554 #puts "*" * @columnWidth
559 def generateAPIcalls(hash) # Makes a C version of the preset ready for coding into the CLI
561 commandString = "if (!strcmp(preset_name, \"" << hash["PresetName"] << "\"))\n{\n "
564 case hash["FileFormat"]
566 commandString << "mux = " << "HB_MUX_MP4;\n "
568 commandString << "mux = " << "HB_MUX_AVI;\n "
570 commandString << "mux = " << "HB_MUX_OGM;\n "
572 commandString << "mux = " << "HB_MUX_MKV;\n "
576 if hash["Mp4iPodCompatible"].to_i == 1
577 commandString << "job->ipod_atom = 1;\n "
581 if hash["VideoEncoder"] != "FFmpeg"
582 commandString << "vcodec = "
583 if hash["VideoEncoder"] == "x264"
584 commandString << "HB_VCODEC_X264;\n "
585 elsif hash["VideoEncoder"].to_s.downcase == "xvid"
586 commandString << "HB_VCODEC_XVID;\n "
591 case hash["VideoQualityType"].to_i
593 commandString << "size = " << hash["VideoTargetSize"] << ";\n "
595 commandString << "job->vbitrate = " << hash["VideoAvgBitrate"] << ";\n "
597 commandString << "job->vquality = " << hash["VideoQualitySlider"] << ";\n "
598 commandString << "job->crf = 1;\n "
602 if hash["VideoFramerate"] != "Same as source"
603 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
604 commandString << "job->vrate_base = " << "1126125;\n "
605 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
606 commandString << "job->vrate_base = " << "900900;\n "
607 # Gotta add the rest of the framerates for completion's sake.
611 # Only include samplerate and bitrate when not performing AC3 passthru
612 if (hash["FileCodecs"].include? "AC-3") == false
614 commandString << "job->abitrate = " << hash["AudioBitRate"] << ";\n "
617 commandString << "job->arate = "
618 case hash["AudioSampleRate"]
620 commandString << "48000"
622 commandString << "44100"
624 commandString << "32000"
626 commandString << "24000"
628 commandString << "22050"
630 commandString << ";\n "
634 commandString << "acodec = "
635 case hash["FileCodecs"]
637 commandString << "HB_ACODEC_FAAC;\n "
639 commandString << "HB_ACODEC_AC3;\n "
641 commandString << "HB_ACODEC_VORBIS;\n "
643 commandString << "HB_ACODEC_LAME;\n "
647 if !hash["PictureAutoCrop"].to_i
648 commandString << "job->crop[0] = " << hash["PictureTopCrop"] << ";\n "
649 commandString << "job->crop[1] = " << hash["PictureBottomCrop"] << ";\n "
650 commandString << "job->crop[2] = " << hash["PictureLeftCrop"] << ";\n "
651 commandString << "job->crop[4] - " << hash["PictureRightCrop"] << ";\n "
655 if hash["PictureWidth"].to_i != 0
656 commandString << "job->width = "
657 commandString << hash["PictureWidth"] << ";\n "
659 if hash["PictureHeight"].to_i != 0
660 commandString << "job->height = "
661 commandString << hash["PictureHeight"] << ";\n "
665 if hash["Subtitles"] != "None"
666 commandString << "job->subtitle = "
667 commandString << ( hash["Subtitles"].to_i - 1).to_s << ";\n "
671 if hash["x264Option"] != ""
672 commandString << "x264opts = strdup(\""
673 commandString << hash["x264Option"] << "\");\n "
677 if hash["UsesPictureFilters"].to_i == 1
679 case hash["PictureDeinterlace"].to_i
681 commandString << "deinterlace = 1;\n "
682 commandString << "deinterlace_opt = \"-1\";\n "
684 commandString << "deinterlace = 1;\n "
685 commandString << "deinterlace_opt = \"0\";\n "
687 commandString << "deinterlace = 1;\n "
688 commandString << "deinterlace_opt = \"2:-1:1\";\n "
690 commandString << "deinterlace = 1;\n "
691 commandString << "deinterlace_opt = \"1:-1:1\";\n "
694 case hash["PictureDenoise"].to_i
696 commandString << "denoise = 1;\n "
697 commandString << "denoise_opt = \"2:1:2:3\";\n "
699 commandString << "denoise = 1;\n "
700 commandString << "denoise_opt = \"3:2:2:3\";\n "
702 commandString << "denoise = 1;\n "
703 commandString << "denoise_opt = \"7:7:5:5\";\n "
706 if hash["PictureDetelecine"].to_i == 1 then commandString << "detelecine = 1;\n " end
707 if hash["PictureDeblock"].to_i == 1 then commandString << "deblock = 1;\n " end
708 if hash["VFR"].to_i == 1 then commandString << "vfr = 1;\n " end
712 if hash["ChapterMarkers"].to_i == 1 then commandString << "job->chapter_markers = 1;\n " end
713 if hash["PicturePAR"].to_i == 1 then commandString << "pixelratio = 1;\n " end
714 if hash["VideoGrayScale"].to_i == 1 then commandString << "job->grayscale = 1;\n " end
715 if hash["VideoTwoPass"].to_i == 1 then commandString << "twoPass = 1;\n " end
716 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << "turbo_opts_enabled = 1;\n" end
720 # That's it, print to screen now
722 #puts "*" * @columnWidth
726 def generateAPIList(hash) # Makes a list of the CLI options a built-in CLI preset uses, for wrappers to parse
728 commandString << " printf(\"\\n+ " << hash["PresetName"] << ": "
731 if hash["VideoEncoder"] != "FFmpeg"
732 commandString << " -e "
733 commandString << hash["VideoEncoder"].to_s.downcase
737 case hash["VideoQualityType"].to_i
739 commandString << " -S " << hash["VideoTargetSize"]
741 commandString << " -b " << hash["VideoAvgBitrate"]
743 commandString << " -q " << hash["VideoQualitySlider"]
747 if hash["VideoFramerate"] != "Same as source"
748 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
749 commandString << " -r " << "23.976"
750 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
751 commandString << " -r " << "29.97"
753 commandString << " -r " << hash["VideoFramerate"]
757 # Only include samplerate and bitrate when not performing AC-3 passthru
758 if (hash["FileCodecs"].include? "AC-3") == false
760 commandString << " -B " << hash["AudioBitRate"]
762 commandString << " -R " << hash["AudioSampleRate"]
766 commandString << " -E "
767 case hash["FileCodecs"]
769 commandString << "faac"
771 commandString << "ac3"
773 commandString << "vorbis"
775 commandString << "lame"
779 commandString << " -f "
780 case hash["FileFormat"]
782 commandString << "mp4"
784 commandString << "avi"
786 commandString << "ogm"
788 commandString << "mkv"
792 if hash["Mp4iPodCompatible"].to_i == 1
793 commandString << " -I"
797 if !hash["PictureAutoCrop"].to_i
798 commandString << " --crop "
799 commandString << hash["PictureTopCrop"]
801 commandString << hash["PictureBottomCrop"]
803 commandString << hash["PictureLeftCrop"]
805 commandString << hash["PictureRightCrop"]
809 if hash["PictureWidth"].to_i != 0
810 commandString << " -w "
811 commandString << hash["PictureWidth"]
813 if hash["PictureHeight"].to_i != 0
814 commandString << " -l "
815 commandString << hash["PictureHeight"]
819 if hash["Subtitles"] != "None"
820 commandString << " -s "
821 commandString << hash["Subtitles"]
825 if hash["UsesPictureFilters"].to_i == 1
827 case hash["PictureDeinterlace"].to_i
829 commandString << " --deinterlace=\\\"fast\\\""
831 commandString << " --deinterlace=\\\slow\\\""
833 commandString << " --deinterlace=\\\"slower\\\""
835 commandString << " --deinterlace=\\\"slowest\\\""
838 case hash["PictureDenoise"].to_i
840 commandString << " --denoise=\\\"weak\\\""
842 commandString << " --denoise=\\\"medium\\\""
844 commandString << " --denoise=\\\"strong\\\""
847 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
848 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
849 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
853 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
854 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
855 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
856 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
857 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
860 if hash["x264Option"] != ""
861 commandString << " -x "
862 commandString << hash["x264Option"]
865 commandString << "\\n\");"
867 # That's it, print to screen now
874 # First grab the specified CLI options
875 options = readOptions
877 # Only run if one of the useful CLI flags have been passed
878 if options.cliraw == true || options.cliparse == true || options.api == true || options.apilist == true
879 # This line is the ignition -- generates hashes of
880 # presets and then displays them to the screen
881 # with the options the user selects on the CLI.
882 Display.new( Presets.new.hashMasterList, options )
884 # Direct the user to the help
885 puts "\n\tUsage: manicure.rb [options]"
886 puts "\tSee help with -h or --help"