├── start_videq.sh ├── sql ├── create_db.sql └── empty_db.sql ├── resources ├── img │ ├── logo.png │ ├── gopher.png │ ├── rvatine.gif │ ├── screen1.png │ ├── size_large.png │ ├── size_small.png │ ├── mama_gopher.png │ └── size_medium.png ├── vid │ ├── loop.jpg │ ├── loop.mp4 │ ├── loop.ogv │ ├── loop.webm │ └── video_overlay.png ├── icomoon │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ ├── icomoon.woff │ │ └── icomoon.svg │ ├── Read Me.txt │ ├── style.css │ ├── demo.html │ └── selection.json ├── js │ ├── custom.js │ └── upload.js └── css │ ├── bootstrap-theme.min.css │ ├── custom.css │ └── bootstrap-theme.css ├── .gitignore ├── handlers ├── home │ └── home.go ├── static │ ├── static_test.go │ └── static.go ├── restart │ └── restart.go ├── free │ └── free.go ├── gzip │ ├── gzip.go │ └── gzip_test.go ├── download │ └── download.go ├── check │ └── check.go ├── done │ └── done.go ├── session │ └── session.go └── upload │ └── upload.go ├── mediainfo.tpl ├── check.go ├── config └── config.go ├── db.go ├── log.go ├── main.go ├── README.md ├── janitor └── janitor.go ├── mediatools ├── encoder.go └── mediainfo.go └── templates └── home.html /start_videq.sh: -------------------------------------------------------------------------------- 1 | ./videq -web="8080" 2 | 3 | -------------------------------------------------------------------------------- /sql/create_db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE `videq` CHARACTER SET utf8 COLLATE utf8_general_ci; 2 | -------------------------------------------------------------------------------- /resources/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/logo.png -------------------------------------------------------------------------------- /resources/vid/loop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/vid/loop.jpg -------------------------------------------------------------------------------- /resources/vid/loop.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/vid/loop.mp4 -------------------------------------------------------------------------------- /resources/vid/loop.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/vid/loop.ogv -------------------------------------------------------------------------------- /resources/vid/loop.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/vid/loop.webm -------------------------------------------------------------------------------- /resources/img/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/gopher.png -------------------------------------------------------------------------------- /resources/img/rvatine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/rvatine.gif -------------------------------------------------------------------------------- /resources/img/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/screen1.png -------------------------------------------------------------------------------- /resources/img/size_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/size_large.png -------------------------------------------------------------------------------- /resources/img/size_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/size_small.png -------------------------------------------------------------------------------- /resources/img/mama_gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/mama_gopher.png -------------------------------------------------------------------------------- /resources/img/size_medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/img/size_medium.png -------------------------------------------------------------------------------- /resources/vid/video_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/vid/video_overlay.png -------------------------------------------------------------------------------- /resources/icomoon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/icomoon/fonts/icomoon.eot -------------------------------------------------------------------------------- /resources/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /resources/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/videq/HEAD/resources/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /resources/icomoon/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 4 | 5 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu > Manage Projects) to retrieve your icon selection. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | _backup 10 | _work 11 | conf/* 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.log 26 | *.exe 27 | 28 | 29 | 30 | storage 31 | videq 32 | -------------------------------------------------------------------------------- /handlers/home/home.go: -------------------------------------------------------------------------------- 1 | package home 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | type Handler struct { 9 | rootPath string 10 | } 11 | 12 | func NewHandler(rootPath string) *Handler { 13 | h := new(Handler) 14 | h.rootPath = rootPath 15 | return h 16 | } 17 | 18 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 19 | tpl := template.Must(template.New("home.html").ParseFiles(h.rootPath + "templates/home.html")) 20 | 21 | p := make(map[string]interface{}) 22 | 23 | tpl.Execute(w, p) 24 | } 25 | -------------------------------------------------------------------------------- /handlers/static/static_test.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestCssFileLoad(t *testing.T) { 11 | resp := httptest.NewRecorder() 12 | 13 | uri := "/resources/css/bootstrap.css" 14 | 15 | req, err := http.NewRequest("GET", uri, nil) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | staticHandler := NewHandler("../../") 21 | 22 | staticHandler.ServeHTTP(resp, req) 23 | 24 | if resp.Code != http.StatusOK { 25 | t.Fatal(fmt.Sprintf("Server repled with status %v expected %v", resp.Code, http.StatusOK)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediainfo.tpl: -------------------------------------------------------------------------------- 1 | General;FileName: %FileName%.%FileExtension%\r\nFileSize_bytes: %FileSize%\r\nVideoCount: %VideoCount%\r\nAudioCount: %AudioCount%\r\nDuration_ms: %Duration%\r\nFormat: %Format%\r\nCodecID: %CodecID%\r\n 2 | Video;Resolution: %Width%x%Height%\r\nWidth: %Width%\r\nHeight: %Height%\r\nStandard: %Standard%\r\nCodec: %Codec/String% %Format_Profile%\r\nBitrate_bps: %BitRate%\r\nFramerate: %FrameRate% fps\r\nAspectRatio: %DisplayAspectRatio/String%\r\n 3 | Audio;Audio: %Language/String% %BitRate/String% %BitRate_Mode% %Channel(s)% chnls %Codec/String%\r\n 4 | Text;%Language/String% 5 | Text_Begin;Subs: 6 | Text_Middle;, 7 | Text_End;.\r\n 8 | -------------------------------------------------------------------------------- /handlers/static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | ) 7 | 8 | type Handler struct { 9 | rootPath string 10 | } 11 | 12 | func NewHandler(rootPath string) *Handler { 13 | h := new(Handler) 14 | h.rootPath = rootPath 15 | return h 16 | } 17 | 18 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 19 | requestedPath := r.URL.Path[len("/"):] 20 | 21 | file, err := os.Open(h.rootPath + requestedPath) 22 | if err != nil { 23 | http.Error(w, err.Error(), http.StatusNotFound) 24 | return 25 | } 26 | 27 | stat, err := file.Stat() 28 | if err != nil { 29 | http.Error(w, err.Error(), http.StatusInternalServerError) 30 | return 31 | } 32 | 33 | http.ServeContent(w, r, requestedPath, stat.ModTime(), file) 34 | } 35 | -------------------------------------------------------------------------------- /handlers/restart/restart.go: -------------------------------------------------------------------------------- 1 | package restart 2 | 3 | import ( 4 | "net/http" 5 | 6 | alog "github.com/cenkalti/log" 7 | "github.com/gophergala/videq/handlers/session" 8 | "github.com/gophergala/videq/janitor" 9 | ) 10 | 11 | type Handler struct { 12 | log alog.Logger 13 | } 14 | 15 | func NewHandler(log alog.Logger) *Handler { 16 | h := new(Handler) 17 | h.log = log 18 | return h 19 | } 20 | 21 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 22 | sid, err := session.Sid(r) 23 | if err != nil { 24 | h.log.Error(err) 25 | http.Error(w, "Internal server error", http.StatusInternalServerError) 26 | return 27 | } 28 | 29 | err = janitor.CleanupUser(sid) 30 | if err != nil { 31 | h.log.Error(err) 32 | http.Error(w, "Internal server error", http.StatusInternalServerError) 33 | return 34 | } 35 | 36 | h.log.Debugf("User %v cleanup on damand", sid) 37 | } 38 | -------------------------------------------------------------------------------- /handlers/free/free.go: -------------------------------------------------------------------------------- 1 | package free 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | alog "github.com/cenkalti/log" 8 | "github.com/gophergala/videq/janitor" 9 | ) 10 | 11 | type Handler struct { 12 | rootPath string 13 | log alog.Logger 14 | } 15 | 16 | func NewHandler(log alog.Logger, rootPath string) *Handler { 17 | h := new(Handler) 18 | h.log = log 19 | h.rootPath = rootPath 20 | return h 21 | } 22 | 23 | type procedeValue struct { 24 | Procede bool 25 | } 26 | 27 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | procede := janitor.IsAllowedToUpload() 29 | 30 | p := &procedeValue{} 31 | p.Procede = procede 32 | 33 | js, err := json.Marshal(p) 34 | if err != nil { 35 | h.log.Error(err) 36 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 37 | return 38 | } 39 | 40 | w.Header().Set("Content-Type", "application/json") 41 | w.Write(js) 42 | } 43 | -------------------------------------------------------------------------------- /handlers/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type Handler struct { 11 | passHandle http.Handler 12 | } 13 | 14 | func NewHandler(passHandler http.Handler) *Handler { 15 | h := new(Handler) 16 | h.passHandle = passHandler 17 | return h 18 | } 19 | 20 | type gzipResponseWriter struct { 21 | io.Writer 22 | http.ResponseWriter 23 | } 24 | 25 | func (w gzipResponseWriter) Write(b []byte) (int, error) { 26 | return w.Writer.Write(b) 27 | } 28 | 29 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 30 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 31 | h.passHandle.ServeHTTP(w, r) 32 | return 33 | } 34 | w.Header().Set("Content-Encoding", "gzip") 35 | gz := gzip.NewWriter(w) 36 | defer gz.Close() 37 | gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w} 38 | h.passHandle.ServeHTTP(gzw, r) 39 | } 40 | -------------------------------------------------------------------------------- /check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/exec" 5 | ) 6 | 7 | var execList = map[string]string{ 8 | "mediainfo": "\r\nsudo apt-get install mediainfo", 9 | "HandBrakeCLI": "\r\nsudo add-apt-repository ppa:stebbins/handbrake-releases\r\napt-get install handbrake-cli\r\n", 10 | "ffmpeg": "\r\nsudo add-apt-repository ppa:jon-severinsson/ffmpeg\r\nsudo apt-get update\r\nsudo apt-get install ffmpeg\r\nsudo apt-get install frei0r-plugins\r\n", 11 | "ffmpeg2theora": "\r\nsudo apt-get install ffmpeg2theora", 12 | } 13 | 14 | func checkExecutables() { 15 | var failed bool = false 16 | for fileName, installHelp := range execList { 17 | err := checkExecutable(fileName) 18 | if err != nil { 19 | log.Errorln(err) 20 | log.Info("run: ", installHelp) 21 | failed = true 22 | } 23 | } 24 | if failed == true { 25 | log.Fatalln("Missing executables, cannot continue. please fix and rerun.") 26 | } 27 | } 28 | 29 | func checkExecutable(exename string) error { 30 | _, err := exec.LookPath(exename) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "code.google.com/p/gcfg" 5 | alog "github.com/cenkalti/log" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // Config structure that holds Config data 11 | type Config struct { 12 | // FILES struct { 13 | // TEMPLATES string 14 | // } 15 | 16 | DB struct { 17 | HOST string 18 | NAME string 19 | USER string 20 | PASS string 21 | DEBUG bool 22 | } 23 | HTTP struct { 24 | HOSTNAME string 25 | LISTENADDRESS string 26 | } 27 | } 28 | 29 | // LoadConfig fills Config struct with file data 30 | func LoadConfig(log alog.Logger, config *Config) { 31 | 32 | hostname, err := os.Hostname() 33 | if err != nil { 34 | log.Fatal("Cannot read hostname: ", err) 35 | } 36 | hostname = strings.ToLower(hostname) 37 | 38 | if err := readConfig("./conf/"+hostname+".config.ini", config); err != nil { 39 | log.Fatal("Cannot read config file: ", err) 40 | } 41 | // log.Info("Loaded config.ini") 42 | // log.Debug("%#v\n\n", &config) 43 | } 44 | func readConfig(file string, config *Config) error { 45 | return gcfg.ReadFileInto(config, file) 46 | } 47 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | _ "github.com/go-sql-driver/mysql" 6 | ) 7 | 8 | type Database struct { 9 | conn *sql.DB // global variable to share it between main and short lived functions (and eg. the HTTP handler) 10 | } 11 | 12 | type DbConfig struct { 13 | DbHost string 14 | DbName string 15 | DbUser string 16 | DbPass string 17 | Debug bool 18 | } 19 | 20 | //func OpenDB(host, name, user, pass string) *Database { 21 | func (d *Database) OpenDB(cfg DbConfig) (error, *Database) { 22 | 23 | dba, err := sql.Open("mysql", cfg.DbUser+":"+cfg.DbPass+"@tcp("+cfg.DbHost+":3306)/"+cfg.DbName+"?charset=utf8mb4,utf8") 24 | if err != nil { 25 | // VAZNO: NIKAD SE NE OKINE!!!! (al svejedno treba provjeravat ... XXX TODO doh) 26 | //log.Debug(err) 27 | // log.Debug("debug %s", Password("secret")) 28 | return err, nil 29 | } 30 | // prebacio u fju iznad koji poziva OpenDB. zasto? da mi se ne zatvori kad izadjem iz ove fje? 31 | // defer db.Close() 32 | 33 | err = dba.Ping() // zato se cesto koristi ping 34 | if err != nil { 35 | //log.Fatal(err) 36 | return err, nil 37 | } 38 | dba.SetMaxIdleConns(100) 39 | dba.SetMaxOpenConns(200) 40 | 41 | return nil, &Database{conn: dba} 42 | } 43 | 44 | func (d *Database) CloseDB() { 45 | d.conn.Close() 46 | } 47 | -------------------------------------------------------------------------------- /handlers/download/download.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strings" 7 | 8 | alog "github.com/cenkalti/log" 9 | "github.com/gophergala/videq/handlers/session" 10 | ) 11 | 12 | type Handler struct { 13 | rootPath string 14 | log alog.Logger 15 | } 16 | 17 | func NewHandler(log alog.Logger, rootPath string) *Handler { 18 | h := new(Handler) 19 | h.rootPath = rootPath 20 | h.log = log 21 | return h 22 | } 23 | 24 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 25 | sid, err := session.Sid(r) 26 | if err != nil { 27 | h.log.Error(err) 28 | http.Error(w, "Internal server error", http.StatusInternalServerError) 29 | return 30 | } 31 | 32 | urlParts := strings.Split(r.URL.Path, "/") 33 | filename := urlParts[len(urlParts)-1] 34 | 35 | requestedPath := h.rootPath + "storage/datastore/" + sid + "/" + filename 36 | 37 | file, err := os.Open(requestedPath) 38 | if err != nil { 39 | h.log.Error(err) 40 | http.Error(w, err.Error(), http.StatusNotFound) 41 | return 42 | } 43 | 44 | stat, err := file.Stat() 45 | if err != nil { 46 | h.log.Error(err) 47 | http.Error(w, err.Error(), http.StatusInternalServerError) 48 | return 49 | } 50 | 51 | h.log.Debugf("Download %v/%v", sid, filename) 52 | 53 | w.Header().Set("Content-Disposition", "attachment; filename="+filename) 54 | w.Header().Set("Content-Type", "application/octet-stream") 55 | 56 | http.ServeContent(w, r, requestedPath, stat.ModTime(), file) 57 | } 58 | -------------------------------------------------------------------------------- /handlers/gzip/gzip_test.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | type testHandler struct{} 12 | 13 | func (h *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 14 | fmt.Fprintln(w, "Hello!") 15 | } 16 | 17 | func TestGzipPassHandler(t *testing.T) { 18 | 19 | gzipHandlerWraper := NewHandler(&testHandler{}) 20 | 21 | ts := httptest.NewServer(gzipHandlerWraper) 22 | defer ts.Close() 23 | 24 | req, err := http.NewRequest("GET", ts.URL, nil) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | req.Header = http.Header{"Accept-Encoding": {"gzip"}} 29 | 30 | client := &http.Client{} 31 | resp, err := client.Do(req) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | if !strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { 37 | t.Fatal(fmt.Sprintf("Gzip header expected in response header")) 38 | } 39 | } 40 | 41 | func TestContentNotGziped(t *testing.T) { 42 | 43 | gzipHandlerWraper := NewHandler(&testHandler{}) 44 | 45 | ts := httptest.NewServer(gzipHandlerWraper) 46 | defer ts.Close() 47 | 48 | req, err := http.NewRequest("GET", ts.URL, nil) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | // client dose not accepts gzip on purpuse 54 | //req.Header = http.Header{"Accept-Encoding": {"gzip"}} 55 | 56 | client := &http.Client{} 57 | resp, err := client.Do(req) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { 63 | t.Fatal(fmt.Sprintf("Gzip header NOT expected in response header")) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/icomoon/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('fonts/icomoon.eot?ke818g'); 4 | src:url('fonts/icomoon.eot?#iefixke818g') format('embedded-opentype'), 5 | url('fonts/icomoon.woff?ke818g') format('woff'), 6 | url('fonts/icomoon.ttf?ke818g') format('truetype'), 7 | url('fonts/icomoon.svg?ke818g#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | font-family: 'icomoon'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .icon-thumb-up:before { 27 | content: "\e600"; 28 | } 29 | 30 | .icon-circle-plus:before { 31 | content: "\e040"; 32 | } 33 | 34 | .icon-circle-minus:before { 35 | content: "\e041"; 36 | } 37 | 38 | .icon-circle-check:before { 39 | content: "\e042"; 40 | } 41 | 42 | .icon-circle-cross:before { 43 | content: "\e043"; 44 | } 45 | 46 | .icon-jump-down:before { 47 | content: "\f072"; 48 | } 49 | 50 | .icon-bug:before { 51 | content: "\e999"; 52 | } 53 | 54 | .icon-warning:before { 55 | content: "\ea07"; 56 | } 57 | 58 | .icon-notification:before { 59 | content: "\ea08"; 60 | } 61 | 62 | .icon-question:before { 63 | content: "\ea09"; 64 | } 65 | 66 | .icon-info:before { 67 | content: "\ea0c"; 68 | } 69 | 70 | .icon-cancel-circle:before { 71 | content: "\ea0d"; 72 | } 73 | 74 | .icon-blocked:before { 75 | content: "\ea0e"; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /handlers/check/check.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | alog "github.com/cenkalti/log" 8 | "github.com/gophergala/videq/handlers/session" 9 | "github.com/gophergala/videq/janitor" 10 | "github.com/gophergala/videq/mediatools" 11 | ) 12 | 13 | type VideoInfo struct { 14 | Procede bool 15 | Err string 16 | OutputDimensions map[string]mediatools.VideoResolution 17 | OriginalInfo mediatools.MediaFileInfo 18 | } 19 | 20 | type Handler struct { 21 | rootPath string 22 | log alog.Logger 23 | } 24 | 25 | func NewHandler(log alog.Logger, rootPath string) *Handler { 26 | h := new(Handler) 27 | h.log = log 28 | h.rootPath = rootPath 29 | return h 30 | } 31 | 32 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 33 | sid, err := session.Sid(r) 34 | if err != nil { 35 | h.log.Error(err) 36 | http.Error(w, "Internal server error", http.StatusInternalServerError) 37 | return 38 | } 39 | 40 | hasFileInUpload, err := janitor.HasFileInUpload(sid) 41 | if err != nil { 42 | h.log.Error(err) 43 | http.Error(w, "Internal server error", http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | if hasFileInUpload == false { 48 | h.log.Error("No upload to check for client " + sid) 49 | http.Error(w, "No file on server", http.StatusNotFound) 50 | return 51 | } 52 | 53 | ok, mediaInfo, res, err := janitor.PossibleToEncode(sid) 54 | errorString := "" 55 | if err != nil { 56 | errorString = err.Error() 57 | } 58 | returnValue := VideoInfo{ 59 | Procede: ok, 60 | Err: errorString, 61 | OutputDimensions: res, 62 | OriginalInfo: mediaInfo} 63 | 64 | js, err := json.Marshal(returnValue) 65 | if err != nil { 66 | h.log.Error(err) 67 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 68 | return 69 | } 70 | 71 | w.Header().Set("Content-Type", "application/json") 72 | w.Write(js) 73 | } 74 | -------------------------------------------------------------------------------- /handlers/done/done.go: -------------------------------------------------------------------------------- 1 | package done 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "net/http" 7 | 8 | alog "github.com/cenkalti/log" 9 | "github.com/gophergala/videq/handlers/session" 10 | "github.com/gophergala/videq/janitor" 11 | ) 12 | 13 | type Handler struct { 14 | rootPath string 15 | log alog.Logger 16 | db *sql.DB 17 | } 18 | 19 | func NewHandler(log alog.Logger, rootPath string, db *sql.DB) *Handler { 20 | h := new(Handler) 21 | h.log = log 22 | h.rootPath = rootPath 23 | h.db = db 24 | return h 25 | } 26 | 27 | type downloadData struct { 28 | Procede bool 29 | Err string 30 | First_frame_jpg string `json:"first_frame_jpg"` 31 | Mp4_link string `json:"mp4_link"` 32 | Webm_link string `json:"webm_link"` 33 | Ogv_link string `json:"ogv_link"` 34 | } 35 | 36 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 37 | sid, err := session.Sid(r) 38 | if err != nil { 39 | h.log.Error(err) 40 | http.Error(w, "Internal server error", http.StatusInternalServerError) 41 | return 42 | } 43 | 44 | dd := &downloadData{} 45 | 46 | var success sql.NullInt64 47 | var encodingErr sql.NullString 48 | 49 | err = h.db.QueryRow("SELECT success, encode_error FROM file WHERE sid=? ", sid).Scan(&success, &encodingErr) 50 | switch { 51 | case err == sql.ErrNoRows: 52 | dd.Procede = false 53 | dd.Err = "No encoding job found" 54 | h.log.Errorf("No encoding job found for sid=%v", sid) 55 | break 56 | 57 | case err != nil: 58 | h.log.Error(err) 59 | dd.Procede = false 60 | dd.Err = err.Error() 61 | break 62 | 63 | default: 64 | if success.Int64 > 0 { 65 | dd.Procede = true 66 | dd.Err = encodingErr.String 67 | dd.First_frame_jpg = "/download/encoded.jpg" 68 | dd.Mp4_link = "/download/encoded.mp4" 69 | dd.Ogv_link = "/download/encoded.ogg" 70 | dd.Webm_link = "/download/encoded.webm" 71 | } else if len(encodingErr.String) > 0 { 72 | dd.Procede = false 73 | dd.Err = encodingErr.String 74 | janitor.CleanupUser(sid) 75 | } else { 76 | dd.Procede = false 77 | } 78 | } 79 | 80 | js, err := json.Marshal(dd) 81 | if err != nil { 82 | h.log.Error(err) 83 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 84 | return 85 | } 86 | 87 | w.Header().Set("Content-Type", "application/json") 88 | w.Write(js) 89 | } 90 | -------------------------------------------------------------------------------- /handlers/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "database/sql" 5 | // "log" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | 10 | alog "github.com/cenkalti/log" 11 | ) 12 | 13 | import _ "github.com/go-sql-driver/mysql" 14 | 15 | func Sid(r *http.Request) (string, error) { 16 | sid, err := r.Cookie("sid") 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | return sid.Value, nil 22 | } 23 | 24 | type Handler struct { 25 | dsn string 26 | passHandle http.Handler 27 | log alog.Logger 28 | db *sql.DB 29 | } 30 | 31 | func NewHandler(log alog.Logger, db *sql.DB, passHandler http.Handler) *Handler { 32 | h := new(Handler) 33 | h.passHandle = passHandler 34 | h.log = log 35 | h.db = db 36 | 37 | return h 38 | } 39 | 40 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | _, err := r.Cookie("sid") 42 | 43 | if err == http.ErrNoCookie { 44 | // db, err := sql.Open("mysql", h.dsn) 45 | // if err != nil { 46 | // h.log.Error(err) 47 | // http.Error(w, "Internal server error", http.StatusInternalServerError) 48 | // return 49 | // } 50 | // defer db.Close() 51 | 52 | var sid string 53 | 54 | for { 55 | getSid := func() (bool, error) { 56 | sid = UniqueKey() 57 | 58 | _, err := h.db.Exec("LOCK TABLE session WRITE") 59 | if err != nil { 60 | return false, err 61 | } 62 | defer func() { 63 | _, err := h.db.Exec("UNLOCK TABLES") 64 | if err != nil { 65 | h.log.Error(err) 66 | } 67 | }() 68 | 69 | var exists int 70 | err = h.db.QueryRow("SELECT 1 FROM session WHERE sid=? ", sid).Scan(&exists) 71 | switch { 72 | 73 | case err == sql.ErrNoRows: 74 | _, err := h.db.Exec("INSERT INTO session (sid, ts) VALUES (?, UNIX_TIMESTAMP())", sid) 75 | if err != nil { 76 | return false, err 77 | } 78 | return true, nil 79 | 80 | case err != nil: 81 | return false, err 82 | 83 | default: 84 | return false, nil 85 | 86 | } 87 | } 88 | 89 | generated, err := getSid() 90 | if err != nil { 91 | h.log.Error(err) 92 | http.Error(w, "Internal server error", http.StatusInternalServerError) 93 | return 94 | } 95 | if generated { 96 | break 97 | } 98 | } 99 | 100 | cookie := &http.Cookie{} 101 | cookie.Name = "sid" 102 | cookie.Value = sid 103 | cookie.Path = "/" 104 | cookie.Expires = time.Now().Add(7 * 24 * time.Hour) 105 | 106 | http.SetCookie(w, cookie) 107 | } 108 | 109 | h.passHandle.ServeHTTP(w, r) 110 | } 111 | 112 | func UniqueKey() string { 113 | letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 114 | 115 | newKey := make([]rune, 32) 116 | for i := range newKey { 117 | newKey[i] = letters[rand.Intn(len(letters))] 118 | } 119 | return string(newKey) 120 | } 121 | -------------------------------------------------------------------------------- /sql/empty_db.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.5.40, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: videq 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.40-0ubuntu0.14.04.1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `file` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `file`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `file` ( 26 | `sid` varchar(255) NOT NULL, 27 | `filename` varchar(255) NOT NULL, 28 | `start_ts` int(11) NOT NULL, 29 | `path_of_original` varchar(255) DEFAULT NULL, 30 | `added_to_encode_queue_ts` int(11) DEFAULT NULL, 31 | `encode_start_ts` int(11) DEFAULT NULL, 32 | `encode_end_ts` int(11) DEFAULT NULL, 33 | `encode_error` text, 34 | `success` tinyint(4) DEFAULT NULL, 35 | PRIMARY KEY (`sid`), 36 | KEY `start_ts_ix` (`start_ts`) 37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 38 | /*!40101 SET character_set_client = @saved_cs_client */; 39 | 40 | -- 41 | -- Dumping data for table `file` 42 | -- 43 | 44 | LOCK TABLES `file` WRITE; 45 | /*!40000 ALTER TABLE `file` DISABLE KEYS */; 46 | INSERT INTO `file` VALUES ('h9h2fhfUVuS9jZ8uVbhV3vC5AWX39IVU','master_1080.mp4',1422211068,'storage/datastore/h9h2fhfUVuS9jZ8uVbhV3vC5AWX39IVU/original.mp4',1422211074,1422211074,NULL,NULL,NULL); 47 | /*!40000 ALTER TABLE `file` ENABLE KEYS */; 48 | UNLOCK TABLES; 49 | 50 | -- 51 | -- Table structure for table `session` 52 | -- 53 | 54 | DROP TABLE IF EXISTS `session`; 55 | /*!40101 SET @saved_cs_client = @@character_set_client */; 56 | /*!40101 SET character_set_client = utf8 */; 57 | CREATE TABLE `session` ( 58 | `sid` varchar(255) NOT NULL, 59 | `ts` int(11) NOT NULL, 60 | PRIMARY KEY (`sid`) 61 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 62 | /*!40101 SET character_set_client = @saved_cs_client */; 63 | 64 | -- 65 | -- Dumping data for table `session` 66 | -- 67 | 68 | LOCK TABLES `session` WRITE; 69 | /*!40000 ALTER TABLE `session` DISABLE KEYS */; 70 | INSERT INTO `session` VALUES ('BpLnfgDsc2WD8F2qNfHK5a84jjJkwzDk',1422117575),('h9h2fhfUVuS9jZ8uVbhV3vC5AWX39IVU',1422117961); 71 | /*!40000 ALTER TABLE `session` ENABLE KEYS */; 72 | UNLOCK TABLES; 73 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 74 | 75 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 76 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 77 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 78 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 79 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 80 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 81 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 82 | 83 | -- Dump completed on 2015-01-25 20:19:52 84 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "strconv" 9 | "strings" 10 | 11 | alog "github.com/cenkalti/log" 12 | ) 13 | 14 | var log alog.Logger 15 | 16 | // // ERROR - 2012-08-17 17:35:35 --> [VuduNonFatalException] D:\Inetpub\wwwvirtual\www.bonbon.hr\vudu_system\voodoo\URI.php: 170 [VuduNonFatalException] (The URI you submitted has disallowed characters. (g=js&121)) (404) 17 | // // log_format := "%{color}[%{level:.4s}] %{time:15:04:05.000000} %{id:03x} %{shortfile} [%{longpkg}] %{longfunc} -> %{color:reset}%{message}" 18 | 19 | func InitLogger() { 20 | processName := path.Base(os.Args[0]) 21 | baseName := strings.Replace(processName, ".exe", "", -1) 22 | logFilename := fmt.Sprintf("%s.log", baseName) 23 | 24 | // Log levels (DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL) 25 | 26 | log = alog.NewLogger(processName) 27 | log.SetLevel(alog.DEBUG) // forward all messages to handler 28 | 29 | consoleLog := alog.NewWriterHandler(os.Stderr) 30 | consoleLog.SetFormatter(logFormatter{}) 31 | consoleLog.SetLevel(alog.DEBUG) 32 | consoleLog.Colorize = true 33 | 34 | fileLog := alog.NewWriterHandler(logFile(logFilename)) 35 | fileLog.SetLevel(alog.NOTICE) 36 | //log.SetHandler(fileLog) 37 | 38 | multi := alog.NewMultiHandler(consoleLog, fileLog) 39 | multi.SetFormatter(logFormatter{}) 40 | 41 | log.SetHandler(multi) 42 | 43 | } 44 | 45 | type logFormatter struct{} 46 | 47 | // %.4s limitira na 4 48 | // %-4s padda lijevo 4 spacea 49 | 50 | // Format outputs a message like "2014-02-28 18:15:57 [example] INFO somethinfig happened" 51 | func (f logFormatter) Format(rec *alog.Record) string { 52 | // return fmt.Sprintf("%s %.4s [%s] %s (%s)", 53 | return fmt.Sprintf("%s %.4s [%s] %s", 54 | fmt.Sprint(rec.Time)[:19], 55 | alog.LevelNames[rec.Level], 56 | // 57 | // XXX TODO 58 | // u nekom trenu poceo je ispisivati full path do filea sto je kriticno necitko 59 | // [C:\Users\Neven\Dropbox\Seven\projects\go\workspace\src\nivas.hr\chatprinter\chatprinter.exe] 60 | // mozda je do gorc1.4 61 | // rec.LoggerName, 62 | //"chatprinter", // hardkodiram za log 63 | filepath.Base(rec.Filename)+":"+strconv.Itoa(rec.Line), 64 | rec.Message) 65 | 66 | // rec.Message+", "+strconv.Itoa(rec.ProcessID)+", "+rec.ProcessName) // 302100, chatapp.exe 67 | } 68 | 69 | // original 70 | // // Format outputs a message like "2014-02-28 18:15:57 [example] INFO somethinfig happened" 71 | // func (f logFormatter) Format(rec *alog.Record) string { 72 | // return fmt.Sprintf("%s %-8s [%s] %-8s %s", 73 | // fmt.Sprint(rec.Time)[:19], 74 | // alog.LevelNames[rec.Level], 75 | // rec.LoggerName, 76 | // filepath.Base(rec.Filename)+":"+strconv.Itoa(rec.Line), 77 | // rec.Message 78 | // } 79 | 80 | func logFile(fileName string) *os.File { 81 | 82 | if file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0660); err == nil { 83 | return file 84 | } else { 85 | //return nil 86 | log.Fatalf("Cannot open log file '%s': %v\n", fileName, err) 87 | return nil 88 | } 89 | } 90 | 91 | //https://github.com/cenkalti/rain/blob/d45493ebdc299b1d88a8e3ddd6b0195f6bc1c2ac/log.go 92 | // func recoverAndLog(l logger) { 93 | // if err := recover(); err != nil { 94 | // buf := make([]byte, 10000) 95 | // l.Critical(err, "\n", string(buf[:runtime.Stack(buf, false)])) 96 | // } 97 | // } 98 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | "os" 7 | "runtime" 8 | 9 | _ "github.com/cenkalti/log" 10 | "github.com/gophergala/videq/config" 11 | "github.com/gophergala/videq/handlers/check" 12 | "github.com/gophergala/videq/handlers/done" 13 | "github.com/gophergala/videq/handlers/download" 14 | "github.com/gophergala/videq/handlers/free" 15 | "github.com/gophergala/videq/handlers/gzip" 16 | "github.com/gophergala/videq/handlers/home" 17 | "github.com/gophergala/videq/handlers/restart" 18 | "github.com/gophergala/videq/handlers/session" 19 | "github.com/gophergala/videq/handlers/static" 20 | "github.com/gophergala/videq/handlers/upload" 21 | "github.com/gophergala/videq/janitor" 22 | //"github.com/gophergala/videq/mediatools" 23 | ) 24 | 25 | const ROOT_PATH = "./" 26 | const NUM_OF_MERGE_WORKERS = 10 27 | const NUM_OF_MERGE_BUFFER = 100 28 | 29 | var cfg config.Config 30 | var db *Database 31 | 32 | func init() { 33 | runtime.GOMAXPROCS(runtime.NumCPU()) 34 | 35 | InitLogger() 36 | config.LoadConfig(log, &cfg) 37 | checkExecutables() 38 | 39 | err := createStorage() 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | func main() { 46 | var err error 47 | err, db = db.OpenDB(DbConfig{cfg.DB.HOST, cfg.DB.NAME, cfg.DB.USER, cfg.DB.PASS, cfg.DB.DEBUG}) 48 | if db == nil { 49 | log.Fatal("Error, cannot connect to db, db.OpenDB ", err) 50 | } 51 | defer db.CloseDB() 52 | 53 | janitor.Init(db.conn, ROOT_PATH+"storage", ROOT_PATH+"storage/datastore/", ROOT_PATH+"storage/.upload/", log) 54 | 55 | webPort := flag.String("web", "", "Start web server on given port") 56 | flag.Parse() 57 | 58 | if *webPort != "" { 59 | webServer(db, *webPort) 60 | return 61 | } 62 | 63 | log.Infoln("Non server mode active") 64 | 65 | // ------------------------------------------------------- 66 | // beware, hard testing bewlow 67 | // ------------------------------------------------------- 68 | 69 | // mt := mediatools.NewMediaInfo(log) 70 | // _ = mt 71 | 72 | // testiranje mediainfo toola 73 | // minfo, err := mt.GetMediaInfo("_test/master_1080.mp4") 74 | // if err != nil { 75 | // log.Fatal(err) 76 | // } 77 | // log.Infof("%#v", minfo) 78 | 79 | // testiranje checkera 80 | // ok, minfob, res, err := mt.CheckMedia("_test/master_1080.mp4") // "test.psd" "r2w_1080p.mov" _test/master_1080.mp4" "videq_sw.mp4" 81 | // if err != nil { 82 | // log.Error(err) 83 | // } 84 | // log.Infof("%#v", ok) 85 | // log.Infof("%#v", minfob) 86 | // log.Infof("%#v", res) 87 | // log.Infof("%#v", err) 88 | 89 | //err = mt.EncodeVideoFile("_test/", "videq_sw.mp4") // "master_1080.mp4" "r2w_1080p.mov" 90 | 91 | } 92 | 93 | func webServer(db *Database, port string) { 94 | staticHandler := static.NewHandler(ROOT_PATH) 95 | gzipStaticHandler := gzip.NewHandler(staticHandler) 96 | http.Handle("/resources/", gzipStaticHandler) 97 | 98 | homeHandler := home.NewHandler(ROOT_PATH) 99 | homeSidHandler := session.NewHandler(log, db.conn, homeHandler) 100 | http.Handle("/", homeSidHandler) 101 | 102 | uploadHandler := upload.NewHandler(log, ROOT_PATH, NUM_OF_MERGE_BUFFER, NUM_OF_MERGE_WORKERS) 103 | http.Handle("/upload/", uploadHandler) 104 | 105 | checkHandler := check.NewHandler(log, ROOT_PATH) 106 | http.Handle("/check/", checkHandler) 107 | 108 | doneHandler := done.NewHandler(log, ROOT_PATH, db.conn) 109 | http.Handle("/done/", doneHandler) 110 | 111 | downloadHandler := download.NewHandler(log, ROOT_PATH) 112 | http.Handle("/download/", downloadHandler) 113 | 114 | restartHandler := restart.NewHandler(log) 115 | http.Handle("/restart/", restartHandler) 116 | 117 | freeHandler := free.NewHandler(log, ROOT_PATH) 118 | http.Handle("/free/", freeHandler) 119 | 120 | log.Infof("Server started on port %v", port) 121 | log.Info(http.ListenAndServe(":"+port, nil)) 122 | 123 | } 124 | 125 | func createStorage() error { 126 | paths := []string{ 127 | ROOT_PATH + "storage/datastore", 128 | ROOT_PATH + "storage/.upload"} 129 | 130 | for _, path := range paths { 131 | err := os.MkdirAll(path, 02750) 132 | if err != nil { 133 | return err 134 | } 135 | } 136 | 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /resources/js/custom.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Video Background 5 | */ 6 | var Video = { 7 | 8 | 9 | init : function () { 10 | 11 | var root = $('body.home'), 12 | vid_cont = $('#intro_vid', root); 13 | 14 | if( vid_cont.length > 0 && !Modernizr.touch && Modernizr.mq('only screen and (min-width: 781px)') ) { 15 | 16 | 17 | var html = '' + 18 | '' + 19 | '' + 20 | '' + 21 | ''; 22 | 23 | vid_cont.prepend(html); 24 | } 25 | 26 | } 27 | 28 | }; 29 | 30 | /** 31 | * Screeen switcher 32 | */ 33 | var Screen = { 34 | 35 | active : false, 36 | first: 'screen-drop-zone', 37 | 38 | init : function () { 39 | 40 | var showFirst = true; 41 | 42 | var cookie_screen = Screen.getScreentFromCookie(); 43 | if(cookie_screen!==undefined && cookie_screen!="") 44 | { 45 | 46 | if(cookie_screen=="screen-progress-bar") 47 | { 48 | showFirst = false; 49 | Screen.show(cookie_screen); 50 | } 51 | 52 | if(cookie_screen=="screen-converting-bar") 53 | { 54 | showFirst = false; 55 | UploadLogic.isDone(); 56 | Screen.show(cookie_screen); 57 | } 58 | 59 | if(cookie_screen=="screen-download-bar") 60 | { 61 | showFirst = false; 62 | Screen.show(cookie_screen); 63 | UploadLogic.atDone(); 64 | } 65 | 66 | if(showFirst) Screen.show(Screen.first); 67 | 68 | return; 69 | } 70 | 71 | $(document).trigger("freespace"); 72 | }, 73 | 74 | show : function (name) { 75 | 76 | if($(".screen." + name ).length>0) 77 | { 78 | this.active = name; 79 | 80 | Screen.addScreenCookie(this.active); 81 | 82 | $(".screen").hide(); 83 | $(".screen." + name ).show(); 84 | } 85 | 86 | }, 87 | 88 | 89 | addScreenCookie : function (screenName) { 90 | 91 | Screen.removeScreenCookie(); 92 | 93 | $.cookie('screen', screenName, { expires: 7, path: '/' }); 94 | }, 95 | 96 | 97 | removeScreenCookie : function () { 98 | $.cookie('screen', '', { expires: 7, path: '/' }); 99 | }, 100 | 101 | getScreentFromCookie : function () { 102 | var cookieStorage = $.cookie('screen'); 103 | 104 | if (cookieStorage == undefined || cookieStorage == ""){ 105 | return false; 106 | } 107 | 108 | return cookieStorage; 109 | } 110 | 111 | 112 | }; 113 | 114 | 115 | /** 116 | * Flash messages 117 | */ 118 | var Msg = { 119 | 120 | init : function () { 121 | this.hide(); 122 | }, 123 | 124 | hide : function () { 125 | $(".flashmsg div").hide(); 126 | }, 127 | 128 | show : function () { 129 | $(".flashmsg div").show(); 130 | }, 131 | 132 | success : function (msg) { 133 | this.hide(); 134 | $(".flashmsg .imoon").addClass("icon-circle-check"); 135 | $(".flashmsg div").attr("class", "").addClass("success"); 136 | $(".flashmsg div p").html(msg); 137 | this.show(); 138 | }, 139 | 140 | error : function (msg) { 141 | this.hide(); 142 | $(".flashmsg .imoon").addClass("icon-notification"); 143 | $(".flashmsg div").attr("class", "").addClass("error"); 144 | $(".flashmsg div p").html(msg); 145 | this.show(); 146 | }, 147 | 148 | info : function (msg) { 149 | this.hide(); 150 | $(".flashmsg .imoon").addClass("icon-info"); 151 | $(".flashmsg div").attr("class", "").addClass("info"); 152 | $(".flashmsg div p").html(msg); 153 | this.show(); 154 | }, 155 | 156 | warning : function (msg) { 157 | this.hide(); 158 | $(".flashmsg .imoon").addClass("icon-warning"); 159 | $(".flashmsg div").attr("class", "").addClass("warning"); 160 | $(".flashmsg div p").html(msg); 161 | this.show(); 162 | } 163 | 164 | }; 165 | 166 | 167 | 168 | $(function(){ 169 | 170 | 171 | 172 | 173 | 174 | var notCompletedFiles = getFilesListFromCookie(); 175 | if (notCompletedFiles != false) { 176 | 177 | var fileString = ""; 178 | 179 | $.each(notCompletedFiles, function(ix, fileName){ 180 | 181 | fileString += fileName; 182 | }); 183 | 184 | 185 | Msg.warning("Drag and drop following files and click upload to resume them: " + fileString); 186 | } 187 | 188 | $("#drop-zone").hover(function(){ 189 | 190 | $(".gopher").toggleClass("over"); 191 | 192 | }); 193 | 194 | 195 | $(".bswitch").bootstrapSwitch(); 196 | 197 | $(".trigger_new_session").on("click", function(e){ 198 | 199 | e.preventDefault(); 200 | Screen.removeScreenCookie(); 201 | $.cookie('files', '', { expires: 7, path: '/' }); 202 | 203 | location.href = ''; 204 | 205 | $.ajax({ 206 | url: "/restart/", 207 | dataType: "json" 208 | }).done(function(data){ 209 | 210 | 211 | }); 212 | 213 | 214 | }); 215 | 216 | $( document ).on( "freespace", {}, function(e) { 217 | 218 | Screen.show(Screen.first); 219 | 220 | $.ajax({ 221 | url: "/free/", 222 | dataType: "json" 223 | }).done(function(data){ 224 | 225 | if(data.Procede!=undefined && data.Procede===false) 226 | { 227 | Screen.show('screen-error-bar'); 228 | Msg.error("There was an system error. Please try again later."); 229 | } 230 | 231 | 232 | }); 233 | 234 | }); 235 | 236 | 237 | Video.init(); 238 | Screen.init(); 239 | Msg.init(); 240 | 241 | 242 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | # videq 4 | High quality video encoding for modern web in golang 5 | 6 | **Project links and final notes** 7 | * compo submission page http://gopher-gala.challengepost.com/submissions/31979-videq 8 | * repo https://github.com/gophergala/videq/ 9 | * demo http://videq.nivas.hr:8080/ 10 | - limitations 11 | - demo is limited to max 5 min videos and 50 gb of user uploaded data 12 | - each user can upload only 1 video, after it is encoded or aborted, user can upload new one 13 | - every hour clenaup will delete old videos 14 | 15 | ## Inspiration 16 | 17 | We work in digital agency and we build a lot of websites that use videos (full screen, background, interviews, product showcase etc.). 18 | Developing compatible cross platform/browser website is a technical nightmare due to thrilling mix of “historical” and "future" standards. In relation to video playback - that means if we want a video on website to be playable on most platforms we need ad least 3 different video formats: 19 | - H.264+AAC+MP4 - Safari, Chrome, Firefox (new versions), IE 20 | - Theora+Vorbis+Ogg – Firefox 21 | - WebM - Chrome, IE 22 | 23 | Each format has its own limitations and settings that must be applied in order to get most compatible and excellent looking output, using free converters available to general public. 24 | 25 | ## The problem(s) 26 | In order to convert one video to specified output formats you need different multidisciplinary knowledge, different desktop software, time and patience. When you are building something under deadline, there is no time and patience, so we wanted it to make it simple for anybody to use (but it’s mostly for web developers). 27 | 28 | Uploads of big files via web browser have always been one of big issues on the web. Hopefully we managed to solve ti by breaking the file in chunks and handling upload of each chunk. 29 | 30 | ## Solution 31 | An UX friendly, easy to use website/webapp for re-coding video files to „safe for web“ video formats. User drags videos, waits, downloads converted videos+html+fallback static image/anim gif. 32 | Added bonus - it can easily be installed locally for team usage (to save time on uploading to internet server) 33 | 34 | ## How it works 35 | User drags file to browser (or selects it), and the file is broken into chunks. We start uploading first chunk and right after first chunk is uploaded to the server, server checks if this is a video file suitable for encoding (is it a video file, is it too big, too long etc). If everything is ok we proceeds with upload and encoding. 36 | If upload breaks or stops at some point, user can drag file again and resume. It will be uploaded from last uploaded chunk. 37 | Video encoding is done in queue with help of go workers. First we double pass encode mp4 file which is then base for ogg and webm files. After all files are created, we output it to the user. 38 | 39 | **How to use** 40 | * Download and compile 41 | go get -u -v https://github.com/gophergala/videq 42 | * create config file 43 | File has to be present in ./conf/ folder and named eg: ubuntu.config.ini, where ubuntu is the name of server on which it is being run. you get it with uname -n 44 | ``` 45 | [http] 46 | HOSTNAME = "localhost" 47 | LISTENADDRESS = ":8080" 48 | 49 | [db] 50 | HOST="localhost" 51 | NAME="videq" 52 | USER="root" 53 | PASS="XXX" 54 | DEBUG=false 55 | ``` 56 | 57 | * create and import empty database 58 | ``` 59 | mysql -u root -p < sql/create_db.sql 60 | mysql -u root -p videq < sql/empty_db.sql 61 | ``` 62 | * run by starting ./start_videq.sh 63 | * goto http://localhost:8080/ 64 | 65 | 66 | **Prerequisites** 67 | * server running Ubuntu 14.04 LTS - project was developed on 14.04, possible it could work on anything but we did not have time to test it 68 | * preinstalled server side applications: 69 | * golang (tested with 1.4.1), 70 | * mysql (tested on 5.5.40-0ubuntu0.14.04.1 (Ubuntu)), 71 | * ffmpeg (tested on 1.2.6-7:1.2.6-1~trusty1), 72 | * HandBrakeCLI (0.10.0) 73 | * ffmpeg2theora (0.29) 74 | 75 | **Tools used in this project** 76 | * compiler http://golang.org 77 | * ide http://www.sublimetext.com/3 78 | * ffmpeg http://ffmpeg.org/ 79 | * handbrake https://handbrake.fr/ 80 | * ffmpeg2theora http://v2v.cc/~j/ffmpeg2theora/ 81 | 82 | ## Challenges we ran into 83 | - Big file upload 84 | - Agile golang development 85 | - Development of go app on Windows which uses Unix only server side stuff. 86 | 87 | ## Accomplishments that we are proud of 88 | We managed to get MVP in just two days. :) 89 | 90 | ## What we learned 91 | We are new in golang and we used this hackaton to learn more about: 92 | - chunked uploads handling (for extreme large files) 93 | - team workflow on short deadline golang projects 94 | 95 | 96 | ## What's next for videq 97 | - more encoding options for quality 98 | - graceful restart 99 | - scalable (multi server workers) … if we manage in that short time we have… 100 | - move all configurable params to config 101 | - cleanup 102 | - send email when encoding is done 103 | - ... 104 | 105 | ## The Team 106 | Big shout to fine http://nivas.hr Videq team members: 107 | - https://github.com/guycalledseven backend 108 | - https://github.com/MatejB backend 109 | - https://github.com/luzel frontend 110 | - https://github.com/alencvitkovic design/ux -------------------------------------------------------------------------------- /janitor/janitor.go: -------------------------------------------------------------------------------- 1 | package janitor 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | alog "github.com/cenkalti/log" 17 | "github.com/gophergala/videq/mediatools" 18 | ) 19 | 20 | var Storage string 21 | var StorageIncomplete string 22 | var StorageComplete string 23 | var DbConn *sql.DB 24 | var log alog.Logger 25 | 26 | var cleanUploadFolderCh chan string 27 | var encodePathCh chan string 28 | var allowedToUlCh chan bool 29 | 30 | func Init(db *sql.DB, s, sc, si string, l alog.Logger) { 31 | DbConn = db 32 | Storage = s 33 | StorageComplete = sc 34 | StorageIncomplete = si 35 | log = l 36 | 37 | cleanUploadFolderCh = make(chan string, 100) 38 | for i := 0; i < 10; i++ { 39 | go cleanupIncompleteFolderWorker(cleanUploadFolderCh) 40 | } 41 | 42 | encodePathCh = make(chan string, 1000000) 43 | for i := 0; i < 3; i++ { 44 | go encodeWorker(encodePathCh) 45 | } 46 | 47 | allowedToUlCh = make(chan bool) 48 | go freeSpaceCheckWorker() 49 | } 50 | 51 | func CleanupUser(sid string) error { 52 | os.RemoveAll(StorageComplete + sid) 53 | os.RemoveAll(StorageIncomplete + sid) 54 | 55 | _, err := DbConn.Exec("DELETE FROM file WHERE sid=?", sid) 56 | if err != nil { 57 | log.Error(err) 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func IsAllowedToUpload() bool { 65 | return <-allowedToUlCh 66 | } 67 | 68 | // check if current user is uploading a file? 69 | func HasFileInUpload(sid string) (bool, error) { 70 | firstPartFilename := StorageIncomplete + sid + "/1" 71 | 72 | _, err := os.Stat(firstPartFilename) 73 | if os.IsNotExist(err) { 74 | return false, nil 75 | } 76 | if err != nil { 77 | return false, err 78 | } 79 | 80 | return true, nil 81 | } 82 | 83 | func RecordFilename(sid, filename string) error { 84 | _, err := DbConn.Exec("REPLACE INTO file (sid, filename, start_ts) VALUES (?, ?, UNIX_TIMESTAMP())", sid, filename) 85 | if err != nil { 86 | return err 87 | } 88 | return nil 89 | } 90 | 91 | func PossibleToEncode(sid string) (bool, mediatools.MediaFileInfo, map[string]mediatools.VideoResolution, error) { 92 | mt := mediatools.NewMediaInfo(log) 93 | 94 | userFolder := StorageIncomplete + sid 95 | 96 | ok, minfob, resolutions, err := mt.CheckMedia(userFolder + "/1") 97 | if err != nil { 98 | log.Error(err) 99 | cleanUploadFolderCh <- userFolder 100 | return false, minfob, resolutions, err 101 | } 102 | 103 | return ok, minfob, resolutions, nil 104 | } 105 | 106 | func PushToEncode(path string) { 107 | sid := strings.Split(path, "/")[2] // todo - make batter 108 | 109 | _, err := DbConn.Exec("UPDATE file SET path_of_original=?, added_to_encode_queue_ts=UNIX_TIMESTAMP() WHERE sid=?", path, sid) 110 | if err != nil { 111 | log.Error(err) 112 | // todo whole cleanup 113 | return 114 | } 115 | 116 | encodePathCh <- path 117 | } 118 | 119 | func encodeWorker(pathCh <-chan string) { 120 | for path := range pathCh { 121 | sid := strings.Split(path, "/")[2] // todo - make batter 122 | 123 | _, err := DbConn.Exec("UPDATE file SET encode_start_ts=UNIX_TIMESTAMP() WHERE sid=?", sid) 124 | if err != nil { 125 | log.Error(err) 126 | // todo whole cleanup 127 | continue 128 | } 129 | 130 | pathSpl := strings.Split(path, "/") 131 | filePath := "./" + strings.Join(pathSpl[0:len(pathSpl)-1], "/") + "/" 132 | fileName := pathSpl[len(pathSpl)-1] 133 | log.Debugln(path, filePath, fileName) 134 | 135 | mt := mediatools.NewMediaInfo(log) 136 | err = mt.EncodeVideoFile(filePath, fileName) 137 | if err != nil { 138 | log.Error(err) 139 | } 140 | 141 | encodeEnded(sid, err) 142 | } 143 | } 144 | 145 | func encodeEnded(sid string, err error) { 146 | errorTxt := "" 147 | success := 1 148 | 149 | if err != nil { 150 | errorTxt = err.Error() 151 | success = 0 152 | } 153 | 154 | _, err = DbConn.Exec("UPDATE file SET encode_end_ts=UNIX_TIMESTAMP(), encode_error=?, success=? WHERE sid=?", errorTxt, success, sid) 155 | if err != nil { 156 | log.Error(err) 157 | return 158 | } 159 | } 160 | 161 | func cleanupIncompleteFolderWorker(pathCh <-chan string) { 162 | for path := range pathCh { 163 | os.RemoveAll(path) 164 | } 165 | } 166 | 167 | func freeSpaceCheckWorker() { 168 | isFree := true 169 | 170 | ct := time.Tick(2 * time.Minute) 171 | 172 | for { 173 | select { 174 | case allowedToUlCh <- isFree: 175 | break 176 | case <-ct: 177 | cmd := exec.Command("du", "-bcs", Storage) 178 | 179 | var out bytes.Buffer 180 | cmd.Stdout = &out 181 | 182 | var stderr bytes.Buffer 183 | cmd.Stderr = &stderr 184 | 185 | cmd.Run() 186 | 187 | readerFromOut := bytes.NewReader(out.Bytes()) 188 | readerFromErr := bytes.NewReader(stderr.Bytes()) 189 | 190 | rM := io.MultiReader(readerFromOut, readerFromErr) 191 | 192 | commandOutputComplete, err := ioutil.ReadAll(rM) 193 | if err != nil { 194 | log.Error(err) 195 | break 196 | } 197 | 198 | sizeInB, err := extractSizeFromString(string(commandOutputComplete)) 199 | if err != nil { 200 | log.Error(err) 201 | break 202 | } 203 | 204 | isFree = true 205 | if sizeInB > 50000000000 { 206 | isFree = false 207 | } 208 | break 209 | } 210 | } 211 | } 212 | 213 | func extractSizeFromString(source string) (int64, error) { 214 | re := regexp.MustCompile("^([0-9]+)") 215 | 216 | submatches := re.FindStringSubmatch(source) 217 | if len(submatches) < 2 { 218 | return 0, errors.New("Size not found") 219 | } 220 | 221 | i, err := strconv.ParseInt(submatches[1], 10, 64) 222 | if err != nil { 223 | return 0, err 224 | } 225 | 226 | return i, nil 227 | } 228 | -------------------------------------------------------------------------------- /resources/icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /handlers/upload/upload.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "path" 8 | // "log" 9 | "net/http" 10 | "os" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | 15 | alog "github.com/cenkalti/log" 16 | "github.com/gophergala/videq/handlers/session" 17 | "github.com/gophergala/videq/janitor" 18 | ) 19 | 20 | type FileToAssemble struct { 21 | PathToParts string 22 | OriginalFilename string 23 | } 24 | 25 | type Handler struct { 26 | rootPath string 27 | numOfCompleteFileChBuffer int 28 | numOfCompleteFileWorkers int 29 | completedFilesCh chan *FileToAssemble 30 | log alog.Logger 31 | } 32 | 33 | func NewHandler(log alog.Logger, rootPath string, numOfCompleteFileChBuffer int, numOfCompleteFileWorkers int) *Handler { 34 | h := new(Handler) 35 | h.log = log 36 | h.rootPath = rootPath 37 | h.numOfCompleteFileChBuffer = numOfCompleteFileChBuffer 38 | h.numOfCompleteFileWorkers = numOfCompleteFileWorkers 39 | 40 | h.completedFilesCh = make(chan *FileToAssemble, h.numOfCompleteFileChBuffer) 41 | 42 | for i := 0; i < h.numOfCompleteFileWorkers; i++ { 43 | go assembleFile(h) 44 | } 45 | 46 | return h 47 | } 48 | 49 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 50 | isAllowedToUpload := janitor.IsAllowedToUpload() 51 | if isAllowedToUpload == false { 52 | http.Error(w, "No upload allowed at this time", http.StatusForbidden) 53 | return 54 | } 55 | 56 | if r.Method == "POST" { 57 | h.streamUpload(w, r) 58 | } else if r.Method == "GET" { 59 | h.checkPart(w, r) 60 | } else { 61 | http.Error(w, "Method not suported", http.StatusMethodNotAllowed) 62 | } 63 | } 64 | 65 | func (h *Handler) checkPart(w http.ResponseWriter, r *http.Request) { 66 | sid, err := session.Sid(r) 67 | if err != nil { 68 | h.log.Error(err) 69 | http.Error(w, "Internal server error", http.StatusInternalServerError) 70 | return 71 | } 72 | 73 | chunkDirPath := h.rootPath + "storage/.upload/" + sid + "/" + r.FormValue("flowChunkNumber") 74 | 75 | stat, err := os.Stat(chunkDirPath) 76 | if err != nil { 77 | h.log.Debug(err) 78 | http.Error(w, "Internal server error", http.StatusNoContent) 79 | return 80 | } 81 | 82 | expectedChunkSize, err := strconv.Atoi(r.URL.Query().Get("flowCurrentChunkSize")) 83 | if err != nil { 84 | h.log.Debug(err) 85 | http.Error(w, "Internal server error", http.StatusBadRequest) 86 | return 87 | } 88 | if int(stat.Size()) != expectedChunkSize { 89 | h.log.Debug(err) 90 | http.Error(w, "Chunk size check failed", http.StatusPartialContent) 91 | return 92 | } 93 | } 94 | 95 | func (h *Handler) streamUpload(w http.ResponseWriter, r *http.Request) { 96 | sid, err := session.Sid(r) 97 | if err != nil { 98 | h.log.Error(err) 99 | http.Error(w, "Internal server error", http.StatusInternalServerError) 100 | return 101 | } 102 | 103 | buf := new(bytes.Buffer) 104 | reader, err := r.MultipartReader() 105 | // Part 1: Chunk Number 106 | // Part 4: Total Size (bytes) 107 | // Part 6: File Name 108 | // Part 8: Total Chunks 109 | // Part 9: Chunk Data 110 | if err != nil { 111 | h.log.Error(err) 112 | http.Error(w, "Internal server error", http.StatusInternalServerError) 113 | return 114 | } 115 | 116 | part, err := reader.NextPart() // 1 117 | if err != nil { 118 | h.log.Error(err) 119 | http.Error(w, "Internal server error", http.StatusInternalServerError) 120 | return 121 | } 122 | io.Copy(buf, part) 123 | chunkNo := buf.String() 124 | buf.Reset() 125 | 126 | for i := 0; i < 3; i++ { // 2 3 4 127 | // move through unused parts 128 | part, err = reader.NextPart() 129 | if err != nil { 130 | h.log.Error(err) 131 | http.Error(w, "Internal server error", http.StatusInternalServerError) 132 | return 133 | } 134 | } 135 | 136 | io.Copy(buf, part) 137 | flowTotalSize := buf.String() 138 | buf.Reset() 139 | 140 | for i := 0; i < 2; i++ { // 5 6 141 | // move through unused parts 142 | part, err = reader.NextPart() 143 | if err != nil { 144 | h.log.Error(err) 145 | http.Error(w, "Internal server error", http.StatusInternalServerError) 146 | return 147 | } 148 | } 149 | 150 | io.Copy(buf, part) 151 | fileName := buf.String() 152 | buf.Reset() 153 | 154 | if chunkNo == "1" { 155 | err = janitor.RecordFilename(sid, fileName) 156 | if err != nil { 157 | h.log.Error(err) 158 | http.Error(w, "Internal server error", http.StatusInternalServerError) 159 | return 160 | } 161 | } 162 | 163 | for i := 0; i < 3; i++ { // 7 8 9 164 | // move through unused parts 165 | part, err = reader.NextPart() 166 | if err != nil { 167 | h.log.Error(err) 168 | http.Error(w, "Internal server error", http.StatusInternalServerError) 169 | return 170 | } 171 | } 172 | 173 | chunkDirPath := h.rootPath + "/storage/.upload/" + sid 174 | err = os.MkdirAll(chunkDirPath, 02750) 175 | if err != nil { 176 | h.log.Error(err) 177 | http.Error(w, "Internal server error", http.StatusInternalServerError) 178 | return 179 | } 180 | 181 | dst, err := os.Create(chunkDirPath + "/" + chunkNo) 182 | if err != nil { 183 | h.log.Error(err) 184 | http.Error(w, "Internal server error", http.StatusInternalServerError) 185 | return 186 | } 187 | defer dst.Close() 188 | io.Copy(dst, part) 189 | 190 | fileInfos, err := ioutil.ReadDir(chunkDirPath) 191 | if err != nil { 192 | h.log.Error(err) 193 | http.Error(w, "Internal server error", http.StatusInternalServerError) 194 | return 195 | } 196 | 197 | currentSize := totalSize(fileInfos) 198 | flowTotalSizeInt64, err := strconv.ParseInt(flowTotalSize, 10, 64) 199 | if err != nil { 200 | h.log.Error(err) 201 | http.Error(w, "Internal server error", http.StatusInternalServerError) 202 | return 203 | } 204 | 205 | if flowTotalSizeInt64 == currentSize { 206 | fta := &FileToAssemble{chunkDirPath, fileName} 207 | h.completedFilesCh <- fta 208 | } 209 | } 210 | 211 | func totalSize(fileInfos []os.FileInfo) int64 { 212 | var sum int64 213 | for _, fi := range fileInfos { 214 | sum += fi.Size() 215 | } 216 | return sum 217 | } 218 | 219 | type ByChunk []os.FileInfo 220 | 221 | func (a ByChunk) Len() int { return len(a) } 222 | func (a ByChunk) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 223 | func (a ByChunk) Less(i, j int) bool { 224 | ai, _ := strconv.Atoi(a[i].Name()) 225 | aj, _ := strconv.Atoi(a[j].Name()) 226 | return ai < aj 227 | } 228 | 229 | func assembleFile(h *Handler) { 230 | for fta := range h.completedFilesCh { 231 | 232 | fileInfos, err := ioutil.ReadDir(fta.PathToParts) 233 | if err != nil { 234 | h.log.Error(err) 235 | return 236 | } 237 | 238 | sid := strings.Split(fta.PathToParts, "/")[4] 239 | 240 | targetDirPath := h.rootPath + "/storage/datastore/" + sid 241 | err = os.MkdirAll(targetDirPath, 02750) 242 | if err != nil { 243 | h.log.Error(err) 244 | return 245 | } 246 | 247 | partFilename := strings.Split(fta.OriginalFilename, ".") 248 | 249 | assabledFilePath := targetDirPath + "/original." + partFilename[len(partFilename)-1] 250 | 251 | // create final file to write to 252 | dst, err := os.Create(assabledFilePath) 253 | if err != nil { 254 | h.log.Error(err) 255 | return 256 | } 257 | defer dst.Close() 258 | 259 | sort.Sort(ByChunk(fileInfos)) 260 | for _, fs := range fileInfos { 261 | func() { 262 | src, err := os.Open(fta.PathToParts + "/" + fs.Name()) 263 | if err != nil { 264 | h.log.Error(err) 265 | return 266 | } 267 | defer src.Close() 268 | io.Copy(dst, src) 269 | }() 270 | } 271 | os.RemoveAll(fta.PathToParts) 272 | 273 | assabledFilePath = path.Clean(assabledFilePath) 274 | 275 | janitor.PushToEncode(assabledFilePath) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /mediatools/encoder.go: -------------------------------------------------------------------------------- 1 | package mediatools 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/codeskyblue/go-sh" 7 | ) 8 | 9 | func (m *MediaInfo) EncodeVideoFile(fileLoc string, fileName string) (err error) { 10 | file := fileLoc + fileName 11 | m.log.Infoln(file) 12 | 13 | outFileMP4, err := m.encodeMP4(fileLoc, fileName) 14 | if err != nil { 15 | // ako je mp4 puko kod encoding, ne mozemo nastaviti jer svi ostali zele taj file kao source 16 | return err 17 | } 18 | outFileOGG, err := m.encodeOGG(fileLoc, outFileMP4) 19 | outFileWEBM, err := m.encodeWEBM(fileLoc, outFileMP4) 20 | 21 | outFileJPG, err := m.generateThumbnailJPG(fileLoc, outFileMP4) 22 | 23 | m.log.Debug("Created: ", outFileMP4, outFileOGG, outFileWEBM, outFileJPG) 24 | return nil 25 | } 26 | 27 | // 28 | // HandBrakeCLI -i _test/master_1080.mp4 -o _test/out/master_1080_2.mp4 -e x264 -q 22 -r 15 -B 64 -X 480 -O -x level=4.0:ref=9:bframes=16:b-adapt=2:direct=auto:analyse=all:8x8dct=0:me=tesa:merange=24:subme=11:trellis=2:fast-pskip=0:vbv-bufsize=25000:vbv-maxrate=20000:rc-lookahead=60 29 | // http://thanhsiang.org/faqing/node/196 30 | /* 31 | alternativno UMEJESTO handbrakea 32 | These are the options i used to convert to a H.264/AAC .mp4 format for html5 playback (i think it may help out for other guys with this this problem in some way): 33 | ffmpeg -i input.flv -vcodec mpeg4 -acodec aac output.mp4 34 | */ 35 | func (m *MediaInfo) encodeMP4(fileLoc string, fileName string) (fileNameOut string, err error) { 36 | fileSource := fileLoc + fileName 37 | //fileNameOut = m.returnBaseFilename(fileName) + ".mp4" 38 | fileNameOut = "encoded.mp4" 39 | fileDestination := fileLoc + fileNameOut 40 | 41 | maxWidth := "1280" 42 | extraParams := `level=4.0:ref=9:bframes=16:b-adapt=2:direct=auto:analyse=all:8x8dct=0:me=tesa:merange=24:subme=11:trellis=2:fast-pskip=0:vbv-bufsize=25000:vbv-maxrate=20000:rc-lookahead=60` 43 | 44 | // test fast preset 45 | // out, err := sh.Command("HandBrakeCLI", "-i", fileSource, "-o", fileDestination, "-e", "x264", "-q", "22", "-r", "15", "-B", "64", "-X", maxWidth, "-O", "-x", extraParams).Output() 46 | 47 | // alen's web master preset 48 | out, err := sh.Command("HandBrakeCLI", "-i", fileSource, "-o", fileDestination, 49 | "-e", "x264", "-b", "500", "-B", "64", "-X", maxWidth, "--keep-display-aspect", "--two-pass", "-O", "-x", extraParams).Output() 50 | if err == sh.ErrExecTimeout { 51 | m.log.Errorf("shell exec timeouteded.", err) 52 | } 53 | if err != nil { 54 | m.log.Errorf("sh.Command error:", err) 55 | return "", err 56 | } 57 | 58 | m.log.Debugf("output:(%s), err(%v)\n", string(out), err) 59 | 60 | ok, err := m.checkIfFileExists(fileDestination) 61 | if err != nil { 62 | return "", err 63 | } 64 | if ok == false { 65 | return "", errors.New(fmt.Sprintf("File '%s' does not exists. Encoding failed?", fileDestination)) 66 | } 67 | 68 | return fileNameOut, nil 69 | 70 | } 71 | 72 | // 73 | // ffmpeg2theora Master_1080.mp4 --two pass --videobitrate 900 -x 1280 -y 720 74 | // 75 | func (m *MediaInfo) encodeOGG(fileLoc string, fileName string) (fileNameOut string, err error) { 76 | fileSource := fileLoc + fileName 77 | // fileNameOut = m.returnBaseFilename(fileName) + ".ogg" 78 | fileNameOut = "encoded.ogg" 79 | fileDestination := fileLoc + fileNameOut 80 | 81 | maxWidth := "1280" 82 | maxHeight := "720" 83 | 84 | out, err := sh.Command("ffmpeg2theora", fileSource, "-o", fileDestination, "--two pass", "--videobitrate", "900", "-x", maxWidth, "-y", maxHeight).Output() 85 | if err == sh.ErrExecTimeout { 86 | m.log.Errorf("shell exec timeouteded.", err) 87 | } 88 | if err != nil { 89 | m.log.Errorf("sh.Command error:", err) 90 | return "", err 91 | } 92 | 93 | //m.log.Debug(out) 94 | m.log.Debugf("output:(%s), err(%v)\n", string(out), err) 95 | ok, err := m.checkIfFileExists(fileDestination) 96 | if err != nil { 97 | return "", err 98 | } 99 | if ok == false { 100 | return "", errors.New(fmt.Sprintf("File '%s' does not exists. Encoding failed?", fileDestination)) 101 | } 102 | 103 | return fileNameOut, nil 104 | } 105 | 106 | /* 107 | ffmpeg -i _test/master_1080.mp4 -pass 1 -passlogfile hattrick.webm -keyint_min 0 -g 250 -skip_threshold 0 -vcodec libvpx -b 600k -s 1280x720 -aspect 16:9 -an -y hattrick.webm 108 | Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used) 109 | 110 | ffmpeg -i _test/master_1080.mp4 -pass 2 -passlogfile hattrick.webm -keyint_min 0 -g 250 -skip_threshold 0 -vcodec libvpx -b 600k -s 1280x720 -aspect 16:9 -acodec libvorbis -y hattrick.webm 111 | */ 112 | 113 | func (m *MediaInfo) encodeWEBM(fileLoc string, fileName string) (fileNameOut string, err error) { 114 | fileSource := fileLoc + fileName 115 | // fileNameOut = m.returnBaseFilename(fileName) + ".webm" 116 | fileNameOut = "encoded.webm" 117 | fileDestination := fileLoc + fileNameOut 118 | 119 | // out, err := sh. 120 | // Command("ffmpeg", "-i", fileSource, "-pass", "1", "-passlogfile", fileDestination, "-keyint_min", "0", "-g", "250", "-skip_threshold", "0", "-vcodec", "libvpx", "-b", "600k", "-s", "1280x720", "-aspect", "16:9", "-an", "-y", fileDestination). 121 | // Command("ffmpeg", "-i", fileSource, "-pass", "2", "-passlogfile", fileDestination, "-keyint_min", "0", "-g", "250", "-skip_threshold", "0", "-vcodec", "libvpx", "-b", "600k", "-s", "1280x720", "-aspect", "16:9", "-acodec", "libvorbis", "-y", fileDestination). 122 | // Run() 123 | out, err := sh. 124 | Command("ffmpeg", "-i", fileSource, "-pass", "1", "-passlogfile", fileDestination, "-keyint_min", "0", "-g", "250", "-skip_threshold", "0", "-vcodec", "libvpx", "-b", "600k", "-s", "1280x720", "-aspect", "16:9", "-an", "-y", fileDestination). 125 | Output() 126 | 127 | if err == sh.ErrExecTimeout { 128 | m.log.Errorf("shell exec timeouteded.", err) 129 | } 130 | if err != nil { 131 | m.log.Errorf("sh.Command error:", err) 132 | return "", err 133 | } 134 | 135 | out, err = sh. 136 | Command("ffmpeg", "-i", fileSource, "-pass", "2", "-passlogfile", fileDestination, "-keyint_min", "0", "-g", "250", "-skip_threshold", "0", "-vcodec", "libvpx", "-b", "600k", "-s", "1280x720", "-aspect", "16:9", "-acodec", "libvorbis", "-y", fileDestination). 137 | Output() 138 | 139 | if err == sh.ErrExecTimeout { 140 | m.log.Errorf("shell exec timeouteded.", err) 141 | } 142 | if err != nil { 143 | m.log.Errorf("sh.Command error:", err) 144 | return "", err 145 | } 146 | 147 | //m.log.Debug(out) 148 | m.log.Debugf("output:(%s), err(%v)\n", string(out), err) 149 | ok, err := m.checkIfFileExists(fileDestination) 150 | if err != nil { 151 | return "", err 152 | } 153 | if ok == false { 154 | return "", errors.New(fmt.Sprintf("File '%s' does not exists. Encoding failed?", fileDestination)) 155 | } 156 | 157 | return fileNameOut, nil 158 | } 159 | 160 | // https://trac.ffmpeg.org/wiki/Create%20a%20thumbnail%20image%20every%20X%20seconds%20of%20the%20video 161 | // ffmpeg -i master_1080.mp4 -t 0.001 -ss 7 -vframes 1 -y -f mjpeg master_test.jpg 162 | // ffmpeg -i input.flv -ss 00:00:14.435 -f image2 -vframes 1 out.png 163 | 164 | func (m *MediaInfo) generateThumbnailJPG(fileLoc string, fileName string) (fileNameOut string, err error) { 165 | fileSource := fileLoc + fileName 166 | // fileNameOut = m.returnBaseFilename(fileName) + ".webm" 167 | fileNameOut = "encoded.jpg" 168 | fileDestination := fileLoc + fileNameOut 169 | 170 | out, err := sh.Command("ffmpeg", "-i", fileSource, "-t", "0.001", "-ss", "7", "-vframes", "1", "-y", "-f", "mjpeg", fileDestination).Output() 171 | if err == sh.ErrExecTimeout { 172 | m.log.Errorf("shell exec timeouteded.", err) 173 | } 174 | if err != nil { 175 | m.log.Errorf("sh.Command error:", err) 176 | return "", err 177 | } 178 | 179 | //m.log.Debug(out) 180 | m.log.Debugf("output:(%s), err(%v)\n", string(out), err) 181 | ok, err := m.checkIfFileExists(fileDestination) 182 | if err != nil { 183 | return "", err 184 | } 185 | if ok == false { 186 | return "", errors.New(fmt.Sprintf("File '%s' does not exists. Encoding failed?", fileDestination)) 187 | } 188 | 189 | return fileNameOut, nil 190 | } 191 | -------------------------------------------------------------------------------- /resources/js/upload.js: -------------------------------------------------------------------------------- 1 | function addToCookie(filename) { 2 | removeFromCookie(filename) 3 | 4 | var cookieStorage = $.cookie('files'); 5 | if (cookieStorage == undefined) { 6 | cookieStorage = ''; 7 | } 8 | 9 | cookieStorage = cookieStorage + filename + '|'; 10 | 11 | $.cookie('files', cookieStorage, { expires: 7, path: '/' }); 12 | } 13 | 14 | function removeFromCookie(filename) { 15 | var cookieStorage = $.cookie('files'); 16 | if (cookieStorage == undefined) { 17 | cookieStorage = ''; 18 | $.cookie('files', cookieStorage, { expires: 7, path: '/' }); 19 | return; 20 | } 21 | 22 | cookieStorage = cookieStorage.replace(filename + '|',""); 23 | 24 | $.cookie('files', cookieStorage, { expires: 7, path: '/' }); 25 | } 26 | 27 | function getFilesListFromCookie() { 28 | var cookieStorage = $.cookie('files'); 29 | if (cookieStorage == undefined || cookieStorage == ""){ 30 | return false; 31 | } 32 | 33 | var parts = cookieStorage.split("|"); 34 | parts.pop(); 35 | 36 | return parts 37 | } 38 | 39 | /** 40 | * Flow JS 41 | */ 42 | var UploadLogic = { 43 | 44 | flow : false, 45 | firstPart: true, 46 | progress: 0, 47 | timer1: false, 48 | 49 | init : function () { 50 | 51 | UploadLogic.flow = new Flow({ 52 | target: '/upload/', 53 | uploadMethod: 'POST', 54 | testChunks: true, 55 | simultaneousUploads: 1, 56 | prioritizeFirstAndLastChunk: true, 57 | progressCallbacksInterval: 0, 58 | singleFile: true 59 | }); 60 | 61 | 62 | if (!UploadLogic.flow.support) { 63 | Msg.error("Browser dose not support modern upload!"); 64 | } 65 | 66 | UploadLogic.bindEvents(); 67 | }, 68 | 69 | 70 | isDone : function () { 71 | 72 | 73 | UploadLogic.timer1 = setInterval(function(){ 74 | 75 | 76 | UploadLogic.atDone(); 77 | 78 | 79 | }, 5000); 80 | 81 | 82 | }, 83 | 84 | atDone : function() { 85 | 86 | $.ajax({ 87 | url: "/done/", 88 | dataType: "json", 89 | }).done(function(data){ 90 | 91 | if(data.Procede===true) 92 | { 93 | clearTimeout( UploadLogic.timer1 ); 94 | 95 | 96 | 97 | var html_code_str = ''; 98 | 99 | 100 | if(data.first_frame_jpg!==undefined) 101 | { 102 | html_code_str += ''; 103 | 104 | $("#jpg_link").attr("href", data.first_frame_jpg); 105 | } 106 | else 107 | { 108 | html_code_str += ''; 109 | } 110 | 111 | if(data.mp4_link!==undefined) 112 | { 113 | $("#mp4_link").attr("href", data.mp4_link); 114 | 115 | html_code_str += ''; 116 | } 117 | 118 | if(data.webm_link!==undefined) 119 | { 120 | $("#webm_link").attr("href", data.webm_link); 121 | 122 | html_code_str += ''; 123 | } 124 | 125 | if(data.ogv_link!==undefined) 126 | { 127 | $("#ogv_link").attr("href", data.ogv_link); 128 | 129 | html_code_str += ''; 130 | } 131 | 132 | html_code_str += ''; 133 | 134 | 135 | $("#html_code").html(html_code_str); 136 | 137 | Screen.show('screen-download-bar'); 138 | 139 | } 140 | 141 | 142 | if(data.Err!==undefined && data.Err!="") 143 | { 144 | clearTimeout( UploadLogic.timer1 ); 145 | Msg.error(data.Err); 146 | Screen.removeScreenCookie(); 147 | } 148 | 149 | }); 150 | 151 | }, 152 | 153 | bindEvents: function () { 154 | 155 | var self = this; 156 | 157 | UploadLogic.flow.assignBrowse(document.getElementById('js-upload-files')); 158 | UploadLogic.flow.assignDrop(document.getElementById('drop-zone')); 159 | 160 | UploadLogic.flow.on('fileAdded', function(file, event){ 161 | addToCookie(file.name); 162 | 163 | setTimeout(function(){ 164 | 165 | UploadLogic.flow.upload(); 166 | Screen.show("screen-error-bar"); 167 | 168 | },1); 169 | 170 | }); 171 | 172 | UploadLogic.flow.on('fileSuccess', function(file,message){ 173 | removeFromCookie(file.name); 174 | $('#fileLog').append('Success' + file.name + ' ' + message + ''); 175 | }); 176 | UploadLogic.flow.on('fileError', function(file, message){ 177 | removeFromCookie(file.name); 178 | $('#fileLog').append('Error' + file.name + ' ' + message + ''); 179 | }); 180 | 181 | UploadLogic.flow.on('fileProgress', function () { 182 | 183 | 184 | if (UploadLogic.flow.progress() > 0) { 185 | UploadLogic.progress = UploadLogic.flow.progress(); 186 | } 187 | 188 | if (UploadLogic.progress == 0) { 189 | //return; 190 | } 191 | 192 | if (UploadLogic.firstPart == true && UploadLogic.progress > 0) { 193 | 194 | 195 | UploadLogic.flow.pause(); 196 | 197 | UploadLogic.firstPart = false; 198 | 199 | 200 | $.ajax({ 201 | url: "/check/", 202 | dataType: "json" 203 | }).done(function(data) { 204 | 205 | 206 | if(data.Procede===true) 207 | { 208 | Screen.show("screen-setup-bar"); 209 | 210 | var videoInfo = "Video info: "; 211 | if(data.OriginalInfo.Duration_string!==undefined) videoInfo += " Duration: " + data.OriginalInfo.Duration_string; 212 | if(data.OriginalInfo.Resolution!==undefined) videoInfo += " | Resolution: " + data.OriginalInfo.Resolution; 213 | if(data.OriginalInfo.Framerate!==undefined) videoInfo += " | Framerate: " + data.OriginalInfo.Framerate + " fps"; 214 | if(data.OriginalInfo.AspectRatio!==undefined) videoInfo += " | AspectRatio: " + data.OriginalInfo.AspectRatio; 215 | if(data.OriginalInfo.Bitrate_bps!==undefined) videoInfo += " | Bitrate: " + data.OriginalInfo.Bitrate_bps + " bps"; 216 | videoInfo += ""; 217 | 218 | Msg.info(videoInfo); 219 | } 220 | else 221 | { 222 | 223 | Screen.show("screen-error-bar"); 224 | Msg.error(data.Err); 225 | } 226 | 227 | }); 228 | } 229 | else if (UploadLogic.progress > 0) 230 | { 231 | 232 | UploadLogic.progress = UploadLogic.flow.progress() * 100; 233 | 234 | if ( UploadLogic.progress > 0 && UploadLogic.progress < 100 ) 235 | { 236 | $('.progress-bar').css('width', Math.round(UploadLogic.progress) + '%'); 237 | } 238 | else 239 | { 240 | $('.progress-bar').css('width', '100%'); 241 | } 242 | 243 | } 244 | }); 245 | 246 | UploadLogic.flow.on('complete', function(){ 247 | 248 | Screen.show('screen-converting-bar'); 249 | 250 | UploadLogic.isDone(); 251 | 252 | }); 253 | 254 | 255 | UploadLogic.flow.on('uploadStart', function(){ 256 | 257 | if(Screen.active=="screen-drop-zone") 258 | { 259 | Screen.show("screen-check-bar"); 260 | } 261 | else 262 | { 263 | Screen.show("screen-progress-bar"); 264 | } 265 | 266 | }); 267 | 268 | 269 | $('.trigger-browse-files').on("click", function(e){ 270 | e.preventDefault(); 271 | $('input[type=file]').click(); 272 | return false; 273 | }); 274 | 275 | $("#drop-zone").on("dragover", function(){ 276 | $(this).addClass("over"); 277 | }); 278 | 279 | $("#drop-zone").on("dragleave", function(){ 280 | $(this).removeClass("over"); 281 | }); 282 | 283 | $('#js-upload-form').on("submit", function(ev){ 284 | ev.preventDefault(); 285 | 286 | setTimeout(function(){ 287 | UploadLogic.flow.upload(); 288 | },1); 289 | }); 290 | 291 | $("form#output_data").on("submit", function(e){ 292 | 293 | e.preventDefault(); 294 | 295 | 296 | setTimeout(function(){ 297 | 298 | UploadLogic.firstPart = false; 299 | UploadLogic.flow.resume(); 300 | 301 | },1); 302 | 303 | var form_data = $(this).serialize(); 304 | 305 | var send_data = { 306 | output_width: 0, 307 | output_height: 0, 308 | output_leave_audio: 0, 309 | output_generate_html: 0 310 | }; 311 | 312 | 313 | 314 | if(form_data.output_size==1) 315 | { 316 | send_data.output_width = 854; 317 | send_data.output_height = 480; 318 | } 319 | else if(form_data.output_size==2) 320 | { 321 | send_data.output_width = 1280; 322 | send_data.output_height = 720; 323 | } 324 | else if(form_data.output_size==3) 325 | { 326 | send_data.output_width = 1920; 327 | send_data.output_height = 1080; 328 | } 329 | 330 | if(form_data.leave_audio!==undefined && form_data.leave_audio==1) 331 | { 332 | send_data.output_leave_audio = 1; 333 | } 334 | 335 | if(form_data.generate_html!==undefined && form_data.generate_html==1) 336 | { 337 | send_data.output_generate_html = 1; 338 | } 339 | 340 | 341 | $.ajax({ 342 | url: "/encode/", 343 | dataType: "json", 344 | data: send_data, 345 | }).done(function(data){ 346 | 347 | 348 | }); 349 | 350 | 351 | }); 352 | 353 | } 354 | }; 355 | 356 | 357 | jQuery(document).ready(function(){ 358 | 359 | UploadLogic.init(); 360 | 361 | }); -------------------------------------------------------------------------------- /mediatools/mediainfo.go: -------------------------------------------------------------------------------- 1 | package mediatools 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | alog "github.com/cenkalti/log" 7 | "github.com/codeskyblue/go-sh" 8 | "github.com/gophergala/videq/config" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | // "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type MediaInfo struct { 18 | log alog.Logger 19 | config config.Config 20 | resolutions map[string]VideoResolution 21 | } 22 | 23 | func NewMediaInfo(log alog.Logger) *MediaInfo { 24 | m := new(MediaInfo) 25 | m.log = log 26 | m.resolutions = resolutions 27 | return m 28 | } 29 | 30 | // http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC 31 | // Commonly supported resolutions and aspect ratios include: 32 | // 854 x 480 (16:9 480p) 33 | // 1280 x 720 (16:9 720p) 34 | // 1920 x 1080 (16:9 1080p) 35 | // 640 x 480 (4:3 480p) 36 | // 1280 x 1024 (5:4) 37 | // 1920 x 1440 (4:3) 38 | 39 | var resolutions = map[string]VideoResolution{ 40 | "854 x 480 (16:9 480p)": {Width: 854, Height: 480, AspectRatio: "16:9", AspectRatioInt: float64(16) / float64(9), Short: "480p"}, 41 | "1280 x 720 (16:9 720p)": {Width: 1280, Height: 720, AspectRatio: "16:9", AspectRatioInt: float64(16) / float64(9), Short: "720p"}, 42 | "1920 x 1080 (16:9 1080p)": {Width: 1920, Height: 1080, AspectRatio: "16:9", AspectRatioInt: float64(16) / float64(9), Short: "1080p"}, 43 | "640 x 480 (4:3 480p)": {Width: 640, Height: 480, AspectRatio: "4:3", AspectRatioInt: float64(4) / float64(3), Short: "480p"}, 44 | "1280 x 1024 (5:4)": {Width: 1280, Height: 1024, AspectRatio: "5:4", AspectRatioInt: float64(4) / float64(3), Short: ""}, 45 | "1920 x 1440 (4:3)": {Width: 1920, Height: 1440, AspectRatio: "4:3", AspectRatioInt: float64(4) / float64(3), Short: ""}, 46 | } 47 | 48 | type VideoResolution struct { 49 | Width int `json:"width"` 50 | Height int `json:"height"` 51 | AspectRatio string `json:"aspectratio"` 52 | AspectRatioInt float64 `json:"aspectratiofloat"` 53 | Short string `json:"short"` // shorthand name for a family of video display resolutions 54 | } 55 | 56 | type MediaFileInfo struct { 57 | FileName string 58 | FileSize_bytes string 59 | VideoCount int 60 | AudioCount int 61 | Duration_ms string 62 | Duration time.Duration 63 | Duration_string string 64 | Format string 65 | CodecID string 66 | Resolution string 67 | Width string 68 | Height string 69 | Standard string 70 | Codec string 71 | Bitrate_bps string 72 | Framerate string 73 | AspectRatio string 74 | Audio string 75 | } 76 | 77 | /* 78 | FileName: r2w_1080p.mov 79 | FileSize_bytes: 104937987 80 | VideoCount: 1 81 | AudioCount: 1 82 | Duration_ms: 90125 83 | Format: MPEG-4 84 | CodecID: qt 85 | Resolution: 1920x816 86 | Width: 1920 87 | Height: 816 88 | Standard: 89 | Codec: AVC Main@L4.0 90 | Bitrate_bps: 9185470 91 | Framerate: 24.000 fps 92 | AspectRatio: 2.35:1 93 | Audio: English 128 Kbps CBR 2 chnls AAC LC 94 | */ 95 | 96 | /* 97 | fetches media info 98 | usage: 99 | mt := mediatools.NewMediaInfo(log) 100 | minfo, err := mt.GetMediaInfo("_test/master_1080.mp4") 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | log.Infof("%#v", minfo) 105 | */ 106 | func (m *MediaInfo) GetMediaInfo(fileName string) (fileInfo MediaFileInfo, err error) { 107 | 108 | // timeout should be a session 109 | // out, err := sh.Command("ping", "-t", "127.0.0.1").SetTimeout(time.Second * 60).Output() 110 | 111 | mediaInfoParams := `--Inform=General;FileName:: %FileName%.%FileExtension%\r\nFileSize_bytes:: %FileSize%\r\nVideoCount:: %VideoCount%\r\nAudioCount:: %AudioCount%\r\nDuration_ms:: %Duration%\r\nFormat:: %Format%\r\nCodecID:: %CodecID%\r\n 112 | Video;Resolution:: %Width%x%Height%\r\nWidth:: %Width%\r\nHeight:: %Height%\r\nStandard:: %Standard%\r\nCodec:: %Codec/String% %Format_Profile%\r\nBitrate_bps:: %BitRate%\r\nFramerate:: %FrameRate% fps\r\nAspectRatio:: %DisplayAspectRatio/String%\r\n 113 | Audio;Audio:: %Language/String% %BitRate/String% %BitRate_Mode% %Channel(s)% chnls %Codec/String%\r\n 114 | Text;%Language/String% 115 | Text_Begin;Subs: 116 | Text_Middle;, 117 | Text_End;.\r\n 118 | ` 119 | out, err := sh.Command("mediainfo", mediaInfoParams, fileName).SetTimeout(time.Second * 60).Output() 120 | // fmt.Printf("output:(%s), err(%v)\n", string(out), err) 121 | if err == sh.ErrExecTimeout { 122 | m.log.Errorf("shell exec timeouteded.", err) 123 | } 124 | if err != nil { 125 | m.log.Errorf("sh.Command error:", err) 126 | return fileInfo, err 127 | } 128 | 129 | //output = strings.Replace(string(out), "\n", "", -1) 130 | 131 | lines := strings.Split(string(out), "\n") 132 | //fileInfo := new(MediaFileInfo) 133 | 134 | for _, line := range lines { 135 | paramArr := strings.Split(line, "::") 136 | if len(paramArr) != 2 { 137 | continue 138 | } 139 | 140 | paramName := strings.Trim(paramArr[0], " ") 141 | paramValue := strings.Trim(paramArr[1], " ") 142 | 143 | switch paramName { 144 | case `FileName`: 145 | fileInfo.FileName = paramValue 146 | case `FileSize_bytes`: 147 | fileInfo.FileSize_bytes = paramValue 148 | case `VideoCount`: 149 | val, err := strconv.ParseInt(paramValue, 10, 0) // int 150 | if err == nil { 151 | fileInfo.VideoCount = int(val) 152 | } else { 153 | fileInfo.VideoCount = 0 154 | } 155 | case `AudioCount`: 156 | val, err := strconv.ParseInt(paramValue, 10, 0) // int 157 | if err == nil { 158 | fileInfo.AudioCount = int(val) 159 | } else { 160 | fileInfo.AudioCount = 0 161 | } 162 | 163 | case `Duration_ms`: 164 | fileInfo.Duration_ms = paramValue 165 | // durationDurationInt64, err := strconv.ParseInt(fileInfo.Duration_ms, 10, 64) 166 | // fileInfo.Duration = time.Millisecond * durationDurationInt64 / 100 167 | dur, err := time.ParseDuration(paramValue + "ms") 168 | if err == nil { 169 | fileInfo.Duration = dur 170 | fileInfo.Duration_string = fmt.Sprintf("%s", dur) 171 | } else { 172 | m.log.Error(dur) 173 | } 174 | case `Format`: 175 | fileInfo.Format = paramValue 176 | case `CodecID`: 177 | fileInfo.CodecID = paramValue 178 | case `Resolution`: 179 | fileInfo.Resolution = paramValue 180 | case `Width`: 181 | fileInfo.Width = paramValue 182 | case `Height`: 183 | fileInfo.Height = paramValue 184 | case `Standard`: 185 | fileInfo.Standard = paramValue 186 | case `Codec`: 187 | fileInfo.Codec = paramValue 188 | case `Bitrate_bps`: 189 | fileInfo.Bitrate_bps = paramValue 190 | case `Framerate`: 191 | fileInfo.Framerate = paramValue 192 | case `AspectRatio`: 193 | fileInfo.AspectRatio = paramValue 194 | case `Audio`: 195 | fileInfo.Audio = paramValue 196 | } 197 | } 198 | 199 | return fileInfo, nil 200 | } 201 | 202 | // CheckMedia checks if video file is ok for encoding 203 | // TODO: more checks 204 | // 1. limit output resolutions ONLY to the same of original video or LOWER ones 205 | // 2. input format check 206 | 207 | func (m *MediaInfo) CheckMedia(fileName string) (ok bool, fileInfo MediaFileInfo, res map[string]VideoResolution, err error) { 208 | res = m.resolutions 209 | 210 | exists, err := m.checkIfFileExists(fileName) 211 | if err != nil { 212 | return false, fileInfo, nil, err 213 | } 214 | if exists == false { 215 | return false, fileInfo, nil, errors.New(fmt.Sprintf("File '%s' does not exists.", fileName)) 216 | } 217 | 218 | fileInfo, err = m.GetMediaInfo(fileName) 219 | if err != nil { 220 | m.log.Error(err) 221 | return false, fileInfo, nil, err 222 | } 223 | 224 | //maxDuration := time.Second * 60 225 | maxDuration := time.Minute * 5 226 | if fileInfo.Duration > maxDuration { 227 | m.log.Infoln(fileInfo.Duration) 228 | return false, fileInfo, nil, errors.New(fmt.Sprintf("File '%s' is too long. Max duration: %s, File duration: %s", fileName, maxDuration, fileInfo.Duration_string)) 229 | } 230 | 231 | if fileInfo.VideoCount == 0 { 232 | return false, fileInfo, nil, errors.New(fmt.Sprintf("File '%s' is no video.", fileName)) 233 | } 234 | 235 | // TODO - more meaningfull checks 236 | 237 | return true, fileInfo, m.resolutions, nil 238 | } 239 | 240 | func (m *MediaInfo) checkIfFileExists(fileName string) (bool, error) { 241 | _, err := os.Stat(fileName) 242 | if os.IsNotExist(err) { 243 | return false, nil 244 | } 245 | if err != nil { 246 | return false, err 247 | } 248 | return true, nil 249 | } 250 | 251 | func (m *MediaInfo) returnBaseFilename(fileName string) string { 252 | // var extension = filepath.Ext(fileName) 253 | // var name = fileName[0 : len(fileName)-len(extension)] 254 | // return name 255 | 256 | return strings.TrimSuffix(fileName, filepath.Ext(fileName)) 257 | } 258 | 259 | func (m *MediaInfo) deleteFile(file string) (err error) { 260 | // check if file/folder exists 261 | if _, err := os.Stat(file); !os.IsNotExist(err) { 262 | // vrati errror samo ako je neki zesci error, ne na file not found 263 | return err 264 | } 265 | return nil 266 | } 267 | 268 | // "bytes" 269 | // "encoding/json" 270 | 271 | // m.log.Debug(m.resolutions) 272 | // b, err := json.Marshal(m.resolutions) 273 | // if err != nil { 274 | // m.log.Fatal(err) 275 | // } 276 | // var out bytes.Buffer 277 | // json.Indent(&out, b, "=", "\t") 278 | // out.WriteTo(os.Stdout) 279 | -------------------------------------------------------------------------------- /resources/icomoon/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IcoMoon Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | Font Name: icomoon (Glyphs: 13) 13 | 14 | 15 | Grid Size: 24 16 | 17 | 18 | 19 | 20 | 21 | icon-thumb-up 22 | 23 | 24 | 25 | 26 | 27 | 28 | liga: 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | icon-circle-plus 38 | 39 | 40 | 41 | 42 | 43 | 44 | liga: 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | icon-circle-minus 54 | 55 | 56 | 57 | 58 | 59 | 60 | liga: 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | icon-circle-check 70 | 71 | 72 | 73 | 74 | 75 | 76 | liga: 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | icon-circle-cross 86 | 87 | 88 | 89 | 90 | 91 | 92 | liga: 93 | 94 | 95 | 96 | 97 | 98 | Grid Size: 16 99 | 100 | 101 | 102 | 103 | 104 | icon-jump-down 105 | 106 | 107 | 108 | 109 | 110 | 111 | liga: 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | icon-bug 121 | 122 | 123 | 124 | 125 | 126 | 127 | liga: 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | icon-warning 137 | 138 | 139 | 140 | 141 | 142 | 143 | liga: 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | icon-notification 153 | 154 | 155 | 156 | 157 | 158 | 159 | liga: 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | icon-question 169 | 170 | 171 | 172 | 173 | 174 | 175 | liga: 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | icon-info 185 | 186 | 187 | 188 | 189 | 190 | 191 | liga: 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | icon-cancel-circle 201 | 202 | 203 | 204 | 205 | 206 | 207 | liga: 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | icon-blocked 217 | 218 | 219 | 220 | 221 | 222 | 223 | liga: 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | Font Test Drive 232 | 233 | Font Size: 235 | px 236 | 237 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | Generated by IcoMoon 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Videq - quality web video converter 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Videq 22 | 23 | !!! Limited for video of 5min duration !!! 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Your last upload was incomplete, you can resume te upload by just draging or selecting the file again. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Drop video or select file 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | Checking your video, one moment, please 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Video size or format not supported. Try again with another file. 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Select your video size: 123 | 124 | 125 | 126 | 127 | 128 | 480p (854x480) 129 | 130 | 131 | 132 | 133 | 134 | 135 | 720p (1280x720) 136 | 137 | 138 | 139 | 140 | 141 | 142 | 1080p (1920x1080) 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Output options: 153 | 154 | 155 | Leave Audio: 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | HTML code: 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | Go, upload video! 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | Your video is being uploaded, please be patient. 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | Your video is converting on our servers, please be patinet. 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | Your video is converted and is waiting for you to download 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | Download .MP4 268 | Download .WEBM 269 | Download .OGV 270 | Download .JPG 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | Start with new video 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | Upload files 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /resources/icomoon/selection.json: -------------------------------------------------------------------------------- 1 | { 2 | "IcoMoonType": "selection", 3 | "icons": [ 4 | { 5 | "icon": { 6 | "paths": [ 7 | "M42.667 896h170.667v-512h-170.667v512zM981.333 426.667c0-47.147-38.187-85.333-85.333-85.333h-269.44l40.747-194.987c0.853-4.267 1.493-8.747 1.493-13.44 0-17.707-7.253-33.707-18.773-45.227l-45.44-45.013-280.96 280.96c-15.36 15.573-24.96 36.907-24.96 60.373v426.667c0 47.147 38.187 85.333 85.333 85.333h384c35.413 0 65.707-21.547 78.507-52.053l128.64-300.8c3.84-9.813 6.187-20.267 6.187-31.147v-81.707l-0.427-0.427 0.427-3.2z" 8 | ], 9 | "attrs": [], 10 | "isMulticolor": false, 11 | "tags": [ 12 | "thumb-up" 13 | ], 14 | "grid": 24 15 | }, 16 | "attrs": [], 17 | "properties": { 18 | "id": 133, 19 | "order": 14, 20 | "prevSize": 24, 21 | "code": 58880, 22 | "name": "thumb-up" 23 | }, 24 | "setIdx": 0, 25 | "iconIdx": 133 26 | }, 27 | { 28 | "icon": { 29 | "paths": [ 30 | "M512 42.667q95.667 0 182.5 37.167t149.667 100 100 149.667 37.167 182.5-37.167 182.5-100 149.667-149.667 100-182.5 37.167-182.5-37.167-149.667-100-100-149.667-37.167-182.5 37.167-182.5 100-149.667 149.667-100 182.5-37.167zM512 128q-78 0-149.167 30.5t-122.5 81.833-81.833 122.5-30.5 149.167 30.5 149.167 81.833 122.5 122.5 81.833 149.167 30.5 149.167-30.5 122.5-81.833 81.833-122.5 30.5-149.167-30.5-149.167-81.833-122.5-122.5-81.833-149.167-30.5zM512 298.667q17.667 0 30.167 12.5t12.5 30.167v128h128q17.667 0 30.167 12.5t12.5 30.167-12.5 30.167-30.167 12.5h-128v128q0 17.667-12.5 30.167t-30.167 12.5-30.167-12.5-12.5-30.167v-128h-128q-17.667 0-30.167-12.5t-12.5-30.167 12.5-30.167 30.167-12.5h128v-128q0-17.667 12.5-30.167t30.167-12.5z" 31 | ], 32 | "attrs": [], 33 | "isMulticolor": false, 34 | "tags": [ 35 | "circle-plus" 36 | ], 37 | "defaultCode": 57408, 38 | "grid": 24 39 | }, 40 | "attrs": [], 41 | "properties": { 42 | "id": 40, 43 | "order": 12, 44 | "prevSize": 24, 45 | "code": 57408, 46 | "name": "circle-plus" 47 | }, 48 | "setIdx": 2, 49 | "iconIdx": 40 50 | }, 51 | { 52 | "icon": { 53 | "paths": [ 54 | "M512 42.667q95.667 0 182.5 37.167t149.667 100 100 149.667 37.167 182.5-37.167 182.5-100 149.667-149.667 100-182.5 37.167-182.5-37.167-149.667-100-100-149.667-37.167-182.5 37.167-182.5 100-149.667 149.667-100 182.5-37.167zM512 128q-78 0-149.167 30.5t-122.5 81.833-81.833 122.5-30.5 149.167 30.5 149.167 81.833 122.5 122.5 81.833 149.167 30.5 149.167-30.5 122.5-81.833 81.833-122.5 30.5-149.167-30.5-149.167-81.833-122.5-122.5-81.833-149.167-30.5zM341.333 469.333h341.333q17.667 0 30.167 12.5t12.5 30.167-12.5 30.167-30.167 12.5h-341.333q-17.667 0-30.167-12.5t-12.5-30.167 12.5-30.167 30.167-12.5z" 55 | ], 56 | "attrs": [], 57 | "isMulticolor": false, 58 | "tags": [ 59 | "circle-minus" 60 | ], 61 | "defaultCode": 57409, 62 | "grid": 24 63 | }, 64 | "attrs": [], 65 | "properties": { 66 | "id": 41, 67 | "order": 11, 68 | "prevSize": 24, 69 | "code": 57409, 70 | "name": "circle-minus" 71 | }, 72 | "setIdx": 2, 73 | "iconIdx": 41 74 | }, 75 | { 76 | "icon": { 77 | "paths": [ 78 | "M512 42.667q95.667 0 182.5 37.167t149.667 100 100 149.667 37.167 182.5-37.167 182.5-100 149.667-149.667 100-182.5 37.167-182.5-37.167-149.667-100-100-149.667-37.167-182.5 37.167-182.5 100-149.667 149.667-100 182.5-37.167zM512 128q-78 0-149.167 30.5t-122.5 81.833-81.833 122.5-30.5 149.167 30.5 149.167 81.833 122.5 122.5 81.833 149.167 30.5 149.167-30.5 122.5-81.833 81.833-122.5 30.5-149.167-30.5-149.167-81.833-122.5-122.5-81.833-149.167-30.5zM655.667 383.667q17.667 0 30.167 12.5t12.5 30.167-12.667 30.333l-181 181q-12.333 12.333-30 12.333-18 0-30.333-12.333l-90.667-90.333q-12.333-12.333-12.333-30.333 0-17.667 12.5-30.167t30.167-12.5 30.333 12.667l60.333 60.333 150.667-151q12.667-12.667 30.333-12.667z" 79 | ], 80 | "attrs": [], 81 | "isMulticolor": false, 82 | "tags": [ 83 | "circle-check" 84 | ], 85 | "defaultCode": 57410, 86 | "grid": 24 87 | }, 88 | "attrs": [], 89 | "properties": { 90 | "id": 42, 91 | "order": 9, 92 | "prevSize": 24, 93 | "code": 57410, 94 | "name": "circle-check" 95 | }, 96 | "setIdx": 2, 97 | "iconIdx": 42 98 | }, 99 | { 100 | "icon": { 101 | "paths": [ 102 | "M512 42.667q95.667 0 182.5 37.167t149.667 100 100 149.667 37.167 182.5-37.167 182.5-100 149.667-149.667 100-182.5 37.167-182.5-37.167-149.667-100-100-149.667-37.167-182.5 37.167-182.5 100-149.667 149.667-100 182.5-37.167zM512 128q-78 0-149.167 30.5t-122.5 81.833-81.833 122.5-30.5 149.167 30.5 149.167 81.833 122.5 122.5 81.833 149.167 30.5 149.167-30.5 122.5-81.833 81.833-122.5 30.5-149.167-30.5-149.167-81.833-122.5-122.5-81.833-149.167-30.5zM632.667 348.333q17.667 0 30.333 12.5t12.667 30.167-12.667 30.333l-90.667 90.667 90.667 90.333q12.667 12.667 12.667 30 0 17.667-12.667 30.167t-30.333 12.5-30-12.333l-90.667-90.333-90.333 90.333q-12.333 12.333-30.333 12.333-17.667 0-30.167-12.333t-12.5-30q0-18 12.333-30.333l90.667-90.333-90.667-90.667q-12.333-12.333-12.333-30t12.5-30.333 30.167-12.667 30.333 12.667l90.333 90.667 90.667-90.667q12.667-12.667 30-12.667z" 103 | ], 104 | "attrs": [], 105 | "isMulticolor": false, 106 | "tags": [ 107 | "circle-cross" 108 | ], 109 | "defaultCode": 57411, 110 | "grid": 24 111 | }, 112 | "attrs": [], 113 | "properties": { 114 | "id": 43, 115 | "order": 10, 116 | "prevSize": 24, 117 | "code": 57411, 118 | "name": "circle-cross" 119 | }, 120 | "setIdx": 2, 121 | "iconIdx": 43 122 | }, 123 | { 124 | "icon": { 125 | "paths": [ 126 | "M767.75 192h-767.5l383.75 383.75 383.75-383.75zM0 704v128h768v-128h-768z" 127 | ], 128 | "width": 768, 129 | "attrs": [], 130 | "isMulticolor": false, 131 | "tags": [ 132 | "jump-down" 133 | ], 134 | "defaultCode": 61554, 135 | "grid": 16 136 | }, 137 | "attrs": [], 138 | "properties": { 139 | "id": 87, 140 | "order": 8, 141 | "prevSize": 32, 142 | "code": 61554, 143 | "name": "jump-down" 144 | }, 145 | "setIdx": 6, 146 | "iconIdx": 87 147 | }, 148 | { 149 | "icon": { 150 | "paths": [ 151 | "M1024 576v-64h-193.29c-5.862-72.686-31.786-139.026-71.67-192.25h161.944l70.060-280.24-62.090-15.522-57.94 231.76h-174.68c-0.892-0.694-1.796-1.374-2.698-2.056 6.71-19.502 10.362-40.422 10.362-62.194 0.002-105.76-85.958-191.498-191.998-191.498s-192 85.738-192 191.5c0 21.772 3.65 42.692 10.362 62.194-0.9 0.684-1.804 1.362-2.698 2.056h-174.68l-57.94-231.76-62.090 15.522 70.060 280.24h161.944c-39.884 53.222-65.806 119.562-71.668 192.248h-193.29v64h193.37c3.802 45.664 15.508 88.812 33.638 127.75h-123.992l-70.060 280.238 62.090 15.524 57.94-231.762h112.354c58.692 78.032 147.396 127.75 246.66 127.75s187.966-49.718 246.662-127.75h112.354l57.94 231.762 62.090-15.524-70.060-280.238h-123.992c18.13-38.938 29.836-82.086 33.636-127.75h193.37z" 152 | ], 153 | "attrs": [], 154 | "tags": [ 155 | "bug", 156 | "virus", 157 | "error" 158 | ], 159 | "defaultCode": 57842, 160 | "grid": 16 161 | }, 162 | "attrs": [], 163 | "properties": { 164 | "id": 588, 165 | "order": 7, 166 | "prevSize": 32, 167 | "code": 59801, 168 | "ligatures": "bug, virus", 169 | "name": "bug" 170 | }, 171 | "setIdx": 7, 172 | "iconIdx": 153 173 | }, 174 | { 175 | "icon": { 176 | "paths": [ 177 | "M512 92.774l429.102 855.226h-858.206l429.104-855.226zM512 0c-22.070 0-44.14 14.882-60.884 44.648l-437.074 871.112c-33.486 59.532-5 108.24 63.304 108.24h869.308c68.3 0 96.792-48.708 63.3-108.24h0.002l-437.074-871.112c-16.742-29.766-38.812-44.648-60.882-44.648v0z", 178 | "M576 832c0 35.346-28.654 64-64 64s-64-28.654-64-64c0-35.346 28.654-64 64-64s64 28.654 64 64z", 179 | "M512 704c-35.346 0-64-28.654-64-64v-192c0-35.346 28.654-64 64-64s64 28.654 64 64v192c0 35.346-28.654 64-64 64z" 180 | ], 181 | "attrs": [], 182 | "tags": [ 183 | "warning", 184 | "sign" 185 | ], 186 | "defaultCode": 58197, 187 | "grid": 16 188 | }, 189 | "attrs": [], 190 | "properties": { 191 | "id": 949, 192 | "order": 2, 193 | "prevSize": 32, 194 | "code": 59911, 195 | "ligatures": "warning, sign", 196 | "name": "warning" 197 | }, 198 | "setIdx": 7, 199 | "iconIdx": 263 200 | }, 201 | { 202 | "icon": { 203 | "paths": [ 204 | "M512 96c-111.118 0-215.584 43.272-294.156 121.844s-121.844 183.038-121.844 294.156c0 111.118 43.272 215.584 121.844 294.156s183.038 121.844 294.156 121.844c111.118 0 215.584-43.272 294.156-121.844s121.844-183.038 121.844-294.156c0-111.118-43.272-215.584-121.844-294.156s-183.038-121.844-294.156-121.844zM512 0v0c282.77 0 512 229.23 512 512s-229.23 512-512 512c-282.77 0-512-229.23-512-512s229.23-512 512-512zM448 704h128v128h-128zM448 192h128v384h-128z" 205 | ], 206 | "attrs": [], 207 | "tags": [ 208 | "notification", 209 | "warning", 210 | "notice", 211 | "note", 212 | "exclamation" 213 | ], 214 | "defaultCode": 58199, 215 | "grid": 16 216 | }, 217 | "attrs": [], 218 | "properties": { 219 | "id": 951, 220 | "order": 1, 221 | "prevSize": 32, 222 | "code": 59912, 223 | "ligatures": "notification, warning2", 224 | "name": "notification" 225 | }, 226 | "setIdx": 7, 227 | "iconIdx": 264 228 | }, 229 | { 230 | "icon": { 231 | "paths": [ 232 | "M448 704h128v128h-128zM704 256c35.346 0 64 28.654 64 64v192l-192 128h-128v-64l192-128v-64h-320v-128h384zM512 96c-111.118 0-215.584 43.272-294.156 121.844s-121.844 183.038-121.844 294.156c0 111.118 43.272 215.584 121.844 294.156s183.038 121.844 294.156 121.844c111.118 0 215.584-43.272 294.156-121.844s121.844-183.038 121.844-294.156c0-111.118-43.272-215.584-121.844-294.156s-183.038-121.844-294.156-121.844zM512 0v0c282.77 0 512 229.23 512 512s-229.23 512-512 512c-282.77 0-512-229.23-512-512s229.23-512 512-512z" 233 | ], 234 | "attrs": [], 235 | "tags": [ 236 | "question", 237 | "help", 238 | "support" 239 | ], 240 | "defaultCode": 58201, 241 | "grid": 16 242 | }, 243 | "attrs": [], 244 | "properties": { 245 | "id": 953, 246 | "order": 3, 247 | "prevSize": 32, 248 | "code": 59913, 249 | "ligatures": "question, help", 250 | "name": "question" 251 | }, 252 | "setIdx": 7, 253 | "iconIdx": 265 254 | }, 255 | { 256 | "icon": { 257 | "paths": [ 258 | "M448 304c0-26.4 21.6-48 48-48h32c26.4 0 48 21.6 48 48v32c0 26.4-21.6 48-48 48h-32c-26.4 0-48-21.6-48-48v-32z", 259 | "M640 768h-256v-64h64v-192h-64v-64h192v256h64z", 260 | "M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416z" 261 | ], 262 | "attrs": [], 263 | "tags": [ 264 | "info", 265 | "information" 266 | ], 267 | "defaultCode": 60768, 268 | "grid": 16 269 | }, 270 | "attrs": [], 271 | "properties": { 272 | "id": 965, 273 | "order": 4, 274 | "prevSize": 32, 275 | "code": 59916, 276 | "ligatures": "info, information", 277 | "name": "info" 278 | }, 279 | "setIdx": 7, 280 | "iconIdx": 268 281 | }, 282 | { 283 | "icon": { 284 | "paths": [ 285 | "M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416z", 286 | "M672 256l-160 160-160-160-96 96 160 160-160 160 96 96 160-160 160 160 96-96-160-160 160-160z" 287 | ], 288 | "attrs": [], 289 | "tags": [ 290 | "cancel-circle", 291 | "close", 292 | "remove", 293 | "delete" 294 | ], 295 | "defaultCode": 60770, 296 | "grid": 16 297 | }, 298 | "attrs": [], 299 | "properties": { 300 | "id": 23, 301 | "order": 5, 302 | "prevSize": 32, 303 | "code": 59917, 304 | "ligatures": "cancel-circle, close", 305 | "name": "cancel-circle" 306 | }, 307 | "setIdx": 7, 308 | "iconIdx": 269 309 | }, 310 | { 311 | "icon": { 312 | "paths": [ 313 | "M874.040 149.96c-96.706-96.702-225.28-149.96-362.040-149.96s-265.334 53.258-362.040 149.96c-96.702 96.706-149.96 225.28-149.96 362.040s53.258 265.334 149.96 362.040c96.706 96.702 225.28 149.96 362.040 149.96s265.334-53.258 362.040-149.96c96.702-96.706 149.96-225.28 149.96-362.040s-53.258-265.334-149.96-362.040zM896 512c0 82.814-26.354 159.588-71.112 222.38l-535.266-535.268c62.792-44.758 139.564-71.112 222.378-71.112 211.738 0 384 172.262 384 384zM128 512c0-82.814 26.354-159.586 71.112-222.378l535.27 535.268c-62.794 44.756-139.568 71.11-222.382 71.11-211.738 0-384-172.262-384-384z" 314 | ], 315 | "attrs": [], 316 | "tags": [ 317 | "blocked", 318 | "forbidden", 319 | "denied", 320 | "banned" 321 | ], 322 | "defaultCode": 58212, 323 | "grid": 16 324 | }, 325 | "attrs": [], 326 | "properties": { 327 | "id": 967, 328 | "order": 6, 329 | "prevSize": 32, 330 | "code": 59918, 331 | "ligatures": "blocked, forbidden", 332 | "name": "blocked" 333 | }, 334 | "setIdx": 7, 335 | "iconIdx": 270 336 | } 337 | ], 338 | "height": 1024, 339 | "metadata": { 340 | "name": "icomoon" 341 | }, 342 | "preferences": { 343 | "showGlyphs": true, 344 | "showQuickUse": true, 345 | "showQuickUse2": true, 346 | "showSVGs": true, 347 | "fontPref": { 348 | "prefix": "icon-", 349 | "metadata": { 350 | "fontFamily": "icomoon" 351 | }, 352 | "metrics": { 353 | "emSize": 1024, 354 | "baseline": 6.25, 355 | "whitespace": 50 356 | } 357 | }, 358 | "imagePref": { 359 | "prefix": "icon-", 360 | "png": true, 361 | "useClassSelector": true, 362 | "color": 4473924, 363 | "bgColor": 16777215 364 | }, 365 | "historySize": 100, 366 | "showCodes": true, 367 | "gridSize": 16 368 | } 369 | } -------------------------------------------------------------------------------- /resources/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:hover,.btn-primary:focus{background-color:#265a88;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#265a88;border-color:#245580}.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /resources/css/custom.css: -------------------------------------------------------------------------------- 1 | body{ 2 | overflow: hidden; 3 | font-family: Helvetica, Arial, sans-serif; 4 | } 5 | 6 | html .container { 7 | max-width: 1004px; 8 | padding: 0 14px; 9 | margin: 0 auto; 10 | } 11 | html .t { 12 | display: table; width: 100%; height: 100%; 13 | } 14 | html .td { 15 | display: table-cell; vertical-align: middle; 16 | } 17 | 18 | 19 | a, 20 | a:link, 21 | a:visited, 22 | a:hover 23 | { 24 | color: #C90E0E; 25 | text-decoration: underline; 26 | } 27 | 28 | p{ 29 | font-size: 18px; 30 | line-height: 1em; 31 | color: #fff; 32 | } 33 | 34 | input, textarea, button{ 35 | color: #000; 36 | } 37 | 38 | /* ------------------------------------------------------------------------------------------------ */ 39 | /* Logo 40 | /* ------------------------------------------------------------------------------------------------ */ 41 | 42 | header{ 43 | position: absolute; 44 | z-index: 10000; 45 | width: 100%; 46 | text-align: center; 47 | width: 329px; 48 | height: 132px; 49 | left: 50%; 50 | margin-left: -164px; 51 | } 52 | 53 | header .logo { 54 | width: 100%; 55 | height: 100%; 56 | display: block; 57 | background: url(../img/logo.png) no-repeat 0 0; 58 | margin-top: 20px; 59 | } 60 | 61 | header h1 { 62 | display: none; 63 | } 64 | 65 | header p{ 66 | margin-top: 10px; 67 | font-size: 14px; 68 | color: #C90E0E; 69 | } 70 | 71 | #main .cta3{ 72 | position: absolute; 73 | top: 20px; 74 | right: 20px; 75 | z-index: 1002; 76 | } 77 | 78 | footer{ 79 | position: absolute; 80 | left: 5%; 81 | bottom: 0px; 82 | width: 100%; 83 | color: #fff; 84 | 85 | } 86 | 87 | footer p{ 88 | font-size: 14px; 89 | } 90 | 91 | 92 | footer .gopher{ 93 | position: fixed; 94 | bottom: 0px; 95 | margin-bottom: -40px; 96 | left: 70%; 97 | width: 303px; 98 | height: 165px; 99 | background: url(../img/gopher.png) no-repeat; 100 | -webkit-transition: all 1000ms ease; 101 | -moz-transition: all 1000ms ease; 102 | -ms-transition: all 1000ms ease; 103 | -o-transition: all 1000ms ease; 104 | transition: all 1000ms ease; 105 | } 106 | 107 | footer .gopher:hover, 108 | footer .gopher.over{ 109 | margin-bottom: 0px; 110 | } 111 | 112 | footer .rvatine{ 113 | width: 10px; 114 | height: 10px; 115 | display: inline-block; 116 | background: url(../img/rvatine.gif) no-repeat; 117 | } 118 | 119 | /* ------------------------------------------------------------------------------------------------ */ 120 | /* Main / Video block (Homepage - template_home) 121 | /* ------------------------------------------------------------------------------------------------ */ 122 | 123 | body #main .block.intro { 124 | height: 100vh; 125 | min-height: 840px; 126 | position: relative; 127 | background-repeat: no-repeat; 128 | background-position: 0 0; 129 | background-size: cover; 130 | } 131 | body #main .block.intro #intro_vid { 132 | height: 100%; 133 | width: 100%; 134 | position: absolute; 135 | overflow: hidden; 136 | top: 0; 137 | left: 0; 138 | } 139 | body #main .block.intro #intro_vid:after, 140 | body #main .block.intro #intro_vid:before { 141 | left: 0; 142 | width: 100%; 143 | content: ''; 144 | position: absolute; 145 | background: repeat-x 0 0; 146 | } 147 | body #main .block.intro #intro_vid:after { 148 | bottom: 0; 149 | height: 394px; 150 | background-position: 0 -165px; 151 | } 152 | body #main .block.intro #intro_vid:before { 153 | top: 0; 154 | height: 165px; 155 | } 156 | body #main .block.intro #intro_vid video { 157 | min-width: 100%; 158 | min-height: 100%; 159 | object-fit: cover; 160 | } 161 | body #main .block.intro #intro_vid .overlay{ 162 | position: absolute; 163 | top: 0; 164 | left: 0; 165 | width: 100%; 166 | height: 100%; 167 | margin-top: 180px; 168 | } 169 | body #main .overlay .container{ 170 | display: none; 171 | } 172 | 173 | body #main .screen{ 174 | width: 100%; 175 | min-height: 280px; 176 | text-align: center; 177 | background-color: rgba(255, 255, 255, 0.15); 178 | color: rgba(255, 255, 255, 0.15); 179 | position: relative; 180 | z-index: 1000; 181 | display: none; 182 | } 183 | 184 | body .cta.cta1{ 185 | -webkit-border-radius: 20px 20px 20px 20px; 186 | border-radius: 20px 20px 20px 20px; 187 | -webkit-box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 188 | box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 189 | display: inline-block; 190 | background-color: #c90f0f; 191 | padding: 10px 50px 10px 50px; 192 | color: #fff; 193 | cursor: pointer; 194 | cursor: hand; 195 | border: none; 196 | outline: none; 197 | font-size: 18px; 198 | text-decoration: none; 199 | } 200 | 201 | body .cta.cta2{ 202 | -webkit-border-radius: 20px 20px 20px 20px; 203 | border-radius: 20px 20px 20px 20px; 204 | -webkit-box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 205 | box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 206 | display: inline-block; 207 | background-color: #c90f0f; 208 | padding: 10px 50px 10px 50px; 209 | color: #fff; 210 | cursor: pointer; 211 | cursor: hand; 212 | border: none; 213 | outline: none; 214 | font-size: 18px; 215 | text-decoration: none; 216 | } 217 | 218 | body .cta.cta3{ 219 | -webkit-border-radius: 20px 20px 20px 20px; 220 | border-radius: 20px 20px 20px 20px; 221 | -webkit-box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 222 | box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 223 | display: inline-block; 224 | background-color: #c90f0f; 225 | padding: 5px 10px 1px 10px; 226 | color: #fff; 227 | cursor: pointer; 228 | cursor: hand; 229 | border: none; 230 | outline: none; 231 | font-size: 28px; 232 | text-decoration: none; 233 | } 234 | 235 | @media screen and (min-height: 1050px) { 236 | 237 | } 238 | 239 | @media screen and (max-width:920px) { 240 | 241 | } 242 | 243 | @media screen and (max-width:780px) { 244 | 245 | body.home #main .block.intro { 246 | height: auto; 247 | min-height: 0; 248 | } 249 | 250 | 251 | } 252 | 253 | @media screen and (max-width:580px) { 254 | 255 | 256 | } 257 | 258 | 259 | 260 | 261 | /* ------------------------------------------------------------------------------------------------ */ 262 | /* Drop Zone 263 | /* ------------------------------------------------------------------------------------------------ */ 264 | body .screen.screen-drop-zone{ 265 | z-index: 1001; 266 | cursor: pointer; 267 | cursor: hand; 268 | } 269 | 270 | body .screen.screen-drop-zone .txt{ 271 | padding-top: 70px; 272 | padding-bottom: 200px; 273 | } 274 | 275 | body .screen.screen-drop-zone .imoon{ 276 | font-size: 90px; 277 | color: rgba(15, 201, 94, 0.5); 278 | width: 1.1em; 279 | height: 1em; 280 | margin-left: auto; 281 | margin-right: auto; 282 | border: 1px solid rgba(255,255,255,0.2); 283 | padding: 40px 100px 40px 100px; 284 | -webkit-border-radius: 5px 5px 5px 5px; 285 | border-radius: 5px 5px 5px 5px; 286 | } 287 | 288 | body .screen.screen-drop-zone #drop-zone { 289 | width: 100%; 290 | height: 100%; 291 | position: absolute; 292 | top: 0px; 293 | left: 0px; 294 | } 295 | 296 | body .screen.screen-drop-zone #drop-zone.over{ 297 | border: 5px dotted #0fc95e; 298 | } 299 | 300 | body .screen.screen-drop-zone #drop-zone p{ 301 | font-size: 22px; 302 | color: #fff; 303 | margin-top: 240px; 304 | } 305 | 306 | body .screen.screen-drop-zone #drop-zone a{ 307 | font-size: 22px; 308 | } 309 | 310 | 311 | /* ------------------------------------------------------------------------------------------------ */ 312 | /* Checking Screen 313 | /* ------------------------------------------------------------------------------------------------ */ 314 | body .screen.screen-check-bar .txt{ 315 | padding-top: 70px; 316 | text-align: center 317 | } 318 | 319 | body .screen.screen-check-bar #floatingBarsG{ 320 | margin-bottom: 40px; 321 | } 322 | 323 | 324 | /* ------------------------------------------------------------------------------------------------ */ 325 | /* Converting Screen 326 | /* ------------------------------------------------------------------------------------------------ */ 327 | body .screen.screen-converting-bar .txt{ 328 | padding-top: 70px; 329 | text-align: center 330 | } 331 | 332 | body .screen.screen-converting-bar #floatingBarsG{ 333 | margin-bottom: 40px; 334 | } 335 | 336 | 337 | 338 | /* ------------------------------------------------------------------------------------------------ */ 339 | /* Error screen 340 | /* ------------------------------------------------------------------------------------------------ */ 341 | body .screen.screen-error-bar .txt{ 342 | padding-bottom: 40px; 343 | } 344 | body .screen.screen-error-bar .txt.icon{ 345 | padding-top: 70px; 346 | text-align: center; 347 | -moz-transform: rotate(-45deg); 348 | -webkit-transform: rotate(-45deg); 349 | -o-transform: rotate(-45deg); 350 | -ms-transform: rotate(-45deg); 351 | transform: rotate(-45deg); 352 | padding-bottom: 40px; 353 | } 354 | 355 | 356 | body .screen.screen-error-bar .imoon{ 357 | font-size: 180px; 358 | padding-bottom: 40px; 359 | } 360 | 361 | 362 | /* ------------------------------------------------------------------------------------------------ */ 363 | /* Download screen 364 | /* ------------------------------------------------------------------------------------------------ */ 365 | 366 | body .screen.screen-download-bar .txt{ 367 | padding-bottom: 40px; 368 | } 369 | body .screen.screen-download-bar .txt.icon{ 370 | padding-top: 40px; 371 | text-align: center; 372 | padding-bottom: 40px; 373 | color: rgba(15, 201, 94, 0.5); 374 | } 375 | 376 | 377 | body .screen.screen-download-bar .txt.icon .imoon{ 378 | font-size: 120px; 379 | padding-bottom: 40px; 380 | } 381 | 382 | 383 | 384 | body .screen.screen-download-bar #html_code{ 385 | -webkit-border-radius: 20px 20px 20px 20px; 386 | border-radius: 20px 20px 20px 20px; 387 | -webkit-box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 388 | box-shadow: 0 0 10px 5px rgba(255,255,255,0.2); 389 | display: block; 390 | background-color: rgba(255, 255, 255, 0.2); 391 | color: rgba(255, 255, 255, 0.5); 392 | padding: 0px; 393 | cursor: pointer; 394 | cursor: hand; 395 | border: none; 396 | outline: none; 397 | font-size: 18px; 398 | width: 100%; 399 | overflow: hidden; 400 | height: 1.8em; 401 | line-height: 1.8em; 402 | } 403 | 404 | 405 | body .screen.screen-download-bar .cta{ 406 | margin-left: 82px; 407 | } 408 | 409 | body .screen.screen-download-bar .cta:first-child{ 410 | margin-left: 0px; 411 | } 412 | 413 | /* ------------------------------------------------------------------------------------------------ */ 414 | /* Setup bar 415 | /* ------------------------------------------------------------------------------------------------ */ 416 | body .screen.screen-setup-bar .inner{ 417 | text-align: left; 418 | } 419 | 420 | body .screen.screen-setup-bar .txt{ 421 | padding-top: 40px; 422 | padding-bottom: 40px; 423 | } 424 | 425 | 426 | body .screen.screen-setup-bar ul.sizes{ 427 | display: block; 428 | list-style-type: none; 429 | padding: 0px; 430 | margin: 0px; 431 | overflow: hidden; 432 | } 433 | 434 | body .screen.screen-setup-bar ul.sizes li{ 435 | display: inline-block; 436 | border: 1px solid rgba(255,255,255,0.2); 437 | -webkit-border-radius: 5px 5px 5px 5px; 438 | border-radius: 5px 5px 5px 5px; 439 | margin-right: 20px; 440 | padding: 20px; 441 | color: #fff; 442 | text-align: center 443 | } 444 | 445 | body .screen.screen-setup-bar ul.sizes li input[type=radio]{ 446 | width: 1.5em; 447 | height: 1.5em; 448 | } 449 | 450 | body .screen.screen-setup-bar label{ 451 | cursor: pointer; 452 | cursor: hand; 453 | color: #fff; 454 | } 455 | 456 | body .screen.screen-setup-bar .bootstrap-switch-handle-on{ 457 | background-color: #0fc95e; 458 | } 459 | 460 | body .screen.screen-setup-bar .actions{ 461 | text-align: center; 462 | } 463 | 464 | 465 | /* ------------------------------------------------------------------------------------------------ */ 466 | /* Progress bar 467 | /* ------------------------------------------------------------------------------------------------ */ 468 | body .screen.screen-progress-bar .inner{ 469 | width: 600px !important; 470 | margin-left: auto; 471 | margin-right: auto; 472 | } 473 | 474 | body .screen.screen-progress-bar .txt{ 475 | padding-top: 40px; 476 | padding-bottom: 40px; 477 | } 478 | 479 | 480 | 481 | /* ------------------------------------------------------------------------------------------------ */ 482 | /* Uplaod File 483 | /* ------------------------------------------------------------------------------------------------ */ 484 | 485 | #browse-file{ 486 | position: absolute; 487 | top: 0px; 488 | left: 0px; 489 | z-index: -1; 490 | visibility: hidden; 491 | } 492 | 493 | 494 | 495 | 496 | /* ------------------------------------------------------------------------------------------------ */ 497 | /* Flash message 498 | /* ------------------------------------------------------------------------------------------------ */ 499 | 500 | .flashmsg{ 501 | font-size: 18px; 502 | color: #fff; 503 | min-height: 65px; 504 | text-align: center 505 | } 506 | 507 | .flashmsg div{ 508 | padding-top: 20px; 509 | padding-bottom: 20px; 510 | display: none; 511 | } 512 | 513 | .flashmsg div p{ 514 | display: inline; 515 | } 516 | 517 | .flashmsg div strong{ 518 | color: #0fc95e; 519 | } 520 | 521 | .flashmsg div.error{ 522 | background-color: #9a1a1c; 523 | 524 | } 525 | 526 | .flashmsg div.success{ 527 | background-color: #0fc95e; 528 | } 529 | 530 | .flashmsg div.info{ 531 | 532 | } 533 | 534 | .flashmsg div.warning{ 535 | background-color: #ffb600; 536 | color: #4F523A !important; 537 | } 538 | 539 | .flashmsg div.warning p{ 540 | color: #4F523A !important; 541 | } 542 | 543 | 544 | /* ------------------------------------------------------------------------------------------------ */ 545 | /* Spinner 546 | /* ------------------------------------------------------------------------------------------------ */ 547 | 548 | 549 | #floatingBarsG{ 550 | position:relative; 551 | width:80px; 552 | height:99px; 553 | margin-left: auto; 554 | margin-right: auto; 555 | } 556 | 557 | .blockG{ 558 | position:absolute; 559 | background-color:#FFFFFF; 560 | width:13px; 561 | height:31px; 562 | -moz-border-radius:11px 11px 0 0; 563 | -moz-transform:scale(0.4); 564 | -moz-animation-name:fadeG; 565 | -moz-animation-duration:1.6800000000000002s; 566 | -moz-animation-iteration-count:infinite; 567 | -moz-animation-direction:linear; 568 | -webkit-border-radius:11px 11px 0 0; 569 | -webkit-transform:scale(0.4); 570 | -webkit-animation-name:fadeG; 571 | -webkit-animation-duration:1.6800000000000002s; 572 | -webkit-animation-iteration-count:infinite; 573 | -webkit-animation-direction:linear; 574 | -ms-border-radius:11px 11px 0 0; 575 | -ms-transform:scale(0.4); 576 | -ms-animation-name:fadeG; 577 | -ms-animation-duration:1.6800000000000002s; 578 | -ms-animation-iteration-count:infinite; 579 | -ms-animation-direction:linear; 580 | -o-border-radius:11px 11px 0 0; 581 | -o-transform:scale(0.4); 582 | -o-animation-name:fadeG; 583 | -o-animation-duration:1.6800000000000002s; 584 | -o-animation-iteration-count:infinite; 585 | -o-animation-direction:linear; 586 | border-radius:11px 11px 0 0; 587 | transform:scale(0.4); 588 | animation-name:fadeG; 589 | animation-duration:1.6800000000000002s; 590 | animation-iteration-count:infinite; 591 | animation-direction:linear; 592 | } 593 | 594 | #rotateG_01{ 595 | left:0; 596 | top:36px; 597 | -moz-animation-delay:0.6300000000000001s; 598 | -moz-transform:rotate(-90deg); 599 | -webkit-animation-delay:0.6300000000000001s; 600 | -webkit-transform:rotate(-90deg); 601 | -ms-animation-delay:0.6300000000000001s; 602 | -ms-transform:rotate(-90deg); 603 | -o-animation-delay:0.6300000000000001s; 604 | -o-transform:rotate(-90deg); 605 | animation-delay:0.6300000000000001s; 606 | transform:rotate(-90deg); 607 | } 608 | 609 | #rotateG_02{ 610 | left:10px; 611 | top:13px; 612 | -moz-animation-delay:0.8400000000000001s; 613 | -moz-transform:rotate(-45deg); 614 | -webkit-animation-delay:0.8400000000000001s; 615 | -webkit-transform:rotate(-45deg); 616 | -ms-animation-delay:0.8400000000000001s; 617 | -ms-transform:rotate(-45deg); 618 | -o-animation-delay:0.8400000000000001s; 619 | -o-transform:rotate(-45deg); 620 | animation-delay:0.8400000000000001s; 621 | transform:rotate(-45deg); 622 | } 623 | 624 | #rotateG_03{ 625 | left:34px; 626 | top:4px; 627 | -moz-animation-delay:1.05s; 628 | -moz-transform:rotate(0deg); 629 | -webkit-animation-delay:1.05s; 630 | -webkit-transform:rotate(0deg); 631 | -ms-animation-delay:1.05s; 632 | -ms-transform:rotate(0deg); 633 | -o-animation-delay:1.05s; 634 | -o-transform:rotate(0deg); 635 | animation-delay:1.05s; 636 | transform:rotate(0deg); 637 | } 638 | 639 | #rotateG_04{ 640 | right:10px; 641 | top:13px; 642 | -moz-animation-delay:1.2600000000000002s; 643 | -moz-transform:rotate(45deg); 644 | -webkit-animation-delay:1.2600000000000002s; 645 | -webkit-transform:rotate(45deg); 646 | -ms-animation-delay:1.2600000000000002s; 647 | -ms-transform:rotate(45deg); 648 | -o-animation-delay:1.2600000000000002s; 649 | -o-transform:rotate(45deg); 650 | animation-delay:1.2600000000000002s; 651 | transform:rotate(45deg); 652 | } 653 | 654 | #rotateG_05{ 655 | right:0; 656 | top:36px; 657 | -moz-animation-delay:1.4700000000000002s; 658 | -moz-transform:rotate(90deg); 659 | -webkit-animation-delay:1.4700000000000002s; 660 | -webkit-transform:rotate(90deg); 661 | -ms-animation-delay:1.4700000000000002s; 662 | -ms-transform:rotate(90deg); 663 | -o-animation-delay:1.4700000000000002s; 664 | -o-transform:rotate(90deg); 665 | animation-delay:1.4700000000000002s; 666 | transform:rotate(90deg); 667 | } 668 | 669 | #rotateG_06{ 670 | right:10px; 671 | bottom:9px; 672 | -moz-animation-delay:1.6800000000000002s; 673 | -moz-transform:rotate(135deg); 674 | -webkit-animation-delay:1.6800000000000002s; 675 | -webkit-transform:rotate(135deg); 676 | -ms-animation-delay:1.6800000000000002s; 677 | -ms-transform:rotate(135deg); 678 | -o-animation-delay:1.6800000000000002s; 679 | -o-transform:rotate(135deg); 680 | animation-delay:1.6800000000000002s; 681 | transform:rotate(135deg); 682 | } 683 | 684 | #rotateG_07{ 685 | bottom:0; 686 | left:34px; 687 | -moz-animation-delay:1.8900000000000001s; 688 | -moz-transform:rotate(180deg); 689 | -webkit-animation-delay:1.8900000000000001s; 690 | -webkit-transform:rotate(180deg); 691 | -ms-animation-delay:1.8900000000000001s; 692 | -ms-transform:rotate(180deg); 693 | -o-animation-delay:1.8900000000000001s; 694 | -o-transform:rotate(180deg); 695 | animation-delay:1.8900000000000001s; 696 | transform:rotate(180deg); 697 | } 698 | 699 | #rotateG_08{ 700 | left:10px; 701 | bottom:9px; 702 | -moz-animation-delay:2.1s; 703 | -moz-transform:rotate(-135deg); 704 | -webkit-animation-delay:2.1s; 705 | -webkit-transform:rotate(-135deg); 706 | -ms-animation-delay:2.1s; 707 | -ms-transform:rotate(-135deg); 708 | -o-animation-delay:2.1s; 709 | -o-transform:rotate(-135deg); 710 | animation-delay:2.1s; 711 | transform:rotate(-135deg); 712 | } 713 | 714 | @-moz-keyframes fadeG{ 715 | 0%{ 716 | background-color:#8caaa8} 717 | 718 | 100%{ 719 | background-color:#FFFFFF} 720 | 721 | } 722 | 723 | @-webkit-keyframes fadeG{ 724 | 0%{ 725 | background-color:#8caaa8} 726 | 727 | 100%{ 728 | background-color:#FFFFFF} 729 | 730 | } 731 | 732 | @-ms-keyframes fadeG{ 733 | 0%{ 734 | background-color:#8caaa8} 735 | 736 | 100%{ 737 | background-color:#FFFFFF} 738 | 739 | } 740 | 741 | @-o-keyframes fadeG{ 742 | 0%{ 743 | background-color:#8caaa8} 744 | 745 | 100%{ 746 | background-color:#FFFFFF} 747 | 748 | } 749 | 750 | @keyframes fadeG{ 751 | 0%{ 752 | background-color:#8caaa8} 753 | 754 | 100%{ 755 | background-color:#FFFFFF} 756 | 757 | } 758 | -------------------------------------------------------------------------------- /resources/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn-default .badge, 33 | .btn-primary .badge, 34 | .btn-success .badge, 35 | .btn-info .badge, 36 | .btn-warning .badge, 37 | .btn-danger .badge { 38 | text-shadow: none; 39 | } 40 | .btn:active, 41 | .btn.active { 42 | background-image: none; 43 | } 44 | .btn-default { 45 | text-shadow: 0 1px 0 #fff; 46 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 47 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 48 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 49 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 52 | background-repeat: repeat-x; 53 | border-color: #dbdbdb; 54 | border-color: #ccc; 55 | } 56 | .btn-default:hover, 57 | .btn-default:focus { 58 | background-color: #e0e0e0; 59 | background-position: 0 -15px; 60 | } 61 | .btn-default:active, 62 | .btn-default.active { 63 | background-color: #e0e0e0; 64 | border-color: #dbdbdb; 65 | } 66 | .btn-default:disabled, 67 | .btn-default[disabled] { 68 | background-color: #e0e0e0; 69 | background-image: none; 70 | } 71 | .btn-primary { 72 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 73 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 74 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 75 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 76 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 77 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 78 | background-repeat: repeat-x; 79 | border-color: #245580; 80 | } 81 | .btn-primary:hover, 82 | .btn-primary:focus { 83 | background-color: #265a88; 84 | background-position: 0 -15px; 85 | } 86 | .btn-primary:active, 87 | .btn-primary.active { 88 | background-color: #265a88; 89 | border-color: #245580; 90 | } 91 | .btn-primary:disabled, 92 | .btn-primary[disabled] { 93 | background-color: #265a88; 94 | background-image: none; 95 | } 96 | .btn-success { 97 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 98 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 99 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 100 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 101 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 102 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 103 | background-repeat: repeat-x; 104 | border-color: #3e8f3e; 105 | } 106 | .btn-success:hover, 107 | .btn-success:focus { 108 | background-color: #419641; 109 | background-position: 0 -15px; 110 | } 111 | .btn-success:active, 112 | .btn-success.active { 113 | background-color: #419641; 114 | border-color: #3e8f3e; 115 | } 116 | .btn-success:disabled, 117 | .btn-success[disabled] { 118 | background-color: #419641; 119 | background-image: none; 120 | } 121 | .btn-info { 122 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 123 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 124 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 125 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 126 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 127 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 128 | background-repeat: repeat-x; 129 | border-color: #28a4c9; 130 | } 131 | .btn-info:hover, 132 | .btn-info:focus { 133 | background-color: #2aabd2; 134 | background-position: 0 -15px; 135 | } 136 | .btn-info:active, 137 | .btn-info.active { 138 | background-color: #2aabd2; 139 | border-color: #28a4c9; 140 | } 141 | .btn-info:disabled, 142 | .btn-info[disabled] { 143 | background-color: #2aabd2; 144 | background-image: none; 145 | } 146 | .btn-warning { 147 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 148 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 149 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 150 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 151 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 152 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 153 | background-repeat: repeat-x; 154 | border-color: #e38d13; 155 | } 156 | .btn-warning:hover, 157 | .btn-warning:focus { 158 | background-color: #eb9316; 159 | background-position: 0 -15px; 160 | } 161 | .btn-warning:active, 162 | .btn-warning.active { 163 | background-color: #eb9316; 164 | border-color: #e38d13; 165 | } 166 | .btn-warning:disabled, 167 | .btn-warning[disabled] { 168 | background-color: #eb9316; 169 | background-image: none; 170 | } 171 | .btn-danger { 172 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 173 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 174 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 175 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 176 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 177 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 178 | background-repeat: repeat-x; 179 | border-color: #b92c28; 180 | } 181 | .btn-danger:hover, 182 | .btn-danger:focus { 183 | background-color: #c12e2a; 184 | background-position: 0 -15px; 185 | } 186 | .btn-danger:active, 187 | .btn-danger.active { 188 | background-color: #c12e2a; 189 | border-color: #b92c28; 190 | } 191 | .btn-danger:disabled, 192 | .btn-danger[disabled] { 193 | background-color: #c12e2a; 194 | background-image: none; 195 | } 196 | .thumbnail, 197 | .img-thumbnail { 198 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 199 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 200 | } 201 | .dropdown-menu > li > a:hover, 202 | .dropdown-menu > li > a:focus { 203 | background-color: #e8e8e8; 204 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 205 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 206 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 207 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 208 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 209 | background-repeat: repeat-x; 210 | } 211 | .dropdown-menu > .active > a, 212 | .dropdown-menu > .active > a:hover, 213 | .dropdown-menu > .active > a:focus { 214 | background-color: #2e6da4; 215 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 216 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 217 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 218 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 220 | background-repeat: repeat-x; 221 | } 222 | .navbar-default { 223 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 224 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 225 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 226 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 227 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 228 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 229 | background-repeat: repeat-x; 230 | border-radius: 4px; 231 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 232 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 233 | } 234 | .navbar-default .navbar-nav > .open > a, 235 | .navbar-default .navbar-nav > .active > a { 236 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 237 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 238 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 239 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 240 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 241 | background-repeat: repeat-x; 242 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 243 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 244 | } 245 | .navbar-brand, 246 | .navbar-nav > li > a { 247 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 248 | } 249 | .navbar-inverse { 250 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 251 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 252 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 253 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 254 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 255 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 256 | background-repeat: repeat-x; 257 | } 258 | .navbar-inverse .navbar-nav > .open > a, 259 | .navbar-inverse .navbar-nav > .active > a { 260 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 261 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 262 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 263 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 264 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 265 | background-repeat: repeat-x; 266 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 267 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 268 | } 269 | .navbar-inverse .navbar-brand, 270 | .navbar-inverse .navbar-nav > li > a { 271 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 272 | } 273 | .navbar-static-top, 274 | .navbar-fixed-top, 275 | .navbar-fixed-bottom { 276 | border-radius: 0; 277 | } 278 | @media (max-width: 767px) { 279 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 280 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 281 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 282 | color: #fff; 283 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 284 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 285 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 286 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 288 | background-repeat: repeat-x; 289 | } 290 | } 291 | .alert { 292 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 293 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 294 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 295 | } 296 | .alert-success { 297 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 298 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 299 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 300 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 301 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 302 | background-repeat: repeat-x; 303 | border-color: #b2dba1; 304 | } 305 | .alert-info { 306 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 307 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 308 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 309 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 310 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 311 | background-repeat: repeat-x; 312 | border-color: #9acfea; 313 | } 314 | .alert-warning { 315 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 316 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 317 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 318 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 319 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 320 | background-repeat: repeat-x; 321 | border-color: #f5e79e; 322 | } 323 | .alert-danger { 324 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 325 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 326 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 327 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 328 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 329 | background-repeat: repeat-x; 330 | border-color: #dca7a7; 331 | } 332 | .progress { 333 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 334 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 335 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 336 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 337 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 338 | background-repeat: repeat-x; 339 | } 340 | .progress-bar { 341 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 342 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 343 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 344 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 345 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 346 | background-repeat: repeat-x; 347 | } 348 | .progress-bar-success { 349 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 350 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 351 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 352 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 353 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 354 | background-repeat: repeat-x; 355 | } 356 | .progress-bar-info { 357 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 358 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 359 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 360 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 361 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 362 | background-repeat: repeat-x; 363 | } 364 | .progress-bar-warning { 365 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 366 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 367 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 368 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 369 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 370 | background-repeat: repeat-x; 371 | } 372 | .progress-bar-danger { 373 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 374 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 375 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 376 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 377 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 378 | background-repeat: repeat-x; 379 | } 380 | .progress-bar-striped { 381 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 382 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 383 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 384 | } 385 | .list-group { 386 | border-radius: 4px; 387 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 388 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 389 | } 390 | .list-group-item.active, 391 | .list-group-item.active:hover, 392 | .list-group-item.active:focus { 393 | text-shadow: 0 -1px 0 #286090; 394 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 395 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 396 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 397 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 398 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 399 | background-repeat: repeat-x; 400 | border-color: #2b669a; 401 | } 402 | .list-group-item.active .badge, 403 | .list-group-item.active:hover .badge, 404 | .list-group-item.active:focus .badge { 405 | text-shadow: none; 406 | } 407 | .panel { 408 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 409 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 410 | } 411 | .panel-default > .panel-heading { 412 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 413 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 414 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 415 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 416 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 417 | background-repeat: repeat-x; 418 | } 419 | .panel-primary > .panel-heading { 420 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 421 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 422 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 423 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 424 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 425 | background-repeat: repeat-x; 426 | } 427 | .panel-success > .panel-heading { 428 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 429 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 430 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 431 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 432 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 433 | background-repeat: repeat-x; 434 | } 435 | .panel-info > .panel-heading { 436 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 437 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 438 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 439 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 440 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 441 | background-repeat: repeat-x; 442 | } 443 | .panel-warning > .panel-heading { 444 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 445 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 446 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 447 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 448 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 449 | background-repeat: repeat-x; 450 | } 451 | .panel-danger > .panel-heading { 452 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 453 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 454 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 455 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 456 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 457 | background-repeat: repeat-x; 458 | } 459 | .well { 460 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 461 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 462 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 463 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 464 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 465 | background-repeat: repeat-x; 466 | border-color: #dcdcdc; 467 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 468 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 469 | } 470 | /*# sourceMappingURL=bootstrap-theme.css.map */ 471 | --------------------------------------------------------------------------------
Generated by IcoMoon
!!! Limited for video of 5min duration !!!
Your last upload was incomplete, you can resume te upload by just draging or selecting the file again.
Drop video or select file
Checking your video, one moment, please
Video size or format not supported. Try again with another file.
Select your video size:
Output options:
Your video is being uploaded, please be patient.
Your video is converting on our servers, please be patinet.
Your video is converted and is waiting for you to download