module OBF::External
Public Class Methods
from_obf(obf_json_or_path, opts)
click to toggle source
# File lib/obf/external.rb, line 300 def self.from_obf(obf_json_or_path, opts) opts ||= {} obj = obf_json_or_path if obj.is_a?(String) obj = OBF::Utils.parse_obf(File.read(obf_json_or_path), opts) else obj = OBF::Utils.parse_obf(obf_json_or_path, opts) end ['images', 'sounds'].each do |type| (obj[type] || []).each do |item| if !item['data'] && item['path'] && opts['zipper'] content_type = item['content_type'] data = opts['zipper'].read(item['path']) str = "data:" + content_type str += ";base64," + Base64.strict_encode64(data) item['data'] = str end if item['path'] opts[type] ||= {} opts[type][item['path']] ||= item end end end obj['license'] = OBF::Utils.parse_license(obj['license']) obj end
from_obz(obz_path, opts)
click to toggle source
# File lib/obf/external.rb, line 380 def self.from_obz(obz_path, opts) boards = [] images = [] sounds = [] OBF::Utils.load_zip(obz_path) do |zipper| obf_opts = {'zipper' => zipper, 'images' => {}, 'sounds' => {}, 'boards' => {}} manifest = JSON.parse(zipper.read('manifest.json')) obf_opts['manifest'] = manifest root = manifest['root'] board = OBF::Utils.parse_obf(zipper.read(root), obf_opts) board['path'] = root unvisited_boards = [board] visited_boards = [] while unvisited_boards.length > 0 board_object = unvisited_boards.shift board_object['id'] ||= rand(9999).to_s + Time.now.to_i.to_s visited_boards << board_object board_object['buttons'].each do |button| if button['load_board'] all_boards = visited_boards + unvisited_boards if all_boards.none?{|b| b['id'] == button['load_board']['id'] || b['path'] == button['load_board']['path'] } path = button['load_board']['path'] || (manifest['paths'] && manifest['paths']['boards'] && manifest['paths']['boards'][button['load_board']['id']]) if path b = OBF::Utils.parse_obf(zipper.read(path), obf_opts) b['path'] = path button['load_board']['id'] = b['id'] unvisited_boards << b end end end end end visited_boards.each do |board_object| res = from_obf(board_object, obf_opts) images += res['images'] || [] sounds += res['sounds'] || [] boards << res end end images.uniq! sounds.uniq! raise "image ids must be present and unique" unless images.map{|i| i['id'] }.uniq.length == images.length raise "sound ids must be present and unique" unless sounds.map{|i| i['id'] }.uniq.length == sounds.length # TODO: try to fix the problem where multiple images or sounds have the same id -- # this involves reaching in and updating image and sound references on generated boards.. res = { 'boards' => boards, 'images' => images, 'sounds' => sounds } res end
to_obf(hash, dest_path, path_hash=nil, to_include=nil)
click to toggle source
# File lib/obf/external.rb, line 2 def self.to_obf(hash, dest_path, path_hash=nil, to_include=nil) to_include ||= {images: true, sounds: true} if hash['boards'] old_hash = hash hash = old_hash['boards'][0] hash['images'] = old_hash['images'] || [] hash['sounds'] = old_hash['sounds'] || [] path_hash = nil end res = OBF::Utils.obf_shell res['id'] = hash['id'] res['locale'] = hash['locale'] || 'en' res['format'] = OBF::OBF::FORMAT res['name'] = hash['name'] res['default_layout'] = hash['default_layout'] || 'landscape' res['background'] = hash['background'] res['url'] = hash['url'] res['data_url'] = hash['data_url'] OBF::Utils.log("compressing board #{res['name'] || res['id']}") res['default_locale'] = hash['default_locale'] if hash['default_locale'] res['label_locale'] = hash['label_locale'] if hash['label_locale'] res['vocalization_locale'] = hash['vocalization_locale'] if hash['vocalization_locale'] res['description_html'] = hash['description_html'] res['protected_content_user_identifier'] = hash['protected_content_user_identifier'] if hash['protected_content_user_identifier'] res['license'] = OBF::Utils.parse_license(hash['license']) hash.each do |key, val| if key && key.match(/^ext_/) res[key] = val end end grid = [] images = [] sounds = [] res['buttons'] = [] buttons = hash['buttons'] #board.settings['buttons'] button_count = buttons.length OBF::Utils.as_progress_percent(0.0, 0.3) do buttons.each_with_index do |original_button, idx| button = { 'id' => original_button['id'], 'label' => original_button['label'], 'vocalization' => original_button['vocalization'], 'action' => original_button['action'], 'actions' => original_button['actions'], 'left' => original_button['left'], 'top' => original_button['top'], 'width' => original_button['width'], 'height' => original_button['height'], 'border_color' => OBF::Utils.fix_color(original_button['border_color'] || "#aaa", 'rgb'), 'background_color' => OBF::Utils.fix_color(original_button['background_color'] || "#fff", 'rgb') } if original_button['load_board'] button['load_board'] = { 'id' => original_button['load_board']['id'], 'url' => original_button['load_board']['url'], 'data_url' => original_button['load_board']['data_url'] } if path_hash && path_hash['included_boards'] && path_hash['included_boards'][original_button['load_board']['id']] button['load_board']['path'] = "board_#{original_button['load_board']['id']}.obf" end end if original_button['translations'] original_button['translations'].each do |loc, hash| next unless hash.is_a?(Hash) button['translations'] ||= {} button['translations'][loc] ||= {} button['translations'][loc]['label'] = hash['label'].to_s if hash['label'] button['translations'][loc]['vocalization'] = hash['vocalization'].to_s if hash['vocalization'] (hash['inflections'] || {}).each do |key, val| if key.match(/^ext_/) button['translations'][loc]['inflections'] ||= {} button['translations'][loc]['inflections'][key] = val else button['translations'][loc]['inflections'] ||= {} button['translations'][loc]['inflections'][key] = val.to_s end end hash.keys.each do |key| button['translations'][loc][key] = hash[key] if key.to_s.match(/^ext_/) end end end if original_button['hidden'] button['hidden'] = original_button['hidden'] end if original_button['url'] button['url'] = original_button['url'] end original_button.each do|key, val| if key.match(/^ext_/) button[key] = val end end if original_button['image_id'] && hash['images'] image = hash['images'].detect{|i| i['id'] == original_button['image_id']} if image images << image button['image_id'] = image['id'] end end if original_button['sound_id'] sound = hash['sounds'].detect{|s| s['id'] == original_button['sound_id']} if sound sounds << sound button['sound_id'] = sound['id'] end end res['buttons'] << trim_empties(button) OBF::Utils.update_current_progress(idx.to_f / button_count.to_f, "generated button #{button['id']} for #{res['id']}") end end OBF::Utils.update_current_progress(0.35, "images for board #{res['id']}") # board_id 1_527892 has some svg's still, not pre-rasterized versions # 1_531854 convert-im6.q16: non-conforming drawing primitive definition `Helvetica''' @ error/draw.c/DrawImage/3265. if to_include[:images] hydra = OBF::Utils.hydra grabs = [] images.each do |img| if path_hash && path_hash['images'] && path_hash['images'][img['id']] elsif img['url'] && !img['data'] got_url = OBF::Utils.get_url(img['url'].to_s, true) if got_url['request'] hydra.queue(got_url['request']) grabs << {req: got_url['request'], img: img, res: got_url} end end end OBF::Utils.log(" batch-retrieving #{grabs.length} images for board #{res['name'] || res['id']}") hydra.run grabs.each do |grab| if grab[:res] && grab[:res]['data'] str = "data:" + grab[:res]['content_type'] str += ";base64," + Base64.strict_encode64(grab[:res]['data']) grab[:img]['data'] = str grab[:img]['content_type'] ||= grab[:res]['content_type'] end end images.each do |original_image| image = { 'id' => original_image['id'], 'width' => original_image['width'], 'height' => original_image['height'], 'license' => OBF::Utils.parse_license(original_image['license']), 'protected' => original_image['protected'], 'protected_source' => original_image['protected_source'], 'url' => original_image['url'], 'data' => original_image['data'], 'data_url' => original_image['data_url'], 'content_type' => original_image['content_type'] } if !path_hash # not zipping image['data'] ||= OBF::Utils.image_base64(image['url']) if image['url'] if image['data'] && (!image['content_type'] || !image['width'] || !image['height']) attrs = OBF::Utils.image_attrs(image['data']) image['content_type'] ||= attrs['content_type'] image['width'] ||= attrs['width'] image['height'] ||= attrs['height'] end else # zipping, so needs to be downloaded and potentially re-used if path_hash['images'] && path_hash['images'][image['id']] image['path'] = path_hash['images'][image['id']]['path'] image['content_type'] ||= path_hash['images'][image['id']]['content_type'] image['width'] ||= path_hash['images'][image['id']]['width'] image['height'] ||= path_hash['images'][image['id']]['height'] else image_fetch = OBF::Utils.image_raw(image['data'] || image['url']) if image_fetch && image_fetch['data'] if !image['content_type'] || !image['width'] || !image['height'] attrs = OBF::Utils.image_attrs(image_fetch['data']) image['content_type'] ||= image_fetch['content_type'] || attrs['content_type'] image['width'] ||= attrs['width'] image['height'] ||= attrs['height'] end zip_path = "images/image_#{image['id']}#{image_fetch['extension']}" path_hash['images'] ||= {} path_hash['images'][image['id']] = { 'path' => zip_path, 'content_type' => image['content_type'], 'width' => image['width'], 'height' => image['height'] } path_hash['zip'].add(zip_path, image_fetch['data']) image['path'] = zip_path image.delete('data') end end end res['images'] << trim_empties(image) end elsif to_include[:image_urls] images.each do |original_image| image = { 'id' => original_image['id'], 'width' => original_image['width'], 'height' => original_image['height'], 'license' => OBF::Utils.parse_license(original_image['license']), 'protected' => original_image['protected'], 'protected_source' => original_image['protected_source'], 'url' => original_image['url'], 'data_url' => original_image['data_url'], 'content_type' => original_image['content_type'] } res['images'] << trim_empties(image) end end OBF::Utils.update_current_progress(0.75, "sounds for board #{res['id']}") if to_include[:sounds] sounds.each do |original_sound| sound = { 'id' => original_sound['id'], 'duration' => original_sound['duration'], 'license' => OBF::Utils.parse_license(original_sound['license']), 'protected' => original_sound['protected'], 'protected_source' => original_sound['protected_source'], 'url' => original_sound['url'], 'data' => original_sound['data'], 'data_url' => original_sound['data_url'], 'content_type' => original_sound['content_type'] } if !path_hash sound['data'] = OBF::Utils.sound_base64(sound['url']) if sound['url'] else if path_hash['sounds'] && path_hash['sounds'][sound['id']] sound['path'] = path_hash['sounds'][sound['id']]['path'] else sound_fetch = OBF::Utils.sound_raw(sound['url'] || sound['data']) if sound_fetch zip_path = "sounds/sound_#{sound['id']}#{sound_fetch['extension']}" path_hash['sounds'] ||= {} path_hash['sounds'][sound['id']] = { 'path' => zip_path } path_hash['zip'].add(zip_path, sound_fetch['data']) sound['path'] = zip_path end end sound['path'] = zip_path end res['sounds'] << trim_empties(sound) end elsif to_include[:sound_urls] sounds.each do |original_sound| sound = { 'id' => original_sound['id'], 'duration' => original_sound['duration'], 'license' => OBF::Utils.parse_license(original_sound['license']), 'protected' => original_sound['protected'], 'protected_source' => original_sound['protected_source'], 'url' => original_sound['url'], 'data_url' => original_sound['data_url'], 'content_type' => original_sound['content_type'] } res['sounds'] << trim_empties(sound) end end OBF::Utils.update_current_progress(0.85, "grid for board #{res['id']}") res['grid'] = OBF::Utils.parse_grid(hash['grid']) # TODO: more robust parsing here if path_hash && path_hash['zip'] zip_path = "board_#{res['id']}.obf" path_hash['boards'] ||= {} path_hash['boards'][res['id']] = { 'path' => zip_path } path_hash['zip'].add(zip_path, JSON.pretty_generate(res)) else File.open(dest_path, 'w') {|f| f.write(JSON.pretty_generate(res)) } end OBF::Utils.log(" done compressing board #{res['name'] || res['id']}") OBF::Utils.update_current_progress(1.0, "done for board #{res['id']}") return dest_path end
to_obz(content, dest_path, opts)
click to toggle source
# File lib/obf/external.rb, line 329 def self.to_obz(content, dest_path, opts) if content['id'] old_content = content content = { 'boards' => [old_content], 'images' => old_content['images'] || [], 'sounds' => old_content['sounds'] || [] } end paths = {} boards = content['boards'] content['images'] ||= boards.map{|b| b['images'] }.flatten.uniq content['sounds'] ||= boards.map{|b| b['sounds'] }.flatten.uniq root_board = boards[0] incr = 1.0 / boards.length.to_f tally = 0.0 OBF::Utils.build_zip(dest_path) do |zipper| paths['zip'] = zipper paths['included_boards'] = {} boards.each do |b| paths['included_boards'][b['id']] = b end boards.each do |b| OBF::Utils.as_progress_percent(tally, tally + incr) do b = paths['included_boards'][b['id']] if b b['images'] = content['images'] || [] b['sounds'] = content['sounds'] || [] to_obf(b, nil, paths, opts[:to_include]) end end tally += incr end manifest = { 'format' => OBF::OBF::FORMAT, 'root' => paths['boards'][root_board['id']]['path'], 'paths' => {} } ['images', 'sounds', 'boards'].each do |type| manifest['paths'][type] = {} (paths[type] || {}).each do |id, opts| manifest['paths'][type][id] = opts['path'] end end zipper.add('manifest.json', JSON.pretty_generate(manifest)) end return dest_path end
to_pdf(board, dest_path, opts)
click to toggle source
# File lib/obf/external.rb, line 434 def self.to_pdf(board, dest_path, opts) if board && board['boards'] opts['packet'] = true end tmp_path = OBF::Utils.temp_path("stash") if opts['packet'] OBF::Utils.as_progress_percent(0.1, 0.3) do opts[:to_include] = {images: true} OBF::External.to_obz(board, tmp_path, opts) end OBF::Utils.as_progress_percent(0.3, 1.0) do OBF::OBZ.to_pdf(tmp_path, dest_path, opts) end else OBF::Utils.as_progress_percent(0, 0.5) do self.to_obf(board, tmp_path, nil, {images: true}) end OBF::Utils.as_progress_percent(0.5, 1.0) do OBF::OBF.to_pdf(tmp_path, dest_path, opts) end end File.unlink(tmp_path) if File.exist?(tmp_path) dest_path end
to_png(board, dest_path, opts)
click to toggle source
# File lib/obf/external.rb, line 459 def self.to_png(board, dest_path, opts) tmp_path = OBF::Utils.temp_path("stash") OBF::Utils.as_progress_percent(0, 0.5) do self.to_pdf(board, tmp_path, opts) end OBF::Utils.as_progress_percent(0.5, 1.0) do OBF::PDF.to_png(tmp_path, dest_path) end File.unlink(tmp_path) if File.exist?(tmp_path) dest_path end
trim_empties(hash)
click to toggle source
# File lib/obf/external.rb, line 292 def self.trim_empties(hash) new_hash = {} hash.each do |key, val| new_hash[key] = val if val != nil end new_hash end