From 89125db5a422388d3f38a48dfe83108e4b95b9b6 Mon Sep 17 00:00:00 2001 From: blackteay Date: Thu, 9 May 2024 16:11:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E4=BA=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FyneApp.toml | 2 +- fyne_metadata_init.go | 26 ++ main.go | 593 +----------------------------------------- paths.go | 101 +++++++ slice.go | 246 ++++++++++++++++++ theme.go | 54 ++++ upload.go | 267 +++++++++++++++++++ 7 files changed, 697 insertions(+), 592 deletions(-) create mode 100644 fyne_metadata_init.go create mode 100644 paths.go create mode 100644 slice.go create mode 100644 theme.go create mode 100644 upload.go diff --git a/FyneApp.toml b/FyneApp.toml index c021100..cf89dac 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -5,4 +5,4 @@ Website = "https://iteay.top" Name = "HLS-builder" ID = "top.iteay.hls-builder" Version = "1.0.0" - Build = 32 + Build = 33 diff --git a/fyne_metadata_init.go b/fyne_metadata_init.go new file mode 100644 index 0000000..17e5917 --- /dev/null +++ b/fyne_metadata_init.go @@ -0,0 +1,26 @@ +package main + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" +) + +func init() { + app.SetMetadata(fyne.AppMetadata{ + ID: "top.iteay.hls-builder", + Name: "HLS-builder.exe", + Version: "1.0.0", + Build: 32, + Icon: &fyne.StaticResource{ + StaticName: "Icon.png", + StaticContent: []byte{ + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 200, 0, 0, 0, 200, 8, 6, 0, 0, 0, 173, 88, 174, 158, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 32, 0, 73, 68, 65, 84, 120, 94, 237, 93, 9, 120, 19, 85, 215, 126, 239, 204, 36, 233, 94, 160, 101, 223, 151, 150, 125, 21, 16, 5, 21, 84, 220, 144, 69, 5, 63, 92, 80, 63, 181, 128, 242, 41, 250, 125, 130, 160, 162, 168, 160, 162, 172, 42, 42, 77, 65, 22, 113, 97, 19, 4, 11, 8, 40, 160, 32, 178, 8, 136, 64, 155, 150, 125, 223, 91, 218, 102, 105, 50, 185, 255, 127, 91, 170, 5, 219, 36, 51, 153, 100, 38, 233, 220, 231, 201, 211, 66, 238, 57, 247, 156, 247, 222, 183, 179, 220, 115, 207, 33, 208, 155, 142, 128, 142, 64, 185, 8, 16, 29, 27, 29, 1, 29, 129, 242, 17, 208, 9, 162, 175, 14, 29, 1, 15, 8, 232, 4, 209, 151, 135, 142, 128, 78, 16, 125, 13, 232, 8, 200, 67, 64, 191, 130, 200, 195, 77, 151, 170, 32, 8, 232, 4, 169, 32, 19, 173, 187, 41, 15, 1, 157, 32, 242, 112, 211, 165, 42, 8, 2, 58, 65, 42, 200, 68, 235, 110, 202, 67, 64, 39, 136, 60, 220, 116, 169, 10, 130, 128, 78, 144, 10, 50, 209, 186, 155, 242, 16, 208, 9, 34, 15, 55, 93, 170, 130, 32, 160, 19, 68, 230, 68, 83, 10, 178, 107, 54, 226, 227, 10, 17, 71, 121, 99, 156, 11, 238, 120, 129, 208, 56, 81, 68, 60, 8, 141, 35, 64, 60, 40, 76, 28, 144, 43, 114, 228, 50, 220, 184, 204, 19, 146, 235, 18, 201, 101, 131, 201, 153, 75, 92, 184, 76, 4, 228, 54, 252, 55, 236, 50, 77, 208, 197, 130, 128, 128, 78, 144, 50, 64, 62, 244, 57, 34, 156, 78, 67, 51, 16, 119, 115, 128, 52, 7, 104, 115, 66, 80, 155, 186, 73, 209, 226, 7, 91, 252, 64, 172, 66, 243, 83, 8, 32, 23, 192, 101, 128, 94, 6, 72, 46, 33, 216, 231, 22, 233, 126, 158, 227, 246, 21, 186, 93, 251, 91, 12, 193, 41, 133, 198, 210, 213, 72, 68, 160, 66, 19, 100, 111, 26, 170, 240, 16, 154, 243, 148, 54, 99, 68, 160, 160, 205, 1, 176, 79, 67, 137, 56, 6, 186, 251, 37, 2, 236, 115, 3, 251, 57, 66, 247, 193, 205, 237, 43, 164, 174, 253, 45, 135, 224, 104, 160, 7, 174, 232, 250, 43, 20, 65, 14, 164, 34, 217, 13, 238, 94, 74, 72, 239, 43, 68, 168, 30, 226, 11, 32, 31, 192, 62, 66, 200, 58, 2, 124, 223, 228, 105, 215, 166, 16, 247, 71, 115, 230, 135, 53, 65, 126, 26, 11, 161, 118, 45, 161, 59, 8, 238, 0, 208, 27, 96, 87, 138, 176, 110, 57, 4, 88, 1, 138, 85, 32, 226, 134, 164, 20, 28, 15, 107, 111, 131, 224, 92, 216, 17, 228, 176, 25, 13, 157, 148, 239, 1, 14, 221, 41, 101, 164, 64, 165, 32, 224, 168, 213, 33, 54, 0, 116, 29, 68, 110, 67, 242, 80, 215, 70, 173, 26, 169, 101, 187, 194, 130, 32, 25, 102, 99, 27, 30, 174, 251, 40, 72, 15, 0, 183, 104, 25, 112, 21, 109, 59, 4, 138, 13, 110, 138, 244, 102, 67, 196, 133, 42, 218, 17, 82, 67, 135, 44, 65, 14, 204, 64, 188, 200, 241, 247, 19, 224, 62, 90, 116, 251, 164, 55, 223, 17, 160, 123, 9, 176, 16, 188, 123, 97, 210, 147, 216, 231, 187, 92, 197, 235, 25, 114, 4, 201, 78, 21, 110, 20, 9, 125, 152, 17, 3, 64, 173, 138, 55, 101, 138, 123, 188, 144, 82, 124, 213, 116, 176, 248, 173, 226, 154, 195, 64, 97, 200, 16, 36, 51, 149, 191, 143, 16, 12, 66, 49, 49, 244, 166, 48, 2, 20, 216, 76, 40, 157, 23, 23, 225, 158, 87, 227, 49, 20, 40, 172, 62, 100, 213, 105, 158, 32, 89, 105, 252, 147, 110, 138, 167, 8, 112, 99, 200, 162, 28, 90, 134, 31, 32, 160, 243, 98, 77, 238, 137, 58, 81, 0, 205, 18, 36, 203, 44, 116, 167, 148, 190, 4, 130, 94, 161, 181, 190, 194, 196, 90, 66, 119, 19, 74, 38, 38, 165, 136, 95, 132, 137, 71, 178, 220, 208, 28, 65, 178, 103, 161, 174, 91, 20, 94, 2, 232, 243, 178, 60, 210, 133, 20, 69, 128, 2, 223, 186, 41, 153, 216, 124, 176, 107, 179, 162, 138, 67, 68, 153, 166, 8, 146, 101, 230, 251, 83, 96, 2, 128, 70, 33, 130, 95, 197, 49, 147, 210, 49, 201, 131, 221, 227, 42, 142, 195, 197, 158, 106, 134, 32, 153, 169, 194, 59, 132, 208, 209, 21, 109, 2, 66, 201, 95, 66, 176, 202, 237, 230, 94, 111, 58, 216, 185, 45, 148, 236, 246, 199, 86, 213, 9, 146, 57, 211, 208, 150, 184, 221, 236, 170, 113, 167, 63, 142, 232, 178, 193, 65, 128, 82, 228, 115, 132, 142, 73, 74, 113, 79, 13, 206, 136, 234, 142, 162, 42, 65, 50, 83, 249, 20, 66, 240, 14, 128, 68, 117, 97, 208, 71, 151, 140, 0, 197, 236, 228, 193, 226, 191, 37, 203, 133, 152, 128, 106, 4, 201, 74, 229, 63, 162, 4, 255, 9, 49, 188, 116, 115, 175, 70, 96, 107, 114, 138, 120, 125, 56, 131, 162, 10, 65, 44, 102, 254, 7, 0, 61, 195, 25, 216, 138, 226, 27, 5, 10, 154, 166, 136, 49, 225, 234, 111, 208, 9, 98, 49, 243, 159, 2, 24, 26, 174, 128, 86, 80, 191, 178, 147, 83, 196, 164, 112, 244, 61, 168, 4, 201, 156, 193, 141, 37, 28, 121, 35, 28, 129, 212, 125, 194, 146, 228, 20, 241, 129, 112, 195, 33, 104, 4, 209, 201, 17, 110, 75, 231, 159, 254, 16, 96, 65, 82, 138, 248, 175, 112, 242, 52, 40, 4, 177, 152, 133, 158, 0, 101, 207, 29, 122, 11, 119, 4, 8, 29, 145, 252, 180, 123, 98, 184, 184, 25, 112, 130, 236, 158, 139, 232, 200, 66, 126, 29, 40, 194, 250, 109, 71, 184, 44, 8, 5, 252, 176, 139, 148, 220, 22, 46, 161, 41, 1, 39, 72, 102, 170, 48, 153, 16, 250, 162, 2, 192, 235, 42, 66, 4, 1, 2, 252, 152, 148, 34, 222, 22, 34, 230, 122, 52, 51, 160, 4, 217, 151, 102, 108, 37, 80, 113, 39, 0, 33, 28, 192, 242, 215, 7, 91, 33, 144, 111, 3, 242, 236, 197, 63, 139, 62, 118, 32, 239, 202, 207, 226, 127, 147, 162, 255, 43, 249, 78, 116, 3, 17, 6, 192, 116, 229, 19, 97, 44, 254, 157, 253, 95, 164, 137, 162, 82, 20, 16, 31, 93, 252, 169, 196, 126, 70, 21, 255, 140, 142, 240, 215, 90, 255, 228, 41, 193, 19, 77, 159, 22, 231, 248, 167, 69, 125, 233, 128, 18, 196, 146, 38, 124, 0, 22, 178, 94, 193, 218, 145, 179, 192, 129, 211, 87, 62, 167, 72, 241, 239, 167, 0, 70, 144, 96, 53, 70, 164, 134, 213, 129, 70, 213, 217, 79, 90, 244, 123, 201, 135, 125, 23, 132, 182, 62, 57, 69, 100, 57, 2, 66, 186, 5, 140, 32, 89, 102, 212, 161, 224, 217, 213, 35, 108, 195, 72, 216, 162, 47, 89, 252, 7, 78, 19, 100, 95, 249, 183, 75, 212, 246, 154, 104, 80, 29, 104, 89, 143, 125, 232, 149, 159, 64, 180, 73, 121, 155, 221, 64, 191, 102, 41, 226, 50, 229, 53, 7, 79, 99, 224, 8, 146, 198, 61, 75, 41, 153, 30, 60, 87, 2, 63, 210, 190, 99, 192, 150, 76, 96, 211, 126, 130, 205, 251, 3, 63, 94, 48, 71, 96, 164, 233, 208, 8, 232, 210, 148, 162, 93, 35, 160, 174, 2, 127, 214, 40, 48, 183, 105, 138, 248, 120, 48, 253, 80, 122, 172, 128, 17, 196, 98, 230, 151, 132, 250, 249, 241, 131, 103, 128, 157, 7, 128, 173, 22, 130, 77, 25, 192, 133, 203, 74, 195, 175, 93, 125, 141, 107, 2, 29, 27, 3, 109, 26, 82, 116, 108, 2, 212, 171, 42, 221, 86, 10, 156, 108, 154, 34, 214, 150, 46, 169, 29, 137, 64, 18, 132, 45, 39, 165, 18, 60, 7, 5, 177, 147, 23, 129, 157, 7, 25, 41, 8, 182, 103, 3, 153, 39, 130, 50, 172, 230, 7, 225, 56, 160, 123, 43, 246, 161, 184, 165, 53, 80, 141, 165, 238, 246, 177, 113, 60, 233, 218, 228, 201, 208, 61, 141, 24, 16, 130, 236, 55, 11, 221, 121, 208, 159, 124, 196, 80, 245, 110, 171, 126, 7, 86, 253, 78, 176, 250, 119, 213, 77, 209, 188, 1, 81, 38, 160, 123, 235, 98, 178, 220, 217, 1, 48, 122, 121, 63, 73, 65, 199, 54, 77, 113, 191, 169, 121, 199, 202, 49, 48, 32, 4, 177, 152, 249, 251, 1, 44, 214, 50, 40, 236, 129, 186, 132, 20, 236, 119, 189, 73, 71, 160, 65, 53, 224, 222, 78, 20, 247, 118, 2, 234, 87, 43, 79, 158, 124, 152, 156, 226, 26, 46, 93, 187, 54, 36, 2, 66, 144, 172, 52, 254, 105, 74, 97, 214, 134, 139, 87, 91, 177, 247, 40, 176, 224, 23, 130, 5, 191, 104, 209, 186, 208, 180, 137, 237, 203, 48, 146, 244, 234, 68, 113, 67, 211, 171, 125, 160, 192, 23, 77, 83, 68, 150, 207, 44, 36, 91, 64, 8, 98, 73, 227, 70, 130, 18, 118, 140, 86, 51, 77, 235, 196, 72, 170, 77, 220, 221, 90, 114, 214, 132, 24, 206, 89, 37, 142, 56, 43, 71, 19, 87, 124, 12, 113, 85, 138, 230, 92, 12, 196, 11, 121, 224, 207, 230, 184, 141, 103, 114, 96, 60, 117, 81, 52, 158, 56, 79, 141, 199, 206, 187, 133, 99, 231, 40, 207, 17, 32, 54, 18, 238, 232, 72, 34, 70, 153, 216, 7, 98, 164, 17, 162, 137, 125, 4, 78, 52, 8, 148, 242, 28, 40, 1, 33, 78, 209, 29, 97, 117, 192, 120, 250, 18, 53, 236, 57, 236, 230, 220, 110, 101, 167, 168, 71, 107, 224, 193, 155, 104, 209, 51, 203, 149, 150, 158, 156, 34, 134, 108, 234, 166, 192, 16, 36, 149, 27, 10, 66, 216, 185, 15, 213, 219, 137, 11, 192, 172, 181, 4, 95, 110, 80, 221, 148, 34, 3, 218, 54, 228, 196, 27, 155, 115, 5, 29, 154, 240, 121, 45, 27, 8, 214, 6, 213, 5, 174, 74, 44, 170, 16, 66, 42, 203, 181, 240, 212, 69, 55, 78, 93, 164, 40, 253, 115, 231, 1, 17, 191, 103, 139, 69, 255, 231, 169, 53, 172, 206, 185, 106, 39, 18, 91, 66, 28, 231, 136, 48, 80, 183, 213, 238, 230, 15, 159, 161, 145, 123, 142, 208, 40, 185, 246, 48, 57, 246, 156, 50, 172, 23, 69, 235, 250, 248, 46, 233, 105, 177, 175, 63, 186, 212, 148, 13, 8, 65, 180, 242, 144, 62, 231, 71, 96, 214, 26, 130, 179, 172, 192, 89, 144, 91, 141, 202, 132, 14, 188, 69, 200, 237, 208, 152, 207, 107, 94, 159, 183, 215, 175, 198, 243, 113, 81, 92, 53, 66, 16, 212, 211, 119, 199, 206, 185, 177, 251, 144, 136, 93, 87, 8, 179, 51, 91, 196, 73, 47, 164, 97, 80, 213, 168, 204, 137, 245, 170, 17, 91, 213, 120, 184, 140, 2, 225, 115, 10, 104, 228, 79, 187, 69, 201, 33, 67, 93, 146, 97, 217, 180, 215, 117, 205, 141, 87, 144, 39, 195, 143, 225, 2, 66, 144, 204, 57, 168, 77, 10, 121, 213, 138, 183, 252, 248, 71, 241, 85, 99, 71, 182, 31, 200, 72, 20, 101, 111, 115, 6, 245, 224, 47, 247, 190, 209, 120, 182, 107, 115, 35, 226, 162, 73, 19, 137, 42, 130, 214, 61, 243, 184, 136, 221, 7, 255, 254, 172, 217, 89, 116, 23, 231, 181, 177, 103, 141, 78, 201, 124, 126, 173, 4, 82, 144, 103, 117, 11, 155, 246, 185, 171, 92, 182, 122, 78, 29, 245, 124, 111, 44, 158, 178, 196, 213, 223, 171, 114, 141, 118, 8, 8, 65, 152, 175, 89, 102, 126, 37, 5, 238, 10, 166, 223, 246, 66, 224, 195, 21, 4, 159, 175, 13, 206, 168, 3, 186, 241, 5, 247, 116, 18, 206, 222, 212, 218, 104, 171, 147, 200, 55, 32, 4, 126, 221, 150, 4, 199, 234, 127, 142, 146, 113, 76, 68, 250, 54, 23, 210, 183, 57, 241, 243, 159, 190, 145, 133, 105, 105, 80, 157, 115, 182, 168, 207, 229, 25, 121, 106, 248, 253, 0, 141, 61, 122, 246, 159, 183, 115, 25, 211, 209, 61, 105, 176, 75, 35, 55, 184, 210, 17, 14, 24, 65, 50, 83, 249, 129, 132, 224, 43, 233, 38, 201, 147, 216, 156, 1, 124, 184, 156, 96, 247, 33, 121, 242, 190, 72, 117, 106, 194, 137, 15, 223, 102, 56, 123, 75, 43, 225, 114, 114, 29, 33, 198, 32, 144, 144, 222, 37, 46, 203, 103, 246, 220, 242, 253, 86, 103, 17, 89, 216, 239, 82, 218, 117, 73, 188, 163, 70, 101, 66, 78, 95, 162, 198, 29, 89, 34, 122, 180, 161, 167, 215, 238, 16, 107, 74, 209, 161, 181, 190, 1, 35, 8, 115, 212, 98, 230, 127, 5, 208, 37, 208, 78, 127, 252, 61, 193, 244, 239, 3, 55, 202, 240, 190, 134, 156, 65, 183, 25, 207, 181, 110, 104, 96, 132, 8, 201, 171, 132, 28, 116, 216, 213, 132, 17, 37, 125, 171, 11, 25, 199, 165, 145, 165, 121, 93, 174, 176, 117, 35, 254, 96, 237, 4, 50, 106, 226, 55, 214, 144, 13, 88, 12, 44, 65, 82, 249, 39, 64, 240, 185, 156, 201, 241, 85, 230, 197, 52, 2, 182, 19, 174, 116, 107, 85, 159, 19, 71, 14, 48, 158, 188, 167, 179, 209, 21, 27, 201, 105, 173, 44, 180, 210, 238, 122, 213, 199, 174, 42, 105, 171, 11, 145, 190, 213, 233, 181, 239, 181, 29, 186, 182, 16, 78, 39, 213, 225, 150, 207, 252, 222, 58, 88, 178, 176, 202, 2, 1, 37, 72, 209, 179, 72, 26, 63, 151, 210, 162, 194, 55, 138, 183, 222, 111, 23, 135, 152, 43, 217, 24, 49, 222, 28, 100, 58, 118, 79, 39, 83, 12, 199, 133, 111, 168, 190, 92, 204, 252, 33, 74, 139, 186, 156, 181, 67, 50, 191, 100, 206, 42, 91, 64, 214, 131, 92, 159, 60, 201, 5, 156, 32, 7, 62, 67, 146, 200, 243, 235, 149, 46, 151, 214, 252, 89, 101, 77, 111, 89, 143, 19, 223, 122, 204, 116, 236, 174, 142, 166, 24, 129, 215, 137, 225, 109, 177, 249, 69, 148, 122, 156, 181, 67, 82, 104, 16, 69, 217, 85, 86, 14, 170, 150, 52, 110, 8, 40, 249, 204, 27, 232, 190, 126, 223, 111, 60, 81, 52, 210, 54, 245, 121, 211, 137, 71, 110, 141, 20, 4, 30, 213, 125, 181, 65, 239, 87, 140, 128, 63, 68, 105, 219, 144, 191, 124, 125, 83, 238, 211, 79, 151, 219, 70, 105, 21, 207, 160, 16, 132, 57, 111, 49, 243, 11, 0, 12, 240, 23, 8, 37, 159, 57, 94, 232, 103, 200, 25, 49, 32, 234, 124, 98, 156, 118, 247, 44, 252, 197, 43, 88, 242, 140, 40, 211, 151, 59, 176, 110, 151, 239, 175, 137, 75, 108, 187, 181, 157, 112, 176, 101, 3, 225, 229, 169, 11, 243, 23, 5, 203, 94, 95, 199, 9, 26, 65, 178, 211, 140, 173, 220, 16, 215, 131, 34, 193, 87, 227, 174, 237, 55, 122, 46, 193, 210, 45, 114, 165, 255, 150, 107, 223, 152, 115, 205, 120, 62, 242, 80, 219, 70, 134, 176, 76, 151, 233, 63, 66, 242, 53, 124, 178, 194, 129, 73, 139, 29, 56, 126, 94, 90, 144, 87, 116, 4, 161, 119, 118, 20, 54, 44, 252, 201, 166, 169, 115, 236, 65, 35, 72, 209, 85, 36, 149, 27, 14, 66, 100, 213, 149, 96, 209, 183, 111, 124, 233, 191, 185, 111, 14, 50, 158, 127, 241, 190, 200, 66, 147, 129, 232, 37, 164, 229, 243, 192, 163, 36, 11, 111, 153, 180, 196, 129, 79, 87, 56, 36, 143, 192, 222, 120, 93, 223, 148, 123, 234, 131, 5, 214, 116, 201, 194, 1, 16, 240, 127, 197, 73, 52, 202, 98, 230, 151, 3, 184, 87, 138, 88, 214, 73, 224, 137, 105, 4, 23, 243, 164, 72, 93, 221, 183, 126, 53, 66, 231, 141, 136, 60, 112, 125, 51, 163, 102, 67, 64, 228, 123, 167, 77, 201, 133, 63, 59, 241, 218, 28, 27, 14, 159, 145, 118, 53, 169, 89, 133, 19, 239, 233, 36, 124, 150, 186, 194, 170, 122, 121, 140, 224, 19, 100, 134, 225, 122, 112, 110, 22, 122, 224, 115, 30, 141, 231, 102, 16, 172, 221, 45, 127, 17, 60, 118, 155, 224, 152, 50, 36, 250, 116, 76, 36, 169, 47, 95, 139, 46, 41, 7, 129, 67, 167, 221, 24, 51, 215, 6, 70, 22, 169, 173, 247, 245, 134, 29, 75, 127, 177, 117, 148, 42, 167, 100, 255, 160, 19, 228, 202, 173, 214, 107, 32, 228, 109, 95, 28, 249, 53, 3, 120, 242, 67, 249, 102, 142, 121, 216, 96, 125, 117, 96, 180, 137, 16, 240, 190, 140, 167, 247, 9, 12, 2, 111, 204, 179, 227, 189, 5, 118, 201, 202, 219, 53, 226, 115, 123, 119, 51, 53, 26, 59, 243, 242, 69, 201, 194, 10, 8, 200, 95, 121, 126, 14, 110, 49, 243, 236, 42, 114, 179, 55, 53, 35, 103, 19, 44, 223, 234, 173, 87, 217, 223, 167, 62, 31, 113, 225, 241, 158, 17, 178, 95, 10, 200, 27, 85, 151, 42, 15, 129, 23, 103, 216, 192, 30, 226, 165, 54, 22, 69, 60, 180, 87, 196, 192, 201, 11, 243, 191, 145, 42, 235, 111, 127, 21, 9, 226, 61, 227, 251, 159, 71, 129, 1, 239, 201, 51, 113, 230, 112, 147, 229, 209, 219, 35, 147, 253, 5, 72, 151, 87, 22, 129, 151, 103, 218, 48, 117, 169, 116, 146, 48, 43, 94, 186, 207, 216, 103, 194, 2, 43, 123, 134, 13, 90, 147, 183, 250, 20, 50, 207, 146, 42, 76, 0, 161, 35, 203, 83, 247, 217, 42, 96, 218, 119, 210, 77, 156, 50, 216, 184, 247, 217, 222, 81, 45, 21, 50, 83, 87, 163, 48, 2, 163, 102, 217, 48, 229, 91, 153, 36, 233, 111, 236, 51, 225, 171, 224, 145, 68, 250, 234, 83, 16, 172, 140, 153, 136, 229, 220, 220, 6, 128, 180, 47, 75, 237, 227, 83, 9, 182, 90, 164, 13, 248, 214, 32, 227, 169, 151, 31, 140, 10, 233, 16, 107, 105, 30, 135, 102, 239, 39, 39, 91, 49, 255, 39, 233, 201, 138, 235, 85, 227, 156, 253, 186, 9, 3, 166, 4, 41, 66, 88, 85, 130, 176, 169, 205, 50, 115, 47, 80, 144, 41, 215, 78, 115, 129, 3, 232, 248, 162, 52, 243, 250, 221, 192, 219, 191, 30, 29, 235, 36, 36, 180, 18, 214, 133, 230, 18, 247, 207, 234, 124, 27, 69, 175, 215, 11, 176, 37, 67, 250, 206, 123, 251, 198, 124, 238, 189, 93, 28, 53, 198, 206, 134, 244, 167, 126, 137, 102, 75, 91, 129, 18, 149, 251, 210, 61, 123, 46, 170, 81, 7, 255, 7, 197, 213, 113, 80, 236, 0, 212, 83, 18, 223, 94, 29, 154, 29, 151, 85, 43, 129, 211, 119, 199, 125, 1, 94, 3, 125, 54, 236, 113, 225, 142, 87, 242, 101, 89, 210, 247, 6, 195, 175, 75, 54, 218, 110, 148, 37, 44, 65, 72, 117, 130, 20, 93, 69, 210, 132, 201, 148, 94, 93, 100, 103, 229, 14, 224, 191, 51, 125, 55, 239, 155, 81, 145, 71, 250, 117, 53, 233, 251, 28, 18, 38, 95, 11, 93, 199, 125, 101, 199, 219, 95, 202, 187, 16, 12, 189, 199, 248, 214, 244, 101, 214, 128, 22, 133, 245, 125, 5, 6, 16, 205, 204, 25, 66, 55, 194, 209, 159, 75, 15, 241, 245, 70, 224, 205, 175, 125, 51, 175, 87, 103, 222, 177, 100, 76, 172, 207, 27, 143, 1, 116, 69, 87, 45, 3, 1, 118, 21, 97, 87, 19, 169, 173, 101, 125, 174, 224, 143, 236, 194, 128, 102, 137, 241, 109, 5, 74, 181, 92, 98, 255, 125, 51, 80, 83, 224, 248, 147, 165, 197, 164, 188, 193, 90, 247, 110, 212, 129, 110, 173, 140, 141, 37, 14, 171, 119, 215, 8, 2, 223, 110, 118, 98, 224, 187, 5, 178, 172, 121, 242, 14, 227, 39, 230, 239, 173, 195, 100, 9, 251, 32, 164, 9, 130, 48, 59, 45, 102, 254, 92, 233, 98, 59, 223, 109, 5, 94, 158, 237, 221, 188, 78, 201, 156, 248, 243, 196, 184, 2, 66, 16, 231, 131, 191, 122, 23, 141, 34, 208, 121, 120, 94, 81, 42, 34, 169, 173, 83, 178, 112, 97, 203, 94, 187, 2, 213, 76, 202, 30, 217, 251, 10, 148, 106, 177, 204, 254, 22, 51, 255, 27, 128, 206, 37, 226, 127, 28, 6, 254, 245, 190, 119, 243, 22, 191, 22, 121, 248, 222, 235, 77, 13, 100, 14, 171, 139, 105, 4, 129, 137, 139, 28, 120, 117, 142, 77, 150, 53, 207, 220, 107, 124, 237, 227, 111, 173, 227, 101, 9, 123, 17, 242, 190, 2, 3, 49, 106, 25, 58, 45, 102, 238, 247, 210, 251, 33, 185, 5, 64, 151, 17, 222, 205, 59, 241, 69, 220, 225, 196, 120, 78, 39, 72, 144, 230, 41, 80, 195, 176, 220, 92, 109, 159, 149, 23, 174, 221, 251, 122, 195, 246, 165, 191, 216, 58, 5, 194, 54, 239, 43, 48, 16, 163, 150, 161, 51, 203, 204, 31, 161, 64, 189, 210, 95, 177, 243, 31, 158, 178, 176, 55, 171, 203, 185, 119, 127, 18, 199, 5, 201, 68, 125, 152, 0, 35, 112, 211, 75, 121, 216, 154, 41, 253, 54, 171, 89, 29, 206, 182, 247, 80, 97, 64, 210, 49, 105, 134, 32, 22, 51, 207, 158, 210, 174, 114, 146, 101, 100, 239, 239, 33, 22, 107, 252, 227, 198, 243, 47, 245, 143, 10, 216, 253, 103, 128, 215, 131, 174, 254, 26, 4, 216, 235, 94, 246, 218, 87, 78, 27, 249, 128, 161, 203, 187, 95, 219, 216, 109, 186, 162, 77, 19, 4, 217, 62, 3, 81, 113, 92, 17, 65, 254, 209, 60, 93, 69, 86, 190, 29, 117, 240, 214, 118, 198, 70, 138, 34, 162, 43, 83, 13, 129, 205, 251, 92, 232, 241, 178, 188, 141, 195, 161, 189, 140, 99, 166, 47, 181, 142, 83, 218, 120, 77, 16, 36, 123, 22, 234, 186, 69, 254, 104, 89, 206, 29, 61, 7, 12, 154, 92, 118, 134, 246, 223, 166, 70, 103, 181, 107, 172, 159, 43, 87, 122, 81, 168, 165, 143, 37, 213, 110, 243, 140, 188, 231, 144, 167, 238, 52, 78, 15, 196, 9, 68, 77, 16, 36, 43, 205, 208, 158, 82, 119, 185, 249, 17, 89, 109, 143, 183, 191, 249, 167, 169, 153, 105, 49, 150, 6, 213, 5, 61, 164, 93, 173, 21, 173, 240, 184, 231, 47, 83, 212, 126, 68, 94, 173, 138, 71, 111, 53, 44, 156, 179, 218, 246, 160, 194, 38, 121, 78, 93, 175, 244, 96, 229, 233, 179, 164, 10, 183, 131, 208, 53, 158, 198, 123, 230, 83, 130, 245, 123, 174, 238, 113, 114, 126, 124, 118, 130, 158, 178, 39, 88, 211, 20, 240, 113, 40, 5, 34, 250, 228, 200, 26, 231, 254, 174, 134, 245, 11, 215, 43, 159, 17, 69, 19, 87, 16, 95, 50, 193, 239, 58, 4, 60, 58, 137, 64, 44, 117, 254, 255, 231, 137, 81, 25, 157, 155, 26, 155, 201, 66, 84, 23, 210, 28, 2, 251, 142, 138, 104, 63, 76, 222, 45, 214, 224, 187, 12, 19, 2, 145, 128, 78, 19, 4, 201, 50, 115, 195, 40, 200, 199, 222, 102, 236, 163, 21, 4, 159, 148, 74, 6, 243, 201, 127, 76, 199, 158, 186, 51, 178, 174, 55, 57, 253, 251, 208, 64, 96, 209, 47, 78, 60, 50, 65, 94, 200, 201, 11, 125, 13, 15, 76, 90, 100, 91, 162, 180, 167, 154, 32, 72, 166, 153, 123, 131, 128, 140, 245, 230, 92, 161, 171, 248, 129, 157, 237, 178, 179, 54, 228, 110, 225, 242, 135, 207, 198, 232, 33, 38, 222, 128, 11, 145, 239, 223, 250, 210, 142, 241, 50, 94, 243, 86, 141, 39, 238, 161, 247, 70, 39, 140, 157, 157, 35, 239, 254, 204, 3, 62, 154, 32, 136, 197, 204, 127, 248, 255, 133, 92, 159, 243, 101, 30, 89, 121, 181, 97, 159, 253, 109, 118, 246, 172, 56, 75, 221, 170, 156, 254, 160, 238, 11, 120, 26, 238, 195, 234, 38, 222, 240, 66, 62, 78, 95, 146, 150, 67, 139, 185, 116, 111, 103, 195, 174, 101, 155, 108, 101, 158, 74, 245, 215, 101, 173, 16, 228, 75, 0, 15, 249, 234, 204, 184, 111, 8, 230, 95, 41, 234, 245, 98, 63, 67, 206, 123, 79, 69, 87, 242, 85, 86, 239, 167, 77, 4, 252, 217, 36, 124, 174, 183, 49, 101, 234, 18, 107, 90, 32, 60, 211, 6, 65, 210, 248, 213, 160, 184, 195, 87, 7, 207, 95, 6, 6, 77, 33, 56, 124, 166, 88, 34, 107, 102, 140, 165, 94, 53, 253, 117, 175, 175, 248, 105, 173, 223, 145, 179, 110, 220, 50, 34, 223, 107, 201, 234, 178, 236, 238, 218, 82, 56, 185, 113, 151, 61, 96, 165, 240, 180, 65, 16, 51, 183, 3, 32, 29, 164, 76, 92, 233, 112, 248, 1, 221, 120, 219, 23, 47, 199, 178, 152, 44, 253, 208, 148, 20, 16, 53, 210, 119, 240, 135, 86, 204, 89, 35, 61, 129, 67, 108, 36, 161, 79, 244, 52, 62, 50, 117, 113, 65, 192, 106, 97, 106, 130, 32, 89, 102, 254, 48, 5, 36, 31, 151, 29, 53, 135, 96, 217, 149, 232, 155, 133, 175, 70, 30, 233, 211, 69, 63, 114, 171, 145, 53, 239, 179, 25, 95, 173, 47, 196, 19, 147, 172, 62, 247, 47, 221, 241, 161, 238, 198, 229, 95, 172, 177, 246, 145, 37, 236, 163, 144, 38, 8, 98, 49, 243, 44, 0, 39, 218, 71, 155, 255, 234, 118, 248, 44, 240, 216, 20, 130, 115, 185, 0, 203, 190, 119, 100, 110, 220, 193, 202, 49, 92, 133, 136, 205, 58, 118, 198, 133, 139, 151, 221, 69, 251, 66, 113, 209, 28, 234, 86, 231, 97, 50, 104, 98, 58, 125, 158, 70, 246, 64, 222, 115, 116, 62, 44, 39, 164, 63, 152, 119, 105, 38, 156, 219, 180, 199, 94, 205, 231, 193, 100, 118, 84, 29, 209, 205, 147, 17, 153, 24, 203, 203, 251, 19, 2, 160, 116, 24, 202, 139, 253, 132, 220, 247, 158, 138, 137, 151, 137, 69, 72, 136, 21, 58, 41, 182, 239, 47, 68, 78, 254, 63, 23, 85, 199, 230, 70, 84, 171, 28, 58, 41, 136, 159, 251, 212, 134, 212, 116, 233, 9, 228, 140, 6, 96, 240, 221, 198, 193, 211, 22, 91, 205, 129, 158, 52, 213, 9, 146, 101, 70, 29, 10, 254, 152, 63, 142, 14, 253, 132, 96, 195, 159, 197, 26, 54, 126, 16, 149, 29, 206, 37, 14, 24, 57, 206, 94, 42, 255, 204, 196, 109, 29, 35, 96, 50, 170, 62, 173, 94, 167, 115, 201, 38, 39, 30, 122, 79, 222, 166, 224, 131, 221, 140, 107, 191, 250, 201, 218, 211, 235, 32, 10, 116, 80, 29, 73, 75, 170, 161, 29, 136, 123, 167, 63, 190, 172, 216, 6, 140, 248, 188, 216, 149, 22, 117, 57, 247, 111, 211, 98, 79, 27, 195, 176, 64, 78, 110, 190, 27, 155, 254, 240, 252, 23, 55, 185, 158, 1, 77, 234, 8, 254, 192, 25, 112, 217, 75, 249, 20, 183, 143, 206, 199, 159, 135, 165, 31, 142, 186, 46, 137, 191, 180, 117, 159, 163, 74, 192, 141, 188, 50, 128, 234, 4, 201, 76, 19, 110, 35, 148, 174, 245, 215, 225, 190, 227, 9, 44, 39, 138, 181, 188, 255, 148, 241, 204, 240, 126, 81, 97, 87, 144, 243, 248, 89, 17, 127, 100, 123, 126, 219, 83, 189, 10, 143, 235, 154, 25, 253, 133, 51, 160, 242, 35, 103, 218, 48, 77, 70, 2, 107, 66, 128, 103, 123, 155, 94, 248, 112, 113, 193, 180, 128, 26, 88, 74, 185, 250, 4, 49, 243, 255, 34, 192, 215, 254, 58, 108, 254, 1, 152, 188, 244, 111, 119, 254, 248, 52, 198, 210, 180, 78, 120, 237, 141, 156, 60, 47, 98, 151, 197, 51, 65, 106, 85, 229, 209, 46, 73, 187, 4, 89, 187, 211, 133, 94, 175, 203, 59, 20, 117, 127, 55, 195, 250, 96, 215, 48, 84, 157, 32, 89, 105, 220, 179, 148, 146, 233, 254, 18, 228, 218, 82, 9, 183, 181, 229, 11, 87, 188, 21, 83, 192, 113, 164, 178, 191, 186, 181, 34, 111, 115, 80, 252, 180, 195, 243, 145, 212, 86, 141, 13, 168, 87, 93, 187, 183, 88, 61, 95, 201, 199, 70, 25, 73, 226, 58, 52, 225, 115, 182, 237, 119, 4, 125, 46, 213, 39, 72, 42, 247, 58, 37, 228, 77, 127, 23, 97, 190, 13, 232, 244, 191, 171, 221, 97, 245, 207, 31, 239, 25, 25, 176, 93, 86, 127, 109, 150, 35, 159, 113, 196, 137, 131, 39, 202, 206, 66, 24, 31, 195, 161, 107, 27, 237, 238, 149, 126, 176, 200, 81, 84, 179, 80, 106, 83, 227, 214, 170, 196, 70, 213, 9, 98, 49, 11, 211, 0, 250, 188, 84, 208, 202, 234, 223, 227, 21, 130, 211, 215, 196, 115, 102, 207, 138, 203, 170, 91, 53, 188, 18, 90, 103, 28, 118, 226, 224, 201, 171, 73, 82, 181, 50, 143, 54, 77, 12, 154, 221, 11, 249, 227, 144, 136, 91, 71, 229, 35, 207, 74, 37, 79, 117, 255, 110, 134, 245, 223, 168, 84, 30, 90, 3, 4, 225, 231, 255, 127, 98, 197, 135, 37, 163, 86, 134, 64, 89, 245, 68, 88, 24, 202, 188, 145, 49, 60, 33, 68, 187, 55, 230, 50, 156, 183, 58, 40, 114, 216, 70, 33, 5, 98, 35, 9, 42, 21, 69, 218, 104, 183, 61, 60, 161, 0, 139, 127, 145, 94, 200, 83, 173, 91, 43, 205, 92, 65, 178, 204, 252, 42, 10, 220, 169, 196, 212, 246, 25, 71, 192, 74, 70, 95, 219, 244, 204, 239, 74, 160, 43, 95, 7, 139, 179, 98, 241, 86, 82, 27, 187, 181, 122, 166, 87, 196, 240, 143, 190, 205, 103, 199, 33, 84, 105, 170, 95, 65, 50, 205, 220, 118, 2, 114, 157, 18, 222, 223, 52, 138, 128, 69, 250, 150, 213, 46, 45, 140, 63, 18, 21, 161, 151, 129, 86, 2, 103, 169, 58, 238, 25, 147, 143, 117, 187, 164, 103, 111, 15, 86, 56, 137, 39, 127, 84, 39, 136, 37, 141, 63, 4, 10, 69, 82, 135, 182, 126, 142, 192, 85, 206, 222, 211, 252, 17, 17, 71, 251, 223, 28, 113, 85, 230, 70, 169, 19, 93, 86, 255, 51, 23, 69, 28, 58, 89, 28, 23, 197, 90, 66, 60, 135, 134, 181, 132, 144, 10, 249, 240, 5, 7, 135, 147, 98, 223, 33, 39, 46, 93, 118, 163, 208, 69, 17, 21, 65, 80, 167, 154, 128, 70, 181, 60, 191, 49, 99, 207, 30, 157, 158, 151, 119, 206, 252, 209, 91, 13, 11, 230, 172, 182, 253, 203, 23, 251, 2, 213, 71, 125, 130, 152, 121, 134, 158, 223, 53, 30, 242, 108, 64, 231, 107, 222, 98, 149, 6, 173, 125, 99, 206, 181, 101, 74, 156, 21, 10, 102, 129, 63, 114, 202, 133, 189, 135, 202, 190, 175, 110, 221, 216, 128, 186, 26, 126, 221, 42, 101, 65, 217, 29, 20, 63, 239, 118, 192, 233, 250, 231, 3, 118, 205, 68, 30, 237, 147, 203, 127, 188, 123, 231, 107, 59, 222, 156, 47, 61, 91, 34, 187, 189, 26, 49, 48, 50, 241, 221, 185, 121, 23, 164, 216, 170, 116, 95, 85, 9, 114, 232, 115, 68, 56, 93, 188, 244, 247, 126, 101, 160, 112, 236, 60, 112, 199, 235, 158, 221, 249, 249, 131, 232, 236, 206, 205, 12, 77, 148, 0, 209, 106, 167, 88, 255, 187, 231, 137, 15, 149, 184, 40, 111, 120, 120, 139, 255, 234, 208, 212, 136, 26, 9, 101, 7, 73, 222, 60, 34, 31, 191, 201, 168, 67, 168, 133, 219, 43, 134, 139, 170, 4, 201, 252, 20, 181, 137, 192, 31, 247, 54, 65, 190, 124, 239, 75, 185, 132, 169, 67, 77, 103, 158, 233, 21, 169, 72, 8, 10, 187, 173, 218, 127, 216, 243, 91, 153, 86, 141, 12, 168, 87, 67, 187, 155, 118, 190, 224, 202, 194, 233, 87, 111, 241, 252, 55, 172, 86, 34, 143, 118, 101, 92, 69, 108, 133, 20, 213, 7, 230, 194, 33, 253, 229, 21, 238, 238, 36, 236, 89, 177, 217, 222, 198, 23, 27, 3, 217, 71, 93, 130, 124, 102, 104, 75, 120, 247, 46, 37, 28, 100, 209, 188, 44, 170, 215, 83, 75, 185, 91, 200, 255, 248, 217, 24, 191, 111, 231, 216, 24, 140, 28, 140, 36, 158, 26, 11, 26, 100, 193, 131, 161, 220, 242, 172, 110, 252, 188, 203, 115, 128, 100, 121, 27, 148, 172, 130, 45, 59, 74, 43, 167, 61, 212, 221, 248, 221, 23, 107, 172, 125, 229, 200, 42, 41, 163, 42, 65, 178, 205, 194, 173, 110, 208, 117, 74, 56, 196, 78, 22, 178, 19, 134, 158, 90, 187, 134, 156, 248, 219, 135, 113, 138, 28, 152, 96, 187, 217, 108, 87, 219, 83, 107, 217, 200, 128, 250, 21, 248, 10, 194, 206, 122, 176, 51, 31, 114, 90, 202, 93, 134, 247, 62, 91, 110, 27, 45, 71, 86, 73, 25, 85, 9, 146, 145, 202, 63, 200, 17, 124, 163, 132, 67, 179, 215, 1, 19, 22, 123, 118, 39, 210, 4, 228, 44, 82, 38, 1, 74, 129, 141, 98, 195, 78, 207, 207, 32, 61, 174, 139, 64, 164, 73, 85, 136, 149, 128, 182, 232, 128, 150, 167, 51, 40, 229, 61, 131, 76, 92, 236, 192, 171, 179, 229, 17, 100, 72, 47, 227, 235, 159, 44, 181, 190, 173, 136, 3, 126, 40, 81, 117, 246, 178, 204, 220, 51, 20, 228, 19, 63, 236, 255, 75, 116, 234, 119, 4, 51, 86, 121, 214, 212, 170, 62, 39, 238, 248, 88, 153, 43, 8, 27, 201, 211, 85, 164, 69, 67, 3, 26, 212, 12, 237, 231, 143, 18, 52, 237, 133, 20, 191, 236, 114, 20, 189, 222, 189, 182, 121, 122, 139, 245, 249, 154, 66, 12, 149, 177, 65, 200, 198, 120, 178, 167, 113, 134, 57, 221, 58, 84, 137, 181, 225, 143, 14, 85, 9, 98, 49, 115, 99, 0, 242, 150, 63, 14, 148, 200, 122, 171, 70, 197, 250, 221, 221, 137, 119, 44, 125, 93, 217, 114, 209, 39, 206, 21, 239, 131, 92, 46, 40, 222, 7, 97, 247, 227, 108, 111, 128, 45, 156, 112, 106, 108, 31, 100, 239, 65, 39, 114, 242, 174, 222, 7, 97, 123, 62, 229, 45, 162, 229, 91, 156, 232, 63, 94, 222, 169, 193, 129, 183, 24, 151, 205, 95, 107, 237, 167, 54, 134, 234, 18, 36, 77, 152, 10, 74, 135, 43, 1, 194, 243, 169, 4, 107, 188, 60, 238, 167, 220, 197, 231, 125, 60, 44, 54, 86, 137, 241, 174, 213, 193, 54, 40, 25, 152, 124, 120, 241, 194, 47, 168, 252, 41, 136, 115, 255, 13, 134, 77, 11, 55, 218, 186, 249, 101, 128, 2, 194, 234, 18, 196, 204, 127, 241, 255, 121, 223, 30, 81, 192, 143, 162, 236, 38, 219, 178, 60, 107, 122, 227, 97, 227, 249, 87, 30, 210, 75, 182, 41, 129, 183, 47, 58, 50, 143, 187, 209, 230, 153, 114, 98, 127, 188, 40, 184, 179, 163, 176, 47, 253, 87, 123, 75, 95, 198, 9, 100, 31, 85, 9, 146, 149, 198, 175, 164, 20, 119, 41, 225, 96, 239, 183, 9, 178, 79, 121, 214, 244, 217, 115, 166, 19, 255, 190, 35, 60, 206, 135, 176, 93, 237, 75, 121, 110, 184, 221, 64, 76, 20, 135, 152, 72, 85, 167, 178, 76, 224, 253, 41, 136, 115, 83, 43, 225, 248, 250, 157, 118, 213, 51, 247, 171, 138, 106, 166, 153, 223, 70, 128, 142, 74, 16, 196, 83, 160, 98, 137, 254, 111, 199, 68, 30, 186, 167, 179, 169, 161, 18, 227, 169, 169, 35, 235, 152, 11, 89, 199, 174, 126, 197, 92, 51, 129, 71, 235, 38, 70, 8, 26, 187, 197, 51, 245, 150, 151, 112, 93, 237, 48, 247, 146, 249, 85, 151, 32, 169, 252, 33, 66, 148, 9, 84, 108, 245, 159, 171, 139, 235, 148, 181, 128, 127, 153, 24, 157, 213, 169, 105, 104, 215, 52, 180, 28, 115, 34, 251, 88, 217, 27, 148, 85, 226, 56, 116, 105, 165, 173, 19, 133, 181, 30, 201, 197, 133, 203, 210, 15, 73, 37, 213, 230, 236, 25, 135, 11, 35, 213, 252, 67, 196, 198, 86, 149, 32, 22, 51, 207, 110, 80, 253, 126, 104, 182, 57, 128, 14, 47, 122, 119, 101, 111, 106, 140, 165, 73, 205, 208, 77, 228, 192, 94, 183, 254, 184, 221, 243, 222, 75, 155, 38, 70, 212, 169, 166, 157, 203, 72, 235, 161, 151, 101, 101, 78, 172, 86, 137, 184, 79, 157, 115, 170, 238, 136, 247, 85, 21, 32, 10, 103, 125, 8, 19, 141, 228, 165, 135, 121, 150, 97, 207, 137, 11, 192, 237, 99, 188, 187, 114, 114, 126, 92, 86, 66, 92, 232, 30, 191, 61, 117, 94, 196, 78, 47, 89, 77, 106, 87, 229, 209, 86, 67, 89, 77, 186, 143, 204, 199, 175, 251, 165, 159, 5, 49, 8, 128, 221, 230, 242, 62, 169, 1, 90, 159, 170, 223, 98, 101, 204, 68, 45, 206, 205, 95, 201, 100, 229, 159, 151, 123, 142, 0, 15, 78, 240, 142, 101, 206, 162, 248, 163, 145, 38, 162, 248, 153, 16, 255, 172, 247, 93, 58, 20, 243, 98, 245, 31, 87, 128, 229, 191, 201, 136, 86, 4, 240, 210, 64, 161, 254, 132, 121, 246, 50, 203, 131, 251, 142, 154, 127, 61, 189, 175, 42, 255, 244, 151, 43, 157, 97, 54, 182, 225, 32, 238, 86, 66, 253, 198, 189, 192, 144, 233, 222, 93, 177, 45, 139, 191, 192, 113, 36, 65, 137, 49, 213, 208, 193, 54, 233, 54, 239, 241, 28, 56, 152, 84, 87, 64, 82, 93, 237, 4, 72, 14, 249, 208, 138, 217, 50, 74, 27, 48, 124, 95, 190, 47, 226, 214, 119, 22, 228, 255, 164, 6, 214, 170, 95, 65, 178, 103, 10, 61, 220, 110, 250, 163, 18, 206, 47, 221, 2, 140, 158, 235, 157, 32, 142, 229, 149, 216, 121, 67, 213, 239, 107, 253, 241, 121, 235, 62, 7, 206, 231, 148, 159, 13, 93, 107, 241, 95, 175, 204, 182, 97, 210, 98, 233, 9, 170, 25, 70, 207, 245, 49, 62, 53, 117, 177, 117, 150, 63, 120, 249, 43, 235, 125, 85, 249, 59, 66, 57, 242, 25, 105, 252, 0, 142, 98, 129, 18, 234, 63, 95, 11, 188, 191, 196, 179, 43, 245, 171, 18, 106, 153, 21, 175, 154, 191, 74, 248, 201, 116, 176, 228, 113, 44, 120, 144, 133, 161, 95, 219, 60, 29, 92, 82, 106, 124, 169, 122, 38, 45, 113, 224, 149, 207, 101, 7, 44, 142, 253, 100, 169, 213, 239, 156, 105, 82, 109, 46, 221, 95, 181, 5, 99, 73, 229, 134, 130, 144, 79, 253, 49, 190, 68, 118, 202, 50, 130, 212, 213, 158, 53, 221, 220, 138, 115, 174, 121, 55, 78, 59, 247, 30, 126, 58, 94, 114, 14, 94, 116, 211, 43, 245, 65, 4, 68, 71, 168, 54, 157, 229, 122, 195, 110, 175, 216, 109, 150, 156, 246, 239, 59, 140, 169, 105, 223, 91, 135, 200, 145, 85, 74, 70, 53, 68, 45, 169, 220, 107, 32, 68, 145, 112, 230, 215, 231, 19, 44, 220, 228, 25, 146, 71, 122, 8, 249, 179, 254, 171, 204, 97, 41, 165, 192, 175, 8, 122, 86, 252, 230, 196, 3, 227, 100, 6, 44, 118, 55, 126, 55, 95, 229, 67, 83, 170, 17, 36, 203, 44, 76, 161, 160, 47, 40, 177, 72, 158, 75, 37, 88, 235, 37, 80, 241, 165, 7, 12, 23, 199, 63, 17, 29, 180, 180, 249, 74, 248, 21, 14, 58, 216, 43, 94, 246, 170, 87, 78, 187, 239, 70, 195, 230, 69, 27, 108, 93, 229, 200, 42, 37, 163, 26, 65, 50, 205, 252, 60, 2, 60, 170, 132, 35, 131, 38, 19, 108, 207, 246, 172, 105, 114, 138, 233, 212, 176, 62, 145, 53, 149, 24, 175, 162, 234, 56, 118, 70, 196, 197, 203, 34, 10, 157, 40, 74, 251, 83, 191, 6, 95, 20, 7, 230, 169, 177, 242, 106, 108, 179, 80, 78, 187, 243, 58, 97, 127, 250, 22, 123, 11, 57, 178, 74, 201, 168, 70, 144, 172, 52, 62, 157, 82, 220, 173, 132, 35, 247, 190, 77, 112, 192, 75, 160, 226, 252, 145, 145, 71, 250, 223, 164, 23, 249, 148, 139, 247, 159, 7, 157, 56, 122, 250, 234, 13, 63, 22, 247, 117, 93, 51, 83, 81, 46, 176, 242, 218, 133, 60, 138, 90, 15, 231, 202, 26, 246, 166, 86, 194, 137, 245, 59, 237, 117, 100, 9, 43, 36, 164, 26, 65, 44, 102, 126, 43, 128, 78, 74, 248, 209, 237, 101, 130, 11, 94, 114, 147, 173, 121, 55, 42, 251, 230, 86, 70, 69, 82, 254, 40, 97, 115, 40, 233, 96, 5, 67, 247, 28, 40, 123, 179, 207, 40, 16, 220, 210, 193, 4, 131, 80, 254, 82, 138, 232, 157, 3, 233, 209, 88, 64, 251, 38, 124, 206, 118, 21, 74, 30, 148, 158, 27, 213, 8, 146, 105, 230, 15, 18, 64, 145, 200, 218, 150, 195, 8, 220, 94, 102, 224, 247, 143, 163, 45, 45, 235, 27, 146, 149, 94, 152, 44, 62, 234, 200, 105, 87, 81, 198, 65, 214, 88, 192, 32, 59, 106, 107, 12, 177, 138, 179, 158, 112, 89, 179, 213, 94, 102, 210, 184, 18, 25, 118, 170, 176, 121, 131, 242, 95, 16, 214, 126, 36, 23, 44, 244, 93, 106, 107, 82, 139, 115, 100, 30, 41, 140, 144, 42, 167, 100, 127, 213, 8, 98, 73, 227, 115, 65, 17, 231, 175, 51, 133, 46, 160, 237, 243, 222, 221, 56, 60, 59, 206, 82, 51, 129, 83, 148, 32, 23, 114, 221, 216, 190, 223, 81, 84, 138, 185, 116, 99, 127, 77, 89, 197, 217, 202, 26, 207, 184, 238, 11, 246, 190, 36, 167, 96, 126, 222, 208, 186, 252, 40, 226, 54, 207, 228, 33, 243, 184, 244, 122, 132, 85, 227, 137, 251, 244, 121, 117, 3, 22, 189, 175, 44, 95, 80, 148, 216, 103, 239, 88, 24, 13, 181, 121, 121, 219, 171, 215, 140, 117, 250, 18, 208, 227, 85, 239, 110, 156, 251, 58, 238, 80, 92, 52, 167, 200, 21, 139, 153, 192, 174, 88, 107, 126, 179, 253, 131, 28, 37, 230, 49, 146, 244, 236, 172, 234, 31, 63, 137, 179, 82, 118, 119, 118, 48, 139, 93, 65, 60, 53, 86, 122, 154, 253, 65, 40, 175, 245, 120, 57, 31, 236, 248, 173, 212, 198, 158, 113, 28, 118, 117, 3, 22, 189, 175, 44, 169, 94, 249, 208, 255, 240, 12, 212, 44, 228, 248, 50, 10, 21, 248, 32, 124, 77, 151, 125, 199, 128, 7, 222, 245, 238, 70, 222, 183, 149, 78, 27, 5, 212, 144, 62, 66, 217, 18, 199, 206, 138, 216, 227, 165, 160, 38, 203, 54, 200, 178, 14, 134, 122, 219, 184, 203, 142, 124, 15, 133, 111, 216, 237, 21, 187, 205, 42, 175, 13, 24, 95, 128, 239, 182, 200, 11, 88, 28, 245, 152, 208, 96, 252, 76, 251, 17, 181, 48, 244, 190, 178, 2, 96, 217, 254, 25, 198, 214, 60, 39, 254, 161, 132, 234, 77, 251, 129, 167, 63, 242, 238, 134, 253, 187, 74, 86, 66, 16, 165, 196, 152, 76, 71, 89, 85, 158, 174, 213, 221, 164, 174, 128, 100, 13, 5, 14, 202, 245, 61, 39, 223, 141, 205, 229, 148, 159, 142, 137, 34, 184, 169, 109, 4, 88, 178, 233, 242, 218, 208, 143, 172, 248, 252, 7, 207, 197, 71, 203, 147, 253, 223, 3, 166, 219, 223, 255, 186, 64, 145, 228, 130, 114, 252, 247, 190, 178, 228, 104, 245, 34, 147, 101, 22, 186, 83, 80, 69, 162, 52, 151, 111, 5, 70, 206, 246, 236, 70, 149, 88, 66, 79, 125, 169, 108, 28, 22, 59, 242, 202, 142, 190, 122, 106, 205, 234, 27, 208, 168, 118, 120, 228, 198, 98, 207, 91, 172, 194, 46, 75, 255, 83, 210, 88, 217, 183, 182, 77, 12, 94, 95, 72, 176, 228, 113, 44, 137, 156, 156, 246, 124, 111, 227, 211, 83, 150, 88, 103, 202, 145, 85, 66, 70, 37, 130, 240, 253, 41, 176, 80, 9, 7, 230, 252, 8, 188, 183, 200, 179, 27, 215, 53, 225, 92, 155, 167, 196, 41, 186, 82, 89, 61, 144, 45, 127, 122, 158, 244, 27, 219, 152, 80, 41, 70, 219, 165, 209, 164, 204, 1, 123, 238, 186, 152, 235, 70, 190, 205, 141, 196, 74, 188, 207, 137, 34, 38, 47, 113, 96, 180, 252, 128, 197, 55, 63, 89, 106, 29, 43, 197, 78, 37, 251, 170, 66, 16, 75, 26, 55, 4, 148, 124, 166, 132, 35, 190, 100, 84, 236, 119, 3, 111, 251, 230, 149, 88, 197, 207, 55, 255, 145, 93, 8, 118, 136, 169, 172, 198, 178, 186, 179, 236, 238, 122, 3, 230, 172, 45, 196, 224, 105, 242, 2, 22, 159, 232, 105, 76, 155, 153, 110, 77, 81, 11, 71, 117, 8, 98, 230, 94, 5, 200, 56, 37, 156, 246, 37, 80, 113, 88, 111, 67, 206, 228, 193, 209, 202, 36, 229, 189, 198, 104, 86, 117, 233, 240, 169, 171, 111, 181, 188, 237, 11, 40, 225, 119, 40, 233, 248, 126, 171, 19, 247, 191, 45, 47, 96, 241, 161, 238, 198, 229, 95, 172, 177, 246, 81, 203, 95, 85, 8, 146, 149, 38, 76, 166, 148, 190, 168, 132, 211, 207, 205, 32, 88, 235, 229, 92, 226, 184, 199, 140, 103, 70, 12, 136, 82, 164, 46, 72, 89, 54, 59, 10, 41, 114, 75, 165, 30, 53, 5, 120, 147, 144, 229, 195, 58, 112, 220, 245, 87, 66, 105, 150, 238, 148, 101, 145, 215, 82, 178, 134, 210, 56, 249, 83, 6, 225, 190, 27, 13, 191, 46, 218, 96, 187, 81, 137, 181, 34, 71, 135, 42, 4, 201, 76, 229, 231, 18, 130, 65, 114, 12, 190, 86, 230, 209, 73, 4, 59, 14, 120, 214, 148, 54, 220, 116, 108, 208, 237, 145, 170, 39, 33, 83, 194, 95, 150, 101, 157, 29, 152, 42, 171, 105, 181, 30, 73, 214, 9, 55, 90, 201, 15, 88, 204, 72, 223, 98, 111, 174, 4, 118, 114, 116, 168, 69, 144, 116, 66, 148, 9, 84, 236, 245, 22, 193, 193, 211, 158, 93, 95, 246, 70, 228, 161, 187, 58, 134, 126, 194, 56, 230, 101, 250, 102, 207, 167, 243, 88, 94, 44, 22, 238, 162, 165, 118, 49, 143, 162, 166, 204, 128, 197, 110, 45, 133, 147, 27, 118, 217, 107, 171, 229, 143, 58, 4, 49, 243, 223, 19, 224, 30, 37, 156, 238, 58, 146, 224, 162, 151, 227, 6, 155, 38, 69, 103, 119, 76, 86, 166, 54, 161, 18, 54, 203, 213, 113, 238, 146, 136, 109, 229, 92, 61, 74, 116, 178, 56, 48, 86, 122, 65, 107, 45, 178, 79, 142, 215, 120, 185, 178, 108, 110, 223, 152, 207, 221, 158, 225, 8, 200, 243, 163, 47, 24, 169, 66, 16, 75, 26, 255, 57, 40, 158, 240, 197, 64, 111, 125, 90, 12, 35, 160, 94, 226, 224, 48, 209, 253, 167, 0, 0, 10, 174, 73, 68, 65, 84, 50, 82, 99, 178, 26, 214, 20, 146, 188, 233, 210, 250, 247, 158, 162, 106, 75, 108, 175, 94, 133, 199, 117, 205, 202, 15, 251, 80, 203, 199, 58, 143, 230, 226, 92, 174, 244, 128, 197, 198, 181, 56, 135, 69, 197, 128, 69, 117, 8, 98, 22, 222, 7, 232, 8, 127, 39, 139, 5, 9, 178, 148, 163, 222, 218, 137, 249, 113, 7, 19, 227, 184, 70, 222, 250, 105, 253, 123, 79, 207, 31, 37, 182, 179, 135, 117, 86, 250, 77, 107, 173, 237, 179, 121, 200, 56, 38, 61, 96, 49, 49, 158, 184, 207, 168, 24, 176, 232, 125, 117, 5, 0, 233, 44, 51, 255, 40, 5, 230, 249, 171, 250, 92, 46, 112, 243, 104, 239, 46, 228, 44, 138, 63, 17, 105, 34, 170, 221, 199, 250, 235, 103, 105, 121, 111, 207, 32, 157, 91, 152, 144, 88, 73, 91, 207, 32, 204, 254, 91, 71, 229, 99, 211, 94, 233, 1, 139, 60, 7, 20, 58, 212, 11, 88, 244, 190, 186, 148, 156, 221, 43, 186, 178, 102, 160, 49, 229, 120, 47, 135, 100, 189, 15, 108, 57, 1, 244, 29, 239, 221, 5, 235, 210, 248, 28, 158, 39, 170, 221, 199, 122, 247, 196, 247, 30, 167, 46, 136, 216, 153, 89, 246, 91, 44, 45, 239, 191, 248, 19, 176, 56, 122, 160, 208, 104, 220, 60, 251, 33, 223, 81, 82, 174, 167, 247, 213, 165, 220, 88, 87, 105, 178, 152, 185, 61, 0, 105, 229, 143, 250, 45, 153, 192, 191, 167, 121, 118, 129, 133, 76, 23, 44, 13, 11, 110, 252, 5, 21, 75, 28, 151, 125, 220, 9, 22, 238, 82, 210, 180, 94, 19, 241, 153, 143, 172, 152, 37, 51, 96, 241, 197, 251, 76, 61, 39, 46, 40, 88, 235, 207, 90, 145, 43, 171, 38, 65, 70, 0, 228, 125, 185, 134, 51, 185, 244, 29, 192, 255, 102, 122, 118, 33, 185, 54, 17, 247, 124, 22, 31, 250, 49, 231, 101, 0, 197, 138, 231, 184, 41, 133, 192, 171, 54, 141, 62, 79, 223, 107, 115, 236, 248, 96, 145, 188, 92, 229, 207, 245, 53, 166, 76, 93, 100, 77, 243, 121, 48, 5, 59, 170, 138, 108, 166, 153, 255, 133, 0, 178, 211, 186, 204, 95, 15, 140, 91, 224, 217, 133, 158, 237, 120, 199, 138, 183, 149, 45, 220, 169, 32, 254, 21, 70, 213, 148, 111, 29, 24, 53, 75, 94, 134, 197, 161, 247, 24, 223, 154, 190, 204, 250, 134, 26, 96, 169, 74, 144, 172, 84, 190, 15, 37, 88, 38, 215, 241, 143, 86, 16, 124, 146, 238, 89, 250, 137, 219, 249, 188, 25, 195, 3, 83, 184, 83, 174, 221, 21, 81, 110, 238, 186, 66, 164, 76, 149, 23, 176, 248, 120, 79, 227, 204, 89, 233, 214, 167, 213, 192, 77, 85, 130, 48, 135, 51, 205, 220, 43, 4, 100, 188, 28, 231, 223, 252, 154, 224, 235, 141, 158, 37, 71, 63, 104, 184, 48, 118, 80, 116, 200, 102, 116, 151, 131, 139, 22, 101, 210, 183, 57, 113, 223, 91, 161, 23, 176, 168, 58, 65, 216, 100, 102, 153, 185, 55, 40, 136, 228, 152, 255, 23, 204, 4, 171, 119, 122, 94, 14, 83, 135, 154, 78, 61, 211, 75, 79, 24, 167, 54, 105, 126, 203, 112, 225, 230, 17, 242, 50, 44, 246, 189, 193, 176, 101, 201, 70, 219, 13, 106, 248, 160, 9, 130, 48, 199, 45, 102, 161, 39, 64, 127, 144, 2, 130, 47, 165, 159, 191, 26, 21, 113, 244, 254, 174, 17, 33, 91, 52, 71, 10, 30, 129, 238, 155, 111, 163, 69, 233, 141, 10, 93, 180, 40, 179, 98, 181, 74, 188, 207, 117, 225, 179, 79, 186, 209, 114, 136, 188, 12, 139, 119, 116, 16, 50, 87, 254, 102, 111, 22, 104, 255, 202, 210, 175, 25, 130, 48, 227, 246, 166, 25, 218, 11, 110, 247, 120, 95, 3, 25, 125, 201, 168, 184, 230, 221, 168, 131, 55, 183, 50, 134, 252, 46, 186, 26, 139, 163, 244, 152, 103, 46, 22, 239, 191, 148, 206, 63, 22, 27, 197, 161, 83, 11, 35, 34, 140, 222, 151, 209, 165, 124, 138, 26, 15, 201, 203, 176, 216, 181, 165, 112, 114, 163, 74, 1, 139, 222, 61, 83, 97, 102, 44, 105, 252, 195, 132, 226, 57, 10, 116, 241, 52, 188, 47, 129, 138, 59, 167, 199, 100, 183, 168, 39, 232, 25, 21, 253, 152, 199, 220, 124, 55, 54, 149, 155, 180, 129, 195, 77, 109, 77, 30, 147, 54, 148, 12, 29, 213, 55, 167, 220, 52, 73, 158, 204, 107, 215, 136, 207, 221, 145, 169, 78, 192, 162, 38, 9, 82, 2, 86, 118, 170, 112, 163, 155, 184, 187, 82, 144, 174, 0, 173, 67, 64, 88, 118, 118, 246, 41, 74, 56, 215, 98, 152, 167, 92, 26, 197, 90, 14, 204, 138, 61, 88, 59, 145, 215, 175, 32, 126, 16, 100, 195, 78, 59, 88, 2, 185, 242, 154, 183, 180, 63, 37, 114, 117, 7, 229, 226, 108, 142, 140, 128, 197, 154, 156, 195, 114, 84, 157, 12, 139, 154, 38, 136, 31, 115, 250, 151, 168, 115, 85, 226, 14, 2, 116, 80, 66, 87, 69, 212, 193, 42, 90, 253, 180, 195, 243, 6, 31, 75, 94, 125, 125, 75, 239, 245, 217, 219, 61, 155, 135, 253, 50, 2, 22, 19, 226, 136, 251, 236, 5, 117, 50, 44, 134, 61, 65, 196, 85, 137, 71, 40, 160, 63, 164, 203, 100, 183, 213, 65, 177, 222, 11, 65, 216, 1, 45, 118, 80, 203, 91, 187, 109, 84, 62, 126, 145, 17, 176, 200, 113, 128, 83, 165, 128, 197, 176, 39, 136, 107, 101, 162, 21, 4, 138, 103, 52, 41, 189, 24, 78, 156, 19, 193, 238, 211, 89, 171, 20, 203, 133, 69, 54, 197, 210, 254, 173, 221, 102, 71, 97, 169, 124, 88, 215, 18, 129, 229, 254, 98, 57, 192, 188, 181, 7, 223, 41, 192, 178, 95, 229, 101, 88, 28, 125, 63, 223, 100, 220, 55, 14, 47, 135, 171, 189, 89, 32, 253, 251, 176, 38, 8, 253, 169, 106, 140, 232, 160, 94, 10, 35, 72, 7, 173, 68, 194, 106, 167, 216, 182, 223, 241, 143, 251, 243, 216, 104, 14, 157, 154, 25, 17, 97, 10, 15, 120, 61, 29, 212, 98, 57, 136, 187, 123, 41, 127, 80, 130, 215, 179, 31, 91, 49, 115, 181, 204, 12, 139, 253, 77, 119, 188, 255, 85, 193, 26, 249, 179, 37, 79, 50, 60, 102, 176, 28, 223, 233, 202, 26, 13, 68, 226, 10, 88, 152, 180, 167, 135, 87, 70, 18, 246, 118, 39, 92, 154, 229, 168, 19, 7, 78, 184, 174, 58, 189, 105, 50, 18, 180, 79, 54, 250, 124, 6, 126, 204, 92, 59, 222, 95, 40, 47, 96, 241, 63, 125, 140, 131, 167, 45, 182, 154, 131, 141, 103, 88, 19, 164, 112, 101, 66, 39, 142, 16, 86, 168, 71, 241, 118, 234, 188, 136, 157, 22, 207, 127, 13, 59, 54, 51, 162, 90, 149, 240, 9, 36, 102, 165, 167, 79, 158, 23, 139, 174, 152, 137, 241, 28, 106, 85, 229, 37, 69, 18, 79, 93, 234, 192, 203, 51, 229, 5, 44, 14, 190, 219, 56, 238, 211, 239, 172, 99, 20, 159, 72, 47, 10, 195, 154, 32, 174, 213, 85, 239, 6, 165, 94, 194, 25, 229, 65, 158, 113, 196, 137, 131, 39, 60, 159, 144, 75, 170, 107, 64, 82, 93, 69, 51, 158, 202, 51, 86, 35, 82, 243, 214, 21, 226, 233, 16, 11, 88, 12, 107, 130, 20, 166, 39, 60, 198, 113, 100, 78, 32, 214, 71, 38, 187, 229, 56, 238, 153, 32, 201, 245, 12, 96, 185, 170, 244, 86, 140, 192, 202, 109, 78, 244, 147, 25, 176, 56, 240, 22, 227, 138, 249, 107, 173, 189, 131, 141, 101, 88, 19, 68, 92, 153, 248, 95, 74, 48, 41, 16, 160, 178, 208, 139, 29, 25, 158, 111, 177, 180, 122, 62, 60, 16, 120, 248, 162, 115, 107, 166, 136, 155, 94, 146, 247, 206, 68, 173, 128, 197, 240, 38, 200, 234, 170, 239, 80, 74, 71, 251, 50, 121, 114, 250, 108, 221, 231, 0, 59, 254, 90, 86, 211, 106, 250, 29, 57, 126, 42, 37, 115, 224, 148, 27, 45, 6, 135, 86, 192, 98, 88, 19, 196, 181, 42, 145, 189, 245, 8, 216, 65, 27, 86, 158, 108, 119, 150, 243, 175, 28, 185, 37, 11, 137, 145, 131, 85, 151, 98, 25, 57, 244, 246, 55, 2, 57, 5, 20, 213, 7, 202, 14, 88, 60, 181, 113, 151, 189, 86, 176, 241, 12, 111, 130, 172, 78, 252, 22, 20, 253, 2, 13, 42, 43, 46, 83, 180, 81, 72, 80, 84, 15, 36, 208, 169, 63, 217, 166, 221, 193, 147, 46, 156, 189, 88, 156, 103, 138, 37, 175, 174, 91, 93, 8, 248, 184, 74, 224, 24, 221, 47, 7, 46, 233, 233, 177, 208, 174, 17, 127, 121, 71, 166, 35, 94, 9, 27, 164, 232, 8, 107, 130, 56, 87, 38, 254, 66, 136, 252, 51, 239, 82, 128, 12, 86, 223, 203, 5, 110, 108, 219, 119, 117, 165, 167, 146, 177, 91, 53, 54, 160, 94, 117, 109, 191, 20, 168, 55, 40, 23, 103, 100, 4, 44, 54, 170, 201, 57, 178, 84, 8, 88, 12, 107, 130, 136, 171, 18, 51, 40, 208, 52, 88, 139, 55, 24, 227, 176, 176, 243, 146, 176, 150, 178, 198, 187, 169, 157, 9, 236, 156, 134, 86, 91, 251, 97, 121, 216, 119, 84, 250, 37, 36, 33, 150, 184, 207, 94, 12, 126, 192, 98, 88, 19, 196, 181, 42, 241, 194, 149, 240, 120, 173, 174, 23, 73, 118, 177, 91, 185, 223, 246, 122, 46, 251, 214, 184, 182, 128, 166, 62, 196, 69, 73, 26, 88, 193, 206, 183, 143, 206, 199, 207, 127, 74, 207, 176, 200, 14, 54, 184, 10, 131, 159, 97, 49, 108, 9, 66, 23, 128, 23, 227, 18, 165, 207, 132, 130, 139, 65, 105, 85, 190, 148, 158, 214, 250, 219, 179, 127, 189, 91, 128, 165, 155, 229, 5, 44, 142, 232, 199, 39, 189, 183, 208, 225, 119, 70, 78, 41, 243, 18, 182, 4, 201, 255, 190, 106, 141, 8, 158, 158, 146, 2, 134, 214, 251, 158, 190, 32, 226, 247, 114, 210, 142, 150, 216, 206, 170, 76, 181, 105, 162, 189, 236, 238, 37, 246, 13, 155, 110, 69, 218, 42, 121, 1, 139, 47, 13, 48, 221, 53, 225, 203, 130, 213, 193, 156, 167, 176, 37, 8, 93, 93, 185, 149, 72, 249, 61, 193, 4, 51, 208, 99, 177, 215, 202, 107, 182, 122, 14, 246, 99, 193, 131, 53, 19, 181, 27, 255, 245, 250, 92, 59, 38, 200, 13, 88, 236, 27, 49, 116, 218, 162, 252, 25, 129, 198, 185, 180, 254, 240, 37, 200, 202, 196, 238, 34, 129, 34, 181, 216, 131, 57, 33, 222, 198, 58, 116, 210, 133, 253, 135, 203, 190, 69, 169, 81, 133, 71, 7, 13, 214, 6, 41, 237, 211, 255, 204, 54, 124, 252, 157, 188, 154, 233, 195, 239, 143, 120, 112, 242, 55, 249, 138, 148, 15, 247, 134, 115, 201, 247, 97, 75, 144, 188, 213, 213, 171, 69, 82, 241, 140, 175, 64, 132, 82, 191, 163, 167, 93, 248, 243, 224, 213, 36, 209, 106, 101, 169, 107, 113, 125, 108, 162, 21, 223, 108, 144, 119, 139, 245, 242, 0, 83, 251, 119, 190, 44, 216, 21, 204, 185, 10, 91, 130, 48, 16, 93, 171, 18, 207, 2, 168, 26, 76, 64, 131, 57, 22, 219, 19, 97, 9, 172, 99, 162, 136, 164, 176, 243, 96, 218, 120, 237, 88, 247, 140, 201, 199, 186, 93, 242, 222, 157, 140, 121, 196, 21, 57, 118, 54, 228, 29, 40, 145, 233, 116, 184, 19, 132, 221, 98, 117, 151, 137, 141, 46, 22, 0, 4, 88, 49, 79, 86, 212, 83, 106, 187, 190, 169, 112, 126, 243, 159, 246, 160, 255, 177, 11, 107, 130, 136, 171, 171, 142, 166, 148, 190, 35, 117, 50, 244, 254, 129, 65, 96, 71, 182, 136, 27, 95, 148, 23, 205, 251, 248, 109, 134, 121, 179, 86, 217, 30, 11, 140, 101, 229, 107, 13, 107, 130, 56, 211, 19, 110, 37, 28, 89, 23, 108, 80, 245, 241, 202, 70, 96, 218, 50, 7, 70, 166, 73, 63, 81, 200, 54, 9, 135, 247, 143, 184, 101, 210, 151, 249, 94, 82, 149, 43, 143, 124, 88, 19, 132, 46, 75, 140, 117, 155, 240, 167, 158, 246, 71, 249, 133, 35, 85, 35, 75, 31, 212, 237, 127, 249, 216, 123, 68, 122, 152, 201, 189, 157, 13, 187, 150, 109, 178, 181, 151, 58, 166, 18, 253, 195, 154, 32, 12, 160, 194, 85, 9, 255, 225, 64, 62, 82, 2, 44, 93, 135, 124, 4, 38, 46, 114, 224, 213, 57, 210, 175, 30, 149, 99, 8, 125, 250, 46, 211, 237, 239, 125, 149, 255, 163, 252, 209, 229, 75, 134, 61, 65, 138, 223, 102, 85, 253, 1, 160, 61, 229, 195, 164, 75, 250, 131, 64, 230, 113, 17, 119, 189, 86, 128, 147, 23, 202, 62, 92, 230, 73, 247, 253, 93, 13, 235, 23, 174, 183, 245, 240, 103, 124, 127, 100, 43, 4, 65, 10, 211, 171, 116, 225, 121, 110, 37, 165, 8, 175, 106, 158, 254, 204, 124, 16, 101, 187, 143, 204, 199, 175, 251, 165, 191, 218, 189, 46, 137, 191, 180, 117, 159, 131, 229, 98, 86, 173, 85, 8, 130, 48, 116, 157, 63, 36, 222, 66, 220, 88, 175, 26, 210, 21, 116, 96, 185, 213, 109, 57, 2, 56, 85, 136, 222, 189, 118, 154, 42, 12, 65, 138, 159, 71, 18, 59, 112, 192, 118, 20, 157, 253, 211, 91, 160, 17, 120, 254, 83, 27, 102, 164, 75, 15, 43, 209, 194, 149, 163, 4, 155, 10, 183, 80, 232, 218, 216, 4, 209, 101, 154, 9, 160, 111, 160, 23, 72, 69, 213, 191, 243, 128, 136, 55, 191, 176, 99, 229, 118, 233, 97, 237, 221, 219, 8, 199, 214, 237, 176, 107, 38, 217, 120, 133, 35, 72, 201, 162, 117, 173, 74, 120, 12, 32, 108, 227, 233, 182, 138, 186, 144, 149, 246, 187, 192, 78, 49, 121, 137, 3, 147, 22, 59, 96, 43, 148, 182, 91, 206, 206, 156, 95, 151, 44, 204, 76, 93, 94, 240, 95, 165, 237, 242, 71, 95, 133, 37, 200, 95, 68, 73, 175, 122, 55, 56, 218, 231, 202, 21, 165, 166, 63, 96, 86, 84, 217, 245, 127, 184, 176, 120, 147, 19, 172, 80, 231, 238, 131, 190, 239, 115, 24, 5, 160, 75, 51, 225, 120, 227, 218, 220, 138, 180, 21, 214, 103, 180, 136, 95, 133, 39, 72, 233, 73, 113, 166, 39, 222, 66, 4, 90, 197, 237, 38, 9, 4, 180, 10, 8, 9, 104, 217, 4, 45, 46, 8, 95, 109, 154, 185, 186, 176, 129, 195, 65, 121, 135, 147, 242, 86, 39, 4, 151, 19, 156, 211, 13, 206, 233, 4, 239, 18, 41, 231, 20, 193, 93, 249, 201, 139, 87, 126, 167, 148, 136, 38, 3, 61, 111, 50, 114, 103, 12, 2, 78, 86, 141, 47, 248, 40, 216, 193, 135, 190, 250, 87, 97, 159, 65, 164, 2, 164, 247, 175, 216, 8, 232, 87, 144, 138, 61, 255, 186, 247, 94, 16, 208, 9, 162, 47, 17, 29, 1, 15, 8, 232, 4, 209, 151, 135, 142, 128, 78, 16, 125, 13, 232, 8, 200, 67, 64, 191, 130, 200, 195, 77, 151, 170, 32, 8, 232, 4, 169, 32, 19, 173, 187, 41, 15, 1, 157, 32, 242, 112, 211, 165, 42, 8, 2, 58, 65, 42, 200, 68, 235, 110, 202, 67, 64, 39, 136, 60, 220, 116, 169, 10, 130, 128, 78, 144, 10, 50, 209, 186, 155, 242, 16, 208, 9, 34, 15, 55, 93, 170, 130, 32, 240, 127, 250, 177, 44, 155, 220, 199, 212, 220, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}}, + + Release: true, + Custom: map[string]string{ + + }, + + }) +} + diff --git a/main.go b/main.go index 7cc4a37..1d93521 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ /* * @Author: BlackTeay * @Date: 2024-04-30 09:37:39 - * @LastEditTime: 2024-05-08 14:43:47 + * @LastEditTime: 2024-05-09 16:08:54 * @LastEditors: BlackTeay * @Description: * @FilePath: /hls_builder/main.go @@ -10,23 +10,13 @@ package main import ( - "bufio" - "embed" _ "embed" "errors" "fmt" - "image/color" "log" - "math" "net/url" "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strconv" - "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" @@ -34,135 +24,10 @@ import ( "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/storage" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/google/uuid" ) -//go:embed "font/NotoSansSC-Regular.ttf" -var ttfBytes []byte - -//go:embed bin/* -var ffmpegFS embed.FS - -// MyTheme 定义了一个自定义的主题结构体。 -type MyTheme struct{} - -// Color 根据给定的主题颜色名称和变种返回相应的颜色。 -// 参数 c 为主题颜色的名称。 -// 参数 v 为颜色的变种。 -// 返回值为符合 color.Color 接口的颜色值。 -func (MyTheme) Color(c fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { - return theme.DefaultTheme().Color(c, v) -} - -// Font 根据给定的文本样式返回相应的字体资源。 -// 参数 s 为文本的样式,决定了使用的字体。 -// 返回值为一个 fyne.Resource 类型的字体资源。 -func (MyTheme) Font(s fyne.TextStyle) fyne.Resource { - return fyne.NewStaticResource("./font/NotoSansSC-Regular.ttf", ttfBytes) -} - -// Icon 根据给定的图标名称返回相应的图标资源。 -// 参数 n 为图标的名称。 -// 返回值为一个 fyne.Resource 类型的图标资源。 -func (MyTheme) Icon(n fyne.ThemeIconName) fyne.Resource { - return theme.DefaultTheme().Icon(n) -} - -// Size 根据给定的主题尺寸名称返回相应的尺寸值。 -// 参数 s 为主题尺寸的名称。 -// 返回值为相应的尺寸值,单位为 float32。 -func (MyTheme) Size(s fyne.ThemeSizeName) float32 { - return theme.DefaultTheme().Size(s) -} - -type UploadStats struct { - Successed int - Failed int - TotalSize string - AverageSpeed string - Elapsed string -} - -// getFFmpegPath 获取FFmpeg可执行文件的路径。 -// 该函数根据运行时操作系统选择对应的FFmpeg文件,将该文件从嵌入的文件系统读出, -// 并写入到一个临时目录中,然后返回这个临时文件的路径。 -// 返回值: -// -// string - FFmpeg可执行文件的临时路径。 -func getFFmpegPath() (string, func(), error) { - var ffmpegFileName string - switch runtime.GOOS { - case "windows": - ffmpegFileName = "ffmpeg_windows.exe" - case "darwin": - ffmpegFileName = "ffmpeg_darwin" - } - - // 根据操作系统选择的FFmpeg文件名,从嵌入的文件系统中读取文件内容 - data, err := ffmpegFS.ReadFile("bin/" + ffmpegFileName) - if err != nil { - log.Fatal(err) - } - - // 创建临时目录并将FFmpeg文件内容写入该目录中的文件 - tmpDir, err := os.MkdirTemp("", "ffmpeg") - if err != nil { - log.Fatal(err) - } - tmpFilePath := filepath.Join(tmpDir, ffmpegFileName) - if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { - log.Fatal(err) - } else { - log.Println("FFmpeg path:", tmpFilePath) - } - cleanup := func() { - os.RemoveAll(tmpDir) // 删除整个临时目录 - log.Println("Cleaned up FFmpeg temporary files.") - } - - return tmpFilePath, cleanup, nil -} - -// getUs3cliPath 获取us3cli二进制文件的临时路径。 -// 该函数根据运行的操作系统选择对应的us3cli文件,并将其从嵌入的文件系统中读出, -// 然后写入到一个临时文件中,最后返回这个临时文件的路径。 -// 返回值: -// - string: us3cli临时文件的路径。 -func getUs3cliPath() (string, func(), error) { - var us3cliFileName string - switch runtime.GOOS { - case "windows": - us3cliFileName = "us3cli-windows.exe" - case "darwin": - us3cliFileName = "us3cli-mac" - } - - // 根据操作系统选择的us3cli文件名,从嵌入的文件系统中读取文件内容 - data, err := ffmpegFS.ReadFile("bin/" + us3cliFileName) - if err != nil { - log.Fatal(err) - } - - // 创建临时目录并将us3cli文件内容写入其中 - tmpDir, err := os.MkdirTemp("", "us3cli") - if err != nil { - log.Fatal(err) - } - tmpFilePath := filepath.Join(tmpDir, us3cliFileName) - if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { - log.Fatal(err) - } else { - log.Println("us3cli path:", tmpFilePath) - } - cleanup := func() { - os.RemoveAll(tmpDir) // 删除整个临时目录 - log.Println("Cleaned up Us3cli temporary files.") - } - return tmpFilePath, cleanup, nil -} - // main 是应用程序的入口点。 func main() { // 打印当前工作目录 @@ -174,7 +39,7 @@ func main() { // 创建主窗口并设置大小 myWindow := myApp.NewWindow("HLS-builder 视频切片上传工具") - myWindow.Resize(fyne.NewSize(800, 400)) + myWindow.Resize(fyne.NewSize(750, 550)) // 初始化UI组件:文件选择提示、输出路径选择提示、进度条 inputFile := widget.NewLabel("请选择mp4文件!") @@ -288,460 +153,6 @@ func main() { myWindow.ShowAndRun() } -// sliceVideo 切割视频文件为HLS格式的多个段。 -// -// 参数: -// inputFile - 输入视频文件的路径。 -// outputDir - 输出目录的路径,切片和播放列表将保存于此目录。 -// progressBar - 用于显示切割过程进度的进度条。 -// onComplete - 切割完成后调用的回调函数。 -// btnSelectFile, btnSelectOutput, btnSlice - 用于用户界面交互的按钮。 -// myWindow - 所属的FYNE窗口,用于显示对话框和其他UI元素。 -// -// 此函数使用FFmpeg工具切割视频,将视频文件分解为多个时长为5秒的HLS段,并创建一个播放列表。 -// 过程中会更新进度条,并在完成时显示一个通知对话框。 -func sliceVideo(inputFile, outputDir string, progressBar *widget.ProgressBar, onComplete func(), btnSelectFile, btnSelectOutput, btnSlice *widget.Button, btnUpload *widget.Button, myWindow fyne.Window) { - // 获取FFmpeg可执行文件的路径 - ffmpegPath, cleanup, err := getFFmpegPath() - if err != nil { - log.Fatal(err) - } - startTime := time.Now() // 记录开始时间以便计算总耗时 - log.Println("开始切割视频") - - // 构建FFmpeg命令行,配置输出为HLS格式,每5秒切一片 - cmd := exec.Command( - ffmpegPath, - "-i", inputFile, // 输入文件 - "-c", "copy", // 复制编解码器设置,避免重新编码 - "-start_number", "0", // HLS段的起始编号 - "-hls_time", "5", // 每个HLS段的最大时长(秒) - "-hls_list_size", "0", // 播放列表中的最大段数,0表示无限制 - "-f", "hls", // 输出格式为HLS - "-hls_segment_filename", outputDir+"/%08d.ts", // 段文件的命名方式 - outputDir+"/playlist.m3u8", // 输出的m3u8文件位置 - ) - - // 设置管道以读取FFmpeg的stderr,用于解析进度信息 - cmdReader, err := cmd.StderrPipe() - if err != nil { - log.Fatal("创建stderr管道失败:", err) - } - - if err := cmd.Start(); err != nil { - log.Fatalf("cmd.Start()失败: %s\n", err) - } - - scanner := bufio.NewScanner(cmdReader) - totalDuration := getTotalDuration(inputFile) // 获取视频的总时长 - log.Printf("视频总时长%f\n", totalDuration) - segment := math.Ceil(totalDuration / 5) // 计算所需的段数 - log.Println(segment) - - // 在后台goroutine中监控FFmpeg的输出,更新进度条并处理完成状态 - go func() { - var completed bool - for scanner.Scan() { - line := scanner.Text() - log.Println("FFmpeg输出:", line) - // 解析FFmpeg的输出,更新进度条 - if segmentCount := parseFFmpegProgressOutput(line); segmentCount > 0 { - log.Println(segmentCount / segment) - progressBar.SetValue(segmentCount / segment) - } - // 检查是否已完成切割 - if checkFFmpegCompletion(line) { - completed = true - endTime := time.Now() // 记录完成时间 - duration := endTime.Sub(startTime) // 计算总耗时 - formattedDuration := formatDuration(duration) // 格式化耗时显示 - progressBar.SetValue(1.0) // 设置进度条为完成状态 - // 显示完成信息的对话框 - dialog.ShowInformation("完成", fmt.Sprintf("✅ 视频切片完成!可以将切片直接上传云存储。\n总用时: %s\n存储位置:%s", formattedDuration, outputDir), myWindow) - log.Println("视频切片完成") - btnUpload.Enable() - cleanup() // 确保在函数返回时删除临时目录 - break - } - } - if err := cmd.Wait(); err != nil { - log.Fatalf("cmd.Wait()失败: %s\n", err) - } - if !completed { - log.Println("❌ 视频切片可能未正确完成") - } - onComplete() // 调用完成回调 - }() -} - -// getTotalDuration 使用 FFmpeg 提取视频文件的总时长 -// 参数: -// -// file string - 视频文件的路径 -// -// 返回值: -// -// float64 - 视频的总时长(单位:秒) -func getTotalDuration(file string) float64 { - // 获取 FFmpeg 可执行文件的路径 - ffmpegPath, cleanup, err := getFFmpegPath() - if err != nil { - log.Fatal(err) - } - defer cleanup() // 确保在函数返回时删除临时目录 - // 构建并启动 FFmpeg 命令,用于获取视频时长 - cmd := exec.Command(ffmpegPath, "-i", file) - - // 由于 FFmpeg 在只有 "-i" 参数时将输出发送到 stderr,因此我们需要捕获 stderr - stderr, err := cmd.StderrPipe() - if err != nil { - log.Fatal("Failed to get stderr pipe:", err) - } - - // 启动 FFmpeg 命令 - if err := cmd.Start(); err != nil { - log.Println("Failed to start ffmpeg:", err) - return 0.0 // 无法获取时长时返回 0.0 - } - - // 使用 Scanner 从 stderr 中读取输出 - scanner := bufio.NewScanner(stderr) - // 正则表达式用于匹配 FFmpeg 输出的时长信息 - durationRegex := regexp.MustCompile(`Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})`) - var duration float64 = 0.0 - for scanner.Scan() { - // 寻找匹配的时长字符串,并解析为秒 - matches := durationRegex.FindStringSubmatch(scanner.Text()) - if matches != nil { - hours, _ := strconv.Atoi(matches[1]) - minutes, _ := strconv.Atoi(matches[2]) - seconds, _ := strconv.ParseFloat(matches[3], 64) - duration = float64(hours)*3600 + float64(minutes)*60 + seconds - break // 找到时长后即刻跳出循环 - } - } - - // 等待 FFmpeg 命令执行完成,不处理命令结束状态 - cmd.Wait() - - return duration -} - -// parseFFmpegProgressOutput 函数解析 FFmpeg 进度输出行,返回匹配到的数字部分。 -// 参数: -// -// line string - FFmpeg 进度输出的一行文本。 -// -// 返回值: -// -// float64 - 匹配到的数字转换为浮点数后返回,如果没有匹配到则返回 0。 -func parseFFmpegProgressOutput(line string) float64 { - re := regexp.MustCompile(`(\d+)\.ts`) // 使用正则表达式编译模式以匹配数字后跟.ts的字符串 - matches := re.FindStringSubmatch(line) - if len(matches) > 1 { - number, err := strconv.Atoi(matches[1]) // 将提取的数字字符串转换为int类型 - if err != nil { - log.Println("Error converting string to integer:", err) // 记录转换错误日志 - return 0 - } - return float64(number) // 返回匹配到的数字转换后的浮点数 - } - return 0 // 如果没有找到匹配的模式,返回0 -} - -// checkFFmpegCompletion 函数用于检查 FFmpeg 处理进程是否完成。 -// 它通过分析 FFmpeg 输出的行中是否包含特定的字符串来判断处理是否完成。 -// -// 参数: -// line string - FFmpeg 输出的一行文本。 -// -// 返回值: -// bool - 如果行中包含指定的字符串,则返回 true,表示处理已完成;否则返回 false。 -func checkFFmpegCompletion(line string) bool { - // 记录接收到的行,以便于调试 - log.Println("line:", line) - - // 正则表达式匹配含有 "out#0/hls" 的行,这通常在 FFmpeg 输出最后阶段显示 - re := regexp.MustCompile(`out#0/hls`) - - // 检查这一行是否匹配正则表达式 - matchFound := re.MatchString(line) - log.Println("re", matchFound) - - return matchFound -} - -// formatDuration 将时间Duration格式化为易读的字符串 -// 参数: -// -// d time.Duration - 需要格式化的时间长度 -// -// 返回值: -// -// string - 格式化后的时间字符串,格式为 "X小时X分X秒X毫秒",根据实际时间长度包含其中的项 -func formatDuration(d time.Duration) string { - // 将时间转换为总秒数 - totalSeconds := int(d.Seconds()) - // 计算小时、分钟和秒 - hours := totalSeconds / 3600 - minutes := (totalSeconds % 3600) / 60 - seconds := totalSeconds % 60 - // 获取毫秒数 - milliseconds := int(d.Milliseconds()) % 1000 - - result := "" - // 如果有小时,则添加到结果中 - if hours > 0 { - result += fmt.Sprintf("%d小时", hours) - } - // 如果有小时或者分钟不为0,则添加分钟到结果中 - if minutes > 0 || hours > 0 { - result += fmt.Sprintf("%d分", minutes) - } - // 如果秒不为0,或者同时小时和分钟都不为0,则添加秒到结果中 - if seconds > 0 || (hours != 0 && minutes != 0) { - result += fmt.Sprintf("%d秒", seconds) - } - // 总是添加毫秒到结果中 - result += fmt.Sprintf("%d毫秒", milliseconds) - - return result -} - -// 上传到Ucloud -func upload(localPath string, keyPath string, progressBar *widget.ProgressBar, myWindow fyne.Window, onComplete func()) { - fmt.Println("上传到Ucloud") - progressBar.SetValue(0) - fmt.Println("localPath:", localPath) - fmt.Println("keyPath:", keyPath) - // 统计 .ts .m3u8 文件数量 - tsFiles, m3u8Files, err := countMediaFiles(localPath) - if err != nil { - log.Fatalf("Error counting media files: %s", err) - } - fmt.Printf("Total .ts files: %d\n", tsFiles) - fmt.Printf("Total .m3u8 files: %d\n", m3u8Files) - filesTotal := float64(tsFiles + m3u8Files) - fmt.Printf("Total files: %d\n", int(filesTotal)) - // 获取 us3cli 的路径 - us3cliPath, cleanup, err := getUs3cliPath() - if err != nil { - log.Fatal(err) - } - defer cleanup() // 确保在函数返回时删除临时目录 - bucket := "us3://jlntv-live/replay/" + keyPath - // us3ConfigPath, cleanup, err := getUs3Config() - // if err != nil { - // log.Fatal(err) - // } - // defer cleanup() // 确保在函数返回时删除临时目录 - // cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--config", us3ConfigPath) - cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--accesskey", "TOKEN_6096c736-12b7-4c20-bfa5-e85e0e9c9b65", "--secretkey", "cc8a5965-3325-4231-9f64-a6626f624049", "--endpoint", "cn-bj.ufileos.com") - //输出cmd - fmt.Println(cmd.String()) - - stdout, err := cmd.StdoutPipe() - if err != nil { - log.Fatal("Error creating stdout pipe:", err) - } - - if err := cmd.Start(); err != nil { - log.Fatalf("Failed to start command: %s", err) - } - progress := 0.0 - go func() { - buffer := make([]byte, 4096) // 创建一个足够大的buffer - for { - n, err := stdout.Read(buffer) - if n > 0 { - fmt.Print("STDOUT:", string(buffer[:n])) // 打印实时输出 - filesUploadedTotal, err := extractTotalValue(string(buffer[:n])) - if err != nil { - fmt.Println("Error:", err) - } else { - fmt.Println("Total:", filesUploadedTotal) - } - stats, err := parseUploadStats(string(buffer[:n])) - if err != nil { - fmt.Println("Error parsing upload stats:", err) - } else { - fmt.Printf("Uploaded: %d, Failed: %d, Total Size: %s, Speed: %s\n", - stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed) - if float64(stats.Successed) == filesTotal { - progressBar.SetValue(1) - showCustomDialog := func() { - replayURL := fmt.Sprintf("https://video.jlntv.cn/replay/%s/playlist.m3u8", keyPath) - copyBtn := widget.NewButton("复制地址", func() { - fmt.Println("copyed:", replayURL) - // dialog.ShowInformation("Submitted", "You submitted: "+input.Text, myWindow) - clipboard := myWindow.Clipboard() - clipboard.SetContent(replayURL) - // showToast(myWindow, "地址已复制到剪贴板", 3*time.Second) - dialog.ShowInformation("提示", "地址已复制到剪贴板!", myWindow) - }) - // 创建自定义内容的容器 - content := container.NewVBox( - widget.NewLabel("Upload Stats:"), - widget.NewLabel(fmt.Sprintf("用时:%s秒", stats.Elapsed)), - widget.NewLabel(fmt.Sprintf("成功:%d", stats.Successed)), - widget.NewLabel(fmt.Sprintf("失败:%d", stats.Failed)), - widget.NewLabel(fmt.Sprintf("平均速度:%s", stats.AverageSpeed)), - widget.NewLabel(fmt.Sprintf("总大小:%s", stats.TotalSize)), - widget.NewLabel(fmt.Sprintf("回看地址: %s", replayURL)), - copyBtn, - ) - // 设置内容容器的最小尺寸 - content.Resize(fyne.NewSize(500, 600)) // 你可以根据需要调整这个尺寸 - // 创建并显示自定义对话框 - customDialog := dialog.NewCustom("上传成功", "关闭", content, myWindow) - customDialog.Show() - } - showCustomDialog() - // dialog.ShowInformation("完成", fmt.Sprintf("✅ HLS上传完毕\n总用时:%s秒\n成功:%d 失败:%d 总大小:%s 平均速度:%s", stats.Elapsed, stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed), myWindow) - fmt.Println("Upload completed!") - } else { - progress = filesUploadedTotal / filesTotal - fmt.Println("Progress:", progress) - progressBar.SetValue(progress) - } - } - - } - if err != nil { - break - } - } - }() - - if err := cmd.Wait(); err != nil { - log.Printf("Command finished with error: %v", err) - } - - if onComplete != nil { - onComplete() - } -} - -// extractTotalValue 函数从给定的文本中提取总值。 -// -// 参数: -// -// text string - 包含待搜索总值的文本。 -// -// 返回值: -// -// float64 - 从文本中提取到的总值,如果未找到则返回默认值200。 -// error - 如果在提取过程中发生错误,则返回相应的错误;否则返回nil。 -func extractTotalValue(text string) (float64, error) { - // 使用正则表达式匹配文本中的总值部分 - re := regexp.MustCompile(`Total:(\d+)`) - matches := re.FindStringSubmatch(text) - if len(matches) > 1 { - // 将匹配到的总值字符串转换为整数 - total, err := strconv.Atoi(matches[1]) - if err != nil { - // 如果转换过程中发生错误,返回错误信息 - return 0, err - } - // 返回转换后的总值 - return float64(total), nil - } - // 如果未在文本中找到总值,返回默认值200和一个错误信息 - return 200, fmt.Errorf("no total found in the text") -} - -// parseUploadStats 函数解析上传统计信息字符串,并返回一个包含上传统计详情的结构体指针。 -// 参数 line 为待解析的上传统计信息字符串。 -// 返回 *UploadStats 指向解析出的上传统计详情结构体。 -// 返回 error 表示在解析过程中遇到的任何错误。 -func parseUploadStats(line string) (*UploadStats, error) { - stats := &UploadStats{} - var err error - - // 初始化正则表达式用于匹配不同的上传统计信息。 - successedRegex := regexp.MustCompile(`(\d+) Successed`) - failedRegex := regexp.MustCompile(`(\d+) Failed`) - sizeRegex := regexp.MustCompile(`Size: ([\d.]+ MB)`) - speedRegex := regexp.MustCompile(`Average speed ([\d.]+ MB/s)`) - elapsedRegex := regexp.MustCompile(`Elapsed\s*:\s*([\d.]+)s`) - - // 提取成功上传的文件数量。 - successedMatches := successedRegex.FindStringSubmatch(line) - if len(successedMatches) > 1 { - stats.Successed, err = strconv.Atoi(successedMatches[1]) - if err != nil { - return nil, fmt.Errorf("error parsing successed number: %v", err) - } - } - - // 提取失败上传的文件数量。 - failedMatches := failedRegex.FindStringSubmatch(line) - if len(failedMatches) > 1 { - stats.Failed, err = strconv.Atoi(failedMatches[1]) - if err != nil { - return nil, fmt.Errorf("error parsing failed number: %v", err) - } - } - - // 提取上传的总大小。 - sizeMatches := sizeRegex.FindStringSubmatch(line) - if len(sizeMatches) > 1 { - stats.TotalSize = sizeMatches[1] - } - - // 提取平均上传速度。 - speedMatches := speedRegex.FindStringSubmatch(line) - if len(speedMatches) > 1 { - stats.AverageSpeed = speedMatches[1] - } - - // 提取上传所花费的时间。 - elapsedMatches := elapsedRegex.FindStringSubmatch(line) - if len(elapsedMatches) > 1 { - stats.Elapsed = elapsedMatches[1] - } - - return stats, nil -} - -// countMediaFiles 统计给定目录中 .ts 和 .m3u8 文件的数量。 -// -// 参数: -// -// directory string - 需要遍历统计文件的目录路径。 -// -// 返回值: -// -// int - .ts 文件的数量。 -// int - .m3u8 文件的数量。 -// error - 遍历目录过程中遇到的任何错误。 -func countMediaFiles(directory string) (int, int, error) { - var tsCount, m3u8Count int // 分别用于记录 .ts 和 .m3u8 文件的数量 - - // 使用 filepath.Walk 遍历指定目录及其子目录中的所有文件 - err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { - // 检查遍历过程是否遇到错误 - if err != nil { - return err // 如果有错误,则终止遍历并返回该错误 - } - - // 检查当前路径项是否为文件,不遍历目录 - if !info.IsDir() { - // 根据文件扩展名统计 .ts 和 .m3u8 文件数量 - switch filepath.Ext(info.Name()) { - case ".ts": - tsCount++ // .ts 文件计数 - case ".m3u8": - m3u8Count++ // .m3u8 文件计数 - } - } - return nil // 继续遍历下一个文件或目录 - }) - - // 返回 .ts 和 .m3u8 文件的计数结果,以及遍历过程中可能遇到的错误 - return tsCount, m3u8Count, err -} - // createURL 是一个用于创建并返回一个url.URL指针的函数。 // 它接受一个字符串类型的参数link,表示待解析的URL链接。 // 如果链接解析失败,将会记录错误信息并终止程序。 diff --git a/paths.go b/paths.go new file mode 100644 index 0000000..7bea4f8 --- /dev/null +++ b/paths.go @@ -0,0 +1,101 @@ +/* + * @Author: BlackTeay + * @Date: 2024-05-09 15:41:11 + * @LastEditTime: 2024-05-09 15:59:27 + * @LastEditors: BlackTeay + * @Description: + * @FilePath: /hls_builder/paths.go + * Copyright 2024 JLNTV NMTD, All Rights Reserved. + */ +// paths.go + +package main + +import ( + "embed" + "log" + "os" + "path/filepath" + "runtime" +) + +//go:embed bin/* +var binFS embed.FS + +// getFFmpegPath 获取FFmpeg可执行文件的路径。 +// 该函数根据运行时操作系统选择对应的FFmpeg文件,将该文件从嵌入的文件系统读出, +// 并写入到一个临时目录中,然后返回这个临时文件的路径。 +// 返回值: +// +// string - FFmpeg可执行文件的临时路径。 +func getFFmpegPath() (string, func(), error) { + var ffmpegFileName string + switch runtime.GOOS { + case "windows": + ffmpegFileName = "ffmpeg_windows.exe" + case "darwin": + ffmpegFileName = "ffmpeg_darwin" + } + + // 根据操作系统选择的FFmpeg文件名,从嵌入的文件系统中读取文件内容 + data, err := binFS.ReadFile("bin/" + ffmpegFileName) + if err != nil { + log.Fatal(err) + } + + // 创建临时目录并将FFmpeg文件内容写入该目录中的文件 + tmpDir, err := os.MkdirTemp("", "ffmpeg") + if err != nil { + log.Fatal(err) + } + tmpFilePath := filepath.Join(tmpDir, ffmpegFileName) + if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { + log.Fatal(err) + } else { + log.Println("FFmpeg path:", tmpFilePath) + } + cleanup := func() { + os.RemoveAll(tmpDir) // 删除整个临时目录 + log.Println("Cleaned up FFmpeg temporary files.") + } + + return tmpFilePath, cleanup, nil +} + +// getUs3cliPath 获取us3cli二进制文件的临时路径。 +// 该函数根据运行的操作系统选择对应的us3cli文件,并将其从嵌入的文件系统中读出, +// 然后写入到一个临时文件中,最后返回这个临时文件的路径。 +// 返回值: +// - string: us3cli临时文件的路径。 +func getUs3cliPath() (string, func(), error) { + var us3cliFileName string + switch runtime.GOOS { + case "windows": + us3cliFileName = "us3cli-windows.exe" + case "darwin": + us3cliFileName = "us3cli-mac" + } + + // 根据操作系统选择的us3cli文件名,从嵌入的文件系统中读取文件内容 + data, err := binFS.ReadFile("bin/" + us3cliFileName) + if err != nil { + log.Fatal(err) + } + + // 创建临时目录并将us3cli文件内容写入其中 + tmpDir, err := os.MkdirTemp("", "us3cli") + if err != nil { + log.Fatal(err) + } + tmpFilePath := filepath.Join(tmpDir, us3cliFileName) + if err := os.WriteFile(tmpFilePath, data, 0755); err != nil { + log.Fatal(err) + } else { + log.Println("us3cli path:", tmpFilePath) + } + cleanup := func() { + os.RemoveAll(tmpDir) // 删除整个临时目录 + log.Println("Cleaned up Us3cli temporary files.") + } + return tmpFilePath, cleanup, nil +} diff --git a/slice.go b/slice.go new file mode 100644 index 0000000..120ab37 --- /dev/null +++ b/slice.go @@ -0,0 +1,246 @@ +/* + * @Author: BlackTeay + * @Date: 2024-05-09 15:47:30 + * @LastEditTime: 2024-05-09 16:07:08 + * @LastEditors: BlackTeay + * @Description: + * @FilePath: /hls_builder/slice.go + * Copyright 2024 JLNTV NMTD, All Rights Reserved. + */ + +package main + +import ( + "bufio" + "fmt" + "log" + "math" + "os/exec" + "regexp" + "strconv" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" +) + +// sliceVideo 切割视频文件为HLS格式的多个段。 +// +// 参数: +// inputFile - 输入视频文件的路径。 +// outputDir - 输出目录的路径,切片和播放列表将保存于此目录。 +// progressBar - 用于显示切割过程进度的进度条。 +// onComplete - 切割完成后调用的回调函数。 +// btnSelectFile, btnSelectOutput, btnSlice - 用于用户界面交互的按钮。 +// myWindow - 所属的FYNE窗口,用于显示对话框和其他UI元素。 +// +// 此函数使用FFmpeg工具切割视频,将视频文件分解为多个时长为5秒的HLS段,并创建一个播放列表。 +// 过程中会更新进度条,并在完成时显示一个通知对话框。 +func sliceVideo(inputFile, outputDir string, progressBar *widget.ProgressBar, onComplete func(), btnSelectFile, btnSelectOutput, btnSlice *widget.Button, btnUpload *widget.Button, myWindow fyne.Window) { + // 获取FFmpeg可执行文件的路径 + ffmpegPath, cleanup, err := getFFmpegPath() + if err != nil { + log.Fatal(err) + } + startTime := time.Now() // 记录开始时间以便计算总耗时 + log.Println("开始切割视频") + + // 构建FFmpeg命令行,配置输出为HLS格式,每5秒切一片 + cmd := exec.Command( + ffmpegPath, + "-i", inputFile, // 输入文件 + "-c", "copy", // 复制编解码器设置,避免重新编码 + "-start_number", "0", // HLS段的起始编号 + "-hls_time", "5", // 每个HLS段的最大时长(秒) + "-hls_list_size", "0", // 播放列表中的最大段数,0表示无限制 + "-f", "hls", // 输出格式为HLS + "-hls_segment_filename", outputDir+"/%08d.ts", // 段文件的命名方式 + outputDir+"/playlist.m3u8", // 输出的m3u8文件位置 + ) + + // 设置管道以读取FFmpeg的stderr,用于解析进度信息 + cmdReader, err := cmd.StderrPipe() + if err != nil { + log.Fatal("创建stderr管道失败:", err) + } + + if err := cmd.Start(); err != nil { + log.Fatalf("cmd.Start()失败: %s\n", err) + } + + scanner := bufio.NewScanner(cmdReader) + totalDuration := getTotalDuration(inputFile) // 获取视频的总时长 + log.Printf("视频总时长%f\n", totalDuration) + segment := math.Ceil(totalDuration / 5) // 计算所需的段数 + log.Println(segment) + + // 在后台goroutine中监控FFmpeg的输出,更新进度条并处理完成状态 + go func() { + var completed bool + for scanner.Scan() { + line := scanner.Text() + log.Println("FFmpeg输出:", line) + // 解析FFmpeg的输出,更新进度条 + if segmentCount := parseFFmpegProgressOutput(line); segmentCount > 0 { + log.Println(segmentCount / segment) + progressBar.SetValue(segmentCount / segment) + } + // 检查是否已完成切割 + if checkFFmpegCompletion(line) { + completed = true + endTime := time.Now() // 记录完成时间 + duration := endTime.Sub(startTime) // 计算总耗时 + formattedDuration := formatDuration(duration) // 格式化耗时显示 + progressBar.SetValue(1.0) // 设置进度条为完成状态 + // 显示完成信息的对话框 + dialog.ShowInformation("完成", fmt.Sprintf("✅ 视频切片完成!可以将切片直接上传云存储。\n总用时: %s\n存储位置:%s", formattedDuration, outputDir), myWindow) + log.Println("视频切片完成") + btnUpload.Enable() + cleanup() // 确保在函数返回时删除临时目录 + break + } + } + if err := cmd.Wait(); err != nil { + log.Fatalf("cmd.Wait()失败: %s\n", err) + } + if !completed { + log.Println("❌ 视频切片可能未正确完成") + } + onComplete() // 调用完成回调 + }() +} + +// checkFFmpegCompletion 函数用于检查 FFmpeg 处理进程是否完成。 +// 它通过分析 FFmpeg 输出的行中是否包含特定的字符串来判断处理是否完成。 +// +// 参数: +// line string - FFmpeg 输出的一行文本。 +// +// 返回值: +// bool - 如果行中包含指定的字符串,则返回 true,表示处理已完成;否则返回 false。 +func checkFFmpegCompletion(line string) bool { + // 记录接收到的行,以便于调试 + log.Println("line:", line) + + // 正则表达式匹配含有 "out#0/hls" 的行,这通常在 FFmpeg 输出最后阶段显示 + re := regexp.MustCompile(`out#0/hls`) + + // 检查这一行是否匹配正则表达式 + matchFound := re.MatchString(line) + log.Println("re", matchFound) + + return matchFound +} + +// getTotalDuration 使用 FFmpeg 提取视频文件的总时长 +// 参数: +// +// file string - 视频文件的路径 +// +// 返回值: +// +// float64 - 视频的总时长(单位:秒) +func getTotalDuration(file string) float64 { + // 获取 FFmpeg 可执行文件的路径 + ffmpegPath, cleanup, err := getFFmpegPath() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 + // 构建并启动 FFmpeg 命令,用于获取视频时长 + cmd := exec.Command(ffmpegPath, "-i", file) + + // 由于 FFmpeg 在只有 "-i" 参数时将输出发送到 stderr,因此我们需要捕获 stderr + stderr, err := cmd.StderrPipe() + if err != nil { + log.Fatal("Failed to get stderr pipe:", err) + } + + // 启动 FFmpeg 命令 + if err := cmd.Start(); err != nil { + log.Println("Failed to start ffmpeg:", err) + return 0.0 // 无法获取时长时返回 0.0 + } + + // 使用 Scanner 从 stderr 中读取输出 + scanner := bufio.NewScanner(stderr) + // 正则表达式用于匹配 FFmpeg 输出的时长信息 + durationRegex := regexp.MustCompile(`Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})`) + var duration float64 = 0.0 + for scanner.Scan() { + // 寻找匹配的时长字符串,并解析为秒 + matches := durationRegex.FindStringSubmatch(scanner.Text()) + if matches != nil { + hours, _ := strconv.Atoi(matches[1]) + minutes, _ := strconv.Atoi(matches[2]) + seconds, _ := strconv.ParseFloat(matches[3], 64) + duration = float64(hours)*3600 + float64(minutes)*60 + seconds + break // 找到时长后即刻跳出循环 + } + } + + // 等待 FFmpeg 命令执行完成,不处理命令结束状态 + cmd.Wait() + + return duration +} + +// parseFFmpegProgressOutput 函数解析 FFmpeg 进度输出行,返回匹配到的数字部分。 +// 参数: +// +// line string - FFmpeg 进度输出的一行文本。 +// +// 返回值: +// +// float64 - 匹配到的数字转换为浮点数后返回,如果没有匹配到则返回 0。 +func parseFFmpegProgressOutput(line string) float64 { + re := regexp.MustCompile(`(\d+)\.ts`) // 使用正则表达式编译模式以匹配数字后跟.ts的字符串 + matches := re.FindStringSubmatch(line) + if len(matches) > 1 { + number, err := strconv.Atoi(matches[1]) // 将提取的数字字符串转换为int类型 + if err != nil { + log.Println("Error converting string to integer:", err) // 记录转换错误日志 + return 0 + } + return float64(number) // 返回匹配到的数字转换后的浮点数 + } + return 0 // 如果没有找到匹配的模式,返回0 +} + +// formatDuration 将时间Duration格式化为易读的字符串 +// 参数: +// +// d time.Duration - 需要格式化的时间长度 +// +// 返回值: +// +// string - 格式化后的时间字符串,格式为 "X小时X分X秒X毫秒",根据实际时间长度包含其中的项 +func formatDuration(d time.Duration) string { + // 将时间转换为总秒数 + totalSeconds := int(d.Seconds()) + // 计算小时、分钟和秒 + hours := totalSeconds / 3600 + minutes := (totalSeconds % 3600) / 60 + seconds := totalSeconds % 60 + // 获取毫秒数 + milliseconds := int(d.Milliseconds()) % 1000 + + result := "" + // 如果有小时,则添加到结果中 + if hours > 0 { + result += fmt.Sprintf("%d小时", hours) + } + // 如果有小时或者分钟不为0,则添加分钟到结果中 + if minutes > 0 || hours > 0 { + result += fmt.Sprintf("%d分", minutes) + } + // 如果秒不为0,或者同时小时和分钟都不为0,则添加秒到结果中 + if seconds > 0 || (hours != 0 && minutes != 0) { + result += fmt.Sprintf("%d秒", seconds) + } + // 总是添加毫秒到结果中 + result += fmt.Sprintf("%d毫秒", milliseconds) + + return result +} diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..2cfd7a9 --- /dev/null +++ b/theme.go @@ -0,0 +1,54 @@ +/* + * @Author: BlackTeay + * @Date: 2024-05-09 15:40:58 + * @LastEditTime: 2024-05-09 15:42:53 + * @LastEditors: BlackTeay + * @Description: + * @FilePath: /hls_builder/theme.go + * Copyright 2024 JLNTV NMTD, All Rights Reserved. + */ + +package main + +import ( + _ "embed" + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +//go:embed "font/NotoSansSC-Regular.ttf" +var ttfBytes []byte + +// MyTheme 定义了一个自定义的主题结构体。 +type MyTheme struct{} + +// Color 根据给定的主题颜色名称和变种返回相应的颜色。 +// 参数 c 为主题颜色的名称。 +// 参数 v 为颜色的变种。 +// 返回值为符合 color.Color 接口的颜色值。 +func (MyTheme) Color(c fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { + return theme.DefaultTheme().Color(c, v) +} + +// Font 根据给定的文本样式返回相应的字体资源。 +// 参数 s 为文本的样式,决定了使用的字体。 +// 返回值为一个 fyne.Resource 类型的字体资源。 +func (MyTheme) Font(s fyne.TextStyle) fyne.Resource { + return fyne.NewStaticResource("./font/NotoSansSC-Regular.ttf", ttfBytes) +} + +// Icon 根据给定的图标名称返回相应的图标资源。 +// 参数 n 为图标的名称。 +// 返回值为一个 fyne.Resource 类型的图标资源。 +func (MyTheme) Icon(n fyne.ThemeIconName) fyne.Resource { + return theme.DefaultTheme().Icon(n) +} + +// Size 根据给定的主题尺寸名称返回相应的尺寸值。 +// 参数 s 为主题尺寸的名称。 +// 返回值为相应的尺寸值,单位为 float32。 +func (MyTheme) Size(s fyne.ThemeSizeName) float32 { + return theme.DefaultTheme().Size(s) +} diff --git a/upload.go b/upload.go new file mode 100644 index 0000000..dc04dfa --- /dev/null +++ b/upload.go @@ -0,0 +1,267 @@ +/* + * @Author: BlackTeay + * @Date: 2024-05-09 15:47:52 + * @LastEditTime: 2024-05-09 16:07:41 + * @LastEditors: BlackTeay + * @Description: + * @FilePath: /hls_builder/upload.go + * Copyright 2024 JLNTV NMTD, All Rights Reserved. + */ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" +) + +type UploadStats struct { + Successed int + Failed int + TotalSize string + AverageSpeed string + Elapsed string +} + +// 上传到Ucloud +func upload(localPath string, keyPath string, progressBar *widget.ProgressBar, myWindow fyne.Window, onComplete func()) { + fmt.Println("上传到Ucloud") + progressBar.SetValue(0) + fmt.Println("localPath:", localPath) + fmt.Println("keyPath:", keyPath) + // 统计 .ts .m3u8 文件数量 + tsFiles, m3u8Files, err := countMediaFiles(localPath) + if err != nil { + log.Fatalf("Error counting media files: %s", err) + } + fmt.Printf("Total .ts files: %d\n", tsFiles) + fmt.Printf("Total .m3u8 files: %d\n", m3u8Files) + filesTotal := float64(tsFiles + m3u8Files) + fmt.Printf("Total files: %d\n", int(filesTotal)) + // 获取 us3cli 的路径 + us3cliPath, cleanup, err := getUs3cliPath() + if err != nil { + log.Fatal(err) + } + defer cleanup() // 确保在函数返回时删除临时目录 + bucket := "us3://jlntv-live/replay/" + keyPath + // us3ConfigPath, cleanup, err := getUs3Config() + // if err != nil { + // log.Fatal(err) + // } + // defer cleanup() // 确保在函数返回时删除临时目录 + // cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--config", us3ConfigPath) + cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--accesskey", "TOKEN_6096c736-12b7-4c20-bfa5-e85e0e9c9b65", "--secretkey", "cc8a5965-3325-4231-9f64-a6626f624049", "--endpoint", "cn-bj.ufileos.com") + //输出cmd + fmt.Println(cmd.String()) + + stdout, err := cmd.StdoutPipe() + if err != nil { + log.Fatal("Error creating stdout pipe:", err) + } + + if err := cmd.Start(); err != nil { + log.Fatalf("Failed to start command: %s", err) + } + progress := 0.0 + go func() { + buffer := make([]byte, 4096) // 创建一个足够大的buffer + for { + n, err := stdout.Read(buffer) + if n > 0 { + fmt.Print("STDOUT:", string(buffer[:n])) // 打印实时输出 + filesUploadedTotal, err := extractTotalValue(string(buffer[:n])) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Println("Total:", filesUploadedTotal) + } + stats, err := parseUploadStats(string(buffer[:n])) + if err != nil { + fmt.Println("Error parsing upload stats:", err) + } else { + fmt.Printf("Uploaded: %d, Failed: %d, Total Size: %s, Speed: %s\n", + stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed) + if float64(stats.Successed) == filesTotal { + progressBar.SetValue(1) + showCustomDialog := func() { + replayURL := fmt.Sprintf("https://video.jlntv.cn/replay/%s/playlist.m3u8", keyPath) + copyBtn := widget.NewButton("复制地址", func() { + fmt.Println("copyed:", replayURL) + // dialog.ShowInformation("Submitted", "You submitted: "+input.Text, myWindow) + clipboard := myWindow.Clipboard() + clipboard.SetContent(replayURL) + // showToast(myWindow, "地址已复制到剪贴板", 3*time.Second) + dialog.ShowInformation("提示", "地址已复制到剪贴板!", myWindow) + }) + // 创建自定义内容的容器 + content := container.NewVBox( + widget.NewLabel("Upload Stats:"), + widget.NewLabel(fmt.Sprintf("用时:%s秒", stats.Elapsed)), + widget.NewLabel(fmt.Sprintf("成功:%d", stats.Successed)), + widget.NewLabel(fmt.Sprintf("失败:%d", stats.Failed)), + widget.NewLabel(fmt.Sprintf("平均速度:%s", stats.AverageSpeed)), + widget.NewLabel(fmt.Sprintf("总大小:%s", stats.TotalSize)), + widget.NewLabel(fmt.Sprintf("回看地址: %s", replayURL)), + copyBtn, + ) + // 设置内容容器的最小尺寸 + content.Resize(fyne.NewSize(500, 600)) // 你可以根据需要调整这个尺寸 + // 创建并显示自定义对话框 + customDialog := dialog.NewCustom("上传成功", "关闭", content, myWindow) + customDialog.Show() + } + showCustomDialog() + // dialog.ShowInformation("完成", fmt.Sprintf("✅ HLS上传完毕\n总用时:%s秒\n成功:%d 失败:%d 总大小:%s 平均速度:%s", stats.Elapsed, stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed), myWindow) + fmt.Println("Upload completed!") + } else { + progress = filesUploadedTotal / filesTotal + fmt.Println("Progress:", progress) + progressBar.SetValue(progress) + } + } + + } + if err != nil { + break + } + } + }() + + if err := cmd.Wait(); err != nil { + log.Printf("Command finished with error: %v", err) + } + + if onComplete != nil { + onComplete() + } +} + +// parseUploadStats 函数解析上传统计信息字符串,并返回一个包含上传统计详情的结构体指针。 +// 参数 line 为待解析的上传统计信息字符串。 +// 返回 *UploadStats 指向解析出的上传统计详情结构体。 +// 返回 error 表示在解析过程中遇到的任何错误。 +func parseUploadStats(line string) (*UploadStats, error) { + stats := &UploadStats{} + var err error + + // 初始化正则表达式用于匹配不同的上传统计信息。 + successedRegex := regexp.MustCompile(`(\d+) Successed`) + failedRegex := regexp.MustCompile(`(\d+) Failed`) + sizeRegex := regexp.MustCompile(`Size: ([\d.]+ MB)`) + speedRegex := regexp.MustCompile(`Average speed ([\d.]+ MB/s)`) + elapsedRegex := regexp.MustCompile(`Elapsed\s*:\s*([\d.]+)s`) + + // 提取成功上传的文件数量。 + successedMatches := successedRegex.FindStringSubmatch(line) + if len(successedMatches) > 1 { + stats.Successed, err = strconv.Atoi(successedMatches[1]) + if err != nil { + return nil, fmt.Errorf("error parsing successed number: %v", err) + } + } + + // 提取失败上传的文件数量。 + failedMatches := failedRegex.FindStringSubmatch(line) + if len(failedMatches) > 1 { + stats.Failed, err = strconv.Atoi(failedMatches[1]) + if err != nil { + return nil, fmt.Errorf("error parsing failed number: %v", err) + } + } + + // 提取上传的总大小。 + sizeMatches := sizeRegex.FindStringSubmatch(line) + if len(sizeMatches) > 1 { + stats.TotalSize = sizeMatches[1] + } + + // 提取平均上传速度。 + speedMatches := speedRegex.FindStringSubmatch(line) + if len(speedMatches) > 1 { + stats.AverageSpeed = speedMatches[1] + } + + // 提取上传所花费的时间。 + elapsedMatches := elapsedRegex.FindStringSubmatch(line) + if len(elapsedMatches) > 1 { + stats.Elapsed = elapsedMatches[1] + } + + return stats, nil +} + +// extractTotalValue 函数从给定的文本中提取总值。 +// +// 参数: +// +// text string - 包含待搜索总值的文本。 +// +// 返回值: +// +// float64 - 从文本中提取到的总值,如果未找到则返回默认值200。 +// error - 如果在提取过程中发生错误,则返回相应的错误;否则返回nil。 +func extractTotalValue(text string) (float64, error) { + // 使用正则表达式匹配文本中的总值部分 + re := regexp.MustCompile(`Total:(\d+)`) + matches := re.FindStringSubmatch(text) + if len(matches) > 1 { + // 将匹配到的总值字符串转换为整数 + total, err := strconv.Atoi(matches[1]) + if err != nil { + // 如果转换过程中发生错误,返回错误信息 + return 0, err + } + // 返回转换后的总值 + return float64(total), nil + } + // 如果未在文本中找到总值,返回默认值200和一个错误信息 + return 200, fmt.Errorf("no total found in the text") +} + +// countMediaFiles 统计给定目录中 .ts 和 .m3u8 文件的数量。 +// +// 参数: +// +// directory string - 需要遍历统计文件的目录路径。 +// +// 返回值: +// +// int - .ts 文件的数量。 +// int - .m3u8 文件的数量。 +// error - 遍历目录过程中遇到的任何错误。 +func countMediaFiles(directory string) (int, int, error) { + var tsCount, m3u8Count int // 分别用于记录 .ts 和 .m3u8 文件的数量 + + // 使用 filepath.Walk 遍历指定目录及其子目录中的所有文件 + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + // 检查遍历过程是否遇到错误 + if err != nil { + return err // 如果有错误,则终止遍历并返回该错误 + } + + // 检查当前路径项是否为文件,不遍历目录 + if !info.IsDir() { + // 根据文件扩展名统计 .ts 和 .m3u8 文件数量 + switch filepath.Ext(info.Name()) { + case ".ts": + tsCount++ // .ts 文件计数 + case ".m3u8": + m3u8Count++ // .m3u8 文件计数 + } + } + return nil // 继续遍历下一个文件或目录 + }) + + // 返回 .ts 和 .m3u8 文件的计数结果,以及遍历过程中可能遇到的错误 + return tsCount, m3u8Count, err +}