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 be displayed in various formats for use by the CLI and various wrappers.
10 # For handling command line arguments to the script
14 # These arrays contain all the other presets and hashes that are going to be used.
15 # Yeah, they're global variables. In an object-oriented scripting language.
17 $presetMasterList = []
20 # This class is pretty much everything. It contains multitudes.
23 # A width of 40 gives nice, compact output.
26 # Running initialization runs everything.
27 # Calling it will also call the parser
29 def initialize(options)
33 # Grab input from the user's presets .plist
34 rawPresets = readPresetPlist
36 # Store all the presets in here
39 # Each item in the array is one line from the .plist
40 presetStew = rawPresets.split("\n")
42 # Now get rid of white space
43 presetStew = cleanStew(presetStew)
45 # This stores the offsets between presets.
46 presetBreaks = findPresetBreaks(presetStew)
48 # Now it's time to use that info to store each
49 # preset individually, in the master list.
51 while i <= presetBreaks.size
52 if i == 0 #first preset
53 # Grab the stew, up to the 1st offset.
54 $presetMasterList[i] = presetStew.slice(0..presetBreaks[i].to_i)
55 elsif i < presetBreaks.size #middle presets
56 # Grab the stew from the last offset to the current..
57 $presetMasterList[i] = presetStew.slice(presetBreaks[i-1].to_i..presetBreaks[i].to_i)
59 # Grab the stew, starting at the last offset, all the way to the end.
60 $presetMasterList[i] = presetStew.slice(presetBreaks[i-1].to_i..presetStew.length)
65 # Parse the presets into hashes
72 def readPresetPlist # Grab the .plist and store it in presets
74 # Grab the user's home path
75 homeLocation = `echo $HOME`.chomp
77 # Use that to build a path to the presets .plist
78 inputFile = homeLocation+'/Library/Application\ Support/HandBrake/UserPresets.plist'
80 # Builds a command that inputs the .plist, but not before stripping all the XML gobbledygook.
81 parseCommand = 'cat '+inputFile+' | sed -e \'s/<[a-z]*>//\' -e \'s/<\/[a-z]*>//\' -e \'/<[?!]/d\' '
85 # Run the command, return the raw presets
86 rawPresets = `#{parseCommand}`
89 def cleanStew(presetStew) #remove tabbed white space
90 presetStew.each do |oneline|
95 def findPresetBreaks(presetStew) #figure out where each preset starts and ends
99 presetStew.each do |presetLine|
100 if presetLine =~ /AudioBitRate/ # This is the first line of a new preset.
101 presetBreaks[j] = i-1 # So mark down how long the last one was.
109 def buildPresetHash #fill up $hashMasterList with hashes of all key/value pairs
112 # Iterate through all presets, treating each in turn as singleServing
113 $presetMasterList.each do |singleServing|
115 # Each key and value are on sequential lines.
116 # Iterating through by twos, use that to build a hash.
117 # Each key, on line i, paired with its value, on line i+1
120 while i < singleServing.length
121 tempHash[singleServing[i]] = singleServing[i+1]
125 # Now store that hash in the master list.
126 $hashMasterList[j]=tempHash
132 def displayCommandStrings # prints everything to screen
134 # Iterate through the hashes.
135 $hashMasterList.each do |hash|
137 # Check to make there are valid contents
138 if hash.key?("PresetName")
140 if @options.header == true
141 # First throw up a header to make each preset distinct
145 if @options.cliraw == true
146 # Show the preset's full CLI string equivalent
147 generateCLIString(hash)
150 if @options.cliparse == true
151 generateCLIParse(hash)
154 if @options.api == true
155 # Show the preset as code for test/test.c, HandBrakeCLI
156 generateAPIcalls(hash)
159 if @options.apilist == true
160 # Show the preset as print statements, for CLI wrappers to parse.
161 generateAPIList(hash)
167 def displayHeader(hash) # A distinct banner to separate each preset
169 # Print a line of asterisks
170 puts "*" * @@columnWidth
172 # Print the name, centered
173 puts '* '+hash["PresetName"].to_s.center(@@columnWidth-4)+' *'
175 # Print a line of dashes
176 puts '~' * @@columnWidth
178 # Print the description, centered and word-wrapped
179 puts hash["PresetDescription"].to_s.center(@@columnWidth).gsub(/\n/," ").scan(/\S.{0,#{@@columnWidth-2}}\S(?=\s|$)|\S+/)
181 # Print another line of dashes
182 puts '~' * @@columnWidth
184 # Print the formats the preset uses
185 puts "#{hash["FileCodecs"]}".center(@@columnWidth)
187 # Note if the preset isn't built-in
188 if hash["Type"].to_i == 1
189 puts "Custom Preset".center(@@columnWidth)
192 # Note if the preset is marked as default.
193 if hash["Default"].to_i == 1
194 puts "This is your default preset.".center(@@columnWidth)
197 # End with a line of tildes.
198 puts "~" * @@columnWidth
202 def generateCLIString(hash) # Makes a full CLI equivalent of a preset
204 commandString << './HandBrakeCLI -i DVD -o ~/Movies/movie.'
207 case hash["FileFormat"]
209 commandString << "mp4 "
211 commandString << "avi "
213 commandString << "ogm "
215 commandString << "mkv "
219 if hash["VideoEncoder"] != "FFmpeg"
220 commandString << " -e "
221 if hash["VideoEncoder"] == "x264 (h.264 Main)"
222 commandString << "x264"
223 elsif hash["VideoEncoder"] == "x264 (h.264 iPod)"
224 commandString << "x264b30"
226 commandString << hash["VideoEncoder"].to_s.downcase
231 case hash["VideoQualityType"].to_i
233 commandString << " -S " << hash["VideoTargetSize"]
235 commandString << " -b " << hash["VideoAvgBitrate"]
237 commandString << " -q " << hash["VideoQualitySlider"]
241 if hash["VideoFramerate"] != "Same as source"
242 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
243 commandString << " -r " << "23.976"
244 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
245 commandString << " -r " << "29.97"
247 commandString << " -r " << hash["VideoFramerate"]
251 #Audio encoder (only specifiy bitrate and samplerate when not doing AC-3 pass-thru)
252 commandString << " -E "
253 case hash["FileCodecs"]
255 commandString << "ac3"
257 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
259 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
261 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
265 commandString << " -f "
266 case hash["FileFormat"]
268 commandString << "mp4"
270 commandString << "avi"
272 commandString << "ogm"
274 commandString << "mkv"
278 if !hash["PictureAutoCrop"].to_i
279 commandString << " --crop "
280 commandString << hash["PictureTopCrop"]
282 commandString << hash["PictureBottomCrop"]
284 commandString << hash["PictureLeftCrop"]
286 commandString << hash["PictureRightCrop"]
290 if hash["PictureWidth"].to_i != 0
291 commandString << " -w "
292 commandString << hash["PictureWidth"]
294 if hash["PictureHeight"].to_i != 0
295 commandString << " -l "
296 commandString << hash["PictureHeight"]
300 if hash["Subtitles"] != "None"
301 commandString << " -s "
302 commandString << hash["Subtitles"]
306 if hash["UsesPictureFilters"].to_i == 1
308 case hash["PictureDeinterlace"].to_i
310 commandString << " --deinterlace=\"fast\""
312 commandString << " --deinterlace=\slow\""
314 commandString << " --deinterlace=\"slower\""
316 commandString << " --deinterlace=\"slowest\""
319 case hash["PictureDenoise"].to_i
321 commandString << " --denoise=\"weak\""
323 commandString << " --denoise=\"medium\""
325 commandString << " --denoise=\"strong\""
328 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
329 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
330 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
334 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
335 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
336 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
337 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
338 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
341 if hash["x264Option"] != ""
342 commandString << " -x "
343 commandString << hash["x264Option"]
346 # That's it, print to screen now
349 #puts "*" * @@columnWidth
354 def generateCLIParse(hash) # Makes a CLI equivalent of all user presets, for wrappers to parse
356 commandString << '+ ' << hash["PresetName"] << ":"
359 if hash["VideoEncoder"] != "FFmpeg"
360 commandString << " -e "
361 if hash["VideoEncoder"] == "x264 (h.264 Main)"
362 commandString << "x264"
363 elsif hash["VideoEncoder"] == "x264 (h.264 iPod)"
364 commandString << "x264b30"
366 commandString << hash["VideoEncoder"].to_s.downcase
371 case hash["VideoQualityType"].to_i
373 commandString << " -S " << hash["VideoTargetSize"]
375 commandString << " -b " << hash["VideoAvgBitrate"]
377 commandString << " -q " << hash["VideoQualitySlider"]
381 if hash["VideoFramerate"] != "Same as source"
382 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
383 commandString << " -r " << "23.976"
384 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
385 commandString << " -r " << "29.97"
387 commandString << " -r " << hash["VideoFramerate"]
391 #Audio encoder (only include bitrate and samplerate when not doing AC3 passthru)
392 commandString << " -E "
393 case hash["FileCodecs"]
395 commandString << "ac3"
397 commandString << "faac" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
399 commandString << "vorbis" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
401 commandString << "lame" << " -B " << hash["AudioBitRate"] << " -R " << hash["AudioSampleRate"]
405 commandString << " -f "
406 case hash["FileFormat"]
408 commandString << "mp4"
410 commandString << "avi"
412 commandString << "ogm"
414 commandString << "mkv"
418 if !hash["PictureAutoCrop"].to_i
419 commandString << " --crop "
420 commandString << hash["PictureTopCrop"]
422 commandString << hash["PictureBottomCrop"]
424 commandString << hash["PictureLeftCrop"]
426 commandString << hash["PictureRightCrop"]
430 if hash["PictureWidth"].to_i != 0
431 commandString << " -w "
432 commandString << hash["PictureWidth"]
434 if hash["PictureHeight"].to_i != 0
435 commandString << " -l "
436 commandString << hash["PictureHeight"]
440 if hash["Subtitles"] != "None"
441 commandString << " -s "
442 commandString << hash["Subtitles"]
446 if hash["UsesPictureFilters"].to_i == 1
448 case hash["PictureDeinterlace"].to_i
450 commandString << " --deinterlace=\"fast\""
452 commandString << " --deinterlace=\slow\""
454 commandString << " --deinterlace=\"slower\""
456 commandString << " --deinterlace=\"slowest\""
459 case hash["PictureDenoise"].to_i
461 commandString << " --denoise=\"weak\""
463 commandString << " --denoise=\"medium\""
465 commandString << " --denoise=\"strong\""
468 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
469 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
470 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
474 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
475 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
476 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
477 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
478 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
481 if hash["x264Option"] != ""
482 commandString << " -x "
483 commandString << hash["x264Option"]
486 # That's it, print to screen now
489 #puts "*" * @@columnWidth
494 def generateAPIcalls(hash) # Makes a C version of the preset ready for coding into the CLI
496 commandString = "if (!strcmp(preset_name, \"" << hash["PresetName"] << "\"))\n{\n "
499 case hash["FileFormat"]
501 commandString << "mux = " << "HB_MUX_MP4;\n "
503 commandString << "mux = " << "HB_MUX_AVI;\n "
505 commandString << "mux = " << "HB_MUX_OGM;\n "
507 commandString << "mux = " << "HB_MUX_MKV;\n "
511 if hash["VideoEncoder"] != "FFmpeg"
512 commandString << "vcodec = "
513 if hash["VideoEncoder"] == "x264 (h.264 Main)"
514 commandString << "HB_VCODEC_X264;\n "
515 elsif hash["VideoEncoder"] == "x264 (h.264 iPod)"
516 commandString << "HB_VCODEC_X264;\njob->h264_level = 30;\n "
517 elsif hash["VideoEncoder"].to_s.downcase == "xvid"
518 commandString << "HB_VCODEC_XVID;\n "
523 case hash["VideoQualityType"].to_i
525 commandString << "size = " << hash["VideoTargetSize"] << ";\n "
527 commandString << "job->vbitrate = " << hash["VideoAvgBitrate"] << ";\n "
529 commandString << "job->vquality = " << hash["VideoQualitySlider"] << ";\n "
530 commandString << "job->crf = 1;\n "
534 if hash["VideoFramerate"] != "Same as source"
535 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
536 commandString << "job->vrate_base = " << "1126125;\n "
537 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
538 commandString << "job->vrate_base = " << "900900;\n "
539 # Gotta add the rest of the framerates for completion's sake.
543 # Only include samplerate and bitrate when not performing AC3 passthru
544 if (hash["FileCodecs"].include? "AC-3") == false
546 commandString << "job->abitrate = " << hash["AudioBitRate"] << ";\n "
549 commandString << "job->arate = "
550 case hash["AudioSampleRate"]
552 commandString << "48000"
554 commandString << "44100"
556 commandString << "32000"
558 commandString << "24000"
560 commandString << "22050"
562 commandString << ";\n "
566 commandString << "acodec = "
567 case hash["FileCodecs"]
569 commandString << "HB_ACODEC_FAAC;\n "
571 commandString << "HB_ACODEC_AC3;\n "
573 commandString << "HB_ACODEC_VORBIS;\n "
575 commandString << "HB_ACODEC_LAME;\n "
579 if !hash["PictureAutoCrop"].to_i
580 commandString << "job->crop[0] = " << hash["PictureTopCrop"] << ";\n "
581 commandString << "job->crop[1] = " << hash["PictureBottomCrop"] << ";\n "
582 commandString << "job->crop[2] = " << hash["PictureLeftCrop"] << ";\n "
583 commandString << "job->crop[4] - " << hash["PictureRightCrop"] << ";\n "
587 if hash["PictureWidth"].to_i != 0
588 commandString << "job->width = "
589 commandString << hash["PictureWidth"] << ";\n "
591 if hash["PictureHeight"].to_i != 0
592 commandString << "job->height = "
593 commandString << hash["PictureHeight"] << ";\n "
597 if hash["Subtitles"] != "None"
598 commandString << "job->subtitle = "
599 commandString << ( hash["Subtitles"].to_i - 1).to_s << ";\n "
603 if hash["x264Option"] != ""
604 commandString << "x264opts = strdup(\""
605 commandString << hash["x264Option"] << "\");\n "
609 if hash["UsesPictureFilters"].to_i == 1
611 case hash["PictureDeinterlace"].to_i
613 commandString << "deinterlace = 1;\n "
614 commandString << "deinterlace_opt = \"-1\";\n "
616 commandString << "deinterlace = 1;\n "
617 commandString << "deinterlace_opt = \"0\";\n "
619 commandString << "deinterlace = 1;\n "
620 commandString << "deinterlace_opt = \"2:-1:1\";\n "
622 commandString << "deinterlace = 1;\n "
623 commandString << "deinterlace_opt = \"1:-1:1\";\n "
626 case hash["PictureDenoise"].to_i
628 commandString << "denoise = 1;\n "
629 commandString << "denoise_opt = \"2:1:2:3\";\n "
631 commandString << "denoise = 1;\n "
632 commandString << "denoise_opt = \"3:2:2:3\";\n "
634 commandString << "denoise = 1;\n "
635 commandString << "denoise_opt = \"7:7:5:5\";\n "
638 if hash["PictureDetelecine"].to_i == 1 then commandString << "detelecine = 1;\n " end
639 if hash["PictureDeblock"].to_i == 1 then commandString << "deblock = 1;\n " end
640 if hash["VFR"].to_i == 1 then commandString << "vfr = 1;\n " end
644 if hash["ChapterMarkers"].to_i == 1 then commandString << "job->chapter_markers = 1;\n " end
645 if hash["PicturePAR"].to_i == 1 then commandString << "pixelratio = 1;\n " end
646 if hash["VideoGrayScale"].to_i == 1 then commandString << "job->grayscale = 1;\n " end
647 if hash["VideoTwoPass"].to_i == 1 then commandString << "twoPass = 1;\n " end
648 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << "turbo_opts_enabled = 1;\n" end
652 # That's it, print to screen now
654 #puts "*" * @@columnWidth
658 def generateAPIList(hash) # Makes a list of the CLI options a built-in CLI preset uses, for wrappers to parse
660 commandString << " printf(\"\\n+ " << hash["PresetName"] << ": "
663 if hash["VideoEncoder"] != "FFmpeg"
664 commandString << " -e "
665 if hash["VideoEncoder"] == "x264 (h.264 Main)"
666 commandString << "x264"
667 elsif hash["VideoEncoder"] == "x264 (h.264 iPod)"
668 commandString << "x264b30"
670 commandString << hash["VideoEncoder"].to_s.downcase
675 case hash["VideoQualityType"].to_i
677 commandString << " -S " << hash["VideoTargetSize"]
679 commandString << " -b " << hash["VideoAvgBitrate"]
681 commandString << " -q " << hash["VideoQualitySlider"]
685 if hash["VideoFramerate"] != "Same as source"
686 if hash["VideoFramerate"] == "23.976 (NTSC Film)"
687 commandString << " -r " << "23.976"
688 elsif hash["VideoFramerate"] == "29.97 (NTSC Video)"
689 commandString << " -r " << "29.97"
691 commandString << " -r " << hash["VideoFramerate"]
695 # Only include samplerate and bitrate when not performing AC-3 passthru
696 if (hash["FileCodecs"].include? "AC-3") == false
698 commandString << " -B " << hash["AudioBitRate"]
700 commandString << " -R " << hash["AudioSampleRate"]
704 commandString << " -E "
705 case hash["FileCodecs"]
707 commandString << "faac"
709 commandString << "ac3"
711 commandString << "vorbis"
713 commandString << "lame"
717 commandString << " -f "
718 case hash["FileFormat"]
720 commandString << "mp4"
722 commandString << "avi"
724 commandString << "ogm"
726 commandString << "mkv"
730 if !hash["PictureAutoCrop"].to_i
731 commandString << " --crop "
732 commandString << hash["PictureTopCrop"]
734 commandString << hash["PictureBottomCrop"]
736 commandString << hash["PictureLeftCrop"]
738 commandString << hash["PictureRightCrop"]
742 if hash["PictureWidth"].to_i != 0
743 commandString << " -w "
744 commandString << hash["PictureWidth"]
746 if hash["PictureHeight"].to_i != 0
747 commandString << " -l "
748 commandString << hash["PictureHeight"]
752 if hash["Subtitles"] != "None"
753 commandString << " -s "
754 commandString << hash["Subtitles"]
758 if hash["UsesPictureFilters"].to_i == 1
760 case hash["PictureDeinterlace"].to_i
762 commandString << " --deinterlace=\\\"fast\\\""
764 commandString << " --deinterlace=\\\slow\\\""
766 commandString << " --deinterlace=\\\"slower\\\""
768 commandString << " --deinterlace=\\\"slowest\\\""
771 case hash["PictureDenoise"].to_i
773 commandString << " --denoise=\\\"weak\\\""
775 commandString << " --denoise=\\\"medium\\\""
777 commandString << " --denoise=\\\"strong\\\""
780 if hash["PictureDetelecine"].to_i == 1 then commandString << " --detelecine" end
781 if hash["PictureDeblock"].to_i == 1 then commandString << " --deblock" end
782 if hash["VFR"].to_i == 1 then commandString << " --vfr" end
786 if hash["ChapterMarkers"].to_i == 1 then commandString << " -m" end
787 if hash["PicturePAR"].to_i == 1 then commandString << " -p" end
788 if hash["VideoGrayScale"].to_i == 1 then commandString << " -g" end
789 if hash["VideoTwoPass"].to_i == 1 then commandString << " -2" end
790 if hash["VideoTurboTwoPass"].to_i == 1 then commandString << " -T" end
793 if hash["x264Option"] != ""
794 commandString << " -x "
795 commandString << hash["x264Option"]
798 commandString << "\\n\");"
800 # That's it, print to screen now
806 # CLI options: (code based on http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/index.html )
807 # --[no-]cli-raw, -r gives raw CLI for wiki
808 # --cli-parse, -p gives CLI strings for wrappers
809 # --api, -a gives preset code for test.c
810 # --api-list, -A gives CLI strings for --preset-list display
811 # --[no-]header, -h turns off banner display
812 options = OpenStruct.new
813 options.cliraw = false
814 options.cliparse = false
816 options.apilist = false
817 options.header = false
819 opts = OptionParser.new do |opts|
820 opts.banner = "Usage: manicure.rb [options]"
823 opts.separator "Options:"
825 opts.on("-r", "--cli-raw", "Gives example strings for the HB wiki") do |r|
830 opts.on("-p", "--cli-parse", "Gives presets as wrapper-parseable CLI", " option strings") do |p|
834 opts.on("-a", "--api", "Gives preset code for test.c") do |api|
838 opts.on("-A", "--api-list", "Gives code for test.c's --preset-list", " options") do |A|
842 opts.on("-H", "--Header", "Display a banner before each preset") do |H|
846 opts.on_tail("-h", "--help", "Show this message") do
852 # Only run if one of the useful CLI flags have been passed
853 if options.cliraw == true || options.cliparse == true || options.api == true || options.apilist == true
854 # This line is the ignition.
855 PresetClass.new(options)
857 # Direct the user to the help
858 puts "\n\tUsage: manicure.rb [options]"
859 puts "\tSee help with -h or --help"