├── .gitignore ├── LICENSE ├── README.md ├── _config.yaml ├── cmd ├── encode.go └── root.go ├── encode └── qrcode.go ├── go.mod ├── go.sum ├── index.html └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | otn-downloader 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alingse/otn-downloader/72334157445a5a61ba0437882c8a861e675bc8ae/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # otn-downloader 2 | 3 | 一个基于单向光学传输网络的下载工具 4 | 5 | a downloader based on the single direction optical transport network (aka data over video) 6 | 7 | 把数据转换成二维码流, 拿手机扫,但是避免使用任何传统互联网,USB 等设备,只需要一个屏幕,就能下载电脑的数据。 8 | 9 | 10 | ## Quick Start 11 | 12 | 1. 安装 cli 工具 13 | 14 | ```bash 15 | go install github.com/alingse/otn-downloader@main 16 | ``` 17 | 18 | 19 | 2. 手机访问 https://alingse.github.io/otn-downloader/index.html 20 | 21 | 点击开始,并给一下摄像头权限,如果有手机支架,建议使用手机支架。 22 | 23 | 24 | 3. 将数据转换为二维码流 25 | 26 | ```bash 27 | otn-downloader encode --fps 10 --loop 10 --chunk-size 60 -f index.html 28 | ``` 29 | 30 | 4. 如果某些切片缺失了,可以使用如下命令, 只输出特定切片的数据,以便补充 31 | 32 | ```bash 33 | otn-downloader encode --fps 10 --loop 100 --chunk-size 60 -f index.html -s 100 -s 104 -s 113 -s 124 34 | ``` 35 | 36 | 37 | ## 数据流向 38 | 39 | 1. data -> otn-downloader -> qrcode video -> terminal 40 | 41 | 2. html -> video -> mobile -> download -> data (use js) need https 42 | 43 | 3. otn-downloader start a server 44 | 45 | 4. video -> otn-downloader extract -> data (use go), this is not todo 46 | 47 | ## TODO 48 | 49 | 未来想做的事 50 | 51 | 1. 优化下载速度 52 | 2. 优化 index.html 的 js 53 | 3. 支持 Video 模式,先录制视频,再根据视频解码,可以使用 cli 也可以使用网页解码 54 | 4. 两台有摄像头和屏幕的电脑,可以通过互相扫描二维码,进行 TCP 通信。真正的 光学传输网络 55 | -------------------------------------------------------------------------------- /_config.yaml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: otn-downloader 3 | description: 单向光学传输网络下载工具 4 | show_downloads: false 5 | -------------------------------------------------------------------------------- /cmd/encode.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | 7 | "github.com/alingse/otn-downloader/encode" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var encodeCmd = &cobra.Command{ 12 | Use: "encode", 13 | Short: "encode data to the output", 14 | Long: ``, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | if *filename == "" { 17 | log.Fatalf("miss input file") 18 | } 19 | 20 | cfg := encode.Config{ 21 | Fps: *fps, 22 | ChunkSize: *chunkSize, 23 | Loop: *loop, 24 | Slices: parseInts(*slices), 25 | } 26 | encode.EncodToQRCode(*filename, cfg) 27 | }, 28 | } 29 | 30 | var ( 31 | fps *int 32 | chunkSize *int 33 | filename *string 34 | loop *int 35 | slices *[]string 36 | ) 37 | 38 | func init() { 39 | rootCmd.AddCommand(encodeCmd) 40 | fps = encodeCmd.Flags().Int("fps", 30, "the data encode fps") 41 | loop = encodeCmd.Flags().Int("loop", 3, "the number of times process") 42 | chunkSize = encodeCmd.Flags().IntP("chunk-size", "c", 60, "the chunk byte size of the input file") 43 | filename = encodeCmd.Flags().StringP("input-file", "f", "", "the source files") 44 | slices = encodeCmd.Flags().StringSliceP("slices", "s", []string{}, "this miss slice of the chunks") 45 | } 46 | 47 | func parseInts(strValues []string) map[int]bool { 48 | result := map[int]bool{} 49 | 50 | for _, str := range strValues { 51 | if val, err := strconv.Atoi(str); err == nil { 52 | result[val] = true 53 | } 54 | } 55 | 56 | return result 57 | } 58 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var rootCmd = &cobra.Command{ 10 | Use: "otn-downloader", 11 | Short: "single direction optical transport network downloader", 12 | Long: `The data is encode as video by the OTN downloader, then the other devices can extract data from the display device or terminal or from some video recorders`, 13 | } 14 | 15 | func Execute() { 16 | err := rootCmd.Execute() 17 | if err != nil { 18 | os.Exit(1) 19 | } 20 | } 21 | 22 | func init() { 23 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 24 | } 25 | -------------------------------------------------------------------------------- /encode/qrcode.go: -------------------------------------------------------------------------------- 1 | package encode 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/mdp/qrterminal/v3" 15 | ) 16 | 17 | type Key string 18 | 19 | const ( 20 | KeyMeta Key = "m" 21 | KeyData Key = "d" 22 | ) 23 | 24 | type Value struct { 25 | Key Key 26 | Index string 27 | Value string 28 | } 29 | 30 | type MetaValue struct { 31 | Filename string `json:"filename"` 32 | Total int `json:"total"` 33 | FileSize int `json:"file_size"` 34 | ChunkSize int `json:"chunk_size"` 35 | } 36 | 37 | func printQRCode(v Value) { 38 | fmt.Printf("\033[0;0H") 39 | text := fmt.Sprintf("%s:%s:%s", v.Key, v.Index, v.Value) 40 | qrterminal.Generate(text, qrterminal.L, os.Stdout) 41 | } 42 | 43 | func loadValues(filepath string, cfg Config) ([]Value, []Value, error) { 44 | file, err := os.Open(filepath) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | defer file.Close() 49 | 50 | datas := make([]Value, 0) 51 | buf := make([]byte, cfg.ChunkSize) 52 | s := 0 53 | i := 0 54 | 55 | for { 56 | n, err := file.Read(buf) 57 | if err == io.EOF { 58 | break 59 | } 60 | 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | s += n 66 | value := base64.StdEncoding.EncodeToString(buf[:n]) 67 | data := Value{ 68 | Key: KeyData, 69 | Index: strconv.FormatInt(int64(i), 10), 70 | Value: value, 71 | } 72 | datas = append(datas, data) 73 | i++ 74 | } 75 | 76 | _, filename := path.Split(filepath) 77 | meta := MetaValue{ 78 | Filename: filename, 79 | Total: i, 80 | FileSize: s, 81 | ChunkSize: cfg.ChunkSize, 82 | } 83 | metaData, _ := json.Marshal(meta) 84 | metas := []Value{ 85 | { 86 | Key: KeyMeta, 87 | Index: "json", 88 | Value: string(metaData), 89 | }, 90 | } 91 | 92 | return metas, datas, nil 93 | } 94 | 95 | const metaSleep = 5 * time.Second 96 | 97 | func encodeToQRCode(filename string, cfg Config) error { 98 | metas, datas, err := loadValues(filename, cfg) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | for _, v := range metas { 104 | printQRCode(v) 105 | time.Sleep(metaSleep) 106 | } 107 | 108 | d := 1 * time.Second / time.Duration(cfg.Fps) 109 | 110 | for i, v := range datas { 111 | if len(cfg.Slices) > 0 && !cfg.Slices[i] { 112 | continue 113 | } 114 | 115 | printQRCode(v) 116 | time.Sleep(d) 117 | } 118 | 119 | return nil 120 | } 121 | 122 | type Config struct { 123 | Fps int 124 | ChunkSize int 125 | Loop int 126 | Slices map[int]bool 127 | } 128 | 129 | func EncodToQRCode(filename string, cfg Config) { 130 | for range cfg.Loop { 131 | err := encodeToQRCode(filename, cfg) 132 | if err != nil { 133 | log.Fatalf("encode file to QRCode failed %+v", err) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alingse/otn-downloader 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/mdp/qrterminal/v3 v3.2.0 7 | github.com/spf13/cobra v1.8.1 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | golang.org/x/sys v0.14.0 // indirect 14 | golang.org/x/term v0.13.0 // indirect 15 | rsc.io/qr v0.2.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 3 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 4 | github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk= 5 | github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk= 6 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 8 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 12 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 13 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 14 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= 18 | rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OTN Downloader 7 | 20 | 21 | 22 | 23 | 24 |
25 |

OTN Downloader

26 |

基于光学传输网络的单向下载工具

27 |

设置

28 |
29 | FPS 30 | 31 | 32 |
33 | 34 |

下载

35 | 36 |
下载文件名:
37 |
38 |
0%
39 |
40 |
下载进度: 0%
41 |
切片进度:
42 |
切片缺失:
43 |
当前切片:
44 |
预计完成时间: --:--:--
45 |
错误信息:
46 |
识别详情: 47 |

 48 |     
49 |
50 |
51 |
52 |
53 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/alingse/otn-downloader/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | --------------------------------------------------------------------------------