├── .gitignore ├── images ├── gomtl-logo-512.png └── sponsors │ ├── la-gare.png │ ├── intel-security.png │ ├── google.svg │ ├── intel-security.svg │ └── shopify-logo.svg ├── event.yaml ├── sponsors.yaml ├── data.yaml ├── technology ├── README.txt ├── technology.css └── technology.html ├── talks.yaml ├── models.go ├── README.md ├── main.go └── template.html /.gitignore: -------------------------------------------------------------------------------- 1 | *~ -------------------------------------------------------------------------------- /images/gomtl-logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/gomtl-logo-512.png -------------------------------------------------------------------------------- /images/sponsors/la-gare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/sponsors/la-gare.png -------------------------------------------------------------------------------- /images/sponsors/intel-security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abourget/video-automation/HEAD/images/sponsors/intel-security.png -------------------------------------------------------------------------------- /event.yaml: -------------------------------------------------------------------------------- 1 | event: 2 | tag: gomtl-03 3 | date: "Tuesday, August 30th 2016" 4 | venue: "La Gare" 5 | sponsors: 6 | - intel-security 7 | - la-gare 8 | title: "#gomtl-03 - August 30th" 9 | -------------------------------------------------------------------------------- /sponsors.yaml: -------------------------------------------------------------------------------- 1 | google: 2 | image: google.svg 3 | name: Google 4 | 5 | intel-security: 6 | image: intel-security.svg 7 | name: Intel Security 8 | url: https://www.truekey.com/ 9 | 10 | shopify: 11 | image: shopify-logo.svg 12 | name: Shopify 13 | url: https://www.shopify.ca 14 | 15 | la-gare: 16 | image: la-gare.png 17 | name: La Gare 18 | url: https://garemtl.com/ 19 | -------------------------------------------------------------------------------- /data.yaml: -------------------------------------------------------------------------------- 1 | - slug: 01-alexandre-bourget 2 | first: Alexandre 3 | last: Bourget 4 | twitter: bourgetalexndre 5 | tagline: Data Scientist, Intel Security 6 | 7 | meetup: "#gomtl-01" 8 | date: 22 février 2016 9 | 10 | - slug: 02-robert-de-niro 11 | first: Robert 12 | last: De Niro 13 | tagline: POS Dev, Redshift Inc 14 | 15 | meetup: "#gomtl-01" 16 | date: February 22nd 2016 17 | -------------------------------------------------------------------------------- /technology/README.txt: -------------------------------------------------------------------------------- 1 | 2 | How to use this theme 3 | --------------------- 4 | 5 | Add the following line inside your tag _AFTER_ the 6 | webcomponents-lite.min.js and other HTML imports: 7 | 8 | 9 | 10 | 11 | 12 | Usage within your own custom elements 13 | ------------------------------------- 14 | 15 | To use this theme within your own custom elements, add the 16 | following line inside your tag in each of your 17 | custom elements: 18 | 19 | 20 | -------------------------------------------------------------------------------- /talks.yaml: -------------------------------------------------------------------------------- 1 | talks: 2 | - title: "Tests in Go" 3 | presenter: 4 | name: "Dave MacFarlane" 5 | tagline: "Sr Software Dev. @ McGill Centre For Integrative Neuroscience" 6 | abstract: | 7 | 8 | Dave will be giving an overview of the testing tools that come built in with Go. 9 | 10 | - title: "Adventure with a race-y Set" 11 | presenter: 12 | name: Steve Venzerul 13 | twitter: ckm2k1 14 | tagline: "Software Engineer at Intel / JS-Montreal organizer" 15 | abstract: | 16 | 17 | Steve will show an example of some subtle bugs when writing concurrent 18 | code, found while implementing a Set data structure. 19 | 20 | - title: "Goa for microservices, or how to Generate All the Things" 21 | presenter: 22 | name: Alexandre Bourget 23 | twitter: bourgetalexndre 24 | tagline: "Data Scientist at Intel Security" 25 | abstract: | 26 | 27 | We'll be building a REST API, with Swagger API output, using the Goa code 28 | generator. 29 | -------------------------------------------------------------------------------- /models.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | yaml "gopkg.in/yaml.v1" 7 | ) 8 | 9 | type Event struct { 10 | Event struct { 11 | Tag string 12 | Date string 13 | Venue string 14 | } 15 | Sponsors []string 16 | Title string 17 | } 18 | 19 | type Talks struct { 20 | Talks []struct { 21 | Title string 22 | Presenter struct { 23 | Name string 24 | Tagline string 25 | Twitter string 26 | } 27 | Abstract string 28 | } 29 | } 30 | 31 | type Sponsors map[string]struct { 32 | Image string 33 | Name string 34 | URL string 35 | } 36 | 37 | var event Event 38 | var talks Talks 39 | var sponsors Sponsors 40 | 41 | func loadTalks(filename string) error { 42 | cnt, err := ioutil.ReadFile(filename) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | err = yaml.Unmarshal(cnt, &talks) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func loadEvent(filename string) error { 56 | cnt, err := ioutil.ReadFile(filename) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | err = yaml.Unmarshal(cnt, &event) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func loadSponsors(filename string) error { 70 | cnt, err := ioutil.ReadFile(filename) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | err = yaml.Unmarshal(cnt, &sponsors) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /images/sponsors/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Generate videos out of animated HTML pages 2 | ========================================== 3 | 4 | This little project does only one simple thing: it generates a series 5 | of videos based on some data in `.yaml` files and a `template.html`, 6 | rendering each as MP4 files (using ffmpeg) from the moment an element 7 | `.capture` is displayed, until it is hidden. 8 | 9 | My primary goal was to create dynamic videos to introduce speakers in 10 | the video recording of the sessions at Golang Montréal meetups 11 | (https://golangmontreal.org). I wanted to cut the post-production 12 | cost of doing videos recordings of the session, so I coded the 13 | repetitive stuff. 14 | 15 | Check `talks.yaml`, `sponsors.yaml` and `event.yaml` to see the 16 | format. This mostly refers to the structure we use in 17 | https://github.com/gomtl/golangmontreal.org `content` and `data` dirs. 18 | 19 | See the included `template.html` for an example template. 20 | 21 | It then uses ChromeDriver (using 22 | [the agouti WebDriver implementation](https://github.com/sclevine/agouti) 23 | ) to open the browser, resize it properly, load the template from its 24 | own internal web server. It waits until an HTML node with a `capture` 25 | class appears (on the body for example). It then starts `ffmpeg` to 26 | capture the screen at 25 fps, writing an .mp4 file in H.264 to disk for each 27 | entry in the `data.yaml`. When the `capture` class disappears, the 28 | video stops. It then proceeds to the next video. 29 | 30 | Pretty simple eh ? 31 | 32 | 33 | Dependencies 34 | ------------ 35 | 36 | You need to have the binary for `ffmpeg` available in your PATH (with 37 | libx264 support). You also need 38 | [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) 39 | to be in your PATH for agouti to pick up. You'll also need Google 40 | Chrome install.. I'll let you figure that one out. 41 | 42 | 43 | Limitations 44 | ----------- 45 | 46 | This currently runs only on Linux (uses the x11grab ffmpeg 47 | module). The window-decorations dimensions are modeled around the 48 | Ubuntu Unity environment and are currently hard-coded. 49 | 50 | It could be adapted to run on Windows though. 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "time" 12 | 13 | "github.com/sclevine/agouti" 14 | ) 15 | 16 | var data map[string]interface{} 17 | var datas []map[string]interface{} 18 | 19 | var videoSize = size{1280, 720} 20 | 21 | var talksFile = flag.String("talks", "talks.yaml", "The talks file in yaml format") 22 | var eventFile = flag.String("event", "event.yaml", "The event data file, in yaml format") 23 | var sponsorsFile = flag.String("sponsors", "sponsors.yaml", "Sponsors metadata, in yaml format") 24 | var eventName = "Golang Montréal" 25 | 26 | func main() { 27 | flag.Parse() 28 | 29 | err := loadTalks(*talksFile) 30 | if err != nil { 31 | log.Fatalln("Error reading talks file:", err) 32 | } 33 | 34 | err = loadEvent(*eventFile) 35 | if err != nil { 36 | log.Fatalln("Error reading event file:", err) 37 | } 38 | 39 | err = loadSponsors(*sponsorsFile) 40 | if err != nil { 41 | log.Fatalln("Error reading sponsors file:", err) 42 | } 43 | 44 | go serveTemplates() 45 | 46 | time.Sleep(100 * time.Millisecond) 47 | 48 | launchVideoRecording() 49 | } 50 | 51 | func serveTemplates() { 52 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 53 | if r.URL.Path != "/" { 54 | http.FileServer(http.Dir(".")).ServeHTTP(w, r) 55 | return 56 | } 57 | 58 | t, err := template.ParseFiles("template.html") 59 | if err != nil { 60 | log.Println("ERROR parsing template.html:", err) 61 | http.Error(w, "ERROR parsing template.html: "+err.Error(), 500) 62 | return 63 | } 64 | 65 | w.Header().Set("Content-Type", "text/html") 66 | 67 | err = t.Execute(w, map[string]interface{}{ 68 | "talks": talks.Talks, 69 | "event": event, 70 | "eventName": eventName, 71 | "baseurl": "http://" + r.Host, 72 | "sponsors": sponsors, 73 | }) 74 | if err != nil { 75 | fmt.Fprintf(w, "\n\nERROR executing template: "+err.Error()) 76 | } 77 | }) 78 | fmt.Println("Listening on 127.0.0.1:7777") 79 | err := http.ListenAndServe(":7777", nil) 80 | if err != nil { 81 | log.Fatalln("Error serving:", err) 82 | } 83 | } 84 | 85 | func launchVideoRecording() { 86 | dr := agouti.ChromeDriver() 87 | failOnError(dr.Start()) 88 | 89 | page, err := dr.NewPage() 90 | if err != nil { 91 | log.Fatalln("couldn't get Page") 92 | } 93 | 94 | failOnError(page.Size(videoSize.W+chrome.Left+chrome.Right, videoSize.H+chrome.Top+chrome.Bottom)) 95 | 96 | failOnError(page.Navigate("http://localhost:7777/")) 97 | 98 | var screenOffset point 99 | failOnError(page.RunScript(` 100 | window.loadVideoAutomation(); 101 | return {x: window.screenX, y: window.screenY}; 102 | `, nil, &screenOffset)) 103 | 104 | for i := 0; i < 10; i++ { 105 | 106 | // Wait for .capture to appear 107 | for { 108 | found, err := page.All(".capture").Count() 109 | failOnError(err) 110 | if found == 1 { 111 | break 112 | } 113 | } 114 | 115 | doneCh := launchFFMPEG(screenOffset.X+chrome.Left, screenOffset.Y+chrome.Top, videoSize.W, videoSize.H, fmt.Sprintf("/tmp/video-automation-%02d.mp4", i+1)) 116 | 117 | // Wait for .capture to disappear 118 | for { 119 | found, _ := page.All(".capture").Count() 120 | if found == 0 { 121 | break 122 | } 123 | } 124 | 125 | doneCh <- true 126 | 127 | time.Sleep(1 * time.Second) 128 | 129 | } 130 | 131 | dr.Stop() 132 | } 133 | 134 | type rect struct { 135 | X, Y, W, H int 136 | } 137 | type point struct { 138 | X, Y int 139 | } 140 | type size struct { 141 | W, H int 142 | } 143 | type dim struct { 144 | Top, Bottom, Left, Right int 145 | } 146 | 147 | //var chrome = dim{77, 5, 5, 5} 148 | var chrome = dim{114, 0, 13, 0} 149 | 150 | func launchFFMPEG(x, y, w, h int, filename string) (done chan bool) { 151 | done = make(chan bool) 152 | 153 | args := []string{"-video_size", fmt.Sprintf("%dx%d", w, h), "-framerate", "25", "-f", "x11grab", "-draw_mouse", "0", "-i", fmt.Sprintf(":0.0+%d,%d", x, y), "-vcodec", "libx264", "-preset", "veryfast", "-y", filename} 154 | fmt.Printf("\n\nCommand: ffmpeg %s\n\n", args) 155 | cmd := exec.Command("ffmpeg", args...) 156 | cmd.Stdout = os.Stdout 157 | cmd.Stderr = os.Stderr 158 | stdinPipe, err := cmd.StdinPipe() 159 | failOnError(err) 160 | 161 | go func() { 162 | failOnError(cmd.Start()) 163 | <-done 164 | stdinPipe.Write([]byte("q")) 165 | }() 166 | 167 | return done 168 | } 169 | 170 | func failOnError(err error) { 171 | if err != nil { 172 | log.Fatalln("Failed", err) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 61 | 62 | 63 | 110 | 111 | Video Automation 112 | 113 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /technology/technology.css: -------------------------------------------------------------------------------- 1 | /* 2 | * PolymerThemes v1.0.3 3 | * Homepage: https://polymerthemes.com 4 | * Copyright 2015 Polymer Themes 5 | * Licensed under BSD 6 | * Based on Polymer: http://www.polymer-project.org/ 7 | * Compatible with Polymer 1.0 8 | */ 9 | 10 | @import url("https://fonts.googleapis.com/css?family=Oxygen:700|Source+Sans+Pro"); 11 | html { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; font-size: 16px; } 12 | 13 | html, body { margin: 0px; padding: 0px; min-height: 100%; color: #444444; background-color: #ffffff; font-family: 'Source Sans Pro', sans-serif; font-weight: 400; } 14 | 15 | section { padding: 40px 0px; } 16 | section[hero] { background-color: #f2f7fa; } 17 | 18 | h1, h2, h3, h4, h5, h6 { font-weight: 400; margin: 0px; padding: 0px; line-height: 1em; } 19 | 20 | h1, h3, h5 { font-family: 'Oxygen', sans-serif; } 21 | 22 | h1 { font-size: 50px; margin: 30px 0px; } 23 | 24 | h2 { color: #444444; font-size: 35px; margin: 20px 0px; } 25 | 26 | h3 { font-size: 25px; margin: 15px 0px; } 27 | 28 | h4 { font-size: 20px; } 29 | 30 | a, a:active, a:visited, a:focus { color: #444444; text-decoration: none; } 31 | 32 | a:hover { color: #444444; text-decoration: underline; } 33 | 34 | :root { --default-primary-color: #373b50; --dark-primary-color: #373b50; --light-primary-color: #c5cae9; --text-primary-color: #ffffff; --accent-color: #ff4081; --primary-background-color: #ffffff; --primary-text-color: #444444; --secondary-text-color: #444444; --disabled-text-color: #313238; --divider-color: #e0e0e0; --paper-checkbox-checked-color: #373b50; --paper-checkbox-checked-ink-color: #373b50; --paper-checkbox-unchecked-color: #444444; --paper-checkbox-unchecked-ink-color: #444444; --paper-checkbox-label-color: #444444; --paper-fab-background: #373b50; --paper-fab-disabled-background: #afb1b9; --paper-fab-disabled-text: #313238; --paper-icon-button-disabled-text: #afb1b9; --paper-input-container-color: #919191; --paper-input-container-focus-color: #373b50; --paper-input-container-invalid-color: #ff6e40; --paper-input-container-input-color: #919191; --paper-menu-background-color: #f2f7fa; --paper-menu-color: #444444; --paper-menu-disabled-color: #afb1b9; --paper-progress-active-color: #373b50; --paper-progress-secondary-color: #90a4ae; --paper-radio-button-checked-color: #373b50; --paper-radio-button-checked-ink-color: #d6d8e3; --paper-radio-button-unchecked-color: #444444; --paper-radio-button-unchecked-ink-color: #444444; --paper-radio-button-label-color: #444444; --paper-slider-knob-color: #373b50; --paper-slider-active-color: #373b50; --paper-slider-pin-color: #373b50; --paper-spinner-layer-1-color: #373b50; --paper-spinner-layer-2-color: #373b50; --paper-spinner-layer-3-color: #373b50; --paper-spinner-layer-4-color: #373b50; --paper-tabs-selection-bar-color: #90a4ae; --paper-tab-ink: #90a4ae; --paper-toggle-button-checked-bar-color: #373b50; --paper-toggle-button-checked-button-color: #373b50; --paper-toggle-button-checked-ink-color: #373b50; --paper-toggle-button-unchecked-bar-color: #444444; --paper-toggle-button-unchecked-button-color: white; --paper-toggle-button-unchecked-ink-color: white; --paper-toolbar-background: #373b50; --paper-toolbar-color: #ffffff; } 35 | 36 | paper-toolbar a, paper-toolbar a:hover, paper-toolbar a:active, paper-toolbar a:visited, paper-toolbar a:focus { color: #ffffff; } 37 | 38 | paper-button.primary, paper-button.btn-primary, paper-button[primary] { color: #373b50; } 39 | paper-button.primary[raised], paper-button.btn-primary[raised], paper-button[primary][raised] { background-color: #373b50; color: #fff; } 40 | paper-button.secondary, paper-button.btn-secondary, paper-button[secondary] { color: #90a4ae; } 41 | paper-button.secondary[raised], paper-button.btn-secondary[raised], paper-button[secondary][raised] { background-color: #90a4ae; color: #2b3134; } 42 | paper-button.success, paper-button.btn-success, paper-button[success] { color: #9dc56e; } 43 | paper-button.success[raised], paper-button.btn-success[raised], paper-button[success][raised] { background-color: #9dc56e; color: #2f3b21; } 44 | paper-button.info, paper-button.btn-info, paper-button[info] { color: #ffb74d; } 45 | paper-button.info[raised], paper-button.btn-info[raised], paper-button[info][raised] { background-color: #ffb74d; color: #4d3717; } 46 | paper-button.warning, paper-button.btn-warning, paper-button[warning] { color: #fadd60; } 47 | paper-button.warning[raised], paper-button.btn-warning[raised], paper-button[warning][raised] { background-color: #fadd60; color: #4b421d; } 48 | paper-button.error, paper-button.btn-error, paper-button[error] { color: #ff6e40; } 49 | paper-button.error[raised], paper-button.btn-error[raised], paper-button[error][raised] { background-color: #ff6e40; color: #4d2113; } 50 | paper-button.link, paper-button.btn-link, paper-button[link] { text-decoration: underline; } 51 | paper-button[raised] { color: #444444; } 52 | paper-button[disabled] { color: #313238 !important; background: #afb1b9 !important; } 53 | 54 | paper-icon-button.primary, paper-icon-button.btn-primary, paper-icon-button[primary] { color: #373b50; } 55 | paper-icon-button.primary:hover, paper-icon-button.btn-primary:hover, paper-icon-button[primary]:hover { background-color: #ebebee; background-color: rgba(55, 59, 80, 0.2); border-radius: 50%; } 56 | paper-icon-button.secondary, paper-icon-button.btn-secondary, paper-icon-button[secondary] { color: #90a4ae; } 57 | paper-icon-button.secondary:hover, paper-icon-button.btn-secondary:hover, paper-icon-button[secondary]:hover { background-color: #f4f6f7; background-color: rgba(144, 164, 174, 0.2); border-radius: 50%; } 58 | paper-icon-button.success, paper-icon-button.btn-success, paper-icon-button[success] { color: #9dc56e; } 59 | paper-icon-button.success:hover, paper-icon-button.btn-success:hover, paper-icon-button[success]:hover { background-color: #f5f9f1; background-color: rgba(157, 197, 110, 0.2); border-radius: 50%; } 60 | paper-icon-button.info, paper-icon-button.btn-info, paper-icon-button[info] { color: #ffb74d; } 61 | paper-icon-button.info:hover, paper-icon-button.btn-info:hover, paper-icon-button[info]:hover { background-color: #fff8ed; background-color: rgba(255, 183, 77, 0.2); border-radius: 50%; } 62 | paper-icon-button.warning, paper-icon-button.btn-warning, paper-icon-button[warning] { color: #fadd60; } 63 | paper-icon-button.warning:hover, paper-icon-button.btn-warning:hover, paper-icon-button[warning]:hover { background-color: #fffcef; background-color: rgba(250, 221, 96, 0.2); border-radius: 50%; } 64 | paper-icon-button.error, paper-icon-button.btn-error, paper-icon-button[error] { color: #ff6e40; } 65 | paper-icon-button.error:hover, paper-icon-button.btn-error:hover, paper-icon-button[error]:hover { background-color: #fff1ec; background-color: rgba(255, 110, 64, 0.2); border-radius: 50%; } 66 | 67 | paper-checkbox #checkmark.paper-checkbox, paper-checkbox /deep/, paper-checkbox::shadow #checkmark.paper-checkbox { border-color: #fff !important; } 68 | 69 | paper-dialog { color: #444444; background-color: #ffffff; font-family: 'Source Sans Pro', sans-serif; font-weight: 400; } 70 | paper-dialog h1, paper-dialog h2, paper-dialog h3, paper-dialog h4, paper-dialog h5, paper-dialog h6 { overflow: visible; padding: 0 1em; text-align: center; } 71 | 72 | paper-fab { --text-primary-color: white; } 73 | 74 | paper-fab { background-color: #373b50; color: #fff; --paper-fab-keyboard-focus-background: #6c749a; } 75 | paper-fab.primary, paper-fab.btn-primary, paper-fab[primary] { background-color: #373b50; color: #fff; --paper-fab-keyboard-focus-background: #6c749a; } 76 | paper-fab.secondary, paper-fab.btn-secondary, paper-fab[secondary] { background-color: #90a4ae; color: #2b3134; --paper-fab-keyboard-focus-background: #50646e; } 77 | paper-fab.success, paper-fab.btn-success, paper-fab[success] { background-color: #9dc56e; color: #2f3b21; --paper-fab-keyboard-focus-background: #5d8033; } 78 | paper-fab.info, paper-fab.btn-info, paper-fab[info] { background-color: #ffb74d; color: #4d3717; --paper-fab-keyboard-focus-background: #cd7a00; } 79 | paper-fab.warning, paper-fab.btn-warning, paper-fab[warning] { background-color: #fadd60; color: #4b421d; --paper-fab-keyboard-focus-background: #d4ad07; } 80 | paper-fab.error, paper-fab.btn-error, paper-fab[error] { background-color: #ff6e40; color: #4d2113; --paper-fab-keyboard-focus-background: #c02e00; } 81 | paper-fab[disabled] { color: #313238 !important; background: #afb1b9 !important; } 82 | 83 | section.hero paper-menu, section[hero] paper-menu { --paper-menu-background-color: #f2f7fa; } 84 | 85 | paper-tabs { background-color: #373b50; color: #ffffff; } 86 | -------------------------------------------------------------------------------- /technology/technology.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/sponsors/intel-security.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml® 82 | -------------------------------------------------------------------------------- /images/sponsors/shopify-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 40 | 45 | 50 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 77 | 79 | 81 | 82 | 83 | 84 | 85 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 104 | 105 | 106 | 109 | 110 | 111 | 112 | 113 | 118 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 134 | 135 | 153 | 154 | 155 | 156 | 157 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 188 | 190 | 191 | 192 | 193 | 194 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 218 | 219 | 220 | 221 | 222 | 223 | 225 | 226 | 227 | 228 | 229 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 245 | 246 | 247 | 248 | 249 | 252 | 253 | 254 | 255 | 256 | 259 | 260 | 261 | 262 | 263 | 264 | 266 | 267 | 268 | 269 | 270 | 272 | 273 | 274 | 275 | 276 | 278 | 279 | 280 | 285 | 290 | 295 | 297 | 299 | 301 | 303 | 305 | 307 | 309 | 311 | 313 | 315 | 317 | 319 | 321 | 322 | 323 | 324 | 325 | 330 | 331 | 332 | 333 | 334 | 335 | 339 | 340 | 341 | 342 | 343 | 344 | 347 | 348 | 349 | 350 | 351 | 352 | 355 | 356 | 357 | 358 | 359 | 362 | 363 | 364 | 369 | 372 | 376 | 380 | 382 | 385 | 389 | 390 | 391 | --------------------------------------------------------------------------------