├── .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 |
基于光学传输网络的单向下载工具
27 |