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"]
315 when /AAC + AC3 Audio/
316 commandString << "aac+ac3"
318 commandString << "ac3"
320 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
322 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
324 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
328 commandString << " -f "
329 case hash["FileFormat"]
331 commandString << "mp4"
333 commandString << "avi"
335 commandString << "ogm"
337 commandString << "mkv"
341 if hash["Mp4iPodCompatible"].to_i == 1
342 commandString << " -I"
346 if hash["Mp4LargeFile"].to_i == 1
347 commandString << " -4"
351 if !hash["PictureAutoCrop"].to_i
352 commandString << " --crop "
353 commandString << hash["PictureTopCrop"]
355 commandString << hash["PictureBottomCrop"]
357 commandString << hash["PictureLeftCrop"]
359 commandString << hash["PictureRightCrop"]
363 if hash["PictureWidth"].to_i != 0
364 commandString << " -w "
365 commandString << hash["PictureWidth"]
367 if hash["PictureHeight"].to_i != 0
368 commandString << " -l "
369 commandString << hash["PictureHeight"]
373 if hash["Subtitles"] != "None"
374 commandString << " -s "
375 commandString << hash["Subtitles"]
379 if hash["UsesPictureFilters"].to_i == 1
381 case hash["PictureDeinterlace"].to_i
383 commandString << " --deinterlace=\"fast\""
385 commandString << " --deinterlace=\slow\""
387 commandString << " --deinterlace=\"slower\""
389 commandString << " --deinterlace=\"slowest\""
392 case hash["PictureDenoise"].to_i
394 commandString << " --denoise=\"weak\""
396 commandString << " --denoise=\"medium\""
398 commandString << " --denoise=\"strong\""
401 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
402 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
403 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
407 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
408 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
409 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
410 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
411 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
414 if hash["x264Option"] != ""
415 commandString << " -x "
416 commandString << hash["x264Option"]
419 # That's it, print to screen now
422 #puts "*" * @columnWidth
427 def generateCLIParse(hash) # Makes a CLI equivalent of all user presets, for wrappers to parse
429 commandString << '+ ' << hash["PresetName"] << ":"
432 if hash["VideoEncoder"] != "FFmpeg"
433 commandString << " -e "
434 commandString << hash["VideoEncoder"].to_s.downcase
438 case hash["VideoQualityType"].to_i
440 commandString << " -S " << hash["VideoTargetSize"]
442 commandString << " -b " << hash["VideoAvgBitrate"]
444 commandString << " -q " << hash["VideoQualitySlider"]
448 if hash["VideoFramerate"] != "Same as source"
449 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
450 commandString << " -r " << "23.976"
451 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
452 commandString << " -r " << "29.97"
454 commandString << " -r " << hash["VideoFramerate"]
458 #Audio encoder (only include bitrate and samplerate when not doing AC3 passthru)
459 commandString << " -E "
460 case hash["FileCodecs"]
462 commandString << "aac+ac3"
464 commandString << "ac3"
466 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
468 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
470 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
474 commandString << " -f "
475 case hash["FileFormat"]
477 commandString << "mp4"
479 commandString << "avi"
481 commandString << "ogm"
483 commandString << "mkv"
487 if hash["Mp4iPodCompatible"].to_i == 1
488 commandString << " -I"
492 if hash["Mp4LargeFile"].to_i == 1
493 commandString << " -4"
497 if !hash["PictureAutoCrop"].to_i
498 commandString << " --crop "
499 commandString << hash["PictureTopCrop"]
501 commandString << hash["PictureBottomCrop"]
503 commandString << hash["PictureLeftCrop"]
505 commandString << hash["PictureRightCrop"]
509 if hash["PictureWidth"].to_i != 0
510 commandString << " -w "
511 commandString << hash["PictureWidth"]
513 if hash["PictureHeight"].to_i != 0
514 commandString << " -l "
515 commandString << hash["PictureHeight"]
519 if hash["Subtitles"] != "None"
520 commandString << " -s "
521 commandString << hash["Subtitles"]
525 if hash["UsesPictureFilters"].to_i == 1
527 case hash["PictureDeinterlace"].to_i
529 commandString << " --deinterlace=\"fast\""
531 commandString << " --deinterlace=\slow\""
533 commandString << " --deinterlace=\"slower\""
535 commandString << " --deinterlace=\"slowest\""
538 case hash["PictureDenoise"].to_i
540 commandString << " --denoise=\"weak\""
542 commandString << " --denoise=\"medium\""
544 commandString << " --denoise=\"strong\""
547 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
548 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
549 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
553 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
554 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
555 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
556 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
557 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
560 if hash["x264Option"] != ""
561 commandString << " -x "
562 commandString << hash["x264Option"]
565 # That's it, print to screen now
568 #puts "*" * @columnWidth
573 def generateAPIcalls(hash) # Makes a C version of the preset ready for coding into the CLI
575 commandString = "if (!strcmp(preset_name, \"" << hash["PresetName"] << "\"))\n{\n "
578 case hash["FileFormat"]
580 commandString << "mux = " << "HB_MUX_MP4;\n "
582 commandString << "mux = " << "HB_MUX_AVI;\n "
584 commandString << "mux = " << "HB_MUX_OGM;\n "
586 commandString << "mux = " << "HB_MUX_MKV;\n "
590 if hash["Mp4iPodCompatible"].to_i == 1
591 commandString << "job->ipod_atom = 1;\n "
595 if hash["Mp4LargeFile"].to_i == 1
596 commandString << "job->largeFileSize = 1;\n"
600 if hash["VideoEncoder"] != "FFmpeg"
601 commandString << "vcodec = "
602 if hash["VideoEncoder"] == "x264"
603 commandString << "HB_VCODEC_X264;\n "
604 elsif hash["VideoEncoder"].to_s.downcase == "xvid"
605 commandString << "HB_VCODEC_XVID;\n "
610 case hash["VideoQualityType"].to_i
612 commandString << "size = " << hash["VideoTargetSize"] << ";\n "
614 commandString << "job->vbitrate = " << hash["VideoAvgBitrate"] << ";\n "
616 commandString << "job->vquality = " << hash["VideoQualitySlider"] << ";\n "
617 commandString << "job->crf = 1;\n "
621 if hash["VideoFramerate"] != "Same as source"
622 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
623 commandString << "job->vrate_base = " << "1126125;\n "
624 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
625 commandString << "job->vrate_base = " << "900900;\n "
626 # Gotta add the rest of the framerates for completion's sake.
630 # Only include samplerate and bitrate when not performing AC3 passthru
631 if (hash["FileCodecs"].include? "AC-3") == false
633 commandString << "job->abitrate = " << hash["AudioBitRate"] << ";\n "
636 commandString << "job->arate = "
637 case hash["AudioSampleRate"]
639 commandString << "48000"
641 commandString << "44100"
643 commandString << "32000"
645 commandString << "24000"
647 commandString << "22050"
649 commandString << ";\n "
653 commandString << "acodec = "
654 case hash["FileCodecs"]
656 commandString << "HB_ACODEC_FAAC;\n "
657 commandString << "audio_mixdown = HB_AMIXDOWN_DOLBYPLII_AC3;\n "
658 commandString << "arate = 48000;\n "
660 commandString << "HB_ACODEC_FAAC;\n "
662 commandString << "HB_ACODEC_AC3;\n "
664 commandString << "HB_ACODEC_VORBIS;\n "
666 commandString << "HB_ACODEC_LAME;\n "
670 if !hash["PictureAutoCrop"].to_i
671 commandString << "job->crop[0] = " << hash["PictureTopCrop"] << ";\n "
672 commandString << "job->crop[1] = " << hash["PictureBottomCrop"] << ";\n "
673 commandString << "job->crop[2] = " << hash["PictureLeftCrop"] << ";\n "
674 commandString << "job->crop[4] - " << hash["PictureRightCrop"] << ";\n "
678 if hash["PictureWidth"].to_i != 0
679 commandString << "job->width = "
680 commandString << hash["PictureWidth"] << ";\n "
682 if hash["PictureHeight"].to_i != 0
683 commandString << "job->height = "
684 commandString << hash["PictureHeight"] << ";\n "
688 if hash["Subtitles"] != "None"
689 commandString << "job->subtitle = "
690 commandString << ( hash["Subtitles"].to_i - 1).to_s << ";\n "
694 if hash["x264Option"] != ""
695 commandString << "x264opts = strdup(\""
696 commandString << hash["x264Option"] << "\");\n "
700 if hash["UsesPictureFilters"].to_i == 1
702 case hash["PictureDeinterlace"].to_i
704 commandString << "deinterlace = 1;\n "
705 commandString << "deinterlace_opt = \"-1\";\n "
707 commandString << "deinterlace = 1;\n "
708 commandString << "deinterlace_opt = \"2\";\n "
710 commandString << "deinterlace = 1;\n "
711 commandString << "deinterlace_opt = \"0\";\n "
713 commandString << "deinterlace = 1;\n "
714 commandString << "deinterlace_opt = \"1:-1:1\";\n "
717 case hash["PictureDenoise"].to_i
719 commandString << "denoise = 1;\n "
720 commandString << "denoise_opt = \"2:1:2:3\";\n "
722 commandString << "denoise = 1;\n "
723 commandString << "denoise_opt = \"3:2:2:3\";\n "
725 commandString << "denoise = 1;\n "
726 commandString << "denoise_opt = \"7:7:5:5\";\n "
729 if hash["PictureDetelecine"].to_i == 1 then commandString << "detelecine = 1;\n " end
730 if hash["PictureDeblock"].to_i == 1 then commandString << "deblock = 1;\n " end
731 if hash["VFR"].to_i == 1 then commandString << "vfr = 1;\n " end
735 if hash["ChapterMarkers"].to_i == 1 then commandString << "job->chapter_markers = 1;\n " end
736 if hash["PicturePAR"].to_i == 1 then commandString << "pixelratio = 1;\n " end
737 if hash["VideoGrayScale"].to_i == 1 then commandString << "job->grayscale = 1;\n " end
738 if hash["VideoTwoPass"].to_i == 1 then commandString << "twoPass = 1;\n " end
739 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << "turbo_opts_enabled = 1;\n" end
743 # That's it, print to screen now
745 #puts "*" * @columnWidth
749 def generateAPIList(hash) # Makes a list of the CLI options a built-in CLI preset uses, for wrappers to parse
751 commandString << " printf(\"\\n+ " << hash["PresetName"] << ": "
754 if hash["VideoEncoder"] != "FFmpeg"
755 commandString << " -e "
756 commandString << hash["VideoEncoder"].to_s.downcase
760 case hash["VideoQualityType"].to_i
762 commandString << " -S " << hash["VideoTargetSize"]
764 commandString << " -b " << hash["VideoAvgBitrate"]
766 commandString << " -q " << hash["VideoQualitySlider"]
770 if hash["VideoFramerate"] != "Same as source"
771 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
772 commandString << " -r " << "23.976"
773 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
774 commandString << " -r " << "29.97"
776 commandString << " -r " << hash["VideoFramerate"]
780 # Only include samplerate and bitrate when not performing AC-3 passthru
781 if (hash["FileCodecs"].include? "AC-3") == false
783 commandString << " -B " << hash["AudioBitRate"]
785 commandString << " -R " << hash["AudioSampleRate"]
789 commandString << " -E "
790 case hash["FileCodecs"]
792 commandString << "aac+ac3"
794 commandString << "faac"
796 commandString << "ac3"
798 commandString << "vorbis"
800 commandString << "lame"
804 commandString << " -f "
805 case hash["FileFormat"]
807 commandString << "mp4"
809 commandString << "avi"
811 commandString << "ogm"
813 commandString << "mkv"
817 if hash["Mp4iPodCompatible"].to_i == 1
818 commandString << " -I"
822 if hash["Mp4LargeFile"].to_i == 1
823 commandString << " -4"
827 if !hash["PictureAutoCrop"].to_i
828 commandString << " --crop "
829 commandString << hash["PictureTopCrop"]
831 commandString << hash["PictureBottomCrop"]
833 commandString << hash["PictureLeftCrop"]
835 commandString << hash["PictureRightCrop"]
839 if hash["PictureWidth"].to_i != 0
840 commandString << " -w "
841 commandString << hash["PictureWidth"]
843 if hash["PictureHeight"].to_i != 0
844 commandString << " -l "
845 commandString << hash["PictureHeight"]
849 if hash["Subtitles"] != "None"
850 commandString << " -s "
851 commandString << hash["Subtitles"]
855 if hash["UsesPictureFilters"].to_i == 1
857 case hash["PictureDeinterlace"].to_i
859 commandString << " --deinterlace=\\\"fast\\\""
861 commandString << " --deinterlace=\\\slow\\\""
863 commandString << " --deinterlace=\\\"slower\\\""
865 commandString << " --deinterlace=\\\"slowest\\\""
868 case hash["PictureDenoise"].to_i
870 commandString << " --denoise=\\\"weak\\\""
872 commandString << " --denoise=\\\"medium\\\""
874 commandString << " --denoise=\\\"strong\\\""
877 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
878 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
879 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
883 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
884 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
885 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
886 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
887 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
890 if hash["x264Option"] != ""
891 commandString << " -x "
892 commandString << hash["x264Option"]
895 commandString << "\\n\");"
897 # That's it, print to screen now
904 # First grab the specified CLI options
905 options = readOptions
907 # Only run if one of the useful CLI flags have been passed
908 if options.cliraw == true || options.cliparse == true || options.api == true || options.apilist == true
909 # This line is the ignition -- generates hashes of
910 # presets and then displays them to the screen
911 # with the options the user selects on the CLI.
912 Display.new( Presets.new.hashMasterList, options )
914 # Direct the user to the help
915 puts "\n\tUsage: manicure.rb [options]"
916 puts "\tSee help with -h or --help"